From dd8a4a0082f54e777b25113a2fd4062626d7dd56 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Thu, 12 May 2022 14:40:55 +0200 Subject: [PATCH 01/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 4 + composer.lock | 1177 ++++++++++++++++- lib/autoload.php | 5 - lib/composer/ClassLoader.php | 2 +- lib/composer/autoload_classmap.php | 3 +- lib/composer/autoload_files.php | 8 +- lib/composer/autoload_namespaces.php | 2 +- lib/composer/autoload_psr4.php | 2 +- lib/composer/autoload_real.php | 30 +- lib/composer/autoload_static.php | 7 +- lib/composer/include_paths.php | 2 +- pages/oauth/ajax.wizard.php | 63 + pages/oauth/landing.php | 26 + pages/oauth/wizard.php | 159 +++ .../OAuth/OAuthWizardController.php | 48 + .../Client/OAuth/IOAuthClientProvider.php | 5 + .../OAuth/IOAuthClientResultDisplay.php | 8 + .../OAuth/OAuthClientProviderAbstract.php | 163 +++ .../Client/OAuth/OAuthClientProviderAzure.php | 46 + .../OAuth/OAuthClientProviderFactory.php | 73 + .../OAuth/OAuthClientProviderGoogle.php | 41 + .../OAuth/OAuthClientResultDisplayConf.php | 40 + .../Client/Smtp/SmtpOAuthLogin.php | 123 ++ sources/Core/Email/EmailFactory.php | 6 + sources/Core/Email/EmailLaminas.php | 643 +++++++++ sources/Core/Email/EmailSwiftMailer.php | 550 ++++++++ .../pages/backoffice/oauth/Wizard.html.twig | 48 + 27 files changed, 3254 insertions(+), 30 deletions(-) create mode 100644 pages/oauth/ajax.wizard.php create mode 100644 pages/oauth/landing.php create mode 100644 pages/oauth/wizard.php create mode 100644 sources/Controller/OAuth/OAuthWizardController.php create mode 100644 sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php create mode 100644 sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php create mode 100644 sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php create mode 100644 sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php create mode 100644 sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php create mode 100644 sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php create mode 100644 sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php create mode 100644 sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php create mode 100644 sources/Core/Email/EmailFactory.php create mode 100644 sources/Core/Email/EmailLaminas.php create mode 100644 sources/Core/Email/EmailSwiftMailer.php create mode 100644 templates/pages/backoffice/oauth/Wizard.html.twig diff --git a/composer.json b/composer.json index 57599b9e1..3b32231e5 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,9 @@ "ext-mysqli": "*", "ext-soap": "*", "combodo/tcpdf": "~6.4.4", + "laminas/laminas-mail": "^2.11", + "laminas/laminas-servicemanager": "^3.5", + "league/oauth2-google": "^3.0", "nikic/php-parser": "~4.13.2", "pear/archive_tar": "~1.4.14", "pelago/emogrifier": "~3.1.0", @@ -21,6 +24,7 @@ "symfony/framework-bundle": "~3.4.47", "symfony/twig-bundle": "~3.4.47", "symfony/yaml": "~3.4.47", + "thenetworg/oauth2-azure": "^2.0", "twig/twig": "~1.42.5" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 7791458dc..f753cd18b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2e22d4aefde79b10575e4de746f22f32", + "content-hash": "16a1d6529177e5ab83a163236d9602ea", "packages": [ { "name": "combodo/tcpdf", @@ -84,6 +84,42 @@ ], "time": "2022-03-10T14:36:39+00:00" }, + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "support": { + "issues": "https://github.com/container-interop/container-interop/issues", + "source": "https://github.com/container-interop/container-interop/tree/master" + }, + "abandoned": "psr/container", + "time": "2017-02-14T19:40:03+00:00" + }, { "name": "doctrine/lexer", "version": "1.0.2", @@ -216,6 +252,941 @@ ], "time": "2020-12-29T14:50:06+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v5.5.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "83b609028194aa042ea33b5af2d41a7427de80e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6", + "reference": "83b609028194aa042ea33b5af2d41a7427de80e6", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <=9" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v5.5.1" + }, + "time": "2021-11-08T20:18:51+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.17.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/6.5" + }, + "time": "2020-06-16T21:01:06+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "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/1.5.1" + }, + "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" + } + ], + "time": "2021-10-22T20:56:57+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.8.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/337e3ad8e5716c15f9657bd214d16cc5e69df268", + "reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "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" + } + ], + "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/1.8.5" + }, + "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" + } + ], + "time": "2022-03-20T21:51:18+00:00" + }, + { + "name": "laminas/laminas-loader", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-loader.git", + "reference": "5d01c2c237ae9e68bec262f339947e2ea18979bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/5d01c2c237ae9e68bec262f339947e2ea18979bc", + "reference": "5d01c2c237ae9e68bec262f339947e2ea18979bc", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-loader": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev", + "dev-develop": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Autoloading and plugin loading strategies", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "loader" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-loader/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-loader/issues", + "rss": "https://github.com/laminas/laminas-loader/releases.atom", + "source": "https://github.com/laminas/laminas-loader" + }, + "time": "2019-12-31T17:18:27+00:00" + }, + { + "name": "laminas/laminas-mail", + "version": "2.11.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mail.git", + "reference": "7f674afeb38100b1869ce8e56bf2ec3cba3c679c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/7f674afeb38100b1869ce8e56bf2ec3cba3c679c", + "reference": "7f674afeb38100b1869ce8e56bf2ec3cba3c679c", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mime": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.10.2", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "true/punycode": "^2.1" + }, + "replace": { + "zendframework/zend-mail": "^2.10.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-crypt": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4" + }, + "suggest": { + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" + }, + "laminas": { + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Mail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mail" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mail/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mail/issues", + "rss": "https://github.com/laminas/laminas-mail/releases.atom", + "source": "https://github.com/laminas/laminas-mail" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-07-28T21:17:48+00:00" + }, + { + "name": "laminas/laminas-mime", + "version": "2.7.4", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mime.git", + "reference": "e45a7d856bf7b4a7b5bd00d6371f9961dc233add" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/e45a7d856bf7b4a7b5bd00d6371f9961dc233add", + "reference": "e45a7d856bf7b4a7b5bd00d6371f9961dc233add", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-mime": "^2.7.2" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-mail": "^2.6", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20" + }, + "suggest": { + "laminas/laminas-mail": "Laminas\\Mail component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Mime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Create and parse MIME messages and parts", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mime" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mime/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mime/issues", + "rss": "https://github.com/laminas/laminas-mime/releases.atom", + "source": "https://github.com/laminas/laminas-mime" + }, + "time": "2020-03-29T13:12:07+00:00" + }, + { + "name": "laminas/laminas-servicemanager", + "version": "3.5.2", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "0669e1eec8d9f61e35a5bc5012796d49f418b259" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/0669e1eec8d9f61e35a5bc5012796d49f418b259", + "reference": "0669e1eec8d9f61e35a5bc5012796d49f418b259", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "psr/container": "^1.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "zendframework/zend-servicemanager": "^3.4.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "mikey179/vfsstream": "^1.6.5", + "ocramius/proxy-manager": "^1.0 || ^2.0", + "phpbench/phpbench": "^0.13.0", + "phpunit/phpunit": "^5.7.25 || ^6.4.4" + }, + "suggest": { + "laminas/laminas-stdlib": "laminas-stdlib ^2.5 if you wish to use the MergeReplaceKey or MergeRemoveKey features in Config instances", + "ocramius/proxy-manager": "ProxyManager 1.* to handle lazy initialization of services" + }, + "bin": [ + "bin/generate-deps-for-config-factory", + "bin/generate-factory-for-class" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev", + "dev-develop": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Factory-Driven Dependency Injection Container", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "dependency-injection", + "di", + "dic", + "laminas", + "service-manager", + "servicemanager" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-servicemanager/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-servicemanager/issues", + "rss": "https://github.com/laminas/laminas-servicemanager/releases.atom", + "source": "https://github.com/laminas/laminas-servicemanager" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-01-17T16:54:43+00:00" + }, + { + "name": "laminas/laminas-stdlib", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "2b18347625a2f06a1a485acfbc870f699dbe51c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/2b18347625a2f06a1a485acfbc870f699dbe51c6", + "reference": "2b18347625a2f06a1a485acfbc870f699dbe51c6", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpbench/phpbench": "^0.13", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "time": "2019-12-31T17:51:15+00:00" + }, + { + "name": "laminas/laminas-validator", + "version": "2.12.2", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-validator.git", + "reference": "0813f234812d9fa9058b6da39eb13dedc90227db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/0813f234812d9fa9058b6da39eb13dedc90227db", + "reference": "0813f234812d9fa9058b6da39eb13dedc90227db", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-validator": "self.version" + }, + "require-dev": { + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.5", + "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "psr/http-message": "^1.0" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" + }, + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "validator" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-validator/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-validator/issues", + "rss": "https://github.com/laminas/laminas-validator/releases.atom", + "source": "https://github.com/laminas/laminas-validator" + }, + "time": "2019-12-31T17:57:44+00:00" + }, + { + "name": "laminas/laminas-zendframework-bridge", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "6ede70583e101030bcace4dcddd648f760ddf642" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/6ede70583e101030bcace4dcddd648f760ddf642", + "reference": "6ede70583e101030bcace4dcddd648f760ddf642", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1 || ^9.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ZendFrameworkBridge\\": "src//" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "keywords": [ + "ZendFramework", + "autoloading", + "laminas", + "zf" + ], + "support": { + "forum": "https://discourse.laminas.dev/", + "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", + "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", + "source": "https://github.com/laminas/laminas-zendframework-bridge" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-09-14T14:23:00+00:00" + }, + { + "name": "league/oauth2-client", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "2334c249907190c132364f5dae0287ab8666aa19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/2334c249907190c132364f5dae0287ab8666aa19", + "reference": "2334c249907190c132364f5dae0287ab8666aa19", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99", + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", + "squizlabs/php_codesniffer": "^2.3 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.6.1" + }, + "time": "2021-12-22T16:42:49+00:00" + }, + { + "name": "league/oauth2-google", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-google.git", + "reference": "6b79441f244040760bed5fdcd092a2bda7cf34c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-google/zipball/6b79441f244040760bed5fdcd092a2bda7cf34c6", + "reference": "6b79441f244040760bed5fdcd092a2bda7cf34c6", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^2.0", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^6.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me" + } + ], + "description": "Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "Authentication", + "authorization", + "client", + "google", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-google/issues", + "source": "https://github.com/thephpleague/oauth2-google/tree/3.0.4" + }, + "time": "2021-01-27T16:09:03+00:00" + }, { "name": "nikic/php-parser", "version": "v4.13.2", @@ -720,6 +1691,59 @@ ], "time": "2017-02-14T16:28:37+00:00" }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://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/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, { "name": "psr/log", "version": "1.1.2", @@ -815,6 +1839,50 @@ ], "time": "2017-10-23T01:57:42+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "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" + }, + "type": "library", + "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" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "scssphp/scssphp", "version": "1.0.6", @@ -3188,6 +4256,111 @@ ], "time": "2020-10-24T10:57:07+00:00" }, + { + "name": "thenetworg/oauth2-azure", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/TheNetworg/oauth2-azure.git", + "reference": "2649422a0dc74af32d21d9d738d37abcd5b03998" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/2649422a0dc74af32d21d9d738d37abcd5b03998", + "reference": "2649422a0dc74af32d21d9d738d37abcd5b03998", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "~3.0||~4.0||~5.0", + "league/oauth2-client": "~2.0", + "php": "^5.6|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "TheNetworg\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Hajek", + "email": "jan.hajek@thenetw.org", + "homepage": "https://thenetw.org" + } + ], + "description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "SSO", + "aad", + "authorization", + "azure", + "azure active directory", + "client", + "microsoft", + "oauth", + "oauth2", + "windows azure" + ], + "support": { + "issues": "https://github.com/TheNetworg/oauth2-azure/issues", + "source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.0.1" + }, + "time": "2021-01-11T12:20:12+00:00" + }, + { + "name": "true/punycode", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/true/php-punycode.git", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "TrueBV\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "homepage": "https://github.com/true/php-punycode", + "keywords": [ + "idna", + "punycode" + ], + "support": { + "issues": "https://github.com/true/php-punycode/issues", + "source": "https://github.com/true/php-punycode/tree/master" + }, + "time": "2016-11-16T10:37:54+00:00" + }, { "name": "twig/twig", "version": "v1.42.5", @@ -3501,5 +4674,5 @@ "platform-overrides": { "php": "7.0.8" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.1.0" } diff --git a/lib/autoload.php b/lib/autoload.php index 64168f99a..79c1600b5 100644 --- a/lib/autoload.php +++ b/lib/autoload.php @@ -2,11 +2,6 @@ // autoload.php @generated by Composer -if (PHP_VERSION_ID < 50600) { - echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; - exit(1); -} - require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b::getLoader(); diff --git a/lib/composer/ClassLoader.php b/lib/composer/ClassLoader.php index afef3fa2a..0cd6055d1 100644 --- a/lib/composer/ClassLoader.php +++ b/lib/composer/ClassLoader.php @@ -149,7 +149,7 @@ class ClassLoader /** * @return string[] Array of classname => path - * @psalm-return array + * @psalm-var array */ public function getClassMap() { diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 3ddc888cc..11766f30e 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -2,7 +2,7 @@ // autoload_classmap.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( @@ -150,6 +150,7 @@ return array( 'Combodo\\iTop\\Application\\TwigBase\\Twig\\TwigHelper' => $baseDir . '/sources/application/TwigBase/Twig/TwigHelper.php', 'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php', + 'Combodo\\iTop\\Controller\\OAuth\\OAuthWizardController' => $baseDir . '/sources/Controller/OAuth/OAuthWizardController.php', 'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php', diff --git a/lib/composer/autoload_files.php b/lib/composer/autoload_files.php index 4e28b93b7..af2e18883 100644 --- a/lib/composer/autoload_files.php +++ b/lib/composer/autoload_files.php @@ -2,20 +2,20 @@ // autoload_files.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', - '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php', + '32dcc8afd4335739640db7d200c1971d' => $vendorDir . '/symfony/polyfill-apcu/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', 'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', - '32dcc8afd4335739640db7d200c1971d' => $vendorDir . '/symfony/polyfill-apcu/bootstrap.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php', - '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', ); diff --git a/lib/composer/autoload_namespaces.php b/lib/composer/autoload_namespaces.php index e6117c750..d12922d08 100644 --- a/lib/composer/autoload_namespaces.php +++ b/lib/composer/autoload_namespaces.php @@ -2,7 +2,7 @@ // autoload_namespaces.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php index 7d6039aea..28fab5d05 100644 --- a/lib/composer/autoload_psr4.php +++ b/lib/composer/autoload_psr4.php @@ -2,7 +2,7 @@ // autoload_psr4.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/autoload_real.php b/lib/composer/autoload_real.php index 752e35fbd..661cd2543 100644 --- a/lib/composer/autoload_real.php +++ b/lib/composer/autoload_real.php @@ -25,20 +25,33 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b require __DIR__ . '/platform_check.php'; spl_autoload_register(array('ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b', 'loadClassLoader')); $includePaths = require __DIR__ . '/include_paths.php'; $includePaths[] = get_include_path(); set_include_path(implode(PATH_SEPARATOR, $includePaths)); - require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::getInitializer($loader)); + $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::getInitializer($loader)); + } else { + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } $loader->setClassMapAuthoritative(true); $loader->register(true); - $includeFiles = \Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$files; + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire0018331147de7601e7552f7da8e3bb8b($fileIdentifier, $file); } @@ -47,16 +60,11 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b } } -/** - * @param string $fileIdentifier - * @param string $file - * @return void - */ function composerRequire0018331147de7601e7552f7da8e3bb8b($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } } diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 8e76a331b..afed08926 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -8,16 +8,16 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b { public static $files = array ( '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', - '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', + '32dcc8afd4335739640db7d200c1971d' => __DIR__ . '/..' . '/symfony/polyfill-apcu/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', 'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', - '32dcc8afd4335739640db7d200c1971d' => __DIR__ . '/..' . '/symfony/polyfill-apcu/bootstrap.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php', - '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', ); @@ -420,6 +420,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Application\\TwigBase\\Twig\\TwigHelper' => __DIR__ . '/../..' . '/sources/application/TwigBase/Twig/TwigHelper.php', 'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php', + 'Combodo\\iTop\\Controller\\OAuth\\OAuthWizardController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthWizardController.php', 'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php', diff --git a/lib/composer/include_paths.php b/lib/composer/include_paths.php index af33c1491..d4fb96718 100644 --- a/lib/composer/include_paths.php +++ b/lib/composer/include_paths.php @@ -2,7 +2,7 @@ // include_paths.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( diff --git a/pages/oauth/ajax.wizard.php b/pages/oauth/ajax.wizard.php new file mode 100644 index 000000000..1d3ca1ce8 --- /dev/null +++ b/pages/oauth/ajax.wizard.php @@ -0,0 +1,63 @@ +SetOutputDataOnly(true); +$aResult = ['status' => 'success', 'data' => []]; +try { + $operation = utils::ReadParam('operation', ''); + + switch ($operation) { + case 'get_authorization_url': + $sProvider = utils::ReadParam('provider', '', false, 'raw'); + $sClientId = utils::ReadParam('client_id', '', false, 'raw'); + $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); + $sScope = utils::ReadParam('scope', '', false, 'raw'); + $sAdditional = utils::ReadParam('additional', '', false, 'raw'); + $aAdditional = []; + parse_str($sAdditional, $aAdditional); + $sAuthorizationUrl = OAuthClientProviderFactory::getVendorProviderForAccessUrl($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); + $aResult['data']['authorization_url'] = $sAuthorizationUrl; + break; + case 'get_display_authentication_results': + $sProvider = utils::ReadParam('provider', '', false, 'raw'); + $sRedirectUrl = utils::ReadParam('redirect_url', '', false, 'raw'); + $sClientId = utils::ReadParam('client_id', '', false, 'raw'); + $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); + $sScope = utils::ReadParam('scope', '', false, 'raw'); + $sAdditional = utils::ReadParam('additional', '', false, 'raw'); + + $sRedirectUrlQuery = parse_url($sRedirectUrl)['query']; + $aOAuthResultDisplayClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); + $aAdditional = []; + parse_str($sAdditional, $aAdditional); + + $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProvider; + $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); + + $aQuery = []; + parse_str($sRedirectUrlQuery, $aQuery); + $sCode = $aQuery['code']; + $oProvider = OAuthClientProviderFactory::getVendorProvider($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); + $oAccessToken = OAuthClientProviderFactory::getAccessTokenFromCode($oProvider, $sCode); + + foreach($aOAuthResultDisplayClasses as $sOAuthClass) { + $aResult['data'][] = $sOAuthClass::GetResultDisplayScript($sClientId, $sClientSecret, $sProvider, $oAccessToken); + } + } +} +catch(Exception $e){ + $aResult['status'] = 'error'; + IssueLog::Error($e->getMessage()); +} +$oPage->SetData($aResult); +$oPage->output(); \ No newline at end of file diff --git a/pages/oauth/landing.php b/pages/oauth/landing.php new file mode 100644 index 000000000..d885ded25 --- /dev/null +++ b/pages/oauth/landing.php @@ -0,0 +1,26 @@ +AddCSSClass('ibo-oauth-wizard--side-pane'); +$oPage = new WebPage(Dict::S('UI:Schema:Title')); + +$sJS = <<add_script($sJS); + + +$oPage->output(); diff --git a/pages/oauth/wizard.php b/pages/oauth/wizard.php new file mode 100644 index 000000000..a88c90950 --- /dev/null +++ b/pages/oauth/wizard.php @@ -0,0 +1,159 @@ +AddCSSClass('ibo-oauth-wizard'); +$oPage = new iTopWebPage(Dict::S('UI:OAuth:Wizard:Page:Title')); +$oPage->SetContentLayout($oLayout); +$sReturnUri = utils::GetAbsoluteUrlAppRoot().'pages/oauth.landing.php'; +$sAjaxUri = utils::GetAbsoluteUrlAppRoot().'pages/ajax.oauth.wizard.php'; + +$sJS = << { + // remove any existing event listeners + + // window features + const strWindowFeatures = + 'toolbar=no, menubar=no, width=600, height=700, top=100, left=100'; + + if (windowObjectReference === null || windowObjectReference.closed) { + /* if the pointer to the window object in memory does not exist + or if such pointer exists but the window was closed */ + windowObjectReference = window.open(url, name, strWindowFeatures); + } else if (previousUrl !== url) { + /* if the resource to load is different, + then we load it in the already opened secondary window and then + we bring such window back on top/in front of its parent window. */ + windowObjectReference = window.open(url, name, strWindowFeatures); + windowObjectReference.focus(); + } else { + /* else the window reference must exist and the window + is not closed; therefore, we can bring it back on top of any other + window with the focus() method. There would be no need to re-create + the window or to reload the referenced resource. */ + windowObjectReference.focus(); + } + let oListener = window.setInterval(function(){ + windowObjectReference.postMessage('anyone', '$sReturnUri'); + }, 1000); + + + window.addEventListener("message", function (event){ + clearInterval(oListener); + $.post( + '$sAjaxUri', + { + operation: 'get_display_authentication_results', + provider: $('[name="provider"]:checked').val(), + client_id: $('[name="client_id"]').val(), + client_secret: $('[name="client_secret"]').val(), + scope: $(this).find('[name="scope"]').val(), + additional: $(this).find('[name="additional"]').val(), + redirect_url: event.data, + }, + function(oData){ + if(oData.status == 'success') + { + oData.data.forEach(function(item, index){ + new Function(item)(); + }); + } + $('.ibo-oauth-wizard--form--submit').trigger('leave_loading_state.button.itop'); + } + ); + }, false); + // add the listener for receiving a message from the popup + // assign the previous URL + previousUrl = url; + }; +JS; + +$oPage->add_script($sJS); +$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'/js/pages/backoffice/oauth.wizard.js'); + +$oOauthInputsPanel = PanelUIBlockFactory::MakeNeutral(Dict::S('UI:OAuth:Wizard:Form:Panel:Title')); +$oOauthInputsPanel->AddCSSClass('ibo-oauth-wizard'); + +$aOAuthClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientProvider', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); +$sFormJs = <<
'); +$oOauthInputsPanel->AddSubBlock($oProviderSelect); +$sIsChecked = ' checked '; +foreach($aOAuthClasses as $sOAuthClass){ + $aColors = $sOAuthClass::GetVendorColors(); + $oProviderSelect->AddHtml(''); + $sIsChecked = ''; +} +$oForm = FormUIBlockFactory::MakeStandard(); +$oForm->AddCSSClasses(['ibo-oauth-wizard--form', 'ibo-oauth-wizard--form-'.strtolower($sOAuthClass::GetVendorName())]); +$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('url', '')); +foreach (['client_id' => Dict::S('UI:OAuth:Wizard:Form:Input:ClientId:Label'), + 'client_secret' => Dict::S('UI:OAuth:Wizard:Form:Input:ClientSecret:Label'), + 'scope' => Dict::S('UI:OAuth:Wizard:Form:Input:Scope:Label'), + 'additional' => Dict::S('UI:OAuth:Wizard:Form:Input:Additional:Label')] as $sName => $sLabel){ + $oForm->AddSubBlock(InputUIBlockFactory::MakeForInputWithLabel($sLabel, $sName, null, null, 'text')); +} +$oRedirectUriInput = InputUIBlockFactory::MakeForInputWithLabel(Dict::S('UI:OAuth:Wizard:Form:Input:RedirectUri:Label'), 'redirect_uri', OAuthClientProviderGoogle::GetRedirectUri(), null, 'text'); +$oRedirectUriInput->GetInput()->SetIsReadonly(true); +$oForm->AddSubBlock($oRedirectUriInput); + +$oForm->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:OAuth:Wizard:Form:Button:Submit:Label'),'submit', '', true)->AddCSSClass('ibo-oauth-wizard--form--submit')); +$oForm->SetOnSubmitJsCode($sFormJs); +$oOauthInputsPanel->AddSubBlock($oForm); +$oProviderSelect->AddHtml('
'); +$oOauthInputsPanel->AddHtml(''); + +$sOnReadyJs = "$('#select_layout').controlgroup(); $('.ibo-oauth-wizard--result--panel .ibo-panel--collapsible-toggler').click();"; +$oPage->add_ready_script($sOnReadyJs); +$oOauthInputsPanel->AddHtml('
'.file_get_contents(APPROOT.'images/illustrations/undraw_access_account.svg').'
'); +$oPage->AddSubBlock($oOauthInputsPanel); + +$aOAuthResultDisplayClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); +foreach($aOAuthResultDisplayClasses as $sOAuthClass) { + $oPage->AddSubBlock($sOAuthClass::GetResultDisplayBlock()); +} + + +$oPage->output(); diff --git a/sources/Controller/OAuth/OAuthWizardController.php b/sources/Controller/OAuth/OAuthWizardController.php new file mode 100644 index 000000000..f109a2370 --- /dev/null +++ b/sources/Controller/OAuth/OAuthWizardController.php @@ -0,0 +1,48 @@ +AddLinkedScript(utils::GetAbsoluteUrlAppRoot().'/js/pages/backoffice/oauth.wizard.js'); + + $aOAuthClasses = [ + 'Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderAzure', + 'Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderGoogle', + ]; + foreach ($aOAuthClasses as $sOAuthClass) { + $aParams['aProviders'][] = [ + 'name' => $sOAuthClass::GetVendorName(), + 'icon' => $sOAuthClass::GetVendorIcon(), + 'colors' => $sOAuthClass::GetVendorColors(), + ]; + } + + $aParams['aInputs'] = [ + 'client_id' => ['type' => 'text', 'label' => Dict::S('UI:OAuth:Wizard:Form:Input:ClientId:Label'), 'read_only' => false, 'value' => ''], + 'client_secret' => ['type' => 'text', 'label' => Dict::S('UI:OAuth:Wizard:Form:Input:ClientSecret:Label'), 'read_only' => false, 'value' => ''], + 'scope' => ['type' => 'text', 'label' => Dict::S('UI:OAuth:Wizard:Form:Input:Scope:Label'), 'read_only' => false, 'value' => ''], + 'additional' => ['type' => 'text', 'label' => Dict::S('UI:OAuth:Wizard:Form:Input:Additional:Label'), 'read_only' => false, 'value' => ''], + 'redirect_uri' => ['type' => 'text', 'label' => Dict::S('UI:OAuth:Wizard:Form:Input:RedirectUri:Label'), 'read_only' => true, 'value' => OAuthClientProviderAbstract::GetRedirectUri()], + ]; + + $this->DisplayPage($aParams); + } +} \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php b/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php new file mode 100644 index 000000000..805686103 --- /dev/null +++ b/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php @@ -0,0 +1,5 @@ +oVendorProvider; + } + + /** + * @param \League\OAuth2\Client\Provider\GenericProvider $oVendorProvider + */ + public function SetVendorProvider(GenericProvider $oVendorProvider) + { + $this->oVendorProvider = $oVendorProvider; + } + + /** + * @return \League\OAuth2\Client\Token\AccessToken + */ + public function GetAccessToken(): AccessToken + { + return $this->oAccessToken; + } + + /** + * @param \League\OAuth2\Client\Token\AccessToken $oAccessToken + */ + public function SetAccessToken(AccessToken $oAccessToken) + { + $this->oAccessToken = $oAccessToken; + } + + /** + * @return string + */ + public static function GetVendorIcon(): string + { + return static::$sVendorIcon; + } + + /** + * @return string + */ + public static function GetVendorName(): string + { + return static::$sVendorName; + } + + + public static function getConfFromAccessToken($oAccessToken, $sClientId, $sClientSecret): string + { + $sAccessToken = $oAccessToken->getToken(); + $sRefreshToken = $oAccessToken->getRefreshToken(); + $sVendor = static::GetVendorName(); + + return << 'SMTP_OAuth', +'email_transport_smtp.oauth.provider' => '$sVendor', +'email_transport_smtp.oauth.client_id' => '$sClientId', +'email_transport_smtp.oauth.client_secret' => '$sClientSecret', +'email_transport_smtp.oauth.access_token' => '$sAccessToken', +'email_transport_smtp.oauth.refresh_token' => '$sRefreshToken', +EOF; + } + + /** + * @return array + */ + public static function GetVendorColors(): array + { + return static::$sVendorColors; + } + + /** + * @return void + * @throws \Exception + */ + public static function InitizalizeRedirectUri() + { + static::$sRedirectUri = utils::GetAbsoluteUrlAppRoot().'pages/oauth/landing.php'; + } + + /** + * @return string + */ + public static function GetRedirectUri(): string + { + if (static::$sRedirectUri === '') { + static::InitizalizeRedirectUri(); + } + + return static::$sRedirectUri; + } + + /** + * @return string + */ + public static function GetRequiredSMTPScope(): string + { + return static::$sRequiredSMTPScope; + } + + /** + * @return string + */ + public static function GetRequiredIMAPScope(): string + { + return static::$sRequiredIMAPScope; + } + + /** + * @return string + */ + public static function GetRequiredPOPScope(): string + { + return static::$sRequiredPOPScope; + } + + /** + * @return mixed + */ + public function GetScope() + { + return $this->sScope; + } + + /** + * @param mixed $sScope + */ + public function SetScope($sScope) + { + $this->sScope = $sScope; + } + +} \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php new file mode 100644 index 000000000..cbf298377 --- /dev/null +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php @@ -0,0 +1,46 @@ +oVendorProvider = new Azure(array_merge([ + 'prompt' => 'consent', + 'scope' => 'offline_access', + 'defaultEndPointVersion' => Azure::ENDPOINT_VERSION_2_0, + ], + $aVendorProvider), $collaborators); + + if (!empty($aAccessTokenParams)) { + $this->oAccessToken = new AccessToken([ + "access_token" => $aAccessTokenParams["access_token"], + "expires_in" => -1, + "refresh_token" => $aAccessTokenParams["refresh_token"], + "token_type" => "Bearer", + ]); + } + + if (isset($aVendorProvider['scope'])) { + $this->SetScope($aVendorProvider['scope']); + } + } +} \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php new file mode 100644 index 000000000..8ede0f6fe --- /dev/null +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php @@ -0,0 +1,73 @@ +Get('email_transport_smtp.oauth.provider'); // email_transport_smtp.oauth.provider + $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProviderVendor; + $aProviderVendorParams = [ + 'clientId' => MetaModel::GetConfig()->Get('email_transport_smtp.oauth.client_id'), // email_transport_smtp.oauth.client_id + 'clientSecret' => MetaModel::GetConfig()->Get('email_transport_smtp.oauth.client_secret'),// email_transport_smtp.oauth.client_secret + 'redirectUri' => $sProviderClass::GetRedirectUri(), + 'scope' => $sProviderClass::GetRequiredSMTPScope() + ]; + $aAccessTokenParams = [ + "access_token" => MetaModel::GetConfig()->Get('email_transport_smtp.oauth.access_token'), // email_transport_smtp.oauth.access_token + "refresh_token" => MetaModel::GetConfig()->Get('email_transport_smtp.oauth.refresh_token'), // email_transport_smtp.oauth.refresh_token + 'scope' => $sProviderClass::GetRequiredSMTPScope() + ]; + $aCollaborators = [ + 'httpClient' => new Client(['verify' => false]), + ]; + + return new $sProviderClass($aProviderVendorParams, $aCollaborators, $aAccessTokenParams); + } + public static function getVendorProvider($sProviderVendor, $sClientId, $sClientSecret, $sScope, $aAdditional){ + $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); + $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProviderVendor; + $aCollaborators = [ + 'httpClient' => new Client(['verify' => false]), + ]; + + return new $sProviderClass(array_merge(['clientId' => $sClientId, 'clientSecret' => $sClientSecret, 'redirectUri' => $sRedirectUrl, 'scope' => $sScope], $aAdditional), $aCollaborators); + } + + public static function getVendorProviderForAccessUrl($sProviderVendor, $sClientId, $sClientSecret, $sScope, $aAdditional){ + $oProvider = static::getVendorProvider($sProviderVendor, $sClientId, $sClientSecret, $sScope, $aAdditional); + return $oProvider->GetVendorProvider()->getAuthorizationUrl([ + 'scope' => [ + $sScope + ], + ]); + } + + /** + * @param \Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderAbstract $oProvider + * @param $sCode + * + * @return AccessTokenInterface + * @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException + */ + public static function getAccessTokenFromCode($oProvider, $sCode) + { + return $oProvider->GetVendorProvider()->getAccessToken('authorization_code', ['code' => $sCode, 'scope' => $oProvider->GetScope()]); + } + + public static function getConfFromRedirectUrl($sProviderVendor, $sClientId, $sClientSecret, $sRedirectUrlQuery) + { + $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); + $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProviderVendor; + $aQuery = []; + parse_str($sRedirectUrlQuery, $aQuery); + $sCode = $aQuery['code']; + $oProvider = new $sProviderClass(['clientId' => $sClientId, 'clientSecret' => $sClientSecret, 'redirectUri' => $sRedirectUrl]); + return $sProviderClass::getConfFromAccessToken($oProvider->GetVendorProvider()->getAccessToken('authorization_code', ['code' => $sCode]), $sClientId, $sClientSecret); + } + +} \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php new file mode 100644 index 000000000..bd2a1e3b6 --- /dev/null +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php @@ -0,0 +1,41 @@ +oVendorProvider = new Google(array_merge(['prompt' => 'consent', 'accessType' => 'offline'], $aVendorProvider), $collaborators); + + if (!empty($aAccessTokenParams)) { + $this->oAccessToken = new AccessToken([ + "access_token" => $aAccessTokenParams["access_token"], + "expires_in" => -1, + "refresh_token" => $aAccessTokenParams["refresh_token"], + "token_type" => "Bearer", + ]); + } + + if (isset($aVendorProvider['scope'])) { + $this->SetScope($aVendorProvider['scope']); + } + } +} \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php b/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php new file mode 100644 index 000000000..92c1bdaa4 --- /dev/null +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php @@ -0,0 +1,40 @@ +AddCSSClass('ibo-oauth-wizard--result--panel'); + $oConfResultPanel->SetIsCollapsible(true); + $oConfResultPanel->AddHtml('

'.Dict::S('UI:OAuthEmailSynchro:Wizard:ResultConf:Panel:Description').'

'); + $oConfResultPanel->AddHtml('
'); + return $oConfResultPanel; + } + + public static function GetResultDisplayScript($sClientId, $sClientSecret, $sVendor, AccessToken $oAccessToken) + { + $sAccessToken = $oAccessToken->getToken(); + $sRefreshToken = $oAccessToken->getRefreshToken(); + $sConf = << 'SMTP_OAuth', +'email_transport_smtp.oauth.provider' => '$sVendor', +'email_transport_smtp.oauth.client_id' => '$sClientId', +'email_transport_smtp.oauth.client_secret' => '$sClientSecret', +'email_transport_smtp.oauth.access_token' => '$sAccessToken', +'email_transport_smtp.oauth.refresh_token' => '$sRefreshToken', +EOF; + $sConf = json_encode($sConf); + + return <<setUsername($config['username']); + } + } + + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); + } + + /** + * @param OAuthClientProviderAbstract $oProvider + * + * @return void + */ + public static function setProvider(OAuthClientProviderAbstract $oProvider): void + { + self::$oProvider = $oProvider; + } + + /** + * Perform LOGIN authentication with supplied credentials + * + */ + public function auth() + { + try { + if (empty(self::$oProvider->GetAccessToken())) { + throw new IdentityProviderException('Not prior authentication to OAuth', 255, []); + } elseif (self::$oProvider->GetAccessToken()->hasExpired()) { + self::$oProvider->SetAccessToken(self::$oProvider->GetVendorProvider()->getAccessToken('refresh_token', [ + 'refresh_token' => self::$oProvider->GetAccessToken()->getRefreshToken(), + 'scope' => self::$oProvider->GetScope(), + ])); + } + } + catch (IdentityProviderException $e) { + IssueLog::Error('Failed to get oAuth credentials for outgoing mails for provider '.self::$oProvider::GetVendorName(), static::LOG_CHANNEL); + + return false; + } + $sAccessToken = self::$oProvider->GetAccessToken()->getToken(); + + if (empty($sAccessToken)) { + IssueLog::Error('No OAuth token for outgoing mails for provider '.self::$oProvider::GetVendorName(), static::LOG_CHANNEL); + + return false; + } + + $this->_send('AUTH XOAUTH2 '.base64_encode("user=$this->username\1auth=Bearer $sAccessToken\1\1")); + IssueLog::Debug("SMTP Oauth sending AUTH XOAUTH2 user=$this->username auth=Bearer $sAccessToken", static::LOG_CHANNEL); + + try { + while (true) { + $sResponse = $this->_receive(60); + + IssueLog::Debug("SMTP Oauth receiving $sResponse", static::LOG_CHANNEL); + + if ($sResponse === '+') { + // Send empty client response. + $this->_send(''); + } else { + if (preg_match('/Unauthorized/i', $sResponse) || + preg_match('/Rejected/i', $sResponse) || + preg_match('/^(535|432|454|534|500|530|538)/', $sResponse)) { + IssueLog::Error('Unable to authenticate for outgoing mails for provider '.self::$oProvider::GetVendorName()." Error: $sResponse", static::LOG_CHANNEL); + + return false; + } + if (preg_match("/OK /i", $sResponse) || + preg_match('/Accepted/i', $sResponse) || + preg_match('/^235/i', $sResponse)) { + $this->auth = true; + + return true; + } + + } + } + } catch (RuntimeException $e) { + IssueLog::Error('Timeout connection for outgoing mails for provider '.self::$oProvider::GetVendorName(), static::LOG_CHANNEL); + } + return false; + } +} diff --git a/sources/Core/Email/EmailFactory.php b/sources/Core/Email/EmailFactory.php new file mode 100644 index 000000000..56d84f21b --- /dev/null +++ b/sources/Core/Email/EmailFactory.php @@ -0,0 +1,6 @@ + + + +/** + * Send an email (abstraction for synchronous/asynchronous modes) + * + * @copyright Copyright (C) 2010-2021 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ +@include APPROOT."/core/oauth.php"; + +use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory; +use Laminas\Mail\Header\ContentType; +use Laminas\Mail\Message; +use Laminas\Mail\Transport\File; +use Laminas\Mail\Transport\FileOptions; +use Laminas\Mail\Transport\Smtp; +use Laminas\Mail\Transport\SmtpOptions; +use Laminas\Mime\Mime; +use Laminas\Mime\Part; +use Pelago\Emogrifier\CssInliner; +use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; +use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; + +define ('EMAIL_SEND_OK', 0); +define ('EMAIL_SEND_PENDING', 1); +define ('EMAIL_SEND_ERROR', 2); + +class EMail +{ + // Serialization formats + const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. + // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string + const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) + + protected static $m_oConfig = null; + protected $m_aData; // For storing data to serialize + + public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE) + { + if (is_null(self::$m_oConfig)) + { + self::$m_oConfig = new Config($sConfigFile); + } + } + + protected $m_oMessage; + + public function __construct() + { + $this->m_aData = array(); + $this->m_oMessage = new Message(); + $this->m_oMessage->setEncoding('UTF-8'); + $this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label')); + } + + /** + * Custom serialization method + * No longer use the brute force "serialize" method since + * 1) It does not work with binary attachments (since they cannot be stored in a UTF-8 text field) + * 2) The size tends to be quite big (sometimes ten times the size of the email) + */ + public function SerializeV2() + { + return serialize($this->m_aData); + } + + /** + * Custom de-serialization method + * + * @param string $sSerializedMessage The serialized representation of the message + * + * @return \EMail + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException + */ + public static function UnSerializeV2($sSerializedMessage) + { + $aData = unserialize($sSerializedMessage); + $oMessage = new Email(); + + if (array_key_exists('body', $aData)) + { + $oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']); + } + if (array_key_exists('message_id', $aData)) + { + $oMessage->SetMessageId($aData['message_id']); + } + if (array_key_exists('bcc', $aData)) + { + $oMessage->SetRecipientBCC($aData['bcc']); + } + if (array_key_exists('cc', $aData)) + { + $oMessage->SetRecipientCC($aData['cc']); + } + if (array_key_exists('from', $aData)) + { + $oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']); + } + if (array_key_exists('reply_to', $aData)) + { + $oMessage->SetRecipientReplyTo($aData['reply_to']['address'], $aData['reply_to']['label']); + } + if (array_key_exists('to', $aData)) + { + $oMessage->SetRecipientTO($aData['to']); + } + if (array_key_exists('subject', $aData)) + { + $oMessage->SetSubject($aData['subject']); + } + + if (array_key_exists('headers', $aData)) + { + foreach($aData['headers'] as $sKey => $sValue) + { + $oMessage->AddToHeader($sKey, $sValue); + } + } + if (array_key_exists('parts', $aData)) + { + foreach($aData['parts'] as $aPart) + { + $oMessage->AddPart($aPart['text'], $aPart['mimeType']); + } + } + if (array_key_exists('attachments', $aData)) + { + foreach($aData['attachments'] as $aAttachment) + { + $oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']); + } + } + return $oMessage; + } + + protected function SendAsynchronous(&$aIssues, $oLog = null) + { + try + { + AsyncSendEmail::AddToQueue($this, $oLog); + } + catch(Exception $e) + { + $aIssues = array($e->GetMessage()); + return EMAIL_SEND_ERROR; + } + $aIssues = array(); + return EMAIL_SEND_PENDING; + } + + /** + * @throws \Exception + */ + protected function SendSynchronous(&$aIssues, $oLog = null) + { + + $this->LoadConfig(); + + $sTransport = self::$m_oConfig->Get('email_transport'); + switch ($sTransport) + { + case 'SMTP': + $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); + $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); + $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); + $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); + $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); + + $oTransport = new Smtp(); + $aOptions= [ + 'host' => $sHost, + 'port' => $sPort, + 'connection_class' => 'login', + 'connection_config' => [ + 'ssl' => $sEncryption, + ], + ]; + if (strlen($sUserName) > 0) + { + $aOptions['connection_config']['username'] = $sUserName; + $aOptions['connection_config']['password'] = $sPassword; + } + $oOptions = new SmtpOptions($aOptions); + $oTransport->setOptions($oOptions); + break; + case 'SMTP_OAuth': + $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); + $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); + $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); + $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); + + $oTransport = new Smtp(); + $aOptions= [ + 'host' => $sHost, + 'port' => $sPort, + 'connection_class' => 'Laminas\Mail\Protocol\Smtp\Auth\Oauth', + 'connection_config' => [ + 'ssl' => $sEncryption, + ], + ]; + if (strlen($sUserName) > 0) + { + $aOptions['connection_config']['username'] = $sUserName; + } + $oOptions = new SmtpOptions($aOptions); + $oTransport->setOptions($oOptions); + + \Laminas\Mail\Protocol\Smtp\Auth\Oauth::setProvider(OAuthClientProviderFactory::getProviderForSMTP()); + break; + case 'Null': + $oTransport = new Smtp(); + break; + + case 'LogFile': + $oTransport = new File(); + $aOptions = new FileOptions([ + 'path' => APPROOT.'log/mail.log', + ]); + $oTransport->setOptions($aOptions); + break; + + case 'PHPMail': + default: + $oTransport = new Smtp(); + } + + $oKPI = new ExecutionKPI(); + try + { + $oTransport->send($this->m_oMessage); + $aIssues = array(); + $oKPI->ComputeStats('Email Sent', 'Succeded'); + return EMAIL_SEND_OK; + } + catch(Laminas\Mail\Transport\Exception\RuntimeException $e){ + IssueLog::Warning('Email sending failed: Some recipients were invalid'); + $aIssues = array('Some recipients were invalid.'); + $oKPI->ComputeStats('Email Sent', 'Error received'); + return EMAIL_SEND_ERROR; + } + catch (Exception $e) + { + $oKPI->ComputeStats('Email Sent', 'Error received'); + throw $e; + } + } + + /** + * Reprocess the body of the message (if it is an HTML message) + * to replace the URL of images based on attachments by a link + * to an embedded image (i.e. cid:....) and returns images to be attached as an array + * + * @param string $sBody Email body to process/alter + * + * @return array Array of Part that needs to be added as inline attachment later to render as embed + * @throws \ArchivedObjectException + * @throws \CoreException + */ + protected function EmbedInlineImages(string &$sBody) + { + $oDOMDoc = new DOMDocument(); + $oDOMDoc->preserveWhitespace = true; + @$oDOMDoc->loadHTML(''.$sBody); // For loading HTML chunks where the character set is not specified + + $oXPath = new DOMXPath($oDOMDoc); + $sXPath = '//img[@'.InlineImage::DOM_ATTR_ID.']'; + $oImagesList = $oXPath->query($sXPath); + $oImagesContent = new \Laminas\Mime\Message(); + $aImagesParts = []; + if ($oImagesList->length != 0) + { + foreach($oImagesList as $oImg) + { + $iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID); + $oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */); + if ($oAttachment) + { + $sImageSecret = $oImg->getAttribute('data-img-secret'); + $sAttachmentSecret = $oAttachment->Get('secret'); + if ($sImageSecret !== $sAttachmentSecret) + { + // @see N°1921 + // If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret + continue; + } + + $oDoc = $oAttachment->Get('contents'); + + $sCid = uniqid('', true); + + $oNewAttachment = new Part($oDoc->GetData()); + $oNewAttachment->id = $sCid; + $oNewAttachment->type = $oDoc->GetMimeType(); + $oNewAttachment->filename = $oDoc->GetFileName(); + $oNewAttachment->disposition = Mime::DISPOSITION_INLINE; + $oNewAttachment->encoding = Mime::ENCODING_BASE64; + + $oImagesContent->addPart($oNewAttachment); + $oImg->setAttribute('src', 'cid:'.$sCid); + $aImagesParts[] = $oNewAttachment; + } + } + } + $sBody = $oDOMDoc->saveHTML(); + return $aImagesParts; + } + + public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null) + { + //select a default sender if none is provided. + if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){ + $this->SetRecipientFrom($this->m_aData['to']); + } + + if ($bForceSynchronous) + { + return $this->SendSynchronous($aIssues, $oLog); + } + else + { + $bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous'); + if ($bConfigASYNC) + { + return $this->SendAsynchronous($aIssues, $oLog); + } + else + { + return $this->SendSynchronous($aIssues, $oLog); + } + } + } + + public function AddToHeader($sKey, $sValue) + { + if (!array_key_exists('headers', $this->m_aData)) + { + $this->m_aData['headers'] = array(); + } + $this->m_aData['headers'][$sKey] = $sValue; + + if (strlen($sValue) > 0) + { + $oHeaders = $this->m_oMessage->getHeaders(); + switch(strtolower($sKey)) + { + case 'return-path': + $this->m_oMessage->setReturnPath($sValue); + break; + + default: + $oHeaders->addHeaderLine($sKey, $sValue); + } + } + } + + public function SetMessageId($sId) + { + $this->m_aData['message_id'] = $sId; + + // Note: Swift will add the angle brackets for you + // so let's remove the angle brackets if present, for historical reasons + $sId = str_replace(array('<', '>'), '', $sId); + + $this->m_oMessage->getHeaders()->addHeaderLine('Message-ID', $sId); + } + + public function SetReferences($sReferences) + { + $this->AddToHeader('References', $sReferences); + } + + /** + * Set the "In-Reply-To" header to allow emails to group as a conversation in modern mail clients (GMail, Outlook 2016+, ...) + * + * @link https://en.wikipedia.org/wiki/Email#Header_fields + * + * @param string $sMessageId + * + * @since 3.0.1 N°4849 + */ + public function SetInReplyTo(string $sMessageId) + { + $this->AddToHeader('In-Reply-To', $sMessageId); + } + + /** + * Set current Email body and process inline images. + * + * @param $sBody + * @param string $sMimeType + * @param $sCustomStyles + * + * @return void + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException + */ + public function SetBody($sBody, string $sMimeType = Mime::TYPE_HTML, $sCustomStyles = null) + { + $oBody = new Laminas\Mime\Message(); + $aAdditionalParts = []; + + if (($sMimeType === Mime::TYPE_HTML) && ($sCustomStyles !== null)) { + $oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument(); + HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone(); + $sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present + } + $this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType); + + // We don't want these modifications in m_aData['body'], otherwise it'll ruin asynchronous mail as they go through this method twice + if ($sMimeType === Mime::TYPE_HTML){ + $aAdditionalParts = $this->EmbedInlineImages($sBody); + } + + // Add body content to as a new part + $oNewPart = new Part($sBody); + $oNewPart->encoding = Mime::ENCODING_8BIT; + $oNewPart->type = $sMimeType; + $oBody->addPart($oNewPart); + + // Add additional images as new body parts + foreach ($aAdditionalParts as $oAdditionalPart) { + $oBody->addPart($oAdditionalPart); + } + + if($oBody->isMultiPart()){ + $oContentTypeHeader = $this->m_oMessage->getHeaders(); + foreach ($oContentTypeHeader as $oHeader) { + if (!$oHeader instanceof ContentType) { + continue; + } + + $oHeader->setType(Mime::MULTIPART_MIXED); + $oHeader->addParameter('boundary', $oBody->getMime()->boundary()); + break; + } + } + + $this->m_oMessage->setBody($oBody); + } + + /** + * Add a new part to the existing body + * @param $sText + * @param string $sMimeType + * + * @return void + */ + public function AddPart($sText, string $sMimeType = Mime::TYPE_HTML) + { + if (!array_key_exists('parts', $this->m_aData)) + { + $this->m_aData['parts'] = array(); + } + $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType); + $oNewPart = new Part($sText); + $oNewPart->encoding = Mime::ENCODING_8BIT; + $oNewPart->type = $sMimeType; + $this->m_oMessage->getBody()->addPart($oNewPart); + } + + public function AddAttachment($data, $sFileName, $sMimeType) + { + $oBody = $this->m_oMessage->getBody(); + + if(!$oBody->isMultiPart()){ + $multipart_content = new Part($oBody->generateMessage()); + $multipart_content->setType($oBody->getParts()[0]->getType()); + $multipart_content->setBoundary($oBody->getMime()->boundary()); + + $oBody = new Laminas\Mime\Message(); + $oBody->addPart($multipart_content); + } + + if (!array_key_exists('attachments', $this->m_aData)) + { + $this->m_aData['attachments'] = array(); + } + $this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType); + $oNewAttachment = new Part($data); + $oNewAttachment->type = $sMimeType; + $oNewAttachment->filename = $sFileName; + $oNewAttachment->disposition = Mime::DISPOSITION_ATTACHMENT; + $oNewAttachment->encoding = Mime::ENCODING_BASE64; + + + $oBody->addPart($oNewAttachment); + + if($oBody->isMultiPart()){ + $oContentTypeHeader = $this->m_oMessage->getHeaders(); + foreach ($oContentTypeHeader as $oHeader) { + if (!$oHeader instanceof ContentType) { + continue; + } + + $oHeader->setType(Mime::MULTIPART_MIXED); + $oHeader->addParameter('boundary', $oBody->getMime()->boundary()); + break; + } + } + + $this->m_oMessage->setBody($oBody); + } + + public function SetSubject($sSubject) + { + $this->m_aData['subject'] = $sSubject; + $this->m_oMessage->setSubject($sSubject); + } + + public function GetSubject() + { + return $this->m_oMessage->getSubject(); + } + + /** + * Helper to transform and sanitize addresses + * - get rid of empty addresses + */ + protected function AddressStringToArray($sAddressCSVList) + { + $aAddresses = array(); + foreach(explode(',', $sAddressCSVList) as $sAddress) + { + $sAddress = trim($sAddress); + if (strlen($sAddress) > 0) + { + $aAddresses[] = $sAddress; + } + } + return $aAddresses; + } + + public function SetRecipientTO($sAddress) + { + $this->m_aData['to'] = $sAddress; + if (!empty($sAddress)) + { + $aAddresses = $this->AddressStringToArray($sAddress); + $this->m_oMessage->setTo($aAddresses); + } + } + + public function GetRecipientTO($bAsString = false) + { + $aRes = $this->m_oMessage->getTo(); + if ($aRes === null || $aRes->count() === 0) + { + // There is no "To" header field + $aRes = array(); + } + if ($bAsString) + { + $aStrings = array(); + foreach ($aRes as $oEmail) + { + $sName = $oEmail->getName(); + $sEmail = $oEmail->getEmail(); + if (is_null($sName)) + { + $aStrings[] = $sEmail; + } + else + { + $sName = str_replace(array('<', '>'), '', $sName); + $aStrings[] = "$sName <$sEmail>"; + } + } + return implode(', ', $aStrings); + } + else + { + return $aRes; + } + } + + public function SetRecipientCC($sAddress) + { + $this->m_aData['cc'] = $sAddress; + if (!empty($sAddress)) + { + $aAddresses = $this->AddressStringToArray($sAddress); + $this->m_oMessage->setCc($aAddresses); + } + } + + public function SetRecipientBCC($sAddress) + { + $this->m_aData['bcc'] = $sAddress; + if (!empty($sAddress)) + { + $aAddresses = $this->AddressStringToArray($sAddress); + $this->m_oMessage->setBcc($aAddresses); + } + } + + public function SetRecipientFrom($sAddress, $sLabel = '') + { + $this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel); + if ($sLabel != '') + { + $this->m_oMessage->setFrom(array($sAddress => $sLabel)); + } + else if (!empty($sAddress)) + { + $this->m_oMessage->setFrom($sAddress); + } + } + + public function SetRecipientReplyTo($sAddress, $sLabel = '') + { + $this->m_aData['reply_to'] = array('address' => $sAddress, 'label' => $sLabel); + if ($sLabel != '') + { + $this->m_oMessage->setReplyTo(array($sAddress => $sLabel)); + } + else if (!empty($sAddress)) + { + $this->m_oMessage->setReplyTo($sAddress); + } + } + +} diff --git a/sources/Core/Email/EmailSwiftMailer.php b/sources/Core/Email/EmailSwiftMailer.php new file mode 100644 index 000000000..b0454a049 --- /dev/null +++ b/sources/Core/Email/EmailSwiftMailer.php @@ -0,0 +1,550 @@ + + + +/** + * Send an email (abstraction for synchronous/asynchronous modes) + * + * @copyright Copyright (C) 2010-2016 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +use Pelago\Emogrifier\CssInliner; +use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; +use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; + +Swift_Preferences::getInstance()->setCharset('UTF-8'); + + +define ('EMAIL_SEND_OK', 0); +define ('EMAIL_SEND_PENDING', 1); +define ('EMAIL_SEND_ERROR', 2); + +class EmailSwiftMailer +{ + // Serialization formats + const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. + // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string + const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) + + protected static $m_oConfig = null; + protected $m_aData; // For storing data to serialize + + public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE) + { + if (is_null(self::$m_oConfig)) + { + self::$m_oConfig = new Config($sConfigFile); + } + } + + protected $m_oMessage; + + public function __construct() + { + $this->m_aData = array(); + $this->m_oMessage = new Swift_Message(); + $this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label')); + } + + /** + * Custom serialization method + * No longer use the brute force "serialize" method since + * 1) It does not work with binary attachments (since they cannot be stored in a UTF-8 text field) + * 2) The size tends to be quite big (sometimes ten times the size of the email) + */ + public function SerializeV2() + { + return serialize($this->m_aData); + } + + /** + * Custom de-serialization method + * @param string $sSerializedMessage The serialized representation of the message + */ + static public function UnSerializeV2($sSerializedMessage) + { + $aData = unserialize($sSerializedMessage); + $oMessage = new Email(); + + if (array_key_exists('body', $aData)) + { + $oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']); + } + if (array_key_exists('message_id', $aData)) + { + $oMessage->SetMessageId($aData['message_id']); + } + if (array_key_exists('bcc', $aData)) + { + $oMessage->SetRecipientBCC($aData['bcc']); + } + if (array_key_exists('cc', $aData)) + { + $oMessage->SetRecipientCC($aData['cc']); + } + if (array_key_exists('from', $aData)) + { + $oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']); + } + if (array_key_exists('reply_to', $aData)) + { + $oMessage->SetRecipientReplyTo($aData['reply_to']); + } + if (array_key_exists('to', $aData)) + { + $oMessage->SetRecipientTO($aData['to']); + } + if (array_key_exists('subject', $aData)) + { + $oMessage->SetSubject($aData['subject']); + } + + + if (array_key_exists('headers', $aData)) + { + foreach($aData['headers'] as $sKey => $sValue) + { + $oMessage->AddToHeader($sKey, $sValue); + } + } + if (array_key_exists('parts', $aData)) + { + foreach($aData['parts'] as $aPart) + { + $oMessage->AddPart($aPart['text'], $aPart['mimeType']); + } + } + if (array_key_exists('attachments', $aData)) + { + foreach($aData['attachments'] as $aAttachment) + { + $oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']); + } + } + return $oMessage; + } + + protected function SendAsynchronous(&$aIssues, $oLog = null) + { + try + { + AsyncSendEmail::AddToQueue($this, $oLog); + } + catch(Exception $e) + { + $aIssues = array($e->GetMessage()); + return EMAIL_SEND_ERROR; + } + $aIssues = array(); + return EMAIL_SEND_PENDING; + } + + protected function SendSynchronous(&$aIssues, $oLog = null) + { + // If the body of the message is in HTML, embed all images based on attachments + $this->EmbedInlineImages(); + + $this->LoadConfig(); + + $sTransport = self::$m_oConfig->Get('email_transport'); + switch ($sTransport) + { + case 'SMTP': + $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); + $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); + $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); + $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); + $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); + + $oTransport = new Swift_SmtpTransport($sHost, $sPort, $sEncryption); + if (strlen($sUserName) > 0) + { + $oTransport->setUsername($sUserName); + $oTransport->setPassword($sPassword); + } + break; + + case 'Null': + $oTransport = new Swift_NullTransport(); + break; + + case 'LogFile': + $oTransport = new Swift_LogFileTransport(); + $oTransport->setLogFile(APPROOT.'log/mail.log'); + break; + + case 'PHPMail': + default: + $oTransport = new Swift_SendmailTransport(); + } + + $oMailer = new Swift_Mailer($oTransport); + + $aFailedRecipients = array(); + $this->m_oMessage->setMaxLineLength(0); + $oKPI = new ExecutionKPI(); + try + { + $iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients); + if ($iSent === 0) + { + // Beware: it seems that $aFailedRecipients sometimes contains the recipients that actually received the message !!! + IssueLog::Warning('Email sending failed: Some recipients were invalid, aFailedRecipients contains: '.implode(', ', $aFailedRecipients)); + $aIssues = array('Some recipients were invalid.'); + $oKPI->ComputeStats('Email Sent', 'Error received'); + return EMAIL_SEND_ERROR; + } + else + { + $aIssues = array(); + $oKPI->ComputeStats('Email Sent', 'Succeded'); + return EMAIL_SEND_OK; + } + } + catch (Exception $e) + { + $oKPI->ComputeStats('Email Sent', 'Error received'); + throw $e; + } + } + + /** + * Reprocess the body of the message (if it is an HTML message) + * to replace the URL of images based on attachments by a link + * to an embedded image (i.e. cid:....) + */ + protected function EmbedInlineImages() + { + if ($this->m_aData['body']['mimeType'] == 'text/html') + { + $oDOMDoc = new DOMDocument(); + $oDOMDoc->preserveWhitespace = true; + @$oDOMDoc->loadHTML(''.$this->m_aData['body']['body']); // For loading HTML chunks where the character set is not specified + + $oXPath = new DOMXPath($oDOMDoc); + $sXPath = '//img[@'.InlineImage::DOM_ATTR_ID.']'; + $oImagesList = $oXPath->query($sXPath); + + if ($oImagesList->length != 0) + { + foreach($oImagesList as $oImg) + { + $iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID); + $oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */); + if ($oAttachment) + { + $sImageSecret = $oImg->getAttribute('data-img-secret'); + $sAttachmentSecret = $oAttachment->Get('secret'); + if ($sImageSecret !== $sAttachmentSecret) + { + // @see N°1921 + // If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret + continue; + } + + $oDoc = $oAttachment->Get('contents'); + $oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType()); + $sCid = $this->m_oMessage->embed($oSwiftImage); + $oImg->setAttribute('src', $sCid); + } + } + } + $sHtmlBody = $oDOMDoc->saveHTML(); + $this->m_oMessage->setBody($sHtmlBody, 'text/html', 'UTF-8'); + } + } + + public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null) + { + //select a default sender if none is provided. + if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){ + $this->SetRecipientFrom($this->m_aData['to']); + } + + if ($bForceSynchronous) + { + return $this->SendSynchronous($aIssues, $oLog); + } + else + { + $bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous'); + if ($bConfigASYNC) + { + return $this->SendAsynchronous($aIssues, $oLog); + } + else + { + return $this->SendSynchronous($aIssues, $oLog); + } + } + } + + public function AddToHeader($sKey, $sValue) + { + if (!array_key_exists('headers', $this->m_aData)) + { + $this->m_aData['headers'] = array(); + } + $this->m_aData['headers'][$sKey] = $sValue; + + if (strlen($sValue) > 0) + { + $oHeaders = $this->m_oMessage->getHeaders(); + switch(strtolower($sKey)) + { + case 'return-path': + $this->m_oMessage->setReturnPath($sValue); + break; + + default: + $oHeaders->addTextHeader($sKey, $sValue); + } + } + } + + public function SetMessageId($sId) + { + $this->m_aData['message_id'] = $sId; + + // Note: Swift will add the angle brackets for you + // so let's remove the angle brackets if present, for historical reasons + $sId = str_replace(array('<', '>'), '', $sId); + + $oMsgId = $this->m_oMessage->getHeaders()->get('Message-ID'); + $oMsgId->SetId($sId); + } + + public function SetReferences($sReferences) + { + $this->AddToHeader('References', $sReferences); + } + + public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null) + { + if (($sMimeType === 'text/html') && ($sCustomStyles !== null)) + { + $oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument(); + HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone(); + $sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present + } + $this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType); + $this->m_oMessage->setBody($sBody, $sMimeType); + } + + public function AddPart($sText, $sMimeType = 'text/html') + { + if (!array_key_exists('parts', $this->m_aData)) + { + $this->m_aData['parts'] = array(); + } + $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType); + $this->m_oMessage->addPart($sText, $sMimeType); + } + + public function AddAttachment($data, $sFileName, $sMimeType) + { + if (!array_key_exists('attachments', $this->m_aData)) + { + $this->m_aData['attachments'] = array(); + } + $this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType); + $this->m_oMessage->attach(new Swift_Attachment($data, $sFileName, $sMimeType)); + } + + public function SetSubject($sSubject) + { + $this->m_aData['subject'] = $sSubject; + $this->m_oMessage->setSubject($sSubject); + } + + public function GetSubject() + { + return $this->m_oMessage->getSubject(); + } + + /** + * Helper to transform and sanitize addresses + * - get rid of empty addresses + */ + protected function AddressStringToArray($sAddressCSVList) + { + $aAddresses = array(); + foreach(explode(',', $sAddressCSVList) as $sAddress) + { + $sAddress = trim($sAddress); + if (strlen($sAddress) > 0) + { + $aAddresses[] = $sAddress; + } + } + return $aAddresses; + } + + public function SetRecipientTO($sAddress) + { + $this->m_aData['to'] = $sAddress; + if (!empty($sAddress)) + { + $aAddresses = $this->AddressStringToArray($sAddress); + $this->m_oMessage->setTo($aAddresses); + } + } + + public function GetRecipientTO($bAsString = false) + { + $aRes = $this->m_oMessage->getTo(); + if ($aRes === null) + { + // There is no "To" header field + $aRes = array(); + } + if ($bAsString) + { + $aStrings = array(); + foreach ($aRes as $sEmail => $sName) + { + if (is_null($sName)) + { + $aStrings[] = $sEmail; + } + else + { + $sName = str_replace(array('<', '>'), '', $sName); + $aStrings[] = "$sName <$sEmail>"; + } + } + return implode(', ', $aStrings); + } + else + { + return $aRes; + } + } + + public function SetRecipientCC($sAddress) + { + $this->m_aData['cc'] = $sAddress; + if (!empty($sAddress)) + { + $aAddresses = $this->AddressStringToArray($sAddress); + $this->m_oMessage->setCc($aAddresses); + } + } + + public function SetRecipientBCC($sAddress) + { + $this->m_aData['bcc'] = $sAddress; + if (!empty($sAddress)) + { + $aAddresses = $this->AddressStringToArray($sAddress); + $this->m_oMessage->setBcc($aAddresses); + } + } + + public function SetRecipientFrom($sAddress, $sLabel = '') + { + $this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel); + if ($sLabel != '') + { + $this->m_oMessage->setFrom(array($sAddress => $sLabel)); + } + else if (!empty($sAddress)) + { + $this->m_oMessage->setFrom($sAddress); + } + } + + public function SetRecipientReplyTo($sAddress) + { + $this->m_aData['reply_to'] = $sAddress; + if (!empty($sAddress)) + { + $this->m_oMessage->setReplyTo($sAddress); + } + } + +} + +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Extension to SwiftMailer: "debug" transport that pretends messages have been sent, + * but just log them to a file. + * + * @package Swift + * @author Denis Flaven + */ +class Swift_Transport_LogFileTransport extends Swift_Transport_NullTransport +{ + protected $sLogFile; + + /** + * @inheritDoc + */ + public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) + { + $hFile = @fopen($this->sLogFile, 'a'); + if ($hFile) { + $sTxt = "================== ".date('Y-m-d H:i:s')." ==================\n"; + $sTxt .= $message->toString()."\n"; + + @fwrite($hFile, $sTxt); + @fclose($hFile); + } + + return parent::send($message, $failedRecipients); + } + + public function setLogFile($sFilename) + { + $this->sLogFile = $sFilename; + } +} + +/** + * Pretends messages have been sent, but just log them to a file. + * + * @package Swift + * @author Denis Flaven + */ +class Swift_LogFileTransport extends Swift_Transport_LogFileTransport +{ + /** + * Create a new LogFileTransport. + */ + public function __construct(Swift_Events_EventDispatcher $eventDispatcher) + { + parent::__construct($eventDispatcher); + call_user_func_array( + array($this, 'Swift_Transport_LogFileTransport::__construct'), + Swift_DependencyContainer::getInstance() + ->createDependenciesFor('transport.null') + ); + } + + /** + * Create a new LogFileTransport instance. + * + * @return Swift_LogFileTransport + */ + public static function newInstance() + { + return new self(); + } +} \ No newline at end of file diff --git a/templates/pages/backoffice/oauth/Wizard.html.twig b/templates/pages/backoffice/oauth/Wizard.html.twig new file mode 100644 index 000000000..d13590525 --- /dev/null +++ b/templates/pages/backoffice/oauth/Wizard.html.twig @@ -0,0 +1,48 @@ +{# @copyright Copyright (C) 2010-2022 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} + +
+

{{ 'UI:OAuth:Wizard:Page:Title'|dict_s }}

+ +
+ +
+
+ {% set sIsChecked = 'checked' %} + {% for aSelect in aProviders %} + + + + {% set sIsChecked = '' %} + {% endfor %} +
+
+ +
+ {{ 'UI:OAuth:Wizard:Form:Panel:Title'|dict_s }} +
+ + {% for sName, aInput in aInputs %} +
+
+
+
+ +
+
+
+ {% endfor %} +
+
+
+ + +
\ No newline at end of file From 4b870bcf1e516c67f5638fd2b95ef01793d14363 Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Thu, 12 May 2022 17:38:38 +0200 Subject: [PATCH 02/41] =?UTF-8?q?N=C2=B02504=20N=C2=B03169=20N=C2=B05102?= =?UTF-8?q?=20Add=20js=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/backoffice/oauth/Wizard.html.twig | 1 + .../backoffice/oauth/Wizard.ready.js.twig | 126 ++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 templates/pages/backoffice/oauth/Wizard.ready.js.twig diff --git a/templates/pages/backoffice/oauth/Wizard.html.twig b/templates/pages/backoffice/oauth/Wizard.html.twig index d13590525..b2b70f92f 100644 --- a/templates/pages/backoffice/oauth/Wizard.html.twig +++ b/templates/pages/backoffice/oauth/Wizard.html.twig @@ -40,6 +40,7 @@ {% endfor %} + diff --git a/templates/pages/backoffice/oauth/Wizard.ready.js.twig b/templates/pages/backoffice/oauth/Wizard.ready.js.twig new file mode 100644 index 000000000..bac63f187 --- /dev/null +++ b/templates/pages/backoffice/oauth/Wizard.ready.js.twig @@ -0,0 +1,126 @@ +// Function used to open OAuth popup +var oWindowObjectReference = null; +var sPreviousUrl = null; +var oListener = null + +const oOnOauthSuccess = function (event){ + clearInterval(oListener); + $.post( + {{ sAjaxUri }}, + { + operation: 'get_display_authentication_results', + provider: $('[name="provider"]:checked').val(), + client_id: $('[name="client_id"]').val(), + client_secret: $('[name="client_secret"]').val(), + scope: $(this).find('[name="scope"]').val(), + additional: $(this).find('[name="additional"]').val(), + redirect_url: event.data + }, + function(oData){ + if(oData.status == 'success') + { + oData.data.forEach(function(item, index){ + new Function(item)(); + }); + } + $('.ibo-oauth-wizard--form--submit').trigger('leave_loading_state.button.itop'); + } + ); +} +const oOpenSignInWindow = function (url, name){ + // Remove any existing event listener + window.removeEventListener('message', oOnOauthSuccess); + if(oListener !== null){ + clearInterval(oListener); + } + + // Window features + const sWindowFeatures = 'toolbar=no, menubar=no, width=600, height=700, top=100, left=100'; + + if (oWindowObjectReference === null || oWindowObjectReference.closed) { + /* If the pointer to the window object in memory does not exist + or if such pointer exists but the window was closed */ + oWindowObjectReference = window.open(url, name, sWindowFeatures); + } else if (sPreviousUrl !== url) { + /* If the resource to load is different, + then we load it in the already opened secondary window, and then + we bring such window back on top/in front of its parent window. */ + oWindowObjectReference = window.open(url, name, sWindowFeatures); + oWindowObjectReference.focus(); + } else { + /* Else the window reference must exist and the window + is not closed; therefore, we can bring it back on top of any other + window with the focus() method. There would be no need to re-create + the window or to reload the referenced resource. */ + oWindowObjectReference.focus(); + } + /* Let know every second our child window that we're waiting for it to complete, + once we reach our landing page, it'll send us a reply + */ + oListener = window.setInterval(function(){ + oWindowObjectReference.postMessage('anyone', {{ sReturnUri }}); + }, 1000); + /* Once we receive a response, transmit it to the server to get authenticate and display + results + */ + window.addEventListener('message', oOnOauthSuccess, false); + // Assign the previous URL + sPreviousUrl = url; +}; + +// Function used when the form is submitted + +const oOnFormSubmit = function(){ + $('.ibo-oauth-wizard--form--submit').trigger('enter_loading_state.button.itop'); + $.post( + {{ sAjaxUri }}, + { + operation: 'get_authorization_url', + provider: $('[name="provider"]:checked').val(), + client_id: $(this).find('[name="client_id"]').val(), + client_secret: $(this).find('[name="client_secret"]').val(), + scope: $(this).find('[name="scope"]').val(), + additional: $(this).find('[name="additional"]').val() + }, + function(oData){ + if(oData.status == 'success') + { + oOpenSignInWindow(oData.data.authorization_url, 'OAuth authorization') + } + else{ + $('.ibo-oauth-wizard--form--submit').trigger('leave_loading_state.button.itop'); + } + } + ); + return false; +} + +// Function used to update provider image + +function oUpdateProviderImage(elem){ + + var oColor1 = $(elem).prev('[name="provider"]').attr('data-color1'); + var oColor2 = $(elem).prev('[name="provider"]').attr('data-color2'); + var oColor4 = $(elem).prev('[name="provider"]').attr('data-color3'); + var oColor3 = $(elem).prev('[name="provider"]').attr('data-color4'); + + $('#fcef55fc-4968-45b2-93bb-1a1080c85fc7').attr('fill', oColor1); + $('#e8fa0310-b872-4adf-aedd-0c6eda09f3b8').attr('fill', oColor1); + $('#a4813fcf-056e-4514-bb8b-e6506f49341f').attr('fill', oColor1); + $('#e73810fe-4cf4-40cc-8c7c-ca544ce30bd4-108').attr('fill', oColor2); + $('#e12ee00d-aa4a-4413-a013-11d20b7f97f7').attr('fill', oColor2); + $('#b4d4939a-c6e6-4f4d-ba6c-e8b05485017d').attr('fill', oColor2); + $('#b06d66ec-6c84-45dd-8c27-1263a6253192-107').attr('fill', oColor3); + $('#f58f497e-6949-45c8-be5f-eee2aa0f6586').attr('fill', oColor3); + $('#aff120b1-519b-4e96-ac87-836aa55663de').attr('fill', oColor3); + $('#aff120b1-519b-4e96-ac87-836aa55663de').attr('fill', oColor3); + $('#ae7af94f-88d7-4204-9f07-e3651de85c05-111').attr('fill', oColor4); + $('#a6768b0e-63d0-4b31-8462-9b2e0b00f0fd-112').attr('fill', oColor4); +} +$('body').on('click', '.ui-checkboxradio-radio-label', function(){ + oUpdateProviderImage(this) +}) +oUpdateProviderImage($('[name="provider"]:checked').next()); + +// Initialize provider buttons +$('#select_layout').controlgroup(); From 134736dce5289b252d1017b3a2f3bbb03b6e63a8 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Fri, 13 May 2022 11:37:09 +0200 Subject: [PATCH 03/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=202.7=20migration=20(wip)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 3 +- .../datamodel.itop-welcome-itil.xml | 5 + lib/composer/autoload_classmap.php | 11 + lib/composer/autoload_static.php | 11 + lib/firebase/php-jwt/LICENSE | 30 + lib/firebase/php-jwt/README.md | 289 ++ lib/firebase/php-jwt/composer.json | 36 + .../php-jwt/src/BeforeValidException.php | 7 + lib/firebase/php-jwt/src/ExpiredException.php | 7 + lib/firebase/php-jwt/src/JWK.php | 172 + lib/firebase/php-jwt/src/JWT.php | 611 ++++ lib/firebase/php-jwt/src/Key.php | 59 + .../php-jwt/src/SignatureInvalidException.php | 7 + lib/guzzlehttp/guzzle/.php_cs | 23 + lib/guzzlehttp/guzzle/CHANGELOG.md | 1338 ++++++++ lib/guzzlehttp/guzzle/Dockerfile | 18 + lib/guzzlehttp/guzzle/LICENSE | 19 + lib/guzzlehttp/guzzle/README.md | 90 + lib/guzzlehttp/guzzle/UPGRADING.md | 1203 +++++++ lib/guzzlehttp/guzzle/composer.json | 59 + lib/guzzlehttp/guzzle/src/Client.php | 501 +++ lib/guzzlehttp/guzzle/src/ClientInterface.php | 87 + .../guzzle/src/Cookie/CookieJar.php | 316 ++ .../guzzle/src/Cookie/CookieJarInterface.php | 84 + .../guzzle/src/Cookie/FileCookieJar.php | 91 + .../guzzle/src/Cookie/SessionCookieJar.php | 72 + .../guzzle/src/Cookie/SetCookie.php | 403 +++ .../src/Exception/BadResponseException.php | 27 + .../guzzle/src/Exception/ClientException.php | 9 + .../guzzle/src/Exception/ConnectException.php | 37 + .../guzzle/src/Exception/GuzzleException.php | 23 + .../Exception/InvalidArgumentException.php | 7 + .../guzzle/src/Exception/RequestException.php | 192 ++ .../guzzle/src/Exception/SeekException.php | 27 + .../guzzle/src/Exception/ServerException.php | 9 + .../Exception/TooManyRedirectsException.php | 6 + .../src/Exception/TransferException.php | 6 + .../guzzle/src/Handler/CurlFactory.php | 585 ++++ .../src/Handler/CurlFactoryInterface.php | 27 + .../guzzle/src/Handler/CurlHandler.php | 45 + .../guzzle/src/Handler/CurlMultiHandler.php | 219 ++ .../guzzle/src/Handler/EasyHandle.php | 92 + .../guzzle/src/Handler/MockHandler.php | 195 ++ lib/guzzlehttp/guzzle/src/Handler/Proxy.php | 55 + .../guzzle/src/Handler/StreamHandler.php | 545 ++++ lib/guzzlehttp/guzzle/src/HandlerStack.php | 277 ++ .../guzzle/src/MessageFormatter.php | 185 ++ lib/guzzlehttp/guzzle/src/Middleware.php | 254 ++ lib/guzzlehttp/guzzle/src/Pool.php | 134 + .../guzzle/src/PrepareBodyMiddleware.php | 111 + .../guzzle/src/RedirectMiddleware.php | 255 ++ lib/guzzlehttp/guzzle/src/RequestOptions.php | 263 ++ lib/guzzlehttp/guzzle/src/RetryMiddleware.php | 128 + lib/guzzlehttp/guzzle/src/TransferStats.php | 126 + lib/guzzlehttp/guzzle/src/UriTemplate.php | 237 ++ lib/guzzlehttp/guzzle/src/Utils.php | 92 + lib/guzzlehttp/guzzle/src/functions.php | 334 ++ .../guzzle/src/functions_include.php | 6 + lib/guzzlehttp/promises/CHANGELOG.md | 103 + lib/guzzlehttp/promises/LICENSE | 24 + lib/guzzlehttp/promises/Makefile | 13 + lib/guzzlehttp/promises/README.md | 547 ++++ lib/guzzlehttp/promises/composer.json | 58 + .../promises/src/AggregateException.php | 17 + .../promises/src/CancellationException.php | 10 + lib/guzzlehttp/promises/src/Coroutine.php | 169 + lib/guzzlehttp/promises/src/Create.php | 84 + lib/guzzlehttp/promises/src/Each.php | 90 + lib/guzzlehttp/promises/src/EachPromise.php | 255 ++ .../promises/src/FulfilledPromise.php | 84 + lib/guzzlehttp/promises/src/Is.php | 46 + lib/guzzlehttp/promises/src/Promise.php | 278 ++ .../promises/src/PromiseInterface.php | 97 + .../promises/src/PromisorInterface.php | 16 + .../promises/src/RejectedPromise.php | 91 + .../promises/src/RejectionException.php | 48 + lib/guzzlehttp/promises/src/TaskQueue.php | 67 + .../promises/src/TaskQueueInterface.php | 24 + lib/guzzlehttp/promises/src/Utils.php | 276 ++ lib/guzzlehttp/promises/src/functions.php | 363 +++ .../promises/src/functions_include.php | 6 + lib/guzzlehttp/psr7/.github/FUNDING.yml | 2 + lib/guzzlehttp/psr7/.github/stale.yml | 14 + lib/guzzlehttp/psr7/.github/workflows/ci.yml | 34 + .../psr7/.github/workflows/integration.yml | 37 + .../psr7/.github/workflows/static.yml | 29 + lib/guzzlehttp/psr7/.php_cs.dist | 56 + lib/guzzlehttp/psr7/CHANGELOG.md | 312 ++ lib/guzzlehttp/psr7/LICENSE | 26 + lib/guzzlehttp/psr7/README.md | 824 +++++ lib/guzzlehttp/psr7/composer.json | 76 + lib/guzzlehttp/psr7/src/AppendStream.php | 246 ++ lib/guzzlehttp/psr7/src/BufferStream.php | 142 + lib/guzzlehttp/psr7/src/CachingStream.php | 147 + lib/guzzlehttp/psr7/src/DroppingStream.php | 45 + lib/guzzlehttp/psr7/src/FnStream.php | 163 + lib/guzzlehttp/psr7/src/Header.php | 71 + lib/guzzlehttp/psr7/src/InflateStream.php | 56 + lib/guzzlehttp/psr7/src/LazyOpenStream.php | 42 + lib/guzzlehttp/psr7/src/LimitStream.php | 157 + lib/guzzlehttp/psr7/src/Message.php | 252 ++ lib/guzzlehttp/psr7/src/MessageTrait.php | 270 ++ lib/guzzlehttp/psr7/src/MimeType.php | 140 + lib/guzzlehttp/psr7/src/MultipartStream.php | 158 + lib/guzzlehttp/psr7/src/NoSeekStream.php | 25 + lib/guzzlehttp/psr7/src/PumpStream.php | 170 + lib/guzzlehttp/psr7/src/Query.php | 113 + lib/guzzlehttp/psr7/src/Request.php | 152 + lib/guzzlehttp/psr7/src/Response.php | 155 + lib/guzzlehttp/psr7/src/Rfc7230.php | 19 + lib/guzzlehttp/psr7/src/ServerRequest.php | 379 +++ lib/guzzlehttp/psr7/src/Stream.php | 270 ++ .../psr7/src/StreamDecoratorTrait.php | 152 + lib/guzzlehttp/psr7/src/StreamWrapper.php | 165 + lib/guzzlehttp/psr7/src/UploadedFile.php | 328 ++ lib/guzzlehttp/psr7/src/Uri.php | 810 +++++ lib/guzzlehttp/psr7/src/UriNormalizer.php | 219 ++ lib/guzzlehttp/psr7/src/UriResolver.php | 222 ++ lib/guzzlehttp/psr7/src/Utils.php | 428 +++ lib/guzzlehttp/psr7/src/functions.php | 422 +++ lib/guzzlehttp/psr7/src/functions_include.php | 6 + lib/laminas/laminas-loader/CHANGELOG.md | 51 + lib/laminas/laminas-loader/COPYRIGHT.md | 2 + lib/laminas/laminas-loader/LICENSE.md | 27 + lib/laminas/laminas-loader/README.md | 9 + lib/laminas/laminas-loader/composer.json | 58 + .../laminas-loader/src/AutoloaderFactory.php | 210 ++ .../laminas-loader/src/ClassMapAutoloader.php | 220 ++ .../src/Exception/BadMethodCallException.php | 16 + .../src/Exception/DomainException.php | 15 + .../src/Exception/ExceptionInterface.php | 13 + .../Exception/InvalidArgumentException.php | 15 + .../src/Exception/InvalidPathException.php | 15 + .../MissingResourceNamespaceException.php | 15 + .../src/Exception/PluginLoaderException.php | 18 + .../src/Exception/RuntimeException.php | 15 + .../src/Exception/SecurityException.php | 15 + .../laminas-loader/src/ModuleAutoloader.php | 441 +++ .../laminas-loader/src/PluginClassLoader.php | 216 ++ .../laminas-loader/src/PluginClassLocator.php | 42 + .../laminas-loader/src/ShortNameLocator.php | 39 + .../laminas-loader/src/SplAutoloader.php | 64 + .../laminas-loader/src/StandardAutoloader.php | 327 ++ lib/laminas/laminas-mail/CHANGELOG.md | 443 +++ lib/laminas/laminas-mail/COPYRIGHT.md | 1 + lib/laminas/laminas-mail/LICENSE.md | 26 + lib/laminas/laminas-mail/README.md | 13 + lib/laminas/laminas-mail/composer.json | 75 + lib/laminas/laminas-mail/src/Address.php | 166 + .../src/Address/AddressInterface.php | 33 + lib/laminas/laminas-mail/src/AddressList.php | 236 ++ .../laminas-mail/src/ConfigProvider.php | 42 + .../src/Exception/BadMethodCallException.php | 17 + .../src/Exception/DomainException.php | 16 + .../src/Exception/ExceptionInterface.php | 13 + .../Exception/InvalidArgumentException.php | 17 + .../src/Exception/OutOfBoundsException.php | 16 + .../src/Exception/RuntimeException.php | 16 + .../src/Header/AbstractAddressList.php | 261 ++ lib/laminas/laminas-mail/src/Header/Bcc.php | 22 + lib/laminas/laminas-mail/src/Header/Cc.php | 15 + .../src/Header/ContentDisposition.php | 296 ++ .../src/Header/ContentTransferEncoding.php | 114 + .../laminas-mail/src/Header/ContentType.php | 202 ++ lib/laminas/laminas-mail/src/Header/Date.php | 69 + .../Exception/BadMethodCallException.php | 15 + .../Header/Exception/ExceptionInterface.php | 15 + .../Exception/InvalidArgumentException.php | 15 + .../src/Header/Exception/RuntimeException.php | 15 + lib/laminas/laminas-mail/src/Header/From.php | 15 + .../laminas-mail/src/Header/GenericHeader.php | 198 ++ .../src/Header/GenericMultiHeader.php | 55 + .../src/Header/HeaderInterface.php | 75 + .../laminas-mail/src/Header/HeaderLoader.php | 49 + .../laminas-mail/src/Header/HeaderName.php | 73 + .../laminas-mail/src/Header/HeaderValue.php | 116 + .../laminas-mail/src/Header/HeaderWrap.php | 161 + .../src/Header/IdentificationField.php | 143 + .../laminas-mail/src/Header/InReplyTo.php | 15 + .../laminas-mail/src/Header/ListParser.php | 99 + .../laminas-mail/src/Header/MessageId.php | 119 + .../laminas-mail/src/Header/MimeVersion.php | 87 + .../src/Header/MultipleHeadersInterface.php | 14 + .../laminas-mail/src/Header/Received.php | 92 + .../laminas-mail/src/Header/References.php | 15 + .../laminas-mail/src/Header/ReplyTo.php | 15 + .../laminas-mail/src/Header/Sender.php | 153 + .../src/Header/StructuredInterface.php | 19 + .../laminas-mail/src/Header/Subject.php | 119 + lib/laminas/laminas-mail/src/Header/To.php | 15 + .../src/Header/UnstructuredInterface.php | 16 + lib/laminas/laminas-mail/src/Headers.php | 531 +++ lib/laminas/laminas-mail/src/Message.php | 576 ++++ .../laminas-mail/src/MessageFactory.php | 64 + lib/laminas/laminas-mail/src/Module.php | 25 + .../src/Protocol/AbstractProtocol.php | 354 ++ .../Protocol/Exception/ExceptionInterface.php | 15 + .../Exception/InvalidArgumentException.php | 18 + .../Protocol/Exception/RuntimeException.php | 18 + .../laminas-mail/src/Protocol/Imap.php | 831 +++++ .../laminas-mail/src/Protocol/Pop3.php | 406 +++ .../src/Protocol/ProtocolTrait.php | 30 + .../laminas-mail/src/Protocol/Smtp.php | 451 +++ .../src/Protocol/Smtp/Auth/Crammd5.php | 138 + .../src/Protocol/Smtp/Auth/Login.php | 126 + .../src/Protocol/Smtp/Auth/Plain.php | 124 + .../src/Protocol/SmtpPluginManager.php | 114 + .../src/Protocol/SmtpPluginManagerFactory.php | 54 + lib/laminas/laminas-mail/src/Storage.php | 23 + .../src/Storage/AbstractStorage.php | 319 ++ .../Storage/Exception/ExceptionInterface.php | 15 + .../Exception/InvalidArgumentException.php | 18 + .../Exception/OutOfBoundsException.php | 18 + .../Storage/Exception/RuntimeException.php | 18 + .../laminas-mail/src/Storage/Folder.php | 209 ++ .../src/Storage/Folder/FolderInterface.php | 38 + .../src/Storage/Folder/Maildir.php | 216 ++ .../laminas-mail/src/Storage/Folder/Mbox.php | 213 ++ lib/laminas/laminas-mail/src/Storage/Imap.php | 546 ++++ .../laminas-mail/src/Storage/Maildir.php | 416 +++ lib/laminas/laminas-mail/src/Storage/Mbox.php | 415 +++ .../laminas-mail/src/Storage/Message.php | 86 + .../laminas-mail/src/Storage/Message/File.php | 70 + .../src/Storage/Message/MessageInterface.php | 34 + lib/laminas/laminas-mail/src/Storage/Part.php | 474 +++ .../Part/Exception/ExceptionInterface.php | 15 + .../Exception/InvalidArgumentException.php | 18 + .../Part/Exception/RuntimeException.php | 18 + .../laminas-mail/src/Storage/Part/File.php | 157 + .../src/Storage/Part/PartInterface.php | 123 + lib/laminas/laminas-mail/src/Storage/Pop3.php | 278 ++ .../src/Storage/Writable/Maildir.php | 953 ++++++ .../Storage/Writable/WritableInterface.php | 92 + .../laminas-mail/src/Transport/Envelope.php | 64 + .../Transport/Exception/DomainException.php | 18 + .../Exception/ExceptionInterface.php | 15 + .../Exception/InvalidArgumentException.php | 18 + .../Transport/Exception/RuntimeException.php | 18 + .../laminas-mail/src/Transport/Factory.php | 85 + .../laminas-mail/src/Transport/File.php | 96 + .../src/Transport/FileOptions.php | 95 + .../laminas-mail/src/Transport/InMemory.php | 46 + .../laminas-mail/src/Transport/Null.php | 34 + .../laminas-mail/src/Transport/Sendmail.php | 338 ++ .../laminas-mail/src/Transport/Smtp.php | 406 +++ .../src/Transport/SmtpOptions.php | 209 ++ .../src/Transport/TransportInterface.php | 25 + lib/laminas/laminas-mime/CHANGELOG.md | 212 ++ lib/laminas/laminas-mime/COPYRIGHT.md | 2 + lib/laminas/laminas-mime/LICENSE.md | 27 + lib/laminas/laminas-mime/README.md | 26 + lib/laminas/laminas-mime/composer.json | 63 + lib/laminas/laminas-mime/src/Decode.php | 228 ++ .../src/Exception/ExceptionInterface.php | 13 + .../Exception/InvalidArgumentException.php | 14 + .../src/Exception/RuntimeException.php | 16 + lib/laminas/laminas-mime/src/Message.php | 310 ++ lib/laminas/laminas-mime/src/Mime.php | 398 +++ lib/laminas/laminas-mime/src/Part.php | 483 +++ .../laminas-servicemanager/CHANGELOG.md | 696 ++++ .../laminas-servicemanager/COPYRIGHT.md | 1 + lib/laminas/laminas-servicemanager/LICENSE.md | 26 + lib/laminas/laminas-servicemanager/README.md | 28 + .../bin/generate-deps-for-config-factory | 26 + .../bin/generate-factory-for-class | 26 + .../laminas-servicemanager/composer.json | 83 + .../AbstractFactory/ConfigAbstractFactory.php | 78 + .../ReflectionBasedAbstractFactory.php | 246 ++ .../src/AbstractFactoryInterface.php | 59 + .../src/AbstractPluginManager.php | 202 ++ .../laminas-servicemanager/src/Config.php | 120 + .../src/ConfigInterface.php | 47 + .../src/DelegatorFactoryInterface.php | 46 + ...tainerModificationsNotAllowedException.php | 18 + .../src/Exception/CyclicAliasException.php | 138 + .../src/Exception/ExceptionInterface.php | 18 + .../Exception/InvalidArgumentException.php | 18 + .../src/Exception/InvalidServiceException.php | 19 + .../Exception/ServiceNotCreatedException.php | 22 + .../Exception/ServiceNotFoundException.php | 22 + .../src/Factory/AbstractFactoryInterface.php | 33 + .../src/Factory/DelegatorFactoryInterface.php | 40 + .../src/Factory/FactoryInterface.php | 39 + .../src/Factory/InvokableFactory.php | 33 + .../src/FactoryInterface.php | 41 + .../src/Initializer/InitializerInterface.php | 29 + .../src/InitializerInterface.php | 41 + .../src/PluginManagerInterface.php | 31 + .../src/Proxy/LazyServiceFactory.php | 68 + .../src/PsrContainerDecorator.php | 52 + .../src/ServiceLocatorInterface.php | 35 + .../src/ServiceManager.php | 963 ++++++ .../src/Test/CommonPluginManagerTrait.php | 115 + .../src/Tool/ConfigDumper.php | 259 ++ .../src/Tool/ConfigDumperCommand.php | 229 ++ .../src/Tool/FactoryCreator.php | 143 + .../src/Tool/FactoryCreatorCommand.php | 150 + lib/laminas/laminas-stdlib/CHANGELOG.md | 385 +++ lib/laminas/laminas-stdlib/COPYRIGHT.md | 2 + lib/laminas/laminas-stdlib/LICENSE.md | 27 + lib/laminas/laminas-stdlib/README.md | 29 + lib/laminas/laminas-stdlib/composer.json | 60 + .../laminas-stdlib/src/AbstractOptions.php | 177 + .../laminas-stdlib/src/ArrayObject.php | 433 +++ .../src/ArraySerializableInterface.php | 27 + lib/laminas/laminas-stdlib/src/ArrayStack.php | 32 + lib/laminas/laminas-stdlib/src/ArrayUtils.php | 313 ++ .../src/ArrayUtils/MergeRemoveKey.php | 13 + .../src/ArrayUtils/MergeReplaceKey.php | 33 + .../ArrayUtils/MergeReplaceKeyInterface.php | 20 + .../laminas-stdlib/src/ConsoleHelper.php | 159 + .../src/DispatchableInterface.php | 21 + .../laminas-stdlib/src/ErrorHandler.php | 114 + .../src/Exception/BadMethodCallException.php | 16 + .../src/Exception/DomainException.php | 16 + .../src/Exception/ExceptionInterface.php | 16 + .../Exception/ExtensionNotLoadedException.php | 16 + .../Exception/InvalidArgumentException.php | 16 + .../src/Exception/LogicException.php | 16 + .../src/Exception/RuntimeException.php | 16 + .../laminas-stdlib/src/FastPriorityQueue.php | 370 +++ lib/laminas/laminas-stdlib/src/Glob.php | 201 ++ .../src/Guard/AllGuardsTrait.php | 19 + .../Guard/ArrayOrTraversableGuardTrait.php | 40 + .../src/Guard/EmptyGuardTrait.php | 34 + .../src/Guard/NullGuardTrait.php | 34 + .../src/InitializableInterface.php | 22 + .../laminas-stdlib/src/JsonSerializable.php | 16 + lib/laminas/laminas-stdlib/src/Message.php | 117 + .../laminas-stdlib/src/MessageInterface.php | 43 + .../src/ParameterObjectInterface.php | 37 + lib/laminas/laminas-stdlib/src/Parameters.php | 114 + .../src/ParametersInterface.php | 85 + .../laminas-stdlib/src/PriorityList.php | 273 ++ .../laminas-stdlib/src/PriorityQueue.php | 300 ++ lib/laminas/laminas-stdlib/src/Request.php | 14 + .../laminas-stdlib/src/RequestInterface.php | 13 + lib/laminas/laminas-stdlib/src/Response.php | 14 + .../laminas-stdlib/src/ResponseInterface.php | 13 + .../laminas-stdlib/src/SplPriorityQueue.php | 92 + lib/laminas/laminas-stdlib/src/SplQueue.php | 54 + lib/laminas/laminas-stdlib/src/SplStack.php | 54 + .../laminas-stdlib/src/StringUtils.php | 186 ++ .../StringWrapper/AbstractStringWrapper.php | 268 ++ .../src/StringWrapper/Iconv.php | 288 ++ .../laminas-stdlib/src/StringWrapper/Intl.php | 87 + .../src/StringWrapper/MbString.php | 120 + .../src/StringWrapper/Native.php | 133 + .../StringWrapper/StringWrapperInterface.php | 110 + lib/laminas/laminas-validator/CHANGELOG.md | 647 ++++ lib/laminas/laminas-validator/COPYRIGHT.md | 2 + lib/laminas/laminas-validator/LICENSE.md | 27 + lib/laminas/laminas-validator/README.md | 26 + .../bin/update_hostname_validator.php | 195 ++ lib/laminas/laminas-validator/composer.json | 86 + .../src/AbstractValidator.php | 573 ++++ lib/laminas/laminas-validator/src/Barcode.php | 186 ++ .../src/Barcode/AbstractAdapter.php | 309 ++ .../src/Barcode/AdapterInterface.php | 65 + .../laminas-validator/src/Barcode/Codabar.php | 65 + .../laminas-validator/src/Barcode/Code128.php | 447 +++ .../laminas-validator/src/Barcode/Code25.php | 23 + .../src/Barcode/Code25interleaved.php | 25 + .../laminas-validator/src/Barcode/Code39.php | 59 + .../src/Barcode/Code39ext.php | 22 + .../laminas-validator/src/Barcode/Code93.php | 79 + .../src/Barcode/Code93ext.php | 22 + .../laminas-validator/src/Barcode/Ean12.php | 22 + .../laminas-validator/src/Barcode/Ean13.php | 22 + .../laminas-validator/src/Barcode/Ean14.php | 22 + .../laminas-validator/src/Barcode/Ean18.php | 22 + .../laminas-validator/src/Barcode/Ean2.php | 22 + .../laminas-validator/src/Barcode/Ean5.php | 24 + .../laminas-validator/src/Barcode/Ean8.php | 39 + .../laminas-validator/src/Barcode/Gtin12.php | 22 + .../laminas-validator/src/Barcode/Gtin13.php | 22 + .../laminas-validator/src/Barcode/Gtin14.php | 22 + .../src/Barcode/Identcode.php | 40 + .../src/Barcode/Intelligentmail.php | 24 + .../laminas-validator/src/Barcode/Issn.php | 89 + .../laminas-validator/src/Barcode/Itf14.php | 22 + .../src/Barcode/Leitcode.php | 22 + .../laminas-validator/src/Barcode/Planet.php | 22 + .../laminas-validator/src/Barcode/Postnet.php | 22 + .../src/Barcode/Royalmail.php | 92 + .../laminas-validator/src/Barcode/Sscc.php | 22 + .../laminas-validator/src/Barcode/Upca.php | 22 + .../laminas-validator/src/Barcode/Upce.php | 39 + lib/laminas/laminas-validator/src/Between.php | 213 ++ lib/laminas/laminas-validator/src/Bitwise.php | 191 ++ .../laminas-validator/src/Callback.php | 149 + .../laminas-validator/src/ConfigProvider.php | 44 + .../laminas-validator/src/CreditCard.php | 332 ++ lib/laminas/laminas-validator/src/Csrf.php | 377 +++ lib/laminas/laminas-validator/src/Date.php | 203 ++ .../laminas-validator/src/DateStep.php | 478 +++ .../laminas-validator/src/Db/AbstractDb.php | 325 ++ .../src/Db/NoRecordExists.php | 38 + .../laminas-validator/src/Db/RecordExists.php | 38 + lib/laminas/laminas-validator/src/Digits.php | 68 + .../laminas-validator/src/EmailAddress.php | 589 ++++ .../src/Exception/BadMethodCallException.php | 13 + .../src/Exception/ExceptionInterface.php | 13 + .../Exception/ExtensionNotLoadedException.php | 13 + .../Exception/InvalidArgumentException.php | 13 + .../InvalidMagicMimeFileException.php | 13 + .../src/Exception/RuntimeException.php | 13 + lib/laminas/laminas-validator/src/Explode.php | 210 ++ .../laminas-validator/src/File/Count.php | 253 ++ .../laminas-validator/src/File/Crc32.php | 115 + .../src/File/ExcludeExtension.php | 74 + .../src/File/ExcludeMimeType.php | 98 + .../laminas-validator/src/File/Exists.php | 182 ++ .../laminas-validator/src/File/Extension.php | 208 ++ .../src/File/FileInformationTrait.php | 167 + .../laminas-validator/src/File/FilesSize.php | 182 ++ .../laminas-validator/src/File/Hash.php | 167 + .../laminas-validator/src/File/ImageSize.php | 380 +++ .../src/File/IsCompressed.php | 92 + .../laminas-validator/src/File/IsImage.php | 116 + .../laminas-validator/src/File/Md5.php | 115 + .../laminas-validator/src/File/MimeType.php | 403 +++ .../laminas-validator/src/File/NotExists.php | 75 + .../laminas-validator/src/File/Sha1.php | 115 + .../laminas-validator/src/File/Size.php | 356 ++ .../laminas-validator/src/File/Upload.php | 269 ++ .../laminas-validator/src/File/UploadFile.php | 168 + .../laminas-validator/src/File/WordCount.php | 204 ++ .../laminas-validator/src/GpsPoint.php | 130 + .../laminas-validator/src/GreaterThan.php | 157 + lib/laminas/laminas-validator/src/Hex.php | 47 + .../laminas-validator/src/Hostname.php | 2293 +++++++++++++ .../laminas-validator/src/Hostname/Biz.php | 2902 +++++++++++++++++ .../laminas-validator/src/Hostname/Cn.php | 2184 +++++++++++++ .../laminas-validator/src/Hostname/Com.php | 181 + .../laminas-validator/src/Hostname/Jp.php | 724 ++++ lib/laminas/laminas-validator/src/Iban.php | 274 ++ .../laminas-validator/src/Identical.php | 201 ++ lib/laminas/laminas-validator/src/InArray.php | 229 ++ lib/laminas/laminas-validator/src/Ip.php | 189 ++ .../laminas-validator/src/IsCountable.php | 198 ++ .../laminas-validator/src/IsInstanceOf.php | 106 + lib/laminas/laminas-validator/src/Isbn.php | 185 ++ .../laminas-validator/src/Isbn/Isbn10.php | 60 + .../laminas-validator/src/Isbn/Isbn13.php | 61 + .../laminas-validator/src/LessThan.php | 160 + lib/laminas/laminas-validator/src/Module.php | 44 + .../laminas-validator/src/NotEmpty.php | 289 ++ lib/laminas/laminas-validator/src/Regex.php | 140 + .../src/Sitemap/Changefreq.php | 74 + .../laminas-validator/src/Sitemap/Lastmod.php | 72 + .../laminas-validator/src/Sitemap/Loc.php | 64 + .../src/Sitemap/Priority.php | 61 + .../laminas-validator/src/StaticValidator.php | 74 + lib/laminas/laminas-validator/src/Step.php | 177 + .../laminas-validator/src/StringLength.php | 234 ++ .../laminas-validator/src/Timezone.php | 173 + .../Translator/TranslatorAwareInterface.php | 68 + .../src/Translator/TranslatorInterface.php | 20 + lib/laminas/laminas-validator/src/Uri.php | 191 ++ lib/laminas/laminas-validator/src/Uuid.php | 64 + .../laminas-validator/src/ValidatorChain.php | 326 ++ .../src/ValidatorInterface.php | 37 + .../src/ValidatorPluginManager.php | 605 ++++ .../ValidatorPluginManagerAwareInterface.php | 26 + .../src/ValidatorPluginManagerFactory.php | 78 + .../src/ValidatorProviderInterface.php | 30 + .../.github/FUNDING.yml | 1 + .../workflows/release-on-milestone-closed.yml | 71 + .../laminas-zendframework-bridge/CHANGELOG.md | 686 ++++ .../laminas-zendframework-bridge/COPYRIGHT.md | 1 + .../laminas-zendframework-bridge/LICENSE.md | 26 + .../laminas-zendframework-bridge/README.md | 24 + .../composer.json | 58 + .../config/replacements.php | 372 +++ .../src/Autoloader.php | 172 + .../src/ConfigPostProcessor.php | 434 +++ .../src/Module.php | 54 + .../src/Replacements.php | 46 + .../src/RewriteRules.php | 79 + .../src/autoload.php | 9 + lib/league/oauth2-client/LICENSE | 21 + lib/league/oauth2-client/README.md | 58 + lib/league/oauth2-client/composer.json | 58 + .../oauth2-client/src/Grant/AbstractGrant.php | 80 + .../src/Grant/AuthorizationCode.php | 41 + .../src/Grant/ClientCredentials.php | 39 + .../Grant/Exception/InvalidGrantException.php | 26 + .../oauth2-client/src/Grant/GrantFactory.php | 104 + .../oauth2-client/src/Grant/Password.php | 42 + .../oauth2-client/src/Grant/RefreshToken.php | 41 + .../HttpBasicAuthOptionProvider.php | 42 + .../OptionProviderInterface.php | 30 + .../OptionProvider/PostAuthOptionProvider.php | 51 + .../src/Provider/AbstractProvider.php | 843 +++++ .../Exception/IdentityProviderException.php | 48 + .../src/Provider/GenericProvider.php | 233 ++ .../src/Provider/GenericResourceOwner.php | 61 + .../src/Provider/ResourceOwnerInterface.php | 36 + .../oauth2-client/src/Token/AccessToken.php | 243 ++ .../src/Token/AccessTokenInterface.php | 74 + .../ResourceOwnerAccessTokenInterface.php | 25 + .../src/Tool/ArrayAccessorTrait.php | 52 + .../src/Tool/BearerAuthorizationTrait.php | 36 + .../src/Tool/GuardedPropertyTrait.php | 70 + .../src/Tool/MacAuthorizationTrait.php | 83 + .../src/Tool/ProviderRedirectTrait.php | 122 + .../src/Tool/QueryBuilderTrait.php | 33 + .../oauth2-client/src/Tool/RequestFactory.php | 87 + .../src/Tool/RequiredParameterTrait.php | 56 + lib/league/oauth2-google/CHANGELOG.md | 72 + lib/league/oauth2-google/CONTRIBUTING.md | 42 + lib/league/oauth2-google/LICENSE | 21 + lib/league/oauth2-google/README.md | 242 ++ lib/league/oauth2-google/composer.json | 44 + lib/league/oauth2-google/examples/index.php | 35 + .../oauth2-google/examples/provider.php | 24 + lib/league/oauth2-google/examples/reset.php | 7 + lib/league/oauth2-google/examples/server.sh | 3 + lib/league/oauth2-google/examples/user.php | 39 + lib/league/oauth2-google/phpunit.xml.dist | 28 + .../src/Exception/HostedDomainException.php | 15 + .../oauth2-google/src/Provider/Google.php | 152 + .../oauth2-google/src/Provider/GoogleUser.php | 112 + lib/ralouphie/getallheaders/LICENSE | 21 + lib/ralouphie/getallheaders/README.md | 27 + lib/ralouphie/getallheaders/composer.json | 26 + .../getallheaders/src/getallheaders.php | 46 + .../oauth2-azure/.devcontainer/Dockerfile | 15 + .../.devcontainer/devcontainer.json | 29 + lib/thenetworg/oauth2-azure/.gitignore | 5 + lib/thenetworg/oauth2-azure/.php_cs | 70 + .../oauth2-azure/.vscode/launch.json | 27 + lib/thenetworg/oauth2-azure/CHANGELOG.md | 5 + lib/thenetworg/oauth2-azure/LICENSE.md | 21 + lib/thenetworg/oauth2-azure/README.md | 283 ++ lib/thenetworg/oauth2-azure/composer.json | 34 + .../oauth2-azure/src/Grant/JwtBearer.php | 19 + .../oauth2-azure/src/Provider/Azure.php | 440 +++ .../src/Provider/AzureResourceOwner.php | 97 + .../oauth2-azure/src/Token/AccessToken.php | 52 + lib/true/punycode/CHANGELOG.md | 45 + lib/true/punycode/LICENSE | 19 + lib/true/punycode/README.md | 45 + lib/true/punycode/composer.json | 26 + .../Exception/DomainOutOfBoundsException.php | 13 + .../Exception/LabelOutOfBoundsException.php | 13 + .../src/Exception/OutOfBoundsException.php | 13 + lib/true/punycode/src/Punycode.php | 360 ++ pages/ajax.oauth.wizard.php | 74 + .../{oauth/landing.php => oauth.landing.php} | 0 pages/oauth.wizard.php | 14 + pages/oauth/ajax.wizard.php | 63 - pages/oauth/wizard.php | 159 - .../Controller/OAuth/OAuthAjaxController.php | 26 + .../OAuth/OAuthWizardController.php | 12 +- .../OAuth/IOAuthClientResultDisplay.php | 3 + .../OAuth/OAuthClientProviderAbstract.php | 2 +- .../OAuth/OAuthClientResultDisplayConf.php | 5 + .../application/TwigBase/Twig/TwigHelper.php | 6 +- .../backoffice/oauth/DisplayConfig.html.twig | 8 + .../pages/backoffice/oauth/Wizard.html.twig | 3 + .../backoffice/oauth/Wizard.ready.js.twig | 12 +- 563 files changed, 78319 insertions(+), 235 deletions(-) create mode 100644 lib/firebase/php-jwt/LICENSE create mode 100644 lib/firebase/php-jwt/README.md create mode 100644 lib/firebase/php-jwt/composer.json create mode 100644 lib/firebase/php-jwt/src/BeforeValidException.php create mode 100644 lib/firebase/php-jwt/src/ExpiredException.php create mode 100644 lib/firebase/php-jwt/src/JWK.php create mode 100644 lib/firebase/php-jwt/src/JWT.php create mode 100644 lib/firebase/php-jwt/src/Key.php create mode 100644 lib/firebase/php-jwt/src/SignatureInvalidException.php create mode 100644 lib/guzzlehttp/guzzle/.php_cs create mode 100644 lib/guzzlehttp/guzzle/CHANGELOG.md create mode 100644 lib/guzzlehttp/guzzle/Dockerfile create mode 100644 lib/guzzlehttp/guzzle/LICENSE create mode 100644 lib/guzzlehttp/guzzle/README.md create mode 100644 lib/guzzlehttp/guzzle/UPGRADING.md create mode 100644 lib/guzzlehttp/guzzle/composer.json create mode 100644 lib/guzzlehttp/guzzle/src/Client.php create mode 100644 lib/guzzlehttp/guzzle/src/ClientInterface.php create mode 100644 lib/guzzlehttp/guzzle/src/Cookie/CookieJar.php create mode 100644 lib/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php create mode 100644 lib/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php create mode 100644 lib/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php create mode 100644 lib/guzzlehttp/guzzle/src/Cookie/SetCookie.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/BadResponseException.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/ClientException.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/ConnectException.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/GuzzleException.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/RequestException.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/SeekException.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/ServerException.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php create mode 100644 lib/guzzlehttp/guzzle/src/Exception/TransferException.php create mode 100644 lib/guzzlehttp/guzzle/src/Handler/CurlFactory.php create mode 100644 lib/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php create mode 100644 lib/guzzlehttp/guzzle/src/Handler/CurlHandler.php create mode 100644 lib/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php create mode 100644 lib/guzzlehttp/guzzle/src/Handler/EasyHandle.php create mode 100644 lib/guzzlehttp/guzzle/src/Handler/MockHandler.php create mode 100644 lib/guzzlehttp/guzzle/src/Handler/Proxy.php create mode 100644 lib/guzzlehttp/guzzle/src/Handler/StreamHandler.php create mode 100644 lib/guzzlehttp/guzzle/src/HandlerStack.php create mode 100644 lib/guzzlehttp/guzzle/src/MessageFormatter.php create mode 100644 lib/guzzlehttp/guzzle/src/Middleware.php create mode 100644 lib/guzzlehttp/guzzle/src/Pool.php create mode 100644 lib/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php create mode 100644 lib/guzzlehttp/guzzle/src/RedirectMiddleware.php create mode 100644 lib/guzzlehttp/guzzle/src/RequestOptions.php create mode 100644 lib/guzzlehttp/guzzle/src/RetryMiddleware.php create mode 100644 lib/guzzlehttp/guzzle/src/TransferStats.php create mode 100644 lib/guzzlehttp/guzzle/src/UriTemplate.php create mode 100644 lib/guzzlehttp/guzzle/src/Utils.php create mode 100644 lib/guzzlehttp/guzzle/src/functions.php create mode 100644 lib/guzzlehttp/guzzle/src/functions_include.php create mode 100644 lib/guzzlehttp/promises/CHANGELOG.md create mode 100644 lib/guzzlehttp/promises/LICENSE create mode 100644 lib/guzzlehttp/promises/Makefile create mode 100644 lib/guzzlehttp/promises/README.md create mode 100644 lib/guzzlehttp/promises/composer.json create mode 100644 lib/guzzlehttp/promises/src/AggregateException.php create mode 100644 lib/guzzlehttp/promises/src/CancellationException.php create mode 100644 lib/guzzlehttp/promises/src/Coroutine.php create mode 100644 lib/guzzlehttp/promises/src/Create.php create mode 100644 lib/guzzlehttp/promises/src/Each.php create mode 100644 lib/guzzlehttp/promises/src/EachPromise.php create mode 100644 lib/guzzlehttp/promises/src/FulfilledPromise.php create mode 100644 lib/guzzlehttp/promises/src/Is.php create mode 100644 lib/guzzlehttp/promises/src/Promise.php create mode 100644 lib/guzzlehttp/promises/src/PromiseInterface.php create mode 100644 lib/guzzlehttp/promises/src/PromisorInterface.php create mode 100644 lib/guzzlehttp/promises/src/RejectedPromise.php create mode 100644 lib/guzzlehttp/promises/src/RejectionException.php create mode 100644 lib/guzzlehttp/promises/src/TaskQueue.php create mode 100644 lib/guzzlehttp/promises/src/TaskQueueInterface.php create mode 100644 lib/guzzlehttp/promises/src/Utils.php create mode 100644 lib/guzzlehttp/promises/src/functions.php create mode 100644 lib/guzzlehttp/promises/src/functions_include.php create mode 100644 lib/guzzlehttp/psr7/.github/FUNDING.yml create mode 100644 lib/guzzlehttp/psr7/.github/stale.yml create mode 100644 lib/guzzlehttp/psr7/.github/workflows/ci.yml create mode 100644 lib/guzzlehttp/psr7/.github/workflows/integration.yml create mode 100644 lib/guzzlehttp/psr7/.github/workflows/static.yml create mode 100644 lib/guzzlehttp/psr7/.php_cs.dist create mode 100644 lib/guzzlehttp/psr7/CHANGELOG.md create mode 100644 lib/guzzlehttp/psr7/LICENSE create mode 100644 lib/guzzlehttp/psr7/README.md create mode 100644 lib/guzzlehttp/psr7/composer.json create mode 100644 lib/guzzlehttp/psr7/src/AppendStream.php create mode 100644 lib/guzzlehttp/psr7/src/BufferStream.php create mode 100644 lib/guzzlehttp/psr7/src/CachingStream.php create mode 100644 lib/guzzlehttp/psr7/src/DroppingStream.php create mode 100644 lib/guzzlehttp/psr7/src/FnStream.php create mode 100644 lib/guzzlehttp/psr7/src/Header.php create mode 100644 lib/guzzlehttp/psr7/src/InflateStream.php create mode 100644 lib/guzzlehttp/psr7/src/LazyOpenStream.php create mode 100644 lib/guzzlehttp/psr7/src/LimitStream.php create mode 100644 lib/guzzlehttp/psr7/src/Message.php create mode 100644 lib/guzzlehttp/psr7/src/MessageTrait.php create mode 100644 lib/guzzlehttp/psr7/src/MimeType.php create mode 100644 lib/guzzlehttp/psr7/src/MultipartStream.php create mode 100644 lib/guzzlehttp/psr7/src/NoSeekStream.php create mode 100644 lib/guzzlehttp/psr7/src/PumpStream.php create mode 100644 lib/guzzlehttp/psr7/src/Query.php create mode 100644 lib/guzzlehttp/psr7/src/Request.php create mode 100644 lib/guzzlehttp/psr7/src/Response.php create mode 100644 lib/guzzlehttp/psr7/src/Rfc7230.php create mode 100644 lib/guzzlehttp/psr7/src/ServerRequest.php create mode 100644 lib/guzzlehttp/psr7/src/Stream.php create mode 100644 lib/guzzlehttp/psr7/src/StreamDecoratorTrait.php create mode 100644 lib/guzzlehttp/psr7/src/StreamWrapper.php create mode 100644 lib/guzzlehttp/psr7/src/UploadedFile.php create mode 100644 lib/guzzlehttp/psr7/src/Uri.php create mode 100644 lib/guzzlehttp/psr7/src/UriNormalizer.php create mode 100644 lib/guzzlehttp/psr7/src/UriResolver.php create mode 100644 lib/guzzlehttp/psr7/src/Utils.php create mode 100644 lib/guzzlehttp/psr7/src/functions.php create mode 100644 lib/guzzlehttp/psr7/src/functions_include.php create mode 100644 lib/laminas/laminas-loader/CHANGELOG.md create mode 100644 lib/laminas/laminas-loader/COPYRIGHT.md create mode 100644 lib/laminas/laminas-loader/LICENSE.md create mode 100644 lib/laminas/laminas-loader/README.md create mode 100644 lib/laminas/laminas-loader/composer.json create mode 100644 lib/laminas/laminas-loader/src/AutoloaderFactory.php create mode 100644 lib/laminas/laminas-loader/src/ClassMapAutoloader.php create mode 100644 lib/laminas/laminas-loader/src/Exception/BadMethodCallException.php create mode 100644 lib/laminas/laminas-loader/src/Exception/DomainException.php create mode 100644 lib/laminas/laminas-loader/src/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-loader/src/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-loader/src/Exception/InvalidPathException.php create mode 100644 lib/laminas/laminas-loader/src/Exception/MissingResourceNamespaceException.php create mode 100644 lib/laminas/laminas-loader/src/Exception/PluginLoaderException.php create mode 100644 lib/laminas/laminas-loader/src/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-loader/src/Exception/SecurityException.php create mode 100644 lib/laminas/laminas-loader/src/ModuleAutoloader.php create mode 100644 lib/laminas/laminas-loader/src/PluginClassLoader.php create mode 100644 lib/laminas/laminas-loader/src/PluginClassLocator.php create mode 100644 lib/laminas/laminas-loader/src/ShortNameLocator.php create mode 100644 lib/laminas/laminas-loader/src/SplAutoloader.php create mode 100644 lib/laminas/laminas-loader/src/StandardAutoloader.php create mode 100644 lib/laminas/laminas-mail/CHANGELOG.md create mode 100644 lib/laminas/laminas-mail/COPYRIGHT.md create mode 100644 lib/laminas/laminas-mail/LICENSE.md create mode 100644 lib/laminas/laminas-mail/README.md create mode 100644 lib/laminas/laminas-mail/composer.json create mode 100644 lib/laminas/laminas-mail/src/Address.php create mode 100644 lib/laminas/laminas-mail/src/Address/AddressInterface.php create mode 100644 lib/laminas/laminas-mail/src/AddressList.php create mode 100644 lib/laminas/laminas-mail/src/ConfigProvider.php create mode 100644 lib/laminas/laminas-mail/src/Exception/BadMethodCallException.php create mode 100644 lib/laminas/laminas-mail/src/Exception/DomainException.php create mode 100644 lib/laminas/laminas-mail/src/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-mail/src/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-mail/src/Exception/OutOfBoundsException.php create mode 100644 lib/laminas/laminas-mail/src/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-mail/src/Header/AbstractAddressList.php create mode 100644 lib/laminas/laminas-mail/src/Header/Bcc.php create mode 100644 lib/laminas/laminas-mail/src/Header/Cc.php create mode 100644 lib/laminas/laminas-mail/src/Header/ContentDisposition.php create mode 100644 lib/laminas/laminas-mail/src/Header/ContentTransferEncoding.php create mode 100644 lib/laminas/laminas-mail/src/Header/ContentType.php create mode 100644 lib/laminas/laminas-mail/src/Header/Date.php create mode 100644 lib/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php create mode 100644 lib/laminas/laminas-mail/src/Header/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-mail/src/Header/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-mail/src/Header/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-mail/src/Header/From.php create mode 100644 lib/laminas/laminas-mail/src/Header/GenericHeader.php create mode 100644 lib/laminas/laminas-mail/src/Header/GenericMultiHeader.php create mode 100644 lib/laminas/laminas-mail/src/Header/HeaderInterface.php create mode 100644 lib/laminas/laminas-mail/src/Header/HeaderLoader.php create mode 100644 lib/laminas/laminas-mail/src/Header/HeaderName.php create mode 100644 lib/laminas/laminas-mail/src/Header/HeaderValue.php create mode 100644 lib/laminas/laminas-mail/src/Header/HeaderWrap.php create mode 100644 lib/laminas/laminas-mail/src/Header/IdentificationField.php create mode 100644 lib/laminas/laminas-mail/src/Header/InReplyTo.php create mode 100644 lib/laminas/laminas-mail/src/Header/ListParser.php create mode 100644 lib/laminas/laminas-mail/src/Header/MessageId.php create mode 100644 lib/laminas/laminas-mail/src/Header/MimeVersion.php create mode 100644 lib/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php create mode 100644 lib/laminas/laminas-mail/src/Header/Received.php create mode 100644 lib/laminas/laminas-mail/src/Header/References.php create mode 100644 lib/laminas/laminas-mail/src/Header/ReplyTo.php create mode 100644 lib/laminas/laminas-mail/src/Header/Sender.php create mode 100644 lib/laminas/laminas-mail/src/Header/StructuredInterface.php create mode 100644 lib/laminas/laminas-mail/src/Header/Subject.php create mode 100644 lib/laminas/laminas-mail/src/Header/To.php create mode 100644 lib/laminas/laminas-mail/src/Header/UnstructuredInterface.php create mode 100644 lib/laminas/laminas-mail/src/Headers.php create mode 100644 lib/laminas/laminas-mail/src/Message.php create mode 100644 lib/laminas/laminas-mail/src/MessageFactory.php create mode 100644 lib/laminas/laminas-mail/src/Module.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/AbstractProtocol.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/Imap.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/Pop3.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/ProtocolTrait.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/Smtp.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php create mode 100644 lib/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php create mode 100644 lib/laminas/laminas-mail/src/Storage.php create mode 100644 lib/laminas/laminas-mail/src/Storage/AbstractStorage.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Exception/OutOfBoundsException.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Folder.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Folder/Maildir.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Folder/Mbox.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Imap.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Maildir.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Mbox.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Message.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Message/File.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Message/MessageInterface.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Part.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Part/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Part/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Part/File.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Part/PartInterface.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Pop3.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Writable/Maildir.php create mode 100644 lib/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php create mode 100644 lib/laminas/laminas-mail/src/Transport/Envelope.php create mode 100644 lib/laminas/laminas-mail/src/Transport/Exception/DomainException.php create mode 100644 lib/laminas/laminas-mail/src/Transport/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-mail/src/Transport/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-mail/src/Transport/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-mail/src/Transport/Factory.php create mode 100644 lib/laminas/laminas-mail/src/Transport/File.php create mode 100644 lib/laminas/laminas-mail/src/Transport/FileOptions.php create mode 100644 lib/laminas/laminas-mail/src/Transport/InMemory.php create mode 100644 lib/laminas/laminas-mail/src/Transport/Null.php create mode 100644 lib/laminas/laminas-mail/src/Transport/Sendmail.php create mode 100644 lib/laminas/laminas-mail/src/Transport/Smtp.php create mode 100644 lib/laminas/laminas-mail/src/Transport/SmtpOptions.php create mode 100644 lib/laminas/laminas-mail/src/Transport/TransportInterface.php create mode 100644 lib/laminas/laminas-mime/CHANGELOG.md create mode 100644 lib/laminas/laminas-mime/COPYRIGHT.md create mode 100644 lib/laminas/laminas-mime/LICENSE.md create mode 100644 lib/laminas/laminas-mime/README.md create mode 100644 lib/laminas/laminas-mime/composer.json create mode 100644 lib/laminas/laminas-mime/src/Decode.php create mode 100644 lib/laminas/laminas-mime/src/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-mime/src/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-mime/src/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-mime/src/Message.php create mode 100644 lib/laminas/laminas-mime/src/Mime.php create mode 100644 lib/laminas/laminas-mime/src/Part.php create mode 100644 lib/laminas/laminas-servicemanager/CHANGELOG.md create mode 100644 lib/laminas/laminas-servicemanager/COPYRIGHT.md create mode 100644 lib/laminas/laminas-servicemanager/LICENSE.md create mode 100644 lib/laminas/laminas-servicemanager/README.md create mode 100644 lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory create mode 100644 lib/laminas/laminas-servicemanager/bin/generate-factory-for-class create mode 100644 lib/laminas/laminas-servicemanager/composer.json create mode 100644 lib/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php create mode 100644 lib/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php create mode 100644 lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/AbstractPluginManager.php create mode 100644 lib/laminas/laminas-servicemanager/src/Config.php create mode 100644 lib/laminas/laminas-servicemanager/src/ConfigInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/Exception/ContainerModificationsNotAllowedException.php create mode 100644 lib/laminas/laminas-servicemanager/src/Exception/CyclicAliasException.php create mode 100644 lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-servicemanager/src/Exception/InvalidServiceException.php create mode 100644 lib/laminas/laminas-servicemanager/src/Exception/ServiceNotCreatedException.php create mode 100644 lib/laminas/laminas-servicemanager/src/Exception/ServiceNotFoundException.php create mode 100644 lib/laminas/laminas-servicemanager/src/Factory/AbstractFactoryInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/Factory/DelegatorFactoryInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/Factory/FactoryInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php create mode 100644 lib/laminas/laminas-servicemanager/src/FactoryInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/Initializer/InitializerInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/InitializerInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/PluginManagerInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/Proxy/LazyServiceFactory.php create mode 100644 lib/laminas/laminas-servicemanager/src/PsrContainerDecorator.php create mode 100644 lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php create mode 100644 lib/laminas/laminas-servicemanager/src/ServiceManager.php create mode 100644 lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php create mode 100644 lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php create mode 100644 lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php create mode 100644 lib/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php create mode 100644 lib/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php create mode 100644 lib/laminas/laminas-stdlib/CHANGELOG.md create mode 100644 lib/laminas/laminas-stdlib/COPYRIGHT.md create mode 100644 lib/laminas/laminas-stdlib/LICENSE.md create mode 100644 lib/laminas/laminas-stdlib/README.md create mode 100644 lib/laminas/laminas-stdlib/composer.json create mode 100644 lib/laminas/laminas-stdlib/src/AbstractOptions.php create mode 100644 lib/laminas/laminas-stdlib/src/ArrayObject.php create mode 100644 lib/laminas/laminas-stdlib/src/ArraySerializableInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/ArrayStack.php create mode 100644 lib/laminas/laminas-stdlib/src/ArrayUtils.php create mode 100644 lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php create mode 100644 lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKey.php create mode 100644 lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/ConsoleHelper.php create mode 100644 lib/laminas/laminas-stdlib/src/DispatchableInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/ErrorHandler.php create mode 100644 lib/laminas/laminas-stdlib/src/Exception/BadMethodCallException.php create mode 100644 lib/laminas/laminas-stdlib/src/Exception/DomainException.php create mode 100644 lib/laminas/laminas-stdlib/src/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/Exception/ExtensionNotLoadedException.php create mode 100644 lib/laminas/laminas-stdlib/src/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-stdlib/src/Exception/LogicException.php create mode 100644 lib/laminas/laminas-stdlib/src/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-stdlib/src/FastPriorityQueue.php create mode 100644 lib/laminas/laminas-stdlib/src/Glob.php create mode 100644 lib/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php create mode 100644 lib/laminas/laminas-stdlib/src/Guard/ArrayOrTraversableGuardTrait.php create mode 100644 lib/laminas/laminas-stdlib/src/Guard/EmptyGuardTrait.php create mode 100644 lib/laminas/laminas-stdlib/src/Guard/NullGuardTrait.php create mode 100644 lib/laminas/laminas-stdlib/src/InitializableInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/JsonSerializable.php create mode 100644 lib/laminas/laminas-stdlib/src/Message.php create mode 100644 lib/laminas/laminas-stdlib/src/MessageInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/ParameterObjectInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/Parameters.php create mode 100644 lib/laminas/laminas-stdlib/src/ParametersInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/PriorityList.php create mode 100644 lib/laminas/laminas-stdlib/src/PriorityQueue.php create mode 100644 lib/laminas/laminas-stdlib/src/Request.php create mode 100644 lib/laminas/laminas-stdlib/src/RequestInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/Response.php create mode 100644 lib/laminas/laminas-stdlib/src/ResponseInterface.php create mode 100644 lib/laminas/laminas-stdlib/src/SplPriorityQueue.php create mode 100644 lib/laminas/laminas-stdlib/src/SplQueue.php create mode 100644 lib/laminas/laminas-stdlib/src/SplStack.php create mode 100644 lib/laminas/laminas-stdlib/src/StringUtils.php create mode 100644 lib/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php create mode 100644 lib/laminas/laminas-stdlib/src/StringWrapper/Iconv.php create mode 100644 lib/laminas/laminas-stdlib/src/StringWrapper/Intl.php create mode 100644 lib/laminas/laminas-stdlib/src/StringWrapper/MbString.php create mode 100644 lib/laminas/laminas-stdlib/src/StringWrapper/Native.php create mode 100644 lib/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php create mode 100644 lib/laminas/laminas-validator/CHANGELOG.md create mode 100644 lib/laminas/laminas-validator/COPYRIGHT.md create mode 100644 lib/laminas/laminas-validator/LICENSE.md create mode 100644 lib/laminas/laminas-validator/README.md create mode 100644 lib/laminas/laminas-validator/bin/update_hostname_validator.php create mode 100644 lib/laminas/laminas-validator/composer.json create mode 100644 lib/laminas/laminas-validator/src/AbstractValidator.php create mode 100644 lib/laminas/laminas-validator/src/Barcode.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/AbstractAdapter.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/AdapterInterface.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Codabar.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Code128.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Code25.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Code25interleaved.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Code39.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Code39ext.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Code93.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Code93ext.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Ean12.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Ean13.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Ean14.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Ean18.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Ean2.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Ean5.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Ean8.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Gtin12.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Gtin13.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Gtin14.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Identcode.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Intelligentmail.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Issn.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Itf14.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Leitcode.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Planet.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Postnet.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Royalmail.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Sscc.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Upca.php create mode 100644 lib/laminas/laminas-validator/src/Barcode/Upce.php create mode 100644 lib/laminas/laminas-validator/src/Between.php create mode 100644 lib/laminas/laminas-validator/src/Bitwise.php create mode 100644 lib/laminas/laminas-validator/src/Callback.php create mode 100644 lib/laminas/laminas-validator/src/ConfigProvider.php create mode 100644 lib/laminas/laminas-validator/src/CreditCard.php create mode 100644 lib/laminas/laminas-validator/src/Csrf.php create mode 100644 lib/laminas/laminas-validator/src/Date.php create mode 100644 lib/laminas/laminas-validator/src/DateStep.php create mode 100644 lib/laminas/laminas-validator/src/Db/AbstractDb.php create mode 100644 lib/laminas/laminas-validator/src/Db/NoRecordExists.php create mode 100644 lib/laminas/laminas-validator/src/Db/RecordExists.php create mode 100644 lib/laminas/laminas-validator/src/Digits.php create mode 100644 lib/laminas/laminas-validator/src/EmailAddress.php create mode 100644 lib/laminas/laminas-validator/src/Exception/BadMethodCallException.php create mode 100644 lib/laminas/laminas-validator/src/Exception/ExceptionInterface.php create mode 100644 lib/laminas/laminas-validator/src/Exception/ExtensionNotLoadedException.php create mode 100644 lib/laminas/laminas-validator/src/Exception/InvalidArgumentException.php create mode 100644 lib/laminas/laminas-validator/src/Exception/InvalidMagicMimeFileException.php create mode 100644 lib/laminas/laminas-validator/src/Exception/RuntimeException.php create mode 100644 lib/laminas/laminas-validator/src/Explode.php create mode 100644 lib/laminas/laminas-validator/src/File/Count.php create mode 100644 lib/laminas/laminas-validator/src/File/Crc32.php create mode 100644 lib/laminas/laminas-validator/src/File/ExcludeExtension.php create mode 100644 lib/laminas/laminas-validator/src/File/ExcludeMimeType.php create mode 100644 lib/laminas/laminas-validator/src/File/Exists.php create mode 100644 lib/laminas/laminas-validator/src/File/Extension.php create mode 100644 lib/laminas/laminas-validator/src/File/FileInformationTrait.php create mode 100644 lib/laminas/laminas-validator/src/File/FilesSize.php create mode 100644 lib/laminas/laminas-validator/src/File/Hash.php create mode 100644 lib/laminas/laminas-validator/src/File/ImageSize.php create mode 100644 lib/laminas/laminas-validator/src/File/IsCompressed.php create mode 100644 lib/laminas/laminas-validator/src/File/IsImage.php create mode 100644 lib/laminas/laminas-validator/src/File/Md5.php create mode 100644 lib/laminas/laminas-validator/src/File/MimeType.php create mode 100644 lib/laminas/laminas-validator/src/File/NotExists.php create mode 100644 lib/laminas/laminas-validator/src/File/Sha1.php create mode 100644 lib/laminas/laminas-validator/src/File/Size.php create mode 100644 lib/laminas/laminas-validator/src/File/Upload.php create mode 100644 lib/laminas/laminas-validator/src/File/UploadFile.php create mode 100644 lib/laminas/laminas-validator/src/File/WordCount.php create mode 100644 lib/laminas/laminas-validator/src/GpsPoint.php create mode 100644 lib/laminas/laminas-validator/src/GreaterThan.php create mode 100644 lib/laminas/laminas-validator/src/Hex.php create mode 100644 lib/laminas/laminas-validator/src/Hostname.php create mode 100644 lib/laminas/laminas-validator/src/Hostname/Biz.php create mode 100644 lib/laminas/laminas-validator/src/Hostname/Cn.php create mode 100644 lib/laminas/laminas-validator/src/Hostname/Com.php create mode 100644 lib/laminas/laminas-validator/src/Hostname/Jp.php create mode 100644 lib/laminas/laminas-validator/src/Iban.php create mode 100644 lib/laminas/laminas-validator/src/Identical.php create mode 100644 lib/laminas/laminas-validator/src/InArray.php create mode 100644 lib/laminas/laminas-validator/src/Ip.php create mode 100644 lib/laminas/laminas-validator/src/IsCountable.php create mode 100644 lib/laminas/laminas-validator/src/IsInstanceOf.php create mode 100644 lib/laminas/laminas-validator/src/Isbn.php create mode 100644 lib/laminas/laminas-validator/src/Isbn/Isbn10.php create mode 100644 lib/laminas/laminas-validator/src/Isbn/Isbn13.php create mode 100644 lib/laminas/laminas-validator/src/LessThan.php create mode 100644 lib/laminas/laminas-validator/src/Module.php create mode 100644 lib/laminas/laminas-validator/src/NotEmpty.php create mode 100644 lib/laminas/laminas-validator/src/Regex.php create mode 100644 lib/laminas/laminas-validator/src/Sitemap/Changefreq.php create mode 100644 lib/laminas/laminas-validator/src/Sitemap/Lastmod.php create mode 100644 lib/laminas/laminas-validator/src/Sitemap/Loc.php create mode 100644 lib/laminas/laminas-validator/src/Sitemap/Priority.php create mode 100644 lib/laminas/laminas-validator/src/StaticValidator.php create mode 100644 lib/laminas/laminas-validator/src/Step.php create mode 100644 lib/laminas/laminas-validator/src/StringLength.php create mode 100644 lib/laminas/laminas-validator/src/Timezone.php create mode 100644 lib/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php create mode 100644 lib/laminas/laminas-validator/src/Translator/TranslatorInterface.php create mode 100644 lib/laminas/laminas-validator/src/Uri.php create mode 100644 lib/laminas/laminas-validator/src/Uuid.php create mode 100644 lib/laminas/laminas-validator/src/ValidatorChain.php create mode 100644 lib/laminas/laminas-validator/src/ValidatorInterface.php create mode 100644 lib/laminas/laminas-validator/src/ValidatorPluginManager.php create mode 100644 lib/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php create mode 100644 lib/laminas/laminas-validator/src/ValidatorPluginManagerFactory.php create mode 100644 lib/laminas/laminas-validator/src/ValidatorProviderInterface.php create mode 100644 lib/laminas/laminas-zendframework-bridge/.github/FUNDING.yml create mode 100644 lib/laminas/laminas-zendframework-bridge/.github/workflows/release-on-milestone-closed.yml create mode 100644 lib/laminas/laminas-zendframework-bridge/CHANGELOG.md create mode 100644 lib/laminas/laminas-zendframework-bridge/COPYRIGHT.md create mode 100644 lib/laminas/laminas-zendframework-bridge/LICENSE.md create mode 100644 lib/laminas/laminas-zendframework-bridge/README.md create mode 100644 lib/laminas/laminas-zendframework-bridge/composer.json create mode 100644 lib/laminas/laminas-zendframework-bridge/config/replacements.php create mode 100644 lib/laminas/laminas-zendframework-bridge/src/Autoloader.php create mode 100644 lib/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php create mode 100644 lib/laminas/laminas-zendframework-bridge/src/Module.php create mode 100644 lib/laminas/laminas-zendframework-bridge/src/Replacements.php create mode 100644 lib/laminas/laminas-zendframework-bridge/src/RewriteRules.php create mode 100644 lib/laminas/laminas-zendframework-bridge/src/autoload.php create mode 100644 lib/league/oauth2-client/LICENSE create mode 100644 lib/league/oauth2-client/README.md create mode 100644 lib/league/oauth2-client/composer.json create mode 100644 lib/league/oauth2-client/src/Grant/AbstractGrant.php create mode 100644 lib/league/oauth2-client/src/Grant/AuthorizationCode.php create mode 100644 lib/league/oauth2-client/src/Grant/ClientCredentials.php create mode 100644 lib/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php create mode 100644 lib/league/oauth2-client/src/Grant/GrantFactory.php create mode 100644 lib/league/oauth2-client/src/Grant/Password.php create mode 100644 lib/league/oauth2-client/src/Grant/RefreshToken.php create mode 100644 lib/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php create mode 100644 lib/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php create mode 100644 lib/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php create mode 100644 lib/league/oauth2-client/src/Provider/AbstractProvider.php create mode 100644 lib/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php create mode 100644 lib/league/oauth2-client/src/Provider/GenericProvider.php create mode 100644 lib/league/oauth2-client/src/Provider/GenericResourceOwner.php create mode 100644 lib/league/oauth2-client/src/Provider/ResourceOwnerInterface.php create mode 100644 lib/league/oauth2-client/src/Token/AccessToken.php create mode 100644 lib/league/oauth2-client/src/Token/AccessTokenInterface.php create mode 100644 lib/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php create mode 100644 lib/league/oauth2-client/src/Tool/ArrayAccessorTrait.php create mode 100644 lib/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php create mode 100644 lib/league/oauth2-client/src/Tool/GuardedPropertyTrait.php create mode 100644 lib/league/oauth2-client/src/Tool/MacAuthorizationTrait.php create mode 100644 lib/league/oauth2-client/src/Tool/ProviderRedirectTrait.php create mode 100644 lib/league/oauth2-client/src/Tool/QueryBuilderTrait.php create mode 100644 lib/league/oauth2-client/src/Tool/RequestFactory.php create mode 100644 lib/league/oauth2-client/src/Tool/RequiredParameterTrait.php create mode 100644 lib/league/oauth2-google/CHANGELOG.md create mode 100644 lib/league/oauth2-google/CONTRIBUTING.md create mode 100644 lib/league/oauth2-google/LICENSE create mode 100644 lib/league/oauth2-google/README.md create mode 100644 lib/league/oauth2-google/composer.json create mode 100644 lib/league/oauth2-google/examples/index.php create mode 100644 lib/league/oauth2-google/examples/provider.php create mode 100644 lib/league/oauth2-google/examples/reset.php create mode 100644 lib/league/oauth2-google/examples/server.sh create mode 100644 lib/league/oauth2-google/examples/user.php create mode 100644 lib/league/oauth2-google/phpunit.xml.dist create mode 100644 lib/league/oauth2-google/src/Exception/HostedDomainException.php create mode 100644 lib/league/oauth2-google/src/Provider/Google.php create mode 100644 lib/league/oauth2-google/src/Provider/GoogleUser.php create mode 100644 lib/ralouphie/getallheaders/LICENSE create mode 100644 lib/ralouphie/getallheaders/README.md create mode 100644 lib/ralouphie/getallheaders/composer.json create mode 100644 lib/ralouphie/getallheaders/src/getallheaders.php create mode 100644 lib/thenetworg/oauth2-azure/.devcontainer/Dockerfile create mode 100644 lib/thenetworg/oauth2-azure/.devcontainer/devcontainer.json create mode 100644 lib/thenetworg/oauth2-azure/.gitignore create mode 100644 lib/thenetworg/oauth2-azure/.php_cs create mode 100644 lib/thenetworg/oauth2-azure/.vscode/launch.json create mode 100644 lib/thenetworg/oauth2-azure/CHANGELOG.md create mode 100644 lib/thenetworg/oauth2-azure/LICENSE.md create mode 100644 lib/thenetworg/oauth2-azure/README.md create mode 100644 lib/thenetworg/oauth2-azure/composer.json create mode 100644 lib/thenetworg/oauth2-azure/src/Grant/JwtBearer.php create mode 100644 lib/thenetworg/oauth2-azure/src/Provider/Azure.php create mode 100644 lib/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php create mode 100644 lib/thenetworg/oauth2-azure/src/Token/AccessToken.php create mode 100644 lib/true/punycode/CHANGELOG.md create mode 100644 lib/true/punycode/LICENSE create mode 100644 lib/true/punycode/README.md create mode 100644 lib/true/punycode/composer.json create mode 100644 lib/true/punycode/src/Exception/DomainOutOfBoundsException.php create mode 100644 lib/true/punycode/src/Exception/LabelOutOfBoundsException.php create mode 100644 lib/true/punycode/src/Exception/OutOfBoundsException.php create mode 100644 lib/true/punycode/src/Punycode.php create mode 100644 pages/ajax.oauth.wizard.php rename pages/{oauth/landing.php => oauth.landing.php} (100%) create mode 100644 pages/oauth.wizard.php delete mode 100644 pages/oauth/ajax.wizard.php delete mode 100644 pages/oauth/wizard.php create mode 100644 sources/Controller/OAuth/OAuthAjaxController.php create mode 100644 templates/pages/backoffice/oauth/DisplayConfig.html.twig diff --git a/composer.json b/composer.json index 3b32231e5..ec3e9df39 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,8 @@ "application", "sources/application", "sources/Composer", - "sources/Controller" + "sources/Controller", + "sources/Core" ], "exclude-from-classmap": [ "core/dbobjectsearch.class.php", diff --git a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml index 70534c9ad..2e3e204d8 100644 --- a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml +++ b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml @@ -186,5 +186,10 @@ Trigger UR_ACTION_MODIFY + + 45 + ConfigurationTools + $pages/oauth.wizard.php + diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 11766f30e..82cbbe76f 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -150,7 +150,15 @@ return array( 'Combodo\\iTop\\Application\\TwigBase\\Twig\\TwigHelper' => $baseDir . '/sources/application/TwigBase/Twig/TwigHelper.php', 'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php', + 'Combodo\\iTop\\Controller\\OAuth\\OAuthAjaxController' => $baseDir . '/sources/Controller/OAuth/OAuthAjaxController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthWizardController' => $baseDir . '/sources/Controller/OAuth/OAuthWizardController.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientResultDisplay' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAzure' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientResultDisplayConf' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php', 'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php', @@ -302,6 +310,8 @@ return array( 'Egulias\\EmailValidator\\Warning\\QuotedString' => $vendorDir . '/egulias/email-validator/src/Warning/QuotedString.php', 'Egulias\\EmailValidator\\Warning\\TLD' => $vendorDir . '/egulias/email-validator/src/Warning/TLD.php', 'Egulias\\EmailValidator\\Warning\\Warning' => $vendorDir . '/egulias/email-validator/src/Warning/Warning.php', + 'EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php', + 'EmailSwiftMailer' => $baseDir . '/sources/Core/Email/EmailSwiftMailer.php', 'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php', 'ErrorPage' => $baseDir . '/application/errorpage.class.inc.php', 'Event' => $baseDir . '/core/event.class.inc.php', @@ -354,6 +364,7 @@ return array( 'JSButtonItem' => $baseDir . '/application/applicationextension.inc.php', 'JSPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', 'KeyValueStore' => $baseDir . '/core/counter.class.inc.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Oauth' => $baseDir . '/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php', 'ListExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'ListOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'LogAPI' => $baseDir . '/core/log.class.inc.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index afed08926..b94fbf549 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -420,7 +420,15 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Application\\TwigBase\\Twig\\TwigHelper' => __DIR__ . '/../..' . '/sources/application/TwigBase/Twig/TwigHelper.php', 'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php', + 'Combodo\\iTop\\Controller\\OAuth\\OAuthAjaxController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthAjaxController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthWizardController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthWizardController.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientResultDisplay' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAzure' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientResultDisplayConf' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php', 'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php', @@ -572,6 +580,8 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Egulias\\EmailValidator\\Warning\\QuotedString' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/QuotedString.php', 'Egulias\\EmailValidator\\Warning\\TLD' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/TLD.php', 'Egulias\\EmailValidator\\Warning\\Warning' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/Warning.php', + 'EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php', + 'EmailSwiftMailer' => __DIR__ . '/../..' . '/sources/Core/Email/EmailSwiftMailer.php', 'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php', 'ErrorPage' => __DIR__ . '/../..' . '/application/errorpage.class.inc.php', 'Event' => __DIR__ . '/../..' . '/core/event.class.inc.php', @@ -624,6 +634,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'JSButtonItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', 'JSPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', 'KeyValueStore' => __DIR__ . '/../..' . '/core/counter.class.inc.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Oauth' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php', 'ListExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'ListOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'LogAPI' => __DIR__ . '/../..' . '/core/log.class.inc.php', diff --git a/lib/firebase/php-jwt/LICENSE b/lib/firebase/php-jwt/LICENSE new file mode 100644 index 000000000..11c014665 --- /dev/null +++ b/lib/firebase/php-jwt/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2011, Neuman Vong + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the copyright holder nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/firebase/php-jwt/README.md b/lib/firebase/php-jwt/README.md new file mode 100644 index 000000000..1d392cd12 --- /dev/null +++ b/lib/firebase/php-jwt/README.md @@ -0,0 +1,289 @@ +[![Build Status](https://travis-ci.org/firebase/php-jwt.png?branch=master)](https://travis-ci.org/firebase/php-jwt) +[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt) +[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt) +[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt) + +PHP-JWT +======= +A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519). + +Installation +------------ + +Use composer to manage your dependencies and download PHP-JWT: + +```bash +composer require firebase/php-jwt +``` + +Optionally, install the `paragonie/sodium_compat` package from composer if your +php is < 7.2 or does not have libsodium installed: + +```bash +composer require paragonie/sodium_compat +``` + +Example +------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +$key = "example_key"; +$payload = array( + "iss" => "http://example.org", + "aud" => "http://example.com", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +/** + * IMPORTANT: + * You must specify supported algorithms for your application. See + * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 + * for a list of spec-compliant algorithms. + */ +$jwt = JWT::encode($payload, $key, 'HS256'); +$decoded = JWT::decode($jwt, new Key($key, 'HS256')); + +print_r($decoded); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; + +/** + * You can add a leeway to account for when there is a clock skew times between + * the signing and verifying servers. It is recommended that this leeway should + * not be bigger than a few minutes. + * + * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef + */ +JWT::$leeway = 60; // $leeway in seconds +$decoded = JWT::decode($jwt, new Key($key, 'HS256')); +``` +Example with RS256 (openssl) +---------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +$privateKey = << "example.org", + "aud" => "example.com", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +$jwt = JWT::encode($payload, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; +echo "Decode:\n" . print_r($decoded_array, true) . "\n"; +``` + +Example with a passphrase +------------------------- + +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Your passphrase +$passphrase = '[YOUR_PASSPHRASE]'; + +// Your private key file with passphrase +// Can be generated with "ssh-keygen -t rsa -m pem" +$privateKeyFile = '/path/to/key-with-passphrase.pem'; + +// Create a private key of type "resource" +$privateKey = openssl_pkey_get_private( + file_get_contents($privateKeyFile), + $passphrase +); + +$payload = array( + "iss" => "example.org", + "aud" => "example.com", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +$jwt = JWT::encode($payload, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +// Get public key from the private key, or pull from from a file. +$publicKey = openssl_pkey_get_details($privateKey)['key']; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); +echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; +``` + +Example with EdDSA (libsodium and Ed25519 signature) +---------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Public and private keys are expected to be Base64 encoded. The last +// non-empty line is used so that keys can be generated with +// sodium_crypto_sign_keypair(). The secret keys generated by other tools may +// need to be adjusted to match the input expected by libsodium. + +$keyPair = sodium_crypto_sign_keypair(); + +$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair)); + +$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); + +$payload = array( + "iss" => "example.org", + "aud" => "example.com", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +$jwt = JWT::encode($payload, $privateKey, 'EdDSA'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA')); +echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; +```` + +Using JWKs +---------- + +```php +use Firebase\JWT\JWK; +use Firebase\JWT\JWT; + +// Set of keys. The "keys" key is required. For example, the JSON response to +// this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk +$jwks = ['keys' => []]; + +// JWK::parseKeySet($jwks) returns an associative array of **kid** to private +// key. Pass this as the second parameter to JWT::decode. +// NOTE: The deprecated $supportedAlgorithm must be supplied when parsing from JWK. +JWT::decode($payload, JWK::parseKeySet($jwks), $supportedAlgorithm); +``` + +Changelog +--------- + +#### 5.0.0 / 2017-06-26 +- Support RS384 and RS512. + See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)! +- Add an example for RS256 openssl. + See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)! +- Detect invalid Base64 encoding in signature. + See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)! +- Update `JWT::verify` to handle OpenSSL errors. + See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)! +- Add `array` type hinting to `decode` method + See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)! +- Add all JSON error types. + See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)! +- Bugfix 'kid' not in given key list. + See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)! +- Miscellaneous cleanup, documentation and test fixes. + See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115), + [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and + [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman), + [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)! + +#### 4.0.0 / 2016-07-17 +- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! +- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! +- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! +- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! + +#### 3.0.0 / 2015-07-22 +- Minimum PHP version updated from `5.2.0` to `5.3.0`. +- Add `\Firebase\JWT` namespace. See +[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to +[@Dashron](https://github.com/Dashron)! +- Require a non-empty key to decode and verify a JWT. See +[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to +[@sjones608](https://github.com/sjones608)! +- Cleaner documentation blocks in the code. See +[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to +[@johanderuijter](https://github.com/johanderuijter)! + +#### 2.2.0 / 2015-06-22 +- Add support for adding custom, optional JWT headers to `JWT::encode()`. See +[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to +[@mcocaro](https://github.com/mcocaro)! + +#### 2.1.0 / 2015-05-20 +- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew +between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! +- Add support for passing an object implementing the `ArrayAccess` interface for +`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! + +#### 2.0.0 / 2015-04-01 +- **Note**: It is strongly recommended that you update to > v2.0.0 to address + known security vulnerabilities in prior versions when both symmetric and + asymmetric keys are used together. +- Update signature for `JWT::decode(...)` to require an array of supported + algorithms to use when verifying token signatures. + + +Tests +----- +Run the tests using phpunit: + +```bash +$ pear install PHPUnit +$ phpunit --configuration phpunit.xml.dist +PHPUnit 3.7.10 by Sebastian Bergmann. +..... +Time: 0 seconds, Memory: 2.50Mb +OK (5 tests, 5 assertions) +``` + +New Lines in private keys +----- + +If your private key contains `\n` characters, be sure to wrap it in double quotes `""` +and not single quotes `''` in order to properly interpret the escaped characters. + +License +------- +[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause). diff --git a/lib/firebase/php-jwt/composer.json b/lib/firebase/php-jwt/composer.json new file mode 100644 index 000000000..6146e2dcc --- /dev/null +++ b/lib/firebase/php-jwt/composer.json @@ -0,0 +1,36 @@ +{ + "name": "firebase/php-jwt", + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "php", + "jwt" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "license": "BSD-3-Clause", + "require": { + "php": ">=5.3.0" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <=9" + } +} diff --git a/lib/firebase/php-jwt/src/BeforeValidException.php b/lib/firebase/php-jwt/src/BeforeValidException.php new file mode 100644 index 000000000..c147852b9 --- /dev/null +++ b/lib/firebase/php-jwt/src/BeforeValidException.php @@ -0,0 +1,7 @@ + + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWK +{ + /** + * Parse a set of JWK keys + * + * @param array $jwks The JSON Web Key Set as an associative array + * + * @return array An associative array that represents the set of keys + * + * @throws InvalidArgumentException Provided JWK Set is empty + * @throws UnexpectedValueException Provided JWK Set was invalid + * @throws DomainException OpenSSL failure + * + * @uses parseKey + */ + public static function parseKeySet(array $jwks) + { + $keys = array(); + + if (!isset($jwks['keys'])) { + throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); + } + if (empty($jwks['keys'])) { + throw new InvalidArgumentException('JWK Set did not contain any keys'); + } + + foreach ($jwks['keys'] as $k => $v) { + $kid = isset($v['kid']) ? $v['kid'] : $k; + if ($key = self::parseKey($v)) { + $keys[$kid] = $key; + } + } + + if (0 === \count($keys)) { + throw new UnexpectedValueException('No supported algorithms found in JWK Set'); + } + + return $keys; + } + + /** + * Parse a JWK key + * + * @param array $jwk An individual JWK + * + * @return resource|array An associative array that represents the key + * + * @throws InvalidArgumentException Provided JWK is empty + * @throws UnexpectedValueException Provided JWK was invalid + * @throws DomainException OpenSSL failure + * + * @uses createPemFromModulusAndExponent + */ + public static function parseKey(array $jwk) + { + if (empty($jwk)) { + throw new InvalidArgumentException('JWK must not be empty'); + } + if (!isset($jwk['kty'])) { + throw new UnexpectedValueException('JWK must contain a "kty" parameter'); + } + + switch ($jwk['kty']) { + case 'RSA': + if (!empty($jwk['d'])) { + throw new UnexpectedValueException('RSA private keys are not supported'); + } + if (!isset($jwk['n']) || !isset($jwk['e'])) { + throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"'); + } + + $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']); + $publicKey = \openssl_pkey_get_public($pem); + if (false === $publicKey) { + throw new DomainException( + 'OpenSSL error: ' . \openssl_error_string() + ); + } + return $publicKey; + default: + // Currently only RSA is supported + break; + } + } + + /** + * Create a public key represented in PEM format from RSA modulus and exponent information + * + * @param string $n The RSA modulus encoded in Base64 + * @param string $e The RSA exponent encoded in Base64 + * + * @return string The RSA public key represented in PEM format + * + * @uses encodeLength + */ + private static function createPemFromModulusAndExponent($n, $e) + { + $modulus = JWT::urlsafeB64Decode($n); + $publicExponent = JWT::urlsafeB64Decode($e); + + $components = array( + 'modulus' => \pack('Ca*a*', 2, self::encodeLength(\strlen($modulus)), $modulus), + 'publicExponent' => \pack('Ca*a*', 2, self::encodeLength(\strlen($publicExponent)), $publicExponent) + ); + + $rsaPublicKey = \pack( + 'Ca*a*a*', + 48, + self::encodeLength(\strlen($components['modulus']) + \strlen($components['publicExponent'])), + $components['modulus'], + $components['publicExponent'] + ); + + // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. + $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $rsaPublicKey = \chr(0) . $rsaPublicKey; + $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey; + + $rsaPublicKey = \pack( + 'Ca*a*', + 48, + self::encodeLength(\strlen($rsaOID . $rsaPublicKey)), + $rsaOID . $rsaPublicKey + ); + + $rsaPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . + \chunk_split(\base64_encode($rsaPublicKey), 64) . + '-----END PUBLIC KEY-----'; + + return $rsaPublicKey; + } + + /** + * DER-encode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. + * + * @param int $length + * @return string + */ + private static function encodeLength($length) + { + if ($length <= 0x7F) { + return \chr($length); + } + + $temp = \ltrim(\pack('N', $length), \chr(0)); + + return \pack('Ca*', 0x80 | \strlen($temp), $temp); + } +} diff --git a/lib/firebase/php-jwt/src/JWT.php b/lib/firebase/php-jwt/src/JWT.php new file mode 100644 index 000000000..ec1641bc4 --- /dev/null +++ b/lib/firebase/php-jwt/src/JWT.php @@ -0,0 +1,611 @@ + + * @author Anant Narayanan + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWT +{ + const ASN1_INTEGER = 0x02; + const ASN1_SEQUENCE = 0x10; + const ASN1_BIT_STRING = 0x03; + + /** + * When checking nbf, iat or expiration times, + * we want to provide some extra leeway time to + * account for clock skew. + */ + public static $leeway = 0; + + /** + * Allow the current timestamp to be specified. + * Useful for fixing a value within unit testing. + * + * Will default to PHP time() value if null. + */ + public static $timestamp = null; + + public static $supported_algs = array( + 'ES384' => array('openssl', 'SHA384'), + 'ES256' => array('openssl', 'SHA256'), + 'HS256' => array('hash_hmac', 'SHA256'), + 'HS384' => array('hash_hmac', 'SHA384'), + 'HS512' => array('hash_hmac', 'SHA512'), + 'RS256' => array('openssl', 'SHA256'), + 'RS384' => array('openssl', 'SHA384'), + 'RS512' => array('openssl', 'SHA512'), + 'EdDSA' => array('sodium_crypto', 'EdDSA'), + ); + + /** + * Decodes a JWT string into a PHP object. + * + * @param string $jwt The JWT + * @param Key|array|mixed $keyOrKeyArray The Key or array of Key objects. + * If the algorithm used is asymmetric, this is the public key + * Each Key object contains an algorithm and matching key. + * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', + * 'HS512', 'RS256', 'RS384', and 'RS512' + * @param array $allowed_algs [DEPRECATED] List of supported verification algorithms. Only + * should be used for backwards compatibility. + * + * @return object The JWT's payload as a PHP object + * + * @throws InvalidArgumentException Provided JWT was empty + * @throws UnexpectedValueException Provided JWT was invalid + * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed + * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' + * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' + * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim + * + * @uses jsonDecode + * @uses urlsafeB64Decode + */ + public static function decode($jwt, $keyOrKeyArray, array $allowed_algs = array()) + { + $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; + + if (empty($keyOrKeyArray)) { + throw new InvalidArgumentException('Key may not be empty'); + } + $tks = \explode('.', $jwt); + if (\count($tks) != 3) { + throw new UnexpectedValueException('Wrong number of segments'); + } + list($headb64, $bodyb64, $cryptob64) = $tks; + if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) { + throw new UnexpectedValueException('Invalid header encoding'); + } + if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) { + throw new UnexpectedValueException('Invalid claims encoding'); + } + if (false === ($sig = static::urlsafeB64Decode($cryptob64))) { + throw new UnexpectedValueException('Invalid signature encoding'); + } + if (empty($header->alg)) { + throw new UnexpectedValueException('Empty algorithm'); + } + if (empty(static::$supported_algs[$header->alg])) { + throw new UnexpectedValueException('Algorithm not supported'); + } + + list($keyMaterial, $algorithm) = self::getKeyMaterialAndAlgorithm( + $keyOrKeyArray, + empty($header->kid) ? null : $header->kid + ); + + if (empty($algorithm)) { + // Use deprecated "allowed_algs" to determine if the algorithm is supported. + // This opens up the possibility of an attack in some implementations. + // @see https://github.com/firebase/php-jwt/issues/351 + if (!\in_array($header->alg, $allowed_algs)) { + throw new UnexpectedValueException('Algorithm not allowed'); + } + } else { + // Check the algorithm + if (!self::constantTimeEquals($algorithm, $header->alg)) { + // See issue #351 + throw new UnexpectedValueException('Incorrect key for this algorithm'); + } + } + if ($header->alg === 'ES256' || $header->alg === 'ES384') { + // OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures + $sig = self::signatureToDER($sig); + } + + if (!static::verify("$headb64.$bodyb64", $sig, $keyMaterial, $header->alg)) { + throw new SignatureInvalidException('Signature verification failed'); + } + + // Check the nbf if it is defined. This is the time that the + // token can actually be used. If it's not yet that time, abort. + if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf) + ); + } + + // Check that this token has been created before 'now'. This prevents + // using tokens that have been created for later use (and haven't + // correctly used the nbf claim). + if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat) + ); + } + + // Check if this token has expired. + if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + throw new ExpiredException('Expired token'); + } + + return $payload; + } + + /** + * Converts and signs a PHP object or array into a JWT string. + * + * @param object|array $payload PHP object or array + * @param string|resource $key The secret key. + * If the algorithm used is asymmetric, this is the private key + * @param string $alg The signing algorithm. + * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', + * 'HS512', 'RS256', 'RS384', and 'RS512' + * @param mixed $keyId + * @param array $head An array with header elements to attach + * + * @return string A signed JWT + * + * @uses jsonEncode + * @uses urlsafeB64Encode + */ + public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null) + { + $header = array('typ' => 'JWT', 'alg' => $alg); + if ($keyId !== null) { + $header['kid'] = $keyId; + } + if (isset($head) && \is_array($head)) { + $header = \array_merge($head, $header); + } + $segments = array(); + $segments[] = static::urlsafeB64Encode(static::jsonEncode($header)); + $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload)); + $signing_input = \implode('.', $segments); + + $signature = static::sign($signing_input, $key, $alg); + $segments[] = static::urlsafeB64Encode($signature); + + return \implode('.', $segments); + } + + /** + * Sign a string with a given key and algorithm. + * + * @param string $msg The message to sign + * @param string|resource $key The secret key + * @param string $alg The signing algorithm. + * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', + * 'HS512', 'RS256', 'RS384', and 'RS512' + * + * @return string An encrypted message + * + * @throws DomainException Unsupported algorithm or bad key was specified + */ + public static function sign($msg, $key, $alg = 'HS256') + { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'hash_hmac': + return \hash_hmac($algorithm, $msg, $key, true); + case 'openssl': + $signature = ''; + $success = \openssl_sign($msg, $signature, $key, $algorithm); + if (!$success) { + throw new DomainException("OpenSSL unable to sign data"); + } + if ($alg === 'ES256') { + $signature = self::signatureFromDER($signature, 256); + } elseif ($alg === 'ES384') { + $signature = self::signatureFromDER($signature, 384); + } + return $signature; + case 'sodium_crypto': + if (!function_exists('sodium_crypto_sign_detached')) { + throw new DomainException('libsodium is not available'); + } + try { + // The last non-empty line is used as the key. + $lines = array_filter(explode("\n", $key)); + $key = base64_decode(end($lines)); + return sodium_crypto_sign_detached($msg, $key); + } catch (Exception $e) { + throw new DomainException($e->getMessage(), 0, $e); + } + } + } + + /** + * Verify a signature with the message, key and method. Not all methods + * are symmetric, so we must have a separate verify and sign method. + * + * @param string $msg The original message (header and body) + * @param string $signature The original signature + * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key + * @param string $alg The algorithm + * + * @return bool + * + * @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure + */ + private static function verify($msg, $signature, $key, $alg) + { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'openssl': + $success = \openssl_verify($msg, $signature, $key, $algorithm); + if ($success === 1) { + return true; + } elseif ($success === 0) { + return false; + } + // returns 1 on success, 0 on failure, -1 on error. + throw new DomainException( + 'OpenSSL error: ' . \openssl_error_string() + ); + case 'sodium_crypto': + if (!function_exists('sodium_crypto_sign_verify_detached')) { + throw new DomainException('libsodium is not available'); + } + try { + // The last non-empty line is used as the key. + $lines = array_filter(explode("\n", $key)); + $key = base64_decode(end($lines)); + return sodium_crypto_sign_verify_detached($signature, $msg, $key); + } catch (Exception $e) { + throw new DomainException($e->getMessage(), 0, $e); + } + case 'hash_hmac': + default: + $hash = \hash_hmac($algorithm, $msg, $key, true); + return self::constantTimeEquals($signature, $hash); + } + } + + /** + * Decode a JSON string into a PHP object. + * + * @param string $input JSON string + * + * @return object Object representation of JSON string + * + * @throws DomainException Provided string was invalid JSON + */ + public static function jsonDecode($input) + { + if (\version_compare(PHP_VERSION, '5.4.0', '>=') && !(\defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) { + /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you + * to specify that large ints (like Steam Transaction IDs) should be treated as + * strings, rather than the PHP default behaviour of converting them to floats. + */ + $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING); + } else { + /** Not all servers will support that, however, so for older versions we must + * manually detect large ints in the JSON string and quote them (thus converting + *them to strings) before decoding, hence the preg_replace() call. + */ + $max_int_length = \strlen((string) PHP_INT_MAX) - 1; + $json_without_bigints = \preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input); + $obj = \json_decode($json_without_bigints); + } + + if ($errno = \json_last_error()) { + static::handleJsonError($errno); + } elseif ($obj === null && $input !== 'null') { + throw new DomainException('Null result with non-null input'); + } + return $obj; + } + + /** + * Encode a PHP object into a JSON string. + * + * @param object|array $input A PHP object or array + * + * @return string JSON representation of the PHP object or array + * + * @throws DomainException Provided object could not be encoded to valid JSON + */ + public static function jsonEncode($input) + { + $json = \json_encode($input); + if ($errno = \json_last_error()) { + static::handleJsonError($errno); + } elseif ($json === 'null' && $input !== null) { + throw new DomainException('Null result with non-null input'); + } + return $json; + } + + /** + * Decode a string with URL-safe Base64. + * + * @param string $input A Base64 encoded string + * + * @return string A decoded string + */ + public static function urlsafeB64Decode($input) + { + $remainder = \strlen($input) % 4; + if ($remainder) { + $padlen = 4 - $remainder; + $input .= \str_repeat('=', $padlen); + } + return \base64_decode(\strtr($input, '-_', '+/')); + } + + /** + * Encode a string with URL-safe Base64. + * + * @param string $input The string you want encoded + * + * @return string The base64 encode of what you passed in + */ + public static function urlsafeB64Encode($input) + { + return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); + } + + + /** + * Determine if an algorithm has been provided for each Key + * + * @param Key|array|mixed $keyOrKeyArray + * @param string|null $kid + * + * @throws UnexpectedValueException + * + * @return array containing the keyMaterial and algorithm + */ + private static function getKeyMaterialAndAlgorithm($keyOrKeyArray, $kid = null) + { + if ( + is_string($keyOrKeyArray) + || is_resource($keyOrKeyArray) + || $keyOrKeyArray instanceof OpenSSLAsymmetricKey + ) { + return array($keyOrKeyArray, null); + } + + if ($keyOrKeyArray instanceof Key) { + return array($keyOrKeyArray->getKeyMaterial(), $keyOrKeyArray->getAlgorithm()); + } + + if (is_array($keyOrKeyArray) || $keyOrKeyArray instanceof ArrayAccess) { + if (!isset($kid)) { + throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); + } + if (!isset($keyOrKeyArray[$kid])) { + throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + } + + $key = $keyOrKeyArray[$kid]; + + if ($key instanceof Key) { + return array($key->getKeyMaterial(), $key->getAlgorithm()); + } + + return array($key, null); + } + + throw new UnexpectedValueException( + '$keyOrKeyArray must be a string|resource key, an array of string|resource keys, ' + . 'an instance of Firebase\JWT\Key key or an array of Firebase\JWT\Key keys' + ); + } + + /** + * @param string $left + * @param string $right + * @return bool + */ + public static function constantTimeEquals($left, $right) + { + if (\function_exists('hash_equals')) { + return \hash_equals($left, $right); + } + $len = \min(static::safeStrlen($left), static::safeStrlen($right)); + + $status = 0; + for ($i = 0; $i < $len; $i++) { + $status |= (\ord($left[$i]) ^ \ord($right[$i])); + } + $status |= (static::safeStrlen($left) ^ static::safeStrlen($right)); + + return ($status === 0); + } + + /** + * Helper method to create a JSON error. + * + * @param int $errno An error number from json_last_error() + * + * @return void + */ + private static function handleJsonError($errno) + { + $messages = array( + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3 + ); + throw new DomainException( + isset($messages[$errno]) + ? $messages[$errno] + : 'Unknown JSON error: ' . $errno + ); + } + + /** + * Get the number of bytes in cryptographic strings. + * + * @param string $str + * + * @return int + */ + private static function safeStrlen($str) + { + if (\function_exists('mb_strlen')) { + return \mb_strlen($str, '8bit'); + } + return \strlen($str); + } + + /** + * Convert an ECDSA signature to an ASN.1 DER sequence + * + * @param string $sig The ECDSA signature to convert + * @return string The encoded DER object + */ + private static function signatureToDER($sig) + { + // Separate the signature into r-value and s-value + list($r, $s) = \str_split($sig, (int) (\strlen($sig) / 2)); + + // Trim leading zeros + $r = \ltrim($r, "\x00"); + $s = \ltrim($s, "\x00"); + + // Convert r-value and s-value from unsigned big-endian integers to + // signed two's complement + if (\ord($r[0]) > 0x7f) { + $r = "\x00" . $r; + } + if (\ord($s[0]) > 0x7f) { + $s = "\x00" . $s; + } + + return self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER(self::ASN1_INTEGER, $r) . + self::encodeDER(self::ASN1_INTEGER, $s) + ); + } + + /** + * Encodes a value into a DER object. + * + * @param int $type DER tag + * @param string $value the value to encode + * @return string the encoded object + */ + private static function encodeDER($type, $value) + { + $tag_header = 0; + if ($type === self::ASN1_SEQUENCE) { + $tag_header |= 0x20; + } + + // Type + $der = \chr($tag_header | $type); + + // Length + $der .= \chr(\strlen($value)); + + return $der . $value; + } + + /** + * Encodes signature from a DER object. + * + * @param string $der binary signature in DER format + * @param int $keySize the number of bits in the key + * @return string the signature + */ + private static function signatureFromDER($der, $keySize) + { + // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE + list($offset, $_) = self::readDER($der); + list($offset, $r) = self::readDER($der, $offset); + list($offset, $s) = self::readDER($der, $offset); + + // Convert r-value and s-value from signed two's compliment to unsigned + // big-endian integers + $r = \ltrim($r, "\x00"); + $s = \ltrim($s, "\x00"); + + // Pad out r and s so that they are $keySize bits long + $r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT); + $s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT); + + return $r . $s; + } + + /** + * Reads binary DER-encoded data and decodes into a single object + * + * @param string $der the binary data in DER format + * @param int $offset the offset of the data stream containing the object + * to decode + * @return array [$offset, $data] the new offset and the decoded object + */ + private static function readDER($der, $offset = 0) + { + $pos = $offset; + $size = \strlen($der); + $constructed = (\ord($der[$pos]) >> 5) & 0x01; + $type = \ord($der[$pos++]) & 0x1f; + + // Length + $len = \ord($der[$pos++]); + if ($len & 0x80) { + $n = $len & 0x1f; + $len = 0; + while ($n-- && $pos < $size) { + $len = ($len << 8) | \ord($der[$pos++]); + } + } + + // Value + if ($type == self::ASN1_BIT_STRING) { + $pos++; // Skip the first contents octet (padding indicator) + $data = \substr($der, $pos, $len - 1); + $pos += $len - 1; + } elseif (!$constructed) { + $data = \substr($der, $pos, $len); + $pos += $len; + } else { + $data = null; + } + + return array($pos, $data); + } +} diff --git a/lib/firebase/php-jwt/src/Key.php b/lib/firebase/php-jwt/src/Key.php new file mode 100644 index 000000000..f1ede6f27 --- /dev/null +++ b/lib/firebase/php-jwt/src/Key.php @@ -0,0 +1,59 @@ +keyMaterial = $keyMaterial; + $this->algorithm = $algorithm; + } + + /** + * Return the algorithm valid for this key + * + * @return string + */ + public function getAlgorithm() + { + return $this->algorithm; + } + + /** + * @return string|resource|OpenSSLAsymmetricKey + */ + public function getKeyMaterial() + { + return $this->keyMaterial; + } +} diff --git a/lib/firebase/php-jwt/src/SignatureInvalidException.php b/lib/firebase/php-jwt/src/SignatureInvalidException.php new file mode 100644 index 000000000..d35dee9f1 --- /dev/null +++ b/lib/firebase/php-jwt/src/SignatureInvalidException.php @@ -0,0 +1,7 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'declare_strict_types' => false, + 'concat_space' => ['spacing'=>'one'], + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'ordered_imports' => true, + // 'phpdoc_align' => ['align'=>'vertical'], + // 'native_function_invocation' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ->name('*.php') + ) +; + +return $config; diff --git a/lib/guzzlehttp/guzzle/CHANGELOG.md b/lib/guzzlehttp/guzzle/CHANGELOG.md new file mode 100644 index 000000000..464cf1c50 --- /dev/null +++ b/lib/guzzlehttp/guzzle/CHANGELOG.md @@ -0,0 +1,1338 @@ +# Change Log + +## 6.5.5 - 2020-06-16 + +* Unpin version constraint for `symfony/polyfill-intl-idn` [#2678](https://github.com/guzzle/guzzle/pull/2678) + +## 6.5.4 - 2020-05-25 + +* Fix various intl icu issues [#2626](https://github.com/guzzle/guzzle/pull/2626) + +## 6.5.3 - 2020-04-18 + +* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550) +* Remove use of internal functions [#2548](https://github.com/guzzle/guzzle/pull/2548) + +## 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 retries due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132) + Previously, `RetryMiddleware` would sleep for 1 millisecond, then 2 milliseconds, then 4 milliseconds. + **After this change, `RetryMiddleware` will sleep for 1 second, then 2 seconds, then 4 seconds.** + `Middleware::retry()` accepts a second callback parameter to override the default timeouts if needed. +* 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 http://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: http://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 more information on the 4.0 transition, see: + http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/ +* 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 http://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/lib/guzzlehttp/guzzle/Dockerfile b/lib/guzzlehttp/guzzle/Dockerfile new file mode 100644 index 000000000..f6a095230 --- /dev/null +++ b/lib/guzzlehttp/guzzle/Dockerfile @@ -0,0 +1,18 @@ +FROM composer:latest as setup + +RUN mkdir /guzzle + +WORKDIR /guzzle + +RUN set -xe \ + && composer init --name=guzzlehttp/test --description="Simple project for testing Guzzle scripts" --author="Márk Sági-Kazár " --no-interaction \ + && composer require guzzlehttp/guzzle + + +FROM php:7.3 + +RUN mkdir /guzzle + +WORKDIR /guzzle + +COPY --from=setup /guzzle /guzzle diff --git a/lib/guzzlehttp/guzzle/LICENSE b/lib/guzzlehttp/guzzle/LICENSE new file mode 100644 index 000000000..50a177b03 --- /dev/null +++ b/lib/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/guzzlehttp/guzzle/README.md b/lib/guzzlehttp/guzzle/README.md new file mode 100644 index 000000000..5fdb6c5f4 --- /dev/null +++ b/lib/guzzlehttp/guzzle/README.md @@ -0,0 +1,90 @@ +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/travis/guzzle/guzzle.svg?style=flat-square)](https://travis-ci.org/guzzle/guzzle) +[![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. +- 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 + +- [Documentation](http://guzzlephp.org/) +- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle) +- [Gitter](https://gitter.im/guzzle/guzzle) + + +## Installing Guzzle + +The recommended way to install Guzzle is through +[Composer](http://getcomposer.org). + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php +``` + +Next, run the Composer command to install the latest stable version of Guzzle: + +```bash +composer require guzzlehttp/guzzle +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` + +You can then later update Guzzle using composer: + + ```bash +composer update + ``` + + +## 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 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 | +| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 | +| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 | + +[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 +[guzzle-3-docs]: http://guzzle3.readthedocs.org +[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/ +[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/ diff --git a/lib/guzzlehttp/guzzle/UPGRADING.md b/lib/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 000000000..91d1dcc99 --- /dev/null +++ b/lib/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1203 @@ +Guzzle Upgrade Guide +==================== + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](http://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](http://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 http://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/lib/guzzlehttp/guzzle/composer.json b/lib/guzzlehttp/guzzle/composer.json new file mode 100644 index 000000000..c01864f01 --- /dev/null +++ b/lib/guzzlehttp/guzzle/composer.json @@ -0,0 +1,59 @@ +{ + "name": "guzzlehttp/guzzle", + "type": "library", + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "framework", + "http", + "rest", + "web service", + "curl", + "client", + "HTTP client" + ], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.5", + "ext-json": "*", + "symfony/polyfill-intl-idn": "^1.17.0", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/Client.php b/lib/guzzlehttp/guzzle/src/Client.php new file mode 100644 index 000000000..315a022cf --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Client.php @@ -0,0 +1,501 @@ + '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\uri_for($config['base_uri']); + } + + $this->configureDefaults($config); + } + + /** + * @param string $method + * @param array $args + * + * @return Promise\PromiseInterface + */ + 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 = isset($args[1]) ? $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. + * + * @return Promise\PromiseInterface + */ + public function sendAsync(RequestInterface $request, array $options = []) + { + // 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. + * + * @return ResponseInterface + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + 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. + * + * @return Promise\PromiseInterface + */ + public function requestAsync($method, $uri = '', array $options = []) + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = isset($options['headers']) ? $options['headers'] : []; + $body = isset($options['body']) ? $options['body'] : null; + $version = isset($options['version']) ? $options['version'] : '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri($uri, $options); + if (is_array($body)) { + $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. + * + * @return ResponseInterface + * @throws GuzzleException + */ + public function request($method, $uri = '', array $options = []) + { + $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 + */ + public function getConfig($option = null) + { + return $option === null + ? $this->config + : (isset($this->config[$option]) ? $this->config[$option] : null); + } + + /** + * @param string|null $uri + * + * @return UriInterface + */ + private function buildUri($uri, array $config) + { + // for BC we accept null which would otherwise fail in uri_for + $uri = Psr7\uri_for($uri === null ? '' : $uri); + + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\uri_for($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. + * + * @param array $config + * @return void + */ + private function configureDefaults(array $config) + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => true, + ]; + + // 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_name() === 'cli' && getenv('HTTP_PROXY')) { + $defaults['proxy']['http'] = getenv('HTTP_PROXY'); + } + + if ($proxy = getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = 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' => default_user_agent()]; + } 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'] = default_user_agent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + * + * @return array + */ + private function prepareDefaults(array $options) + { + $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. + * + * @return Promise\PromiseInterface + */ + private function transfer(RequestInterface $request, array $options) + { + // save_to -> sink + if (isset($options['save_to'])) { + $options['sink'] = $options['save_to']; + unset($options['save_to']); + } + + // exceptions -> http_errors + if (isset($options['exceptions'])) { + $options['http_errors'] = $options['exceptions']; + unset($options['exceptions']); + } + + $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ + $handler = $options['handler']; + + try { + return Promise\promise_for($handler($request, $options)); + } catch (\Exception $e) { + return Promise\rejection_for($e); + } + } + + /** + * Applies the array of request options to a request. + * + * @param RequestInterface $request + * @param array $options + * + * @return RequestInterface + */ + private function applyOptions(RequestInterface $request, array &$options) + { + $modify = [ + 'set_headers' => [], + ]; + + if (isset($options['headers'])) { + $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\_caseless_remove(['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'] = \GuzzleHttp\json_encode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['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\_caseless_remove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['body'])) { + if (is_array($options['body'])) { + $this->invalidBody(); + } + $modify['body'] = Psr7\stream_for($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\_caseless_remove(['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, null, '&', 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'); + } + } + + $request = Psr7\modify_request($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\_caseless_remove(['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\modify_request($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + /** + * Throw Exception with pre-set message. + * @return void + * @throws \InvalidArgumentException Invalid body. + */ + private function invalidBody() + { + throw new \InvalidArgumentException('Passing in the "body" request ' + . 'option as an array to send a POST request has been deprecated. ' + . '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/lib/guzzlehttp/guzzle/src/ClientInterface.php b/lib/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 000000000..638b75dca --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,87 @@ +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 + * + * @return self + */ + public static function fromArray(array $cookies, $domain) + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * @deprecated + */ + public static function getCookieValue($value) + { + return $value; + } + + /** + * 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 + * @return bool + */ + public static function shouldPersist( + SetCookie $cookie, + $allowSessionCookies = false + ) { + 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($name) + { + // don't allow a non string name + if ($name === null || !is_scalar($name)) { + return null; + } + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + + return null; + } + + public function toArray() + { + return array_map(function (SetCookie $cookie) { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + public function clear($domain = null, $path = null, $name = null) + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($domain) { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + public function clearSessionCookies() + { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + public function setCookie(SetCookie $cookie) + { + // 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); + } else { + $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() + { + return count($this->cookies); + } + + public function getIterator() + { + return new \ArrayIterator(array_values($this->cookies)); + } + + public function extractCookies( + RequestInterface $request, + ResponseInterface $response + ) { + 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)); + } + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + * + * @param RequestInterface $request + * @return string + */ + private function getCookiePathFromRequest(RequestInterface $request) + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + if (0 === $lastSlashPos = strrpos($uriPath, '/')) { + return '/'; + } + + return substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request) + { + $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. + * + * @param SetCookie $cookie + */ + private function removeCookieIfEmpty(SetCookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/lib/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 000000000..6ee11885e --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,84 @@ +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($filename) + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $cookie */ + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = \GuzzleHttp\json_encode($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($filename) + { + $json = file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } elseif ($json === '') { + return; + } + + $data = \GuzzleHttp\json_decode($json, true); + if (is_array($data)) { + foreach (json_decode($json, true) as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/lib/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 000000000..0224a2447 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,72 @@ +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() + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $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() + { + 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/lib/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/lib/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 000000000..3d776a70b --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,403 @@ + 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 + * + * @return self + */ + public static function fromString($cookie) + { + // 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 (empty($pieces[0]) || !strpos($pieces[0], '=')) { + 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 (empty($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (array_keys(self::$defaults) as $search) { + if (!strcasecmp($search, $key)) { + $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 = array_replace(self::$defaults, $data); + // 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 ($this->getExpires() && !is_numeric($this->getExpires())) { + $this->setExpires($this->getExpires()); + } + } + + 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() + { + 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) + { + $this->data['Name'] = $name; + } + + /** + * Get the cookie value + * + * @return string + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value + * + * @param string $value Cookie value + */ + public function setValue($value) + { + $this->data['Value'] = $value; + } + + /** + * Get the domain + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie + * + * @param string $domain + */ + public function setDomain($domain) + { + $this->data['Domain'] = $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) + { + $this->data['Path'] = $path; + } + + /** + * Maximum lifetime of the cookie in seconds + * + * @return int|null + */ + public function getMaxAge() + { + return $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie + * + * @param int $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge) + { + $this->data['Max-Age'] = $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires + * + * @return mixed + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire + * + * @param int $timestamp Unix timestamp + */ + public function setExpires($timestamp) + { + $this->data['Expires'] = is_numeric($timestamp) + ? (int) $timestamp + : strtotime($timestamp); + } + + /** + * Get whether or not this is a secure cookie + * + * @return bool|null + */ + 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) + { + $this->data['Secure'] = $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) + { + $this->data['Discard'] = $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) + { + $this->data['HttpOnly'] = $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 + * + * @return bool + */ + public function matchesPath($requestPath) + { + $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 + * + * @return bool + */ + public function matchesDomain($domain) + { + // Remove the leading '.' as per spec in RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim($this->getDomain(), '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // http://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 + * + * @return bool + */ + public function isExpired() + { + 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() + { + // Names must not be empty, but can be 0 + $name = $this->getName(); + if (empty($name) && !is_numeric($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 empty, but can be 0 + $value = $this->getValue(); + if (empty($value) && !is_numeric($value)) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0 + // A "0" is not a valid internet domain, but may be used as server name + // in a private network. + $domain = $this->getDomain(); + if (empty($domain) && !is_numeric($domain)) { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/lib/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 000000000..427d896fb --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,27 @@ +getStatusCode() + : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + * + * @param RequestInterface $request + * @param \Exception $e + * + * @return RequestException + */ + public static function wrapException(RequestInterface $request, \Exception $e) + { + 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 + * @param ResponseInterface $response Response received + * @param \Exception $previous Previous exception + * @param array $ctx Optional handler context. + * + * @return self + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $previous = null, + array $ctx = [] + ) { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $ctx + ); + } + + $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, + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = static::getResponseBodySummary($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + return new $className($message, $request, $response, $previous, $ctx); + } + + /** + * Get a short summary of the response + * + * Will return `null` if the response is not printable. + * + * @param ResponseInterface $response + * + * @return string|null + */ + public static function getResponseBodySummary(ResponseInterface $response) + { + return \GuzzleHttp\Psr7\get_message_body_summary($response); + } + + /** + * Obfuscates URI if there is a username and a password present + * + * @param UriInterface $uri + * + * @return UriInterface + */ + private static function obfuscateUri(UriInterface $uri) + { + $userInfo = $uri->getUserInfo(); + + if (false !== ($pos = strpos($userInfo, ':'))) { + return $uri->withUserInfo(substr($userInfo, 0, $pos), '***'); + } + + return $uri; + } + + /** + * Get the request that caused the exception + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the associated response + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Check if a response was received + * + * @return bool + */ + public function hasResponse() + { + 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. + * + * @return array + */ + public function getHandlerContext() + { + return $this->handlerContext; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Exception/SeekException.php b/lib/guzzlehttp/guzzle/src/Exception/SeekException.php new file mode 100644 index 000000000..a77c28926 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Exception/SeekException.php @@ -0,0 +1,27 @@ +stream = $stream; + $msg = $msg ?: 'Could not seek the stream to position ' . $pos; + parent::__construct($msg); + } + + /** + * @return StreamInterface + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Exception/ServerException.php b/lib/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 000000000..127094c14 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,9 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options) + { + 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) + { + $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 $handler + * @param EasyHandle $easy + * @param CurlFactoryInterface $factory Dictates how the handle is released + * + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public static function finish( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + 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) + { + $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 + ); + call_user_func($easy->options['on_stats'], $stats); + } + + private static function finishError( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + // 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) + { + 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 an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return \GuzzleHttp\Promise\rejection_for( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) { + $message = sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + } else { + $message = sprintf( + 'cURL error %s: %s (%s) for %s', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html', + $easy->request->getUri() + ); + } + + // 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 \GuzzleHttp\Promise\rejection_for($error); + } + + private function getDefaultConf(EasyHandle $easy) + { + $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 => 150, + ]; + + 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) + { + $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 http://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) + { + $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] = 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) + { + 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($name, array &$options) + { + foreach (array_keys($options['_headers']) as $key) { + if (!strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf) + { + $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']) && is_dir(readlink($options['verify'])))) { + $conf[CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[CURLOPT_ENCODING] = $accept; + } else { + $conf[CURLOPT_ENCODING] = ''; + // Don't let curl send the header over the wire + $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (isset($options['sink'])) { + $sink = $options['sink']; + if (!is_string($sink)) { + $sink = \GuzzleHttp\Psr7\stream_for($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] = function ($ch, $write) use ($sink) { + return $sink->write($write); + }; + } else { + // Use a default temp stream if no sink was set. + $conf[CURLOPT_FILE] = fopen('php://temp', 'w+'); + $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]); + } + $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']) || + !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no']) + ) { + $conf[CURLOPT_PROXY] = $options['proxy'][$scheme]; + } + } + } + } + + 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}" + ); + } + $conf[CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + if (is_array($options['ssl_key'])) { + if (count($options['ssl_key']) === 2) { + list($sslKey, $conf[CURLOPT_SSLKEYPASSWD]) = $options['ssl_key']; + } else { + list($sslKey) = $options['ssl_key']; + } + } + + $sslKey = isset($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] = function () use ($progress) { + $args = func_get_args(); + // PHP 5.5 pushed the handle onto the start of the args + if (is_resource($args[0])) { + array_shift($args); + } + call_user_func_array($progress, $args); + }; + } + + if (!empty($options['debug'])) { + $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($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. + */ + private static function retryFailedRewind( + callable $handler, + EasyHandle $easy, + array $ctx + ) { + 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) + { + 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 function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = trim($h); + if ($value === '') { + $startingResponse = true; + $easy->createResponse(); + 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/lib/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/lib/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 000000000..b0fc23685 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,27 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] + : new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options) + { + 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/lib/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/lib/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 000000000..564c95f48 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,219 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] : new CurlFactory(50); + + if (isset($options['select_timeout'])) { + $this->selectTimeout = $options['select_timeout']; + } elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { + $this->selectTimeout = $selectTimeout; + } else { + $this->selectTimeout = 1; + } + + $this->options = isset($options['options']) ? $options['options'] : []; + } + + public function __get($name) + { + if ($name === '_mh') { + $this->_mh = curl_multi_init(); + + foreach ($this->options as $option => $value) { + // A warning is raised in case of a wrong option. + curl_multi_setopt($this->_mh, $option, $value); + } + + // Further calls to _mh will return the value directly, without entering the + // __get() method at all. + return $this->_mh; + } + + throw new \BadMethodCallException(); + } + + public function __destruct() + { + if (isset($this->_mh)) { + curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + $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() + { + // 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\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() + { + $queue = P\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) + { + $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) + { + // 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() + { + while ($done = curl_multi_info_read($this->_mh)) { + $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() + { + $currentTime = Utils::currentTime(); + $nextTime = PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return max(0, $nextTime - $currentTime) * 1000000; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/lib/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 000000000..7754e9111 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,92 @@ +headers)) { + throw new \RuntimeException('No headers have been received'); + } + + // HTTP-version SP status-code SP reason-phrase + $startLine = explode(' ', array_shift($this->headers), 3); + $headers = \GuzzleHttp\headers_from_lines($this->headers); + $normalizedKeys = \GuzzleHttp\normalize_header_keys($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( + $startLine[1], + $headers, + $this->sink, + substr($startLine[0], 5), + isset($startLine[2]) ? (string) $startLine[2] : null + ); + } + + public function __get($name) + { + $msg = $name === 'handle' + ? 'The EasyHandle has been released' + : 'Invalid property: ' . $name; + throw new \BadMethodCallException($msg); + } +} diff --git a/lib/guzzlehttp/guzzle/src/Handler/MockHandler.php b/lib/guzzlehttp/guzzle/src/Handler/MockHandler.php new file mode 100644 index 000000000..5b312bc04 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/MockHandler.php @@ -0,0 +1,195 @@ +onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + call_user_func_array([$this, 'append'], $queue); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay']) && is_numeric($options['delay'])) { + usleep($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 = call_user_func($response, $request, $options); + } + + $response = $response instanceof \Exception + ? \GuzzleHttp\Promise\rejection_for($response) + : \GuzzleHttp\Promise\promise_for($response); + + return $response->then( + function ($value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + call_user_func($this->onFulfilled, $value); + } + if (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 \Psr\Http\Message\StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + call_user_func($this->onRejected, $reason); + } + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + */ + public function append() + { + foreach (func_get_args() as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Exception + || $value instanceof PromiseInterface + || is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \InvalidArgumentException('Expected a response or ' + . 'exception. Found ' . \GuzzleHttp\describe_type($value)); + } + } + } + + /** + * Get the last received request. + * + * @return RequestInterface + */ + public function getLastRequest() + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + * + * @return array + */ + public function getLastOptions() + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + * + * @return int + */ + public function count() + { + return count($this->queue); + } + + public function reset() + { + $this->queue = []; + } + + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ) { + if (isset($options['on_stats'])) { + $transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); + call_user_func($options['on_stats'], $stats); + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/Handler/Proxy.php b/lib/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 000000000..f8b00be0b --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,55 @@ +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 (strpos($message, 'getaddrinfo') // DNS lookup failed + || strpos($message, 'Connection refused') + || strpos($message, "couldn't connect to host") // error on HHVM + || strpos($message, "connection attempt failed") + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } + $e = RequestException::wrapException($request, $e); + $this->invokeStats($options, $request, $startTime, null, $e); + + return \GuzzleHttp\Promise\rejection_for($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + $startTime, + ResponseInterface $response = null, + $error = null + ) { + if (isset($options['on_stats'])) { + $stats = new TransferStats( + $request, + $response, + Utils::currentTime() - $startTime, + $error, + [] + ); + call_user_func($options['on_stats'], $stats); + } + } + + private function createResponse( + RequestInterface $request, + array $options, + $stream, + $startTime + ) { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + $parts = explode(' ', array_shift($hdrs), 3); + $ver = explode('/', $parts[0])[1]; + $status = $parts[1]; + $reason = isset($parts[2]) ? $parts[2] : null; + $headers = \GuzzleHttp\headers_from_lines($hdrs); + list($stream, $headers) = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\stream_for($stream); + $sink = $stream; + + if (strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $ex = new RequestException($msg, $request, $response, $e); + return \GuzzleHttp\Promise\rejection_for($ex); + } + } + + // 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) + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = isset($options['sink']) + ? $options['sink'] + : fopen('php://temp', 'r+'); + + return is_string($sink) + ? new Psr7\LazyOpenStream($sink, 'w+') + : Psr7\stream_for($sink); + } + + private function checkDecode(array $options, array $headers, $stream) + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream( + Psr7\stream_for($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 StreamInterface $source + * @param StreamInterface $sink + * @param string $contentLength Header specifying the amount of + * data to read. + * + * @return StreamInterface + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain( + StreamInterface $source, + StreamInterface $sink, + $contentLength + ) { + // 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\copy_to_stream( + $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 = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + $resource = $callback(); + 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; + } + + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = array_flip(get_class_methods(__CLASS__)); + } + + // 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']) + && is_array($options['auth']) + && isset($options['auth'][2]) + && 'ntlm' == $options['auth'][2] + ) { + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + + $uri = $this->resolveHost($request, $options); + + $context = $this->createResource( + function () use ($context, $params) { + return stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($uri, &$http_response_header, $context, $options) { + $resource = fopen((string) $uri, 'r', null, $context); + $this->lastHeaders = $http_response_header; + + 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) + { + $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 (!isset($records[0]['ip'])) { + throw new ConnectException( + sprintf( + "Could not resolve IPv4 address for host '%s'", + $uri->getHost() + ), + $request + ); + } + $uri = $uri->withHost($records[0]['ip']); + } elseif ('v6' === $options['force_ip_resolve']) { + $records = dns_get_record($uri->getHost(), DNS_AAAA); + if (!isset($records[0]['ipv6'])) { + throw new ConnectException( + sprintf( + "Could not resolve IPv6 address for host '%s'", + $uri->getHost() + ), + $request + ); + } + $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request) + { + $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, + ], + ]; + + $body = (string) $request->getBody(); + + if (!empty($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; + } + + private function add_proxy(RequestInterface $request, &$options, $value, &$params) + { + if (!is_array($value)) { + $options['http']['proxy'] = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) + || !\GuzzleHttp\is_host_in_noproxy( + $request->getUri()->getHost(), + $value['no'] + ) + ) { + $options['http']['proxy'] = $value[$scheme]; + } + } + } + } + + private function add_timeout(RequestInterface $request, &$options, $value, &$params) + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + + private function add_verify(RequestInterface $request, &$options, $value, &$params) + { + if ($value === true) { + // PHP 5.6 or greater will find the system cert by default. When + // < 5.6, use the Guzzle bundled cacert. + if (PHP_VERSION_ID < 50600) { + $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle(); + } + } elseif (is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + return; + } else { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + private function add_cert(RequestInterface $request, &$options, $value, &$params) + { + 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; + } + + private function add_progress(RequestInterface $request, &$options, $value, &$params) + { + $this->addNotification( + $params, + function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == STREAM_NOTIFY_PROGRESS) { + $value($total, $transferred, null, null); + } + } + ); + } + + private function add_debug(RequestInterface $request, &$options, $value, &$params) + { + 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 = \GuzzleHttp\debug_resource($value); + $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); + $this->addNotification( + $params, + function () use ($ident, $value, $map, $args) { + $passed = func_get_args(); + $code = array_shift($passed); + fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (array_filter($passed) as $i => $v) { + fwrite($value, $args[$i] . ': "' . $v . '" '); + } + fwrite($value, "\n"); + } + ); + } + + private function addNotification(array &$params, callable $notify) + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = $this->callArray([ + $params['notification'], + $notify + ]); + } + } + + private function callArray(array $functions) + { + return function () use ($functions) { + $args = func_get_args(); + foreach ($functions as $fn) { + call_user_func_array($fn, $args); + } + }; + } +} diff --git a/lib/guzzlehttp/guzzle/src/HandlerStack.php b/lib/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 000000000..6a49cc069 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,277 @@ +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 $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @param RequestInterface $request + * @param array $options + * + * @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) { + $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 $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler) + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + * + * @return bool + */ + public function hasHandler() + { + return (bool) $this->handler; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, $name = null) + { + array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, $name = '') + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before($findName, callable $middleware, $withName = '') + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after($findName, callable $middleware, $withName = '') + { + $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) + { + $this->cached = null; + $idx = is_callable($remove) ? 0 : 1; + $this->stack = array_values(array_filter( + $this->stack, + function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable + */ + public function resolve() + { + if (!$this->cached) { + if (!($prev = $this->handler)) { + throw new \LogicException('No handler has been specified'); + } + + foreach (array_reverse($this->stack) as $fn) { + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + /** + * @param string $name + * @return int + */ + private function findByName($name) + { + 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. + * + * @param string $findName + * @param string $withName + * @param callable $middleware + * @param bool $before + */ + private function splice($findName, $withName, callable $middleware, $before) + { + $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 array|callable $fn Function to write as a string. + * + * @return string + */ + private function debugCallable($fn) + { + 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]}'])"; + } + + return 'callable(' . spl_object_hash($fn) . ')'; + } +} diff --git a/lib/guzzlehttp/guzzle/src/MessageFormatter.php b/lib/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 000000000..dc36bb524 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,185 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + 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($template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + * @param \Exception $error Exception that was received + * + * @return string + */ + public function format( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $error = null + ) { + $cache = []; + + 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\str($request); + break; + case 'response': + $result = $response ? Psr7\str($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(); + break; + case 'res_body': + $result = $response ? $response->getBody() : 'NULL'; + 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(); + 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 + * + * @return string + */ + private function headers(MessageInterface $message) + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . implode(', ', $values) . "\r\n"; + } + + return trim($result); + } +} diff --git a/lib/guzzlehttp/guzzle/src/Middleware.php b/lib/guzzlehttp/guzzle/src/Middleware.php new file mode 100644 index 000000000..bffc1974b --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Middleware.php @@ -0,0 +1,254 @@ +withCookieHeader($request); + return $handler($request, $options) + ->then( + function ($response) use ($cookieJar, $request) { + $cookieJar->extractCookies($request, $response); + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_error" request option is set to true. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function httpErrors() + { + return function (callable $handler) { + return function ($request, array $options) use ($handler) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then( + function (ResponseInterface $response) use ($request) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable Returns a function that accepts the next handler. + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container) + { + if (!is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return function (callable $handler) use (&$container) { + return function ($request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options + ]; + return $value; + }, + function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options + ]; + return \GuzzleHttp\Promise\rejection_for($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) + { + return function (callable $handler) use ($before, $after) { + return function ($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() + { + return function (callable $handler) { + 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) + { + return function (callable $handler) use ($decider, $delay) { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @param LoggerInterface $logger Logs messages. + * @param 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, MessageFormatter $formatter, $logLevel = 'info' /* \Psr\Log\LogLevel::INFO */) + { + return function (callable $handler) use ($logger, $formatter, $logLevel) { + return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + function ($response) use ($logger, $request, $formatter, $logLevel) { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + return $response; + }, + function ($reason) use ($logger, $request, $formatter) { + $response = $reason instanceof RequestException + ? $reason->getResponse() + : null; + $message = $formatter->format($request, $response, $reason); + $logger->notice($message); + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + * + * @return callable + */ + public static function prepareBody() + { + return function (callable $handler) { + 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. + * @return callable + */ + public static function mapRequest(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($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. + * @return callable + */ + public static function mapResponse(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($request, array $options) use ($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Pool.php b/lib/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 000000000..5838db4f4 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,134 @@ + $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 + * + * @return PromiseInterface + */ + public function promise() + { + 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 = [] + ) { + $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) + * + * @return void + */ + private static function cmpCallback(array &$options, $name, array &$results) + { + if (!isset($options[$name])) { + $options[$name] = function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/lib/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 000000000..568a1e906 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,111 @@ +nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $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 ($type = Psr7\mimetype_from_filename($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\modify_request($request, $modify), $options); + } + + /** + * Add expect header + * + * @return void + */ + private function addExpectHeader( + RequestInterface $request, + array $options, + array &$modify + ) { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = isset($options['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/lib/guzzlehttp/guzzle/src/RedirectMiddleware.php b/lib/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 000000000..e4644b7ac --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,255 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** @var callable */ + private $nextHandler; + + /** + * @param callable $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $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); + }); + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface $response + * + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + if (substr($response->getStatusCode(), 0, 1) != '3' + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + if (isset($options['allow_redirects']['on_redirect'])) { + call_user_func( + $options['allow_redirects']['on_redirect'], + $request, + $response, + $nextRequest->getUri() + ); + } + + /** @var PromiseInterface|ResponseInterface $promise */ + $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. + * + * @return PromiseInterface + */ + private function withTracking(PromiseInterface $promise, $uri, $statusCode) + { + return $promise->then( + 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, $statusCode); + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + /** + * Check for too many redirects + * + * @return void + * + * @throws TooManyRedirectsException Too many redirects. + */ + private function guardMax(RequestInterface $request, array &$options) + { + $current = isset($options['__redirect_count']) + ? $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 + ); + } + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface $response + * + * @return RequestInterface + */ + public function modifyRequest( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + // 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']) + ) { + $modify['method'] = 'GET'; + $modify['body'] = ''; + } + + $uri = $this->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\rewind_body($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 header if host is different. + if ($request->getUri()->getHost() !== $modify['uri']->getHost()) { + $modify['remove_headers'][] = 'Authorization'; + } + + return Psr7\modify_request($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array $protocols + * + * @return UriInterface + */ + private function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ) { + $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/lib/guzzlehttp/guzzle/src/RequestOptions.php b/lib/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 000000000..355f658f0 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,263 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @param int $retries + * + * @return int milliseconds. + */ + public static function exponentialDelay($retries) + { + return (int) pow(2, $retries - 1) * 1000; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + 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 + * + * @return mixed + */ + private function onFulfilled(RequestInterface $req, array $options) + { + return function ($value) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + $value, + null + )) { + return $value; + } + return $this->doRetry($req, $options, $value); + }; + } + + /** + * Execute rejected closure + * + * @return callable + */ + private function onRejected(RequestInterface $req, array $options) + { + return function ($reason) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + null, + $reason + )) { + return \GuzzleHttp\Promise\rejection_for($reason); + } + return $this->doRetry($req, $options); + }; + } + + /** + * @return self + */ + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null) + { + $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response); + + return $this($request, $options); + } +} diff --git a/lib/guzzlehttp/guzzle/src/TransferStats.php b/lib/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 000000000..87fb3c001 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,126 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + /** + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Returns true if a response was received. + * + * @return bool + */ + public function hasResponse() + { + 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. + * + * @return UriInterface + */ + public function getEffectiveUri() + { + 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() + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + * + * @return array + */ + public function getHandlerStats() + { + 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($stat) + { + return isset($this->handlerStats[$stat]) + ? $this->handlerStats[$stat] + : null; + } +} diff --git a/lib/guzzlehttp/guzzle/src/UriTemplate.php b/lib/guzzlehttp/guzzle/src/UriTemplate.php new file mode 100644 index 000000000..96dcfd09c --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/UriTemplate.php @@ -0,0 +1,237 @@ + ['prefix' => '', 'joiner' => ',', 'query' => false], + '+' => ['prefix' => '', 'joiner' => ',', 'query' => false], + '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false], + '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false], + '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false], + ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true], + '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true], + '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true] + ]; + + /** @var array Delimiters */ + private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$', + '&', '\'', '(', ')', '*', '+', ',', ';', '=']; + + /** @var array Percent encoded delimiters */ + private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D', + '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', + '%3B', '%3D']; + + public function expand($template, array $variables) + { + if (false === strpos($template, '{')) { + return $template; + } + + $this->template = $template; + $this->variables = $variables; + + return preg_replace_callback( + '/\{([^\}]+)\}/', + [$this, 'expandMatch'], + $this->template + ); + } + + /** + * Parse an expression into parts + * + * @param string $expression Expression to parse + * + * @return array Returns an associative array of parts + */ + private function parseExpression($expression) + { + $result = []; + + if (isset(self::$operatorHash[$expression[0]])) { + $result['operator'] = $expression[0]; + $expression = substr($expression, 1); + } else { + $result['operator'] = ''; + } + + foreach (explode(',', $expression) as $value) { + $value = trim($value); + $varspec = []; + if ($colonPos = strpos($value, ':')) { + $varspec['value'] = substr($value, 0, $colonPos); + $varspec['modifier'] = ':'; + $varspec['position'] = (int) substr($value, $colonPos + 1); + } elseif (substr($value, -1) === '*') { + $varspec['modifier'] = '*'; + $varspec['value'] = substr($value, 0, -1); + } else { + $varspec['value'] = (string) $value; + $varspec['modifier'] = ''; + } + $result['values'][] = $varspec; + } + + return $result; + } + + /** + * Process an expansion + * + * @param array $matches Matches met in the preg_replace_callback + * + * @return string Returns the replacement string + */ + private function expandMatch(array $matches) + { + static $rfc1738to3986 = ['+' => '%20', '%7e' => '~']; + + $replacements = []; + $parsed = self::parseExpression($matches[1]); + $prefix = self::$operatorHash[$parsed['operator']]['prefix']; + $joiner = self::$operatorHash[$parsed['operator']]['joiner']; + $useQuery = self::$operatorHash[$parsed['operator']]['query']; + + foreach ($parsed['values'] as $value) { + if (!isset($this->variables[$value['value']])) { + continue; + } + + $variable = $this->variables[$value['value']]; + $actuallyUseQuery = $useQuery; + $expanded = ''; + + if (is_array($variable)) { + $isAssoc = $this->isAssoc($variable); + $kvp = []; + foreach ($variable as $key => $var) { + if ($isAssoc) { + $key = rawurlencode($key); + $isNestedArray = is_array($var); + } else { + $isNestedArray = false; + } + + if (!$isNestedArray) { + $var = rawurlencode($var); + if ($parsed['operator'] === '+' || + $parsed['operator'] === '#' + ) { + $var = $this->decodeReserved($var); + } + } + + if ($value['modifier'] === '*') { + if ($isAssoc) { + if ($isNestedArray) { + // Nested arrays must allow for deeply nested + // structures. + $var = strtr( + http_build_query([$key => $var]), + $rfc1738to3986 + ); + } else { + $var = $key . '=' . $var; + } + } elseif ($key > 0 && $actuallyUseQuery) { + $var = $value['value'] . '=' . $var; + } + } + + $kvp[$key] = $var; + } + + if (empty($variable)) { + $actuallyUseQuery = false; + } elseif ($value['modifier'] === '*') { + $expanded = implode($joiner, $kvp); + if ($isAssoc) { + // Don't prepend the value name when using the explode + // modifier with an associative array. + $actuallyUseQuery = false; + } + } else { + if ($isAssoc) { + // When an associative array is encountered and the + // explode modifier is not set, then the result must be + // a comma separated list of keys followed by their + // respective values. + foreach ($kvp as $k => &$v) { + $v = $k . ',' . $v; + } + } + $expanded = implode(',', $kvp); + } + } else { + if ($value['modifier'] === ':') { + $variable = substr($variable, 0, $value['position']); + } + $expanded = rawurlencode($variable); + if ($parsed['operator'] === '+' || $parsed['operator'] === '#') { + $expanded = $this->decodeReserved($expanded); + } + } + + if ($actuallyUseQuery) { + if (!$expanded && $joiner !== '&') { + $expanded = $value['value']; + } else { + $expanded = $value['value'] . '=' . $expanded; + } + } + + $replacements[] = $expanded; + } + + $ret = implode($joiner, $replacements); + if ($ret && $prefix) { + return $prefix . $ret; + } + + return $ret; + } + + /** + * Determines if an array is associative. + * + * This makes the assumption that input arrays are sequences or hashes. + * This assumption is a tradeoff for accuracy in favor of speed, but it + * should work in almost every case where input is supplied for a URI + * template. + * + * @param array $array Array to check + * + * @return bool + */ + private function isAssoc(array $array) + { + return $array && array_keys($array)[0] !== 0; + } + + /** + * Removes percent encoding on reserved characters (used with + and # + * modifiers). + * + * @param string $string String to fix + * + * @return string + */ + private function decodeReserved($string) + { + return str_replace(self::$delimsPct, self::$delims, $string); + } +} diff --git a/lib/guzzlehttp/guzzle/src/Utils.php b/lib/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 000000000..c698acbf0 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,92 @@ +getHost()) { + $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); + if ($asciiHost === false) { + $errorBitSet = isset($info['errors']) ? $info['errors'] : 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), function ($name) { + 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); + } else { + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + } + + return $uri; + } + + /** + * @param string $domain + * @param int $options + * @param array $info + * + * @return string|false + */ + private static function idnToAsci($domain, $options, &$info = []) + { + if (\preg_match('%^[ -~]+$%', $domain) === 1) { + return $domain; + } + + if (\extension_loaded('intl') && defined('INTL_IDNA_VARIANT_UTS46')) { + return \idn_to_ascii($domain, $options, INTL_IDNA_VARIANT_UTS46, $info); + } + + /* + * The Idn class is marked as @internal. Verify that class and method exists. + */ + if (method_exists(Idn::class, 'idn_to_ascii')) { + return Idn::idn_to_ascii($domain, $options, Idn::INTL_IDNA_VARIANT_UTS46, $info); + } + + throw new \RuntimeException('ext-intl or symfony/polyfill-intl-idn not loaded or too old'); + } +} diff --git a/lib/guzzlehttp/guzzle/src/functions.php b/lib/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 000000000..c2afd8c7b --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,334 @@ +expand($template, $variables); +} + +/** + * Debug function used to describe the provided value type and class. + * + * @param mixed $input + * + * @return string Returns a string containing the type of the variable and + * if a class is provided, the class name. + */ +function describe_type($input) +{ + switch (gettype($input)) { + case 'object': + return 'object(' . get_class($input) . ')'; + case 'array': + return 'array(' . count($input) . ')'; + default: + ob_start(); + var_dump($input); + // normalize float vs double + return str_replace('double(', 'float(', rtrim(ob_get_clean())); + } +} + +/** + * Parses an array of header lines into an associative array of headers. + * + * @param iterable $lines Header lines array of strings in the following + * format: "Name: Value" + * @return array + */ +function headers_from_lines($lines) +{ + $headers = []; + + foreach ($lines as $line) { + $parts = explode(':', $line, 2); + $headers[trim($parts[0])][] = isset($parts[1]) + ? trim($parts[1]) + : null; + } + + return $headers; +} + +/** + * Returns a debug stream based on the provided variable. + * + * @param mixed $value Optional value + * + * @return resource + */ +function debug_resource($value = null) +{ + if (is_resource($value)) { + return $value; + } elseif (defined('STDOUT')) { + return STDOUT; + } + + return fopen('php://output', 'w'); +} + +/** + * Chooses and creates a default handler to use based on the environment. + * + * The returned handler is not wrapped by any default middlewares. + * + * @return callable Returns the best handler for the given system. + * @throws \RuntimeException if no viable Handler is available. + */ +function choose_handler() +{ + $handler = null; + if (function_exists('curl_multi_exec') && function_exists('curl_exec')) { + $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); + } elseif (function_exists('curl_exec')) { + $handler = new CurlHandler(); + } elseif (function_exists('curl_multi_exec')) { + $handler = new CurlMultiHandler(); + } + + if (ini_get('allow_url_fopen')) { + $handler = $handler + ? Proxy::wrapStreaming($handler, new StreamHandler()) + : new StreamHandler(); + } elseif (!$handler) { + throw new \RuntimeException('GuzzleHttp requires cURL, the ' + . 'allow_url_fopen ini setting, or a custom HTTP handler.'); + } + + return $handler; +} + +/** + * Get the default User-Agent string to use with Guzzle + * + * @return string + */ +function default_user_agent() +{ + static $defaultAgent = ''; + + if (!$defaultAgent) { + $defaultAgent = 'GuzzleHttp/' . Client::VERSION; + if (extension_loaded('curl') && function_exists('curl_version')) { + $defaultAgent .= ' curl/' . \curl_version()['version']; + } + $defaultAgent .= ' PHP/' . PHP_VERSION; + } + + return $defaultAgent; +} + +/** + * Returns the default cacert bundle for the current system. + * + * First, the openssl.cafile and curl.cainfo php.ini settings are checked. + * If those settings are not configured, then the common locations for + * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X + * and Windows are checked. If any of these file locations are found on + * disk, they will be utilized. + * + * Note: the result of this function is cached for subsequent calls. + * + * @return string + * @throws \RuntimeException if no bundle can be found. + */ +function default_ca_bundle() +{ + static $cached = null; + static $cafiles = [ + // Red Hat, CentOS, Fedora (provided by the ca-certificates package) + '/etc/pki/tls/certs/ca-bundle.crt', + // Ubuntu, Debian (provided by the ca-certificates package) + '/etc/ssl/certs/ca-certificates.crt', + // FreeBSD (provided by the ca_root_nss package) + '/usr/local/share/certs/ca-root-nss.crt', + // SLES 12 (provided by the ca-certificates package) + '/var/lib/ca-certificates/ca-bundle.pem', + // OS X provided by homebrew (using the default path) + '/usr/local/etc/openssl/cert.pem', + // Google app engine + '/etc/ca-certificates.crt', + // Windows? + 'C:\\windows\\system32\\curl-ca-bundle.crt', + 'C:\\windows\\curl-ca-bundle.crt', + ]; + + if ($cached) { + return $cached; + } + + if ($ca = ini_get('openssl.cafile')) { + return $cached = $ca; + } + + if ($ca = ini_get('curl.cainfo')) { + return $cached = $ca; + } + + foreach ($cafiles as $filename) { + if (file_exists($filename)) { + return $cached = $filename; + } + } + + throw new \RuntimeException( + <<< EOT +No system CA bundle could be found in any of the the common system locations. +PHP versions earlier than 5.6 are not properly configured to use the system's +CA bundle by default. In order to verify peer certificates, you will need to +supply the path on disk to a certificate bundle to the 'verify' request +option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not +need a specific certificate bundle, then Mozilla provides a commonly used CA +bundle which can be downloaded here (provided by the maintainer of cURL): +https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once +you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP +ini setting to point to the path to the file, allowing you to omit the 'verify' +request option. See http://curl.haxx.se/docs/sslcerts.html for more +information. +EOT + ); +} + +/** + * Creates an associative array of lowercase header names to the actual + * header casing. + * + * @param array $headers + * + * @return array + */ +function normalize_header_keys(array $headers) +{ + $result = []; + foreach (array_keys($headers) as $key) { + $result[strtolower($key)] = $key; + } + + return $result; +} + +/** + * Returns true if the provided host matches any of the no proxy areas. + * + * This method will strip a port from the host if it is present. Each pattern + * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a + * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" == + * "baz.foo.com", but ".foo.com" != "foo.com"). + * + * Areas are matched in the following cases: + * 1. "*" (without quotes) always matches any hosts. + * 2. An exact match. + * 3. The area starts with "." and the area is the last part of the host. e.g. + * '.mit.edu' will match any host that ends with '.mit.edu'. + * + * @param string $host Host to check against the patterns. + * @param array $noProxyArray An array of host patterns. + * + * @return bool + */ +function is_host_in_noproxy($host, array $noProxyArray) +{ + if (strlen($host) === 0) { + throw new \InvalidArgumentException('Empty host provided'); + } + + // Strip port if present. + if (strpos($host, ':')) { + $host = explode($host, ':', 2)[0]; + } + + foreach ($noProxyArray as $area) { + // Always match on wildcards. + if ($area === '*') { + return true; + } elseif (empty($area)) { + // Don't match on empty values. + continue; + } elseif ($area === $host) { + // Exact matches. + return true; + } else { + // Special match if the area when prefixed with ".". Remove any + // existing leading "." and add a new leading ".". + $area = '.' . ltrim($area, '.'); + if (substr($host, -(strlen($area))) === $area) { + return true; + } + } + } + + return false; +} + +/** + * Wrapper for json_decode that throws when an error occurs. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * @param int $options Bitmask of JSON decode options. + * + * @return mixed + * @throws Exception\InvalidArgumentException if the JSON cannot be decoded. + * @link http://www.php.net/manual/en/function.json-decode.php + */ +function json_decode($json, $assoc = false, $depth = 512, $options = 0) +{ + $data = \json_decode($json, $assoc, $depth, $options); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new Exception\InvalidArgumentException( + 'json_decode error: ' . json_last_error_msg() + ); + } + + return $data; +} + +/** + * Wrapper for JSON encoding that throws when an error occurs. + * + * @param mixed $value The value being encoded + * @param int $options JSON encode option bitmask + * @param int $depth Set the maximum depth. Must be greater than zero. + * + * @return string + * @throws Exception\InvalidArgumentException if the JSON cannot be encoded. + * @link http://www.php.net/manual/en/function.json-encode.php + */ +function json_encode($value, $options = 0, $depth = 512) +{ + $json = \json_encode($value, $options, $depth); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new Exception\InvalidArgumentException( + 'json_encode error: ' . json_last_error_msg() + ); + } + + return $json; +} diff --git a/lib/guzzlehttp/guzzle/src/functions_include.php b/lib/guzzlehttp/guzzle/src/functions_include.php new file mode 100644 index 000000000..a93393acc --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/functions_include.php @@ -0,0 +1,6 @@ + +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/lib/guzzlehttp/promises/Makefile b/lib/guzzlehttp/promises/Makefile new file mode 100644 index 000000000..8d5b3ef95 --- /dev/null +++ b/lib/guzzlehttp/promises/Makefile @@ -0,0 +1,13 @@ +all: clean test + +test: + vendor/bin/phpunit + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* diff --git a/lib/guzzlehttp/promises/README.md b/lib/guzzlehttp/promises/README.md new file mode 100644 index 000000000..c175fec76 --- /dev/null +++ b/lib/guzzlehttp/promises/README.md @@ -0,0 +1,547 @@ +# 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()`. + + +# 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 promises triggers callbacks +registered with the promises'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 interop + +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']); +``` + +*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? + + +# 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 will be 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/lib/guzzlehttp/promises/composer.json b/lib/guzzlehttp/promises/composer.json new file mode 100644 index 000000000..c959fb32b --- /dev/null +++ b/lib/guzzlehttp/promises/composer.json @@ -0,0 +1,58 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "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" + } + ], + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Promise\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/simple-phpunit", + "test-ci": "vendor/bin/simple-phpunit --coverage-text" + }, + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/lib/guzzlehttp/promises/src/AggregateException.php b/lib/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 000000000..d2b5712b9 --- /dev/null +++ b/lib/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,17 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * + * @link 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 () { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + try { + $this->nextCoroutine($this->generator->current()); + } catch (\Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * Create a new coroutine. + * + * @return self + */ + public static function of(callable $generatorFn) + { + return new self($generatorFn); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected) + { + return $this->result->otherwise($onRejected); + } + + public function wait($unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState() + { + return $this->result->getState(); + } + + public function resolve($value) + { + $this->result->resolve($value); + } + + public function reject($reason) + { + $this->result->reject($reason); + } + + public function cancel() + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded) + { + $this->currentPromise = Create::promiseFor($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value) + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason) + { + 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 (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/lib/guzzlehttp/promises/src/Create.php b/lib/guzzlehttp/promises/src/Create.php new file mode 100644 index 000000000..8d038e9c1 --- /dev/null +++ b/lib/guzzlehttp/promises/src/Create.php @@ -0,0 +1,84 @@ +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. + * + * @return PromiseInterface + */ + public static function rejectionFor($reason) + { + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); + } + + /** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + */ + public static function exceptionFor($reason) + { + if ($reason instanceof \Exception || $reason instanceof \Throwable) { + return $reason; + } + + return new RejectionException($reason); + } + + /** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ + public static function iterFor($value) + { + if ($value instanceof \Iterator) { + return $value; + } + + if (is_array($value)) { + return new \ArrayIterator($value); + } + + return new \ArrayIterator([$value]); + } +} diff --git a/lib/guzzlehttp/promises/src/Each.php b/lib/guzzlehttp/promises/src/Each.php new file mode 100644 index 000000000..1dda35499 --- /dev/null +++ b/lib/guzzlehttp/promises/src/Each.php @@ -0,0 +1,90 @@ + $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 + * + * @return PromiseInterface + */ + public static function ofLimit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null + ) { + 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 + * + * @return PromiseInterface + */ + public static function ofLimitAll( + $iterable, + $concurrency, + callable $onFulfilled = null + ) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); + } +} diff --git a/lib/guzzlehttp/promises/src/EachPromise.php b/lib/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 000000000..38ecb59b1 --- /dev/null +++ b/lib/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,255 @@ +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() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + /** @psalm-assert Promise $this->aggregate */ + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + /** + * @psalm-suppress NullReference + * @phpstan-ignore-next-line + */ + $this->aggregate->reject($e); + } catch (\Exception $e) { + /** + * @psalm-suppress NullReference + * @phpstan-ignore-next-line + */ + $this->aggregate->reject($e); + } + + /** + * @psalm-suppress NullableReturnStatement + * @phpstan-ignore-next-line + */ + return $this->aggregate; + } + + private function createPromise() + { + $this->mutex = false; + $this->aggregate = new Promise(function () { + 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 () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + $this->nextPendingIndex = 0; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + 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() + { + 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) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, + $value, + $key, + $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx, $key) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, + $reason, + $key, + $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + // 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; + } catch (\Exception $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } + } + + private function step($idx) + { + // 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() + { + 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/lib/guzzlehttp/promises/src/FulfilledPromise.php b/lib/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 000000000..98f72a62a --- /dev/null +++ b/lib/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,84 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // 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) { + if (Is::pending($p)) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/lib/guzzlehttp/promises/src/Is.php b/lib/guzzlehttp/promises/src/Is.php new file mode 100644 index 000000000..c3ed8d014 --- /dev/null +++ b/lib/guzzlehttp/promises/src/Is.php @@ -0,0 +1,46 @@ +getState() === PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + */ + public static function settled(PromiseInterface $promise) + { + return $promise->getState() !== PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled. + * + * @return bool + */ + public static function fulfilled(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + + /** + * Returns true if a promise is rejected. + * + * @return bool + */ + public static function rejected(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/lib/guzzlehttp/promises/src/Promise.php b/lib/guzzlehttp/promises/src/Promise.php new file mode 100644 index 000000000..75939057b --- /dev/null +++ b/lib/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,278 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + 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) + { + return $this->then(null, $onRejected); + } + + public function wait($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() + { + return $this->state; + } + + public function cancel() + { + 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); + } catch (\Exception $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) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + 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) { + 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) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + 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($index, $value, array $handler) + { + /** @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); + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + 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() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $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() + { + $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/lib/guzzlehttp/promises/src/PromiseInterface.php b/lib/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 000000000..e59833143 --- /dev/null +++ b/lib/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,97 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // 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) { + 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); + } catch (\Exception $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw Create::exceptionFor($this->reason); + } + + return null; + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/lib/guzzlehttp/promises/src/RejectionException.php b/lib/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 000000000..e2f137707 --- /dev/null +++ b/lib/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,48 @@ +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/lib/guzzlehttp/promises/src/TaskQueue.php b/lib/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 000000000..f0fba2c59 --- /dev/null +++ b/lib/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,67 @@ +run(); + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + 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() + { + return !$this->queue; + } + + public function add(callable $task) + { + $this->queue[] = $task; + } + + public function run() + { + 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() + { + $this->enableShutdown = false; + } +} diff --git a/lib/guzzlehttp/promises/src/TaskQueueInterface.php b/lib/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 000000000..723d4d54e --- /dev/null +++ b/lib/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,24 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\Utils::queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + */ + public static function queue(TaskQueueInterface $assign = null) + { + 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. + * + * @return PromiseInterface + */ + public static function task(callable $task) + { + $queue = self::queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + if (Is::pending($promise)) { + $promise->resolve($task()); + } + } catch (\Throwable $e) { + $promise->reject($e); + } catch (\Exception $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. + * + * @return array + */ + public static function inspect(PromiseInterface $promise) + { + 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]; + } catch (\Exception $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. + * + * @return array + */ + public static function inspectAll($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = 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. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + */ + public static function unwrap($promises) + { + $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. + * + * @return PromiseInterface + */ + public static function all($promises, $recursive = false) + { + $results = []; + $promise = Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $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. + * + * @return PromiseInterface + */ + public static function some($count, $promises) + { + $results = []; + $rejections = []; + + return Each::of( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if (Is::settled($p)) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $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. + * + * @return PromiseInterface + */ + public static function any($promises) + { + 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. + * + * @return PromiseInterface + */ + public static function settle($promises) + { + $results = []; + + return Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + } +} diff --git a/lib/guzzlehttp/promises/src/functions.php b/lib/guzzlehttp/promises/src/functions.php new file mode 100644 index 000000000..c03d39d02 --- /dev/null +++ b/lib/guzzlehttp/promises/src/functions.php @@ -0,0 +1,363 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + * + * @deprecated queue will be removed in guzzlehttp/promises:2.0. Use Utils::queue instead. + */ +function queue(TaskQueueInterface $assign = null) +{ + return Utils::queue($assign); +} + +/** + * 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. + * + * @return PromiseInterface + * + * @deprecated task will be removed in guzzlehttp/promises:2.0. Use Utils::task instead. + */ +function task(callable $task) +{ + return Utils::task($task); +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + * + * @deprecated promise_for will be removed in guzzlehttp/promises:2.0. Use Create::promiseFor instead. + */ +function promise_for($value) +{ + return Create::promiseFor($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. + * + * @return PromiseInterface + * + * @deprecated rejection_for will be removed in guzzlehttp/promises:2.0. Use Create::rejectionFor instead. + */ +function rejection_for($reason) +{ + return Create::rejectionFor($reason); +} + +/** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + * + * @deprecated exception_for will be removed in guzzlehttp/promises:2.0. Use Create::exceptionFor instead. + */ +function exception_for($reason) +{ + return Create::exceptionFor($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + * + * @deprecated iter_for will be removed in guzzlehttp/promises:2.0. Use Create::iterFor instead. + */ +function iter_for($value) +{ + return Create::iterFor($value); +} + +/** + * 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. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspect instead. + */ +function inspect(PromiseInterface $promise) +{ + return Utils::inspect($promise); +} + +/** + * 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. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspectAll instead. + */ +function inspect_all($promises) +{ + return Utils::inspectAll($promises); +} + +/** + * 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. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + * + * @deprecated unwrap will be removed in guzzlehttp/promises:2.0. Use Utils::unwrap instead. + */ +function unwrap($promises) +{ + return Utils::unwrap($promises); +} + +/** + * 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. + * + * @return PromiseInterface + * + * @deprecated all will be removed in guzzlehttp/promises:2.0. Use Utils::all instead. + */ +function all($promises, $recursive = false) +{ + return Utils::all($promises, $recursive); +} + +/** + * 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. + * + * @return PromiseInterface + * + * @deprecated some will be removed in guzzlehttp/promises:2.0. Use Utils::some instead. + */ +function some($count, $promises) +{ + return Utils::some($count, $promises); +} + +/** + * 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. + * + * @return PromiseInterface + * + * @deprecated any will be removed in guzzlehttp/promises:2.0. Use Utils::any instead. + */ +function any($promises) +{ + return Utils::any($promises); +} + +/** + * 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. + * + * @return PromiseInterface + * + * @deprecated settle will be removed in guzzlehttp/promises:2.0. Use Utils::settle instead. + */ +function settle($promises) +{ + return Utils::settle($promises); +} + +/** + * Given an iterator that yields promises or values, returns a promise that is + * fulfilled with a null value when the iterator has been consumed or the + * aggregate promise has been fulfilled or rejected. + * + * $onFulfilled is a function that accepts the fulfilled value, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * $onRejected is a function that accepts the rejection reason, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + * + * @deprecated each will be removed in guzzlehttp/promises:2.0. Use Each::of instead. + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::of($iterable, $onFulfilled, $onRejected); +} + +/** + * Like each, 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 + * + * @return PromiseInterface + * + * @deprecated each_limit will be removed in guzzlehttp/promises:2.0. Use Each::ofLimit instead. + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::ofLimit($iterable, $concurrency, $onFulfilled, $onRejected); +} + +/** + * Like each_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 + * + * @return PromiseInterface + * + * @deprecated each_limit_all will be removed in guzzlehttp/promises:2.0. Use Each::ofLimitAll instead. + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return Each::ofLimitAll($iterable, $concurrency, $onFulfilled); +} + +/** + * Returns true if a promise is fulfilled. + * + * @return bool + * + * @deprecated is_fulfilled will be removed in guzzlehttp/promises:2.0. Use Is::fulfilled instead. + */ +function is_fulfilled(PromiseInterface $promise) +{ + return Is::fulfilled($promise); +} + +/** + * Returns true if a promise is rejected. + * + * @return bool + * + * @deprecated is_rejected will be removed in guzzlehttp/promises:2.0. Use Is::rejected instead. + */ +function is_rejected(PromiseInterface $promise) +{ + return Is::rejected($promise); +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + * + * @deprecated is_settled will be removed in guzzlehttp/promises:2.0. Use Is::settled instead. + */ +function is_settled(PromiseInterface $promise) +{ + return Is::settled($promise); +} + +/** + * Create a new coroutine. + * + * @see Coroutine + * + * @return PromiseInterface + * + * @deprecated coroutine will be removed in guzzlehttp/promises:2.0. Use Coroutine::of instead. + */ +function coroutine(callable $generatorFn) +{ + return Coroutine::of($generatorFn); +} diff --git a/lib/guzzlehttp/promises/src/functions_include.php b/lib/guzzlehttp/promises/src/functions_include.php new file mode 100644 index 000000000..34cd1710a --- /dev/null +++ b/lib/guzzlehttp/promises/src/functions_include.php @@ -0,0 +1,6 @@ + + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed after 2 weeks if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/lib/guzzlehttp/psr7/.github/workflows/ci.yml b/lib/guzzlehttp/psr7/.github/workflows/ci.yml new file mode 100644 index 000000000..eda7dceb5 --- /dev/null +++ b/lib/guzzlehttp/psr7/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + pull_request: + +jobs: + build: + name: Build + runs-on: ubuntu-latest + strategy: + max-parallel: 10 + matrix: + php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: 'none' + extensions: mbstring + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Mimic PHP 8.0 + run: composer config platform.php 8.0.999 + if: matrix.php > 8 + + - name: Install dependencies + run: composer update --no-interaction --no-progress + + - name: Run tests + run: make test diff --git a/lib/guzzlehttp/psr7/.github/workflows/integration.yml b/lib/guzzlehttp/psr7/.github/workflows/integration.yml new file mode 100644 index 000000000..3c31f9ef2 --- /dev/null +++ b/lib/guzzlehttp/psr7/.github/workflows/integration.yml @@ -0,0 +1,37 @@ +name: Integration + +on: + pull_request: + +jobs: + + build: + name: Test + runs-on: ubuntu-latest + strategy: + max-parallel: 10 + matrix: + php: ['7.2', '7.3', '7.4', '8.0'] + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Download dependencies + uses: ramsey/composer-install@v1 + with: + composer-options: --no-interaction --optimize-autoloader + + - name: Start server + run: php -S 127.0.0.1:10002 tests/Integration/server.php & + + - name: Run tests + env: + TEST_SERVER: 127.0.0.1:10002 + run: ./vendor/bin/phpunit --testsuite Integration diff --git a/lib/guzzlehttp/psr7/.github/workflows/static.yml b/lib/guzzlehttp/psr7/.github/workflows/static.yml new file mode 100644 index 000000000..ab4d68ba3 --- /dev/null +++ b/lib/guzzlehttp/psr7/.github/workflows/static.yml @@ -0,0 +1,29 @@ +name: Static analysis + +on: + pull_request: + +jobs: + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + extensions: mbstring + + - name: Download dependencies + run: composer update --no-interaction --no-progress + + - name: Download PHP CS Fixer + run: composer require "friendsofphp/php-cs-fixer:2.18.4" + + - name: Execute PHP CS Fixer + run: vendor/bin/php-cs-fixer fix --diff-format udiff --dry-run diff --git a/lib/guzzlehttp/psr7/.php_cs.dist b/lib/guzzlehttp/psr7/.php_cs.dist new file mode 100644 index 000000000..e4f0bd535 --- /dev/null +++ b/lib/guzzlehttp/psr7/.php_cs.dist @@ -0,0 +1,56 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'concat_space' => ['spacing' => 'one'], + 'declare_strict_types' => false, + 'final_static_access' => true, + 'fully_qualified_strict_types' => true, + 'header_comment' => false, + 'is_null' => ['use_yoda_style' => true], + 'list_syntax' => ['syntax' => 'long'], + 'lowercase_cast' => true, + 'magic_method_casing' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'no_alias_functions' => true, + 'no_alternative_syntax' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unset_cast' => true, + 'no_unused_imports' => true, + 'no_whitespace_in_blank_line' => true, + 'ordered_imports' => true, + 'php_unit_ordered_covers' => true, + 'php_unit_test_annotation' => ['style' => 'prefix'], + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'phpdoc_align' => ['align' => 'vertical'], + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'phpdoc_var_without_name' => true, + 'single_trait_insert_per_statement' => true, + 'standardize_not_equals' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ->name('*.php') + ) +; + +return $config; diff --git a/lib/guzzlehttp/psr7/CHANGELOG.md b/lib/guzzlehttp/psr7/CHANGELOG.md new file mode 100644 index 000000000..f177f583f --- /dev/null +++ b/lib/guzzlehttp/psr7/CHANGELOG.md @@ -0,0 +1,312 @@ +# Change Log + + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + + +## Unreleased + +## 1.8.5 - 2022-03-20 + +### Fixed + +- Correct header value validation + +## 1.8.4 - 2022-03-20 + +### Fixed + +- Validate header values properly + +## 1.8.3 - 2021-10-05 + +### Fixed + +- Return `null` in caching stream size if remote size is `null` + +## 1.8.2 - 2021-04-26 + +### Fixed + +- Handle possibly unset `url` in `stream_get_meta_data` + +## 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/lib/guzzlehttp/psr7/LICENSE b/lib/guzzlehttp/psr7/LICENSE new file mode 100644 index 000000000..51c7ec81c --- /dev/null +++ b/lib/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/lib/guzzlehttp/psr7/README.md b/lib/guzzlehttp/psr7/README.md new file mode 100644 index 000000000..464cae4f2 --- /dev/null +++ b/lib/guzzlehttp/psr7/README.md @@ -0,0 +1,824 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + + +[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7) + + +# Stream implementation + +This package comes with a number of stream implementations and stream +decorators. + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\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 deflate or gzipped content. + +This stream decorator skips the first 10 bytes of the given stream to remove +the gzip header, converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\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; + + 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::normalize` + +`public static function normalize(string|array $header): array` + +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::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 will be 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`](http://php.net/manual/en/function.parse-url.php) components. + + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string values that exactly match the +provided key are removed and replaced with the given key value pair. A value of null will set the query string +key without a value, e.g. "key" instead of "key=value". + +### `GuzzleHttp\Psr7\Uri::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. + +## 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. + + +## 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/lib/guzzlehttp/psr7/composer.json b/lib/guzzlehttp/psr7/composer.json new file mode 100644 index 000000000..7ecdc8ba3 --- /dev/null +++ b/lib/guzzlehttp/psr7/composer.json @@ -0,0 +1,76 @@ +{ + "name": "guzzlehttp/psr7", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": ["request", "response", "message", "stream", "http", "uri", "url", "psr-7"], + "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" + } + ], + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10", + "ext-zlib": "*" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Psr7\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "allow-plugins": { + "bamarni/composer-bin-plugin": true + } + } +} diff --git a/lib/guzzlehttp/psr7/src/AppendStream.php b/lib/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 000000000..fa9153d78 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,246 @@ +addStream($stream); + } + } + + public function __toString() + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream) + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents() + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + * + * {@inheritdoc} + */ + public function close() + { + $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. + * + * {@inheritdoc} + */ + 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() + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + * + * {@inheritdoc} + */ + public function getSize() + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof() + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind() + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + * + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + * + * {@inheritdoc} + */ + public function read($length) + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + // Using a loose comparison here to match on '', false, and null + if ($result == null) { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/lib/guzzlehttp/psr7/src/BufferStream.php b/lib/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 000000000..783859c19 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,142 @@ +hwm = $hwm; + } + + public function __toString() + { + return $this->getContents(); + } + + public function getContents() + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close() + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + + return null; + } + + public function getSize() + { + return strlen($this->buffer); + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof() + { + return strlen($this->buffer) === 0; + } + + public function tell() + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length) + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string) + { + $this->buffer .= $string; + + // TODO: What should happen here? + if (strlen($this->buffer) >= $this->hwm) { + return false; + } + + return strlen($string); + } + + public function getMetadata($key = null) + { + if ($key == 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/lib/guzzlehttp/psr7/src/CachingStream.php b/lib/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 000000000..febade9f4 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,147 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); + } + + public function getSize() + { + $remoteSize = $this->remoteStream->getSize(); + + if (null === $remoteSize) { + return null; + } + + return max($this->stream->getSize(), $remoteSize); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence == SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $this->read($diff); + $diff = $byte - $this->stream->getSize(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof() + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + $this->remoteStream->close() && $this->stream->close(); + } + + private function cacheEntireStream() + { + $target = new FnStream(['write' => 'strlen']); + Utils::copyToStream($this, $target); + + return $this->tell(); + } +} diff --git a/lib/guzzlehttp/psr7/src/DroppingStream.php b/lib/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 000000000..9f7420c40 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,45 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string) + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/lib/guzzlehttp/psr7/src/FnStream.php b/lib/guzzlehttp/psr7/src/FnStream.php new file mode 100644 index 000000000..76a8cc7ba --- /dev/null +++ b/lib/guzzlehttp/psr7/src/FnStream.php @@ -0,0 +1,163 @@ +methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * An unserialize would allow the __destruct to run when the unserialized value goes out of scope. + * + * @throws \LogicException + */ + public function __wakeup() + { + 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) { + $methods[$diff] = [$stream, $diff]; + } + + return new self($methods); + } + + public function __toString() + { + return call_user_func($this->_fn___toString); + } + + public function close() + { + return call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize() + { + return call_user_func($this->_fn_getSize); + } + + public function tell() + { + return call_user_func($this->_fn_tell); + } + + public function eof() + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable() + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind() + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET) + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable() + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string) + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable() + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length) + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents() + { + return call_user_func($this->_fn_getContents); + } + + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/lib/guzzlehttp/psr7/src/Header.php b/lib/guzzlehttp/psr7/src/Header.php new file mode 100644 index 000000000..865d74214 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Header.php @@ -0,0 +1,71 @@ +]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @return array Returns the normalized header field values. + */ + public static function normalize($header) + { + if (!is_array($header)) { + return array_map('trim', explode(',', $header)); + } + + $result = []; + foreach ($header as $value) { + foreach ((array) $value as $v) { + if (strpos($v, ',') === false) { + $result[] = $v; + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { + $result[] = trim($vv); + } + } + } + + return $result; + } +} diff --git a/lib/guzzlehttp/psr7/src/InflateStream.php b/lib/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 000000000..0cbd2cce2 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,56 @@ +read(10); + $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header); + // Skip the header, that is 10 + length of filename + 1 (nil) bytes + $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength); + $resource = StreamWrapper::getResource($stream); + stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); + } + + /** + * @param StreamInterface $stream + * @param $header + * + * @return int + */ + private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header) + { + $filename_header_length = 0; + + if (substr(bin2hex($header), 6, 2) === '08') { + // we have a filename, read until nil + $filename_header_length = 1; + while ($stream->read(1) !== chr(0)) { + $filename_header_length++; + } + } + + return $filename_header_length; + } +} diff --git a/lib/guzzlehttp/psr7/src/LazyOpenStream.php b/lib/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 000000000..911e127d3 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,42 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/lib/guzzlehttp/psr7/src/LimitStream.php b/lib/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 000000000..1173ec40d --- /dev/null +++ b/lib/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,157 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof() + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit == -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + * {@inheritdoc} + */ + public function getSize() + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit == -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset %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() + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset($offset) + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function read($length) + { + if ($this->limit == -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/lib/guzzlehttp/psr7/src/Message.php b/lib/guzzlehttp/psr7/src/Message.php new file mode 100644 index 000000000..516d1cb84 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Message.php @@ -0,0 +1,252 @@ +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 + * + * @return string|null + */ + public static function bodySummary(MessageInterface $message, $truncateAt = 120) + { + $body = $message->getBody(); + + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $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)) { + 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) + { + $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. + * + * @return array + */ + public static function parseMessage($message) + { + 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'); + } + + list($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'); + } + + list($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). + * + * @return string + */ + public static function parseRequestUri($path, array $headers) + { + $hostKey = array_filter(array_keys($headers), function ($k) { + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); + } + + /** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + * + * @return Request + */ + public static function parseRequest($message) + { + $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. + * + * @return Response + */ + public static function parseResponse($message) + { + $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], + isset($parts[2]) ? $parts[2] : null + ); + } +} diff --git a/lib/guzzlehttp/psr7/src/MessageTrait.php b/lib/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 000000000..0ac8663da --- /dev/null +++ b/lib/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,270 @@ + 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() + { + return $this->protocol; + } + + public function withProtocolVersion($version) + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders() + { + return $this->headers; + } + + public function hasHeader($header) + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header) + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header) + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value) + { + $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) + { + $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) + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody() + { + if (!$this->stream) { + $this->stream = Utils::streamFor(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body) + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + private function setHeaders(array $headers) + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + if (is_int($header)) { + // Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec + // and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass. + $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) + { + 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) + { + 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 + * + * @return void + */ + private function assertHeader($header) + { + 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 ($header === '') { + throw new \InvalidArgumentException('Header name can not be empty.'); + } + + if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $header)) { + throw new \InvalidArgumentException( + sprintf( + '"%s" is not valid header name', + $header + ) + ); + } + } + + /** + * @param string $value + * + * @return void + * + * @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($value) + { + // 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]*$/', $value)) { + throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value)); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/MimeType.php b/lib/guzzlehttp/psr7/src/MimeType.php new file mode 100644 index 000000000..205c7b1fa --- /dev/null +++ b/lib/guzzlehttp/psr7/src/MimeType.php @@ -0,0 +1,140 @@ + 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aac' => 'audio/x-aac', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'atom' => 'application/atom+xml', + 'avi' => 'video/x-msvideo', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'deb' => 'application/x-debian-package', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dvi' => 'application/x-dvi', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'etx' => 'text/x-setext', + 'flac' => 'audio/flac', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ini' => 'text/plain', + 'iso' => 'application/x-iso9660-image', + 'jar' => 'application/java-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'latex' => 'application/x-latex', + 'log' => 'text/plain', + 'm4a' => 'audio/mp4', + 'm4v' => 'video/mp4', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mkv' => 'video/x-matroska', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4v' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'pbm' => 'image/x-portable-bitmap', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'tar' => 'application/x-tar', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'ttf' => 'application/x-font-ttf', + 'txt' => 'text/plain', + 'wav' => 'audio/x-wav', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'video/x-ms-wmv', + 'woff' => 'application/x-font-woff', + 'wsdl' => 'application/wsdl+xml', + 'xbm' => 'image/x-xbitmap', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'yaml' => 'text/yaml', + 'yml' => 'text/yaml', + 'zip' => 'application/zip', + ]; + + $extension = strtolower($extension); + + return isset($mimetypes[$extension]) + ? $mimetypes[$extension] + : null; + } +} diff --git a/lib/guzzlehttp/psr7/src/MultipartStream.php b/lib/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 000000000..5a6079a89 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,158 @@ +boundary = $boundary ?: sha1(uniqid('', true)); + $this->stream = $this->createStream($elements); + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + public function isWritable() + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + */ + private function getHeaders(array $headers) + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements) + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element) + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = Utils::streamFor($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if (substr($uri, 0, 6) !== 'php://') { + $element['filename'] = $uri; + } + } + + list($body, $headers) = $this->createElement( + $element['name'], + $element['contents'], + isset($element['filename']) ? $element['filename'] : null, + isset($element['headers']) ? $element['headers'] : [] + ); + + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(Utils::streamFor("\r\n")); + } + + /** + * @return array + */ + private function createElement($name, StreamInterface $stream, $filename, array $headers) + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf( + 'form-data; name="%s"; filename="%s"', + $name, + basename($filename) + ) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = MimeType::fromFilename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/lib/guzzlehttp/psr7/src/NoSeekStream.php b/lib/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 000000000..d66bdde46 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,25 @@ +source = $source; + $this->size = isset($options['size']) ? $options['size'] : null; + $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; + $this->buffer = new BufferStream(); + } + + public function __toString() + { + try { + return Utils::copyToString($this); + } catch (\Exception $e) { + return ''; + } + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = false; + $this->source = null; + + return null; + } + + public function getSize() + { + return $this->size; + } + + public function tell() + { + return $this->tellPos; + } + + public function eof() + { + return !$this->source; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents() + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return isset($this->metadata[$key]) ? $this->metadata[$key] : null; + } + + private function pump($length) + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/Query.php b/lib/guzzlehttp/psr7/src/Query.php new file mode 100644 index 000000000..5a7cc0359 --- /dev/null +++ b/lib/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 + * + * @return array + */ + public static function parse($str, $urlEncoding = true) + { + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', $value)); + }; + } elseif ($urlEncoding === PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding === PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { + return $str; + }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; + } + + /** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + * + * @return string + */ + public static function build(array $params, $encoding = PHP_QUERY_RFC3986) + { + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function ($str) { + return $str; + }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder($k); + if (!is_array($v)) { + $qs .= $k; + if ($v !== null) { + $qs .= '=' . $encoder($v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + if ($vv !== null) { + $qs .= '=' . $encoder($vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; + } +} diff --git a/lib/guzzlehttp/psr7/src/Request.php b/lib/guzzlehttp/psr7/src/Request.php new file mode 100644 index 000000000..c1cdaebff --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,152 @@ +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() + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target == '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget) + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + $this->assertMethod($method); + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !isset($this->headerNames['host'])) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri() + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } + + private function assertMethod($method) + { + if (!is_string($method) || $method === '') { + throw new \InvalidArgumentException('Method must be a non-empty string.'); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/Response.php b/lib/guzzlehttp/psr7/src/Response.php new file mode 100644 index 000000000..8c01a0f5a --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,155 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode = 200; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|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( + $status = 200, + array $headers = [], + $body = null, + $version = '1.1', + $reason = null + ) { + $this->assertStatusCodeIsInteger($status); + $status = (int) $status; + $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() + { + return $this->statusCode; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = '') + { + $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; + } + + private function assertStatusCodeIsInteger($statusCode) + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange($statusCode) + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/Rfc7230.php b/lib/guzzlehttp/psr7/src/Rfc7230.php new file mode 100644 index 000000000..51b571f24 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Rfc7230.php @@ -0,0 +1,19 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; +} diff --git a/lib/guzzlehttp/psr7/src/ServerRequest.php b/lib/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 000000000..e6d26f5ff --- /dev/null +++ b/lib/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,379 @@ +serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files A array which respect $_FILES structure + * + * @return array + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalizeFiles(array $files) + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return array|UploadedFileInterface + */ + private static function createUploadedFileFromSpec(array $value) + { + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @param array $files + * + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []) + { + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key], + 'type' => $files['type'][$key], + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + * + * @return ServerRequestInterface + */ + public static function fromGlobals() + { + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; + $headers = 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($authority) + { + $uri = 'http://' . $authority; + $parts = parse_url($uri); + if (false === $parts) { + return [null, null]; + } + + $host = isset($parts['host']) ? $parts['host'] : null; + $port = isset($parts['port']) ? $parts['port'] : null; + + return [$host, $port]; + } + + /** + * Get a Uri populated with values from $_SERVER. + * + * @return UriInterface + */ + public static function getUriFromGlobals() + { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + list($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; + } + + /** + * {@inheritdoc} + */ + public function getServerParams() + { + return $this->serverParams; + } + + /** + * {@inheritdoc} + */ + public function getUploadedFiles() + { + return $this->uploadedFiles; + } + + /** + * {@inheritdoc} + */ + public function withUploadedFiles(array $uploadedFiles) + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getCookieParams() + { + return $this->cookieParams; + } + + /** + * {@inheritdoc} + */ + public function withCookieParams(array $cookies) + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getQueryParams() + { + return $this->queryParams; + } + + /** + * {@inheritdoc} + */ + public function withQueryParams(array $query) + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + /** + * {@inheritdoc} + */ + public function withParsedBody($data) + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + /** + * {@inheritdoc} + */ + public function withAttribute($attribute, $value) + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withoutAttribute($attribute) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/lib/guzzlehttp/psr7/src/Stream.php b/lib/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 000000000..3865d6d6a --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,270 @@ +size = $options['size']; + } + + $this->customMetadata = isset($options['metadata']) + ? $options['metadata'] + : []; + + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = (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() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + public function getContents() + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $contents = stream_get_contents($this->stream); + + if ($contents === false) { + throw new \RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function close() + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function eof() + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + return feof($this->stream); + } + + public function tell() + { + 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() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + $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) + { + 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 ''; + } + + $string = fread($this->stream, $length); + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string) + { + 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; + } + + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } +} diff --git a/lib/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/lib/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 000000000..5025dd67b --- /dev/null +++ b/lib/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,152 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @param string $name Name of the property (allows "stream" only). + * + * @return StreamInterface + */ + public function __get($name) + { + if ($name == 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Exception $e) { + // Really, PHP? https://bugs.php.net/bug.php?id=53648 + trigger_error('StreamDecorator::__toString exception: ' + . (string) $e, E_USER_ERROR); + return ''; + } + } + + public function getContents() + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + $result = call_user_func_array([$this->stream, $method], $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close() + { + $this->stream->close(); + } + + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize() + { + return $this->stream->getSize(); + } + + public function eof() + { + return $this->stream->eof(); + } + + public function tell() + { + return $this->stream->tell(); + } + + public function isReadable() + { + return $this->stream->isReadable(); + } + + public function isWritable() + { + return $this->stream->isWritable(); + } + + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + $this->stream->seek($offset, $whence); + } + + public function read($length) + { + return $this->stream->read($length); + } + + public function write($string) + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @return StreamInterface + * + * @throws \BadMethodCallException + */ + protected function createStream() + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/lib/guzzlehttp/psr7/src/StreamWrapper.php b/lib/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 000000000..fc7cb969b --- /dev/null +++ b/lib/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,165 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, null, self::createStreamContext($stream)); + } + + /** + * Creates a stream context that can be used to open a stream as a php stream resource. + * + * @param StreamInterface $stream + * + * @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() + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read($count) + { + return $this->stream->read($count); + } + + public function stream_write($data) + { + return (int) $this->stream->write($data); + } + + public function stream_tell() + { + return $this->stream->tell(); + } + + public function stream_eof() + { + return $this->stream->eof(); + } + + public function stream_seek($offset, $whence) + { + $this->stream->seek($offset, $whence); + + return true; + } + + public function stream_cast($cast_as) + { + $stream = clone($this->stream); + + return $stream->detach(); + } + + public function stream_stat() + { + 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 + ]; + } + + public function url_stat($path, $flags) + { + 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/lib/guzzlehttp/psr7/src/UploadedFile.php b/lib/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 000000000..bf342c4de --- /dev/null +++ b/lib/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,328 @@ +setError($errorStatus); + $this->setSize($size); + $this->setClientFilename($clientFilename); + $this->setClientMediaType($clientMediaType); + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param mixed $streamOrFile + * + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile) + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @param int $error + * + * @throws InvalidArgumentException + */ + private function setError($error) + { + if (false === is_int($error)) { + throw new InvalidArgumentException( + 'Upload file error status must be an integer' + ); + } + + if (false === in_array($error, UploadedFile::$errors)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + /** + * @param int $size + * + * @throws InvalidArgumentException + */ + private function setSize($size) + { + if (false === is_int($size)) { + throw new InvalidArgumentException( + 'Upload file size must be an integer' + ); + } + + $this->size = $size; + } + + /** + * @param mixed $param + * + * @return bool + */ + private function isStringOrNull($param) + { + return in_array(gettype($param), ['string', 'NULL']); + } + + /** + * @param mixed $param + * + * @return bool + */ + private function isStringNotEmpty($param) + { + return is_string($param) && false === empty($param); + } + + /** + * @param string|null $clientFilename + * + * @throws InvalidArgumentException + */ + private function setClientFilename($clientFilename) + { + if (false === $this->isStringOrNull($clientFilename)) { + throw new InvalidArgumentException( + 'Upload file client filename must be a string or null' + ); + } + + $this->clientFilename = $clientFilename; + } + + /** + * @param string|null $clientMediaType + * + * @throws InvalidArgumentException + */ + private function setClientMediaType($clientMediaType) + { + if (false === $this->isStringOrNull($clientMediaType)) { + throw new InvalidArgumentException( + 'Upload file client media type must be a string or null' + ); + } + + $this->clientMediaType = $clientMediaType; + } + + /** + * Return true if there is no upload error + * + * @return bool + */ + private function isOk() + { + return $this->error === UPLOAD_ERR_OK; + } + + /** + * @return bool + */ + public function isMoved() + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive() + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + /** + * {@inheritdoc} + * + * @throws RuntimeException if the upload was not successful. + */ + public function getStream() + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + return new LazyOpenStream($this->file, 'r+'); + } + + /** + * {@inheritdoc} + * + * @see http://php.net/is_uploaded_file + * @see http://php.net/move_uploaded_file + * + * @param string $targetPath Path to which to move the uploaded file. + * + * @throws RuntimeException if the upload was not successful. + * @throws InvalidArgumentException if the $path specified is invalid. + * @throws RuntimeException on any error during the move operation, or on + * the second or subsequent call to the method. + */ + public function moveTo($targetPath) + { + $this->validateActive(); + + if (false === $this->isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = php_sapi_name() == 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + 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) + ); + } + } + + /** + * {@inheritdoc} + * + * @return int|null The file size in bytes or null if unknown. + */ + public function getSize() + { + return $this->size; + } + + /** + * {@inheritdoc} + * + * @see http://php.net/manual/en/features.file-upload.errors.php + * + * @return int One of PHP's UPLOAD_ERR_XXX constants. + */ + public function getError() + { + return $this->error; + } + + /** + * {@inheritdoc} + * + * @return string|null The filename sent by the client or null if none + * was provided. + */ + public function getClientFilename() + { + return $this->clientFilename; + } + + /** + * {@inheritdoc} + */ + public function getClientMediaType() + { + return $this->clientMediaType; + } +} diff --git a/lib/guzzlehttp/psr7/src/Uri.php b/lib/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 000000000..0f9f020d3 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,810 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + private static $charUnreserved = 'a-zA-Z0-9_\-\.~'; + private static $charSubDelims = '!\$&\'\(\)\*\+,;='; + private static $replaceQuery = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** + * @param string $uri URI to parse + */ + public function __construct($uri = '') + { + // weak type check to also accept null until we can add scalar type hints + if ($uri != '') { + $parts = self::parse($uri); + if ($parts === false) { + throw new \InvalidArgumentException("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 + * + * @param string $url + * + * @return array|false + */ + private static function parse($url) + { + // If IPv6 + $prefix = ''; + if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { + $prefix = $matches[1]; + $url = $matches[2]; + } + + $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() + { + return self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @param string $scheme + * @param string $authority + * @param string $path + * @param string $query + * @param string $fragment + * + * @return string + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents($scheme, $authority, $path, $query, $fragment) + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + + if ($authority != ''|| $scheme === 'file') { + $uri .= '//' . $authority; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?' . $query; + } + + if ($fragment != '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + * + * @param UriInterface $uri + * + * @return bool + */ + public static function isDefaultPort(UriInterface $uri) + { + return $uri->getPort() === null + || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @param UriInterface $uri + * + * @return bool + * + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri) + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @param UriInterface $uri + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri) + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @param UriInterface $uri + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @param UriInterface $uri + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null) + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + + return ($uri->getScheme() === $base->getScheme()) + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Removes dot segments from a path and returns the new path. + * + * @param string $path + * + * @return string + * + * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead. + * @see UriResolver::removeDotSegments + */ + public static function removeDotSegments($path) + { + return UriResolver::removeDotSegments($path); + } + + /** + * Converts the relative URI into a new URI that is resolved against the base URI. + * + * @param UriInterface $base Base URI + * @param string|UriInterface $rel Relative URI + * + * @return UriInterface + * + * @deprecated since version 1.4. Use UriResolver::resolve instead. + * @see UriResolver::resolve + */ + public static function resolve(UriInterface $base, $rel) + { + if (!($rel instanceof UriInterface)) { + $rel = new self($rel); + } + + return UriResolver::resolve($base, $rel); + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + * + * @return UriInterface + */ + public static function withoutQueryValue(UriInterface $uri, $key) + { + $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 + * + * @return UriInterface + */ + public static function withQueryValue(UriInterface $uri, $key, $value) + { + $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 + * + * @return UriInterface + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray) + { + $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); + + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString($key, $value); + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @param array $parts + * + * @return UriInterface + * + * @link http://php.net/manual/en/function.parse-url.php + * + * @throws \InvalidArgumentException If the components do not form a valid URI. + */ + public static function fromParts(array $parts) + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo() + { + return $this->userInfo; + } + + public function getHost() + { + return $this->host; + } + + public function getPort() + { + return $this->port; + } + + public function getPath() + { + return $this->path; + } + + public function getQuery() + { + return $this->query; + } + + public function getFragment() + { + return $this->fragment; + } + + public function withScheme($scheme) + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null) + { + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->validateState(); + + return $new; + } + + public function withHost($host) + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->validateState(); + + return $new; + } + + public function withPort($port) + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path) + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->validateState(); + + return $new; + } + + public function withQuery($query) + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + public function withFragment($fragment) + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts) + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) + ? $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 string $scheme + * + * @return string + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme) + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param string $component + * + * @return string + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component) + { + if (!is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + + return preg_replace_callback( + '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $component + ); + } + + /** + * @param string $host + * + * @return string + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host) + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param int|null $port + * + * @return int|null + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port) + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 0 and 65535', $port) + ); + } + + return $port; + } + + /** + * @param UriInterface $uri + * @param array $keys + * + * @return array + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys) + { + $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); + }); + } + + /** + * @param string $key + * @param string|null $value + * + * @return string + */ + private static function generateQueryString($key, $value) + { + // 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::$replaceQuery); + + if ($value !== null) { + $queryString .= '=' . strtr($value, self::$replaceQuery); + } + + return $queryString; + } + + private function removeDefaultPort() + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param string $path + * + * @return string + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path) + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param string $str + * + * @return string + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str) + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match) + { + return rawurlencode($match[0]); + } + + private function validateState() + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } elseif (isset($this->path[0]) && $this->path[0] !== '/') { + @trigger_error( + 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' . + 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', + E_USER_DEPRECATED + ); + $this->path = '/' . $this->path; + //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty'); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/UriNormalizer.php b/lib/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 000000000..81419ead4 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,219 @@ +getPath() === '' && + ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + return $uri; + } + + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS) + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri) + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match) { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private static function decodeUnreservedCharacters(UriInterface $uri) + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + + $callback = function (array $match) { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/lib/guzzlehttp/psr7/src/UriResolver.php b/lib/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 000000000..a3cb15d57 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,222 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() != '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority != '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + * + * @param UriInterface $base Base URI + * @param UriInterface $target Target URI + * + * @return UriInterface The relative URI reference + */ + public static function relativize(UriInterface $base, UriInterface $target) + { + if ($target->getScheme() !== '' && + ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target) + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/lib/guzzlehttp/psr7/src/Utils.php b/lib/guzzlehttp/psr7/src/Utils.php new file mode 100644 index 000000000..6b6c8cced --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Utils.php @@ -0,0 +1,428 @@ + $keys + * + * @return array + */ + public static function caselessRemove($keys, array $data) + { + $result = []; + + foreach ($keys as &$key) { + $key = strtolower($key); + } + + foreach ($data as $k => $v) { + if (!in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; + } + + /** + * 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, $maxLen = -1) + { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } + } + + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @return string + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, $maxLen = -1) + { + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * 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 + * + * @return string Returns the hash of the stream + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, $algo, $rawOutput = false) + { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * 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. + * + * @return RequestInterface + */ + public static function modifyRequest(RequestInterface $request, array $changes) + { + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':' . $port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = 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( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion(), + $request->getServerParams() + )) + ->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( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $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 + * + * @return string + */ + public static function readLine(StreamInterface $stream, $maxLength = null) + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + // Using a loose equality here to match on '' and false. + if (null == ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * 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 $options Additional options + * + * @return StreamInterface + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function streamFor($resource = '', array $options = []) + { + if (is_scalar($resource)) { + $stream = self::tryFopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $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 + */ + $metaData = \stream_get_meta_data($resource); + if (isset($metaData['uri']) && $metaData['uri'] === 'php://input') { + $stream = self::tryFopen('php://temp', 'w+'); + fwrite($stream, stream_get_contents($resource)); + fseek($stream, 0); + $resource = $stream; + } + return new Stream($resource, $options); + case 'object': + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return Utils::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($filename, $mode) + { + $ex = null; + set_error_handler(function () use ($filename, $mode, &$ex) { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + func_get_args()[1] + )); + + return true; + }); + + try { + $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; + } + + /** + * 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 + * + * @return UriInterface + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri) + { + 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/lib/guzzlehttp/psr7/src/functions.php b/lib/guzzlehttp/psr7/src/functions.php new file mode 100644 index 000000000..b0901fadd --- /dev/null +++ b/lib/guzzlehttp/psr7/src/functions.php @@ -0,0 +1,422 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + * + * @return array + * + * @deprecated parse_query will be removed in guzzlehttp/psr7:2.0. Use Query::parse instead. + */ +function parse_query($str, $urlEncoding = true) +{ + return Query::parse($str, $urlEncoding); +} + +/** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse_query()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + * + * @return string + * + * @deprecated build_query will be removed in guzzlehttp/psr7:2.0. Use Query::build instead. + */ +function build_query(array $params, $encoding = PHP_QUERY_RFC3986) +{ + return Query::build($params, $encoding); +} + +/** + * Determines the mimetype of a file by looking at its extension. + * + * @param string $filename + * + * @return string|null + * + * @deprecated mimetype_from_filename will be removed in guzzlehttp/psr7:2.0. Use MimeType::fromFilename instead. + */ +function mimetype_from_filename($filename) +{ + return MimeType::fromFilename($filename); +} + +/** + * Maps a file extensions to a mimetype. + * + * @param $extension string The file extension. + * + * @return string|null + * + * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types + * @deprecated mimetype_from_extension will be removed in guzzlehttp/psr7:2.0. Use MimeType::fromExtension instead. + */ +function mimetype_from_extension($extension) +{ + return MimeType::fromExtension($extension); +} + +/** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + * + * @return array + * + * @internal + * + * @deprecated _parse_message will be removed in guzzlehttp/psr7:2.0. Use Message::parseMessage instead. + */ +function _parse_message($message) +{ + return Message::parseMessage($message); +} + +/** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + * + * @return string + * + * @internal + * + * @deprecated _parse_request_uri will be removed in guzzlehttp/psr7:2.0. Use Message::parseRequestUri instead. + */ +function _parse_request_uri($path, array $headers) +{ + return Message::parseRequestUri($path, $headers); +} + +/** + * 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 + * + * @return string|null + * + * @deprecated get_message_body_summary will be removed in guzzlehttp/psr7:2.0. Use Message::bodySummary instead. + */ +function get_message_body_summary(MessageInterface $message, $truncateAt = 120) +{ + return Message::bodySummary($message, $truncateAt); +} + +/** + * Remove the items given by the keys, case insensitively from the data. + * + * @param iterable $keys + * + * @return array + * + * @internal + * + * @deprecated _caseless_remove will be removed in guzzlehttp/psr7:2.0. Use Utils::caselessRemove instead. + */ +function _caseless_remove($keys, array $data) +{ + return Utils::caselessRemove($keys, $data); +} diff --git a/lib/guzzlehttp/psr7/src/functions_include.php b/lib/guzzlehttp/psr7/src/functions_include.php new file mode 100644 index 000000000..96a4a83a0 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/functions_include.php @@ -0,0 +1,6 @@ + + * array( + * '' => $autoloaderOptions, + * ) + * + * + * The factory will then loop through and instantiate each autoloader with + * the specified options, and register each with the spl_autoloader. + * + * You may retrieve the concrete autoloader instances later using + * {@link getRegisteredAutoloaders()}. + * + * Note that the class names must be resolvable on the include_path or via + * the Laminas library, using PSR-0 rules (unless the class has already been + * loaded). + * + * @param array|Traversable $options (optional) options to use. Defaults to Laminas\Loader\StandardAutoloader + * @return void + * @throws Exception\InvalidArgumentException for invalid options + * @throws Exception\InvalidArgumentException for unloadable autoloader classes + * @throws Exception\DomainException for autoloader classes not implementing SplAutoloader + */ + public static function factory($options = null) + { + if (null === $options) { + if (! isset(static::$loaders[static::STANDARD_AUTOLOADER])) { + $autoloader = static::getStandardAutoloader(); + $autoloader->register(); + static::$loaders[static::STANDARD_AUTOLOADER] = $autoloader; + } + + // Return so we don't hit the next check's exception (we're done here anyway) + return; + } + + if (! is_array($options) && ! ($options instanceof Traversable)) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException( + 'Options provided must be an array or Traversable' + ); + } + + foreach ($options as $class => $autoloaderOptions) { + if (! isset(static::$loaders[$class])) { + $autoloader = static::getStandardAutoloader(); + if (! class_exists($class) && ! $autoloader->autoload($class)) { + require_once 'Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException( + sprintf('Autoloader class "%s" not loaded', $class) + ); + } + + if (! is_subclass_of($class, 'Laminas\Loader\SplAutoloader')) { + require_once 'Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException( + sprintf('Autoloader class %s must implement Laminas\\Loader\\SplAutoloader', $class) + ); + } + + if ($class === static::STANDARD_AUTOLOADER) { + $autoloader->setOptions($autoloaderOptions); + } else { + $autoloader = new $class($autoloaderOptions); + } + $autoloader->register(); + static::$loaders[$class] = $autoloader; + } else { + static::$loaders[$class]->setOptions($autoloaderOptions); + } + } + } + + /** + * Get a list of all autoloaders registered with the factory + * + * Returns an array of autoloader instances. + * + * @return array + */ + public static function getRegisteredAutoloaders() + { + return static::$loaders; + } + + /** + * Retrieves an autoloader by class name + * + * @param string $class + * @return SplAutoloader + * @throws Exception\InvalidArgumentException for non-registered class + */ + public static function getRegisteredAutoloader($class) + { + if (! isset(static::$loaders[$class])) { + require_once 'Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(sprintf('Autoloader class "%s" not loaded', $class)); + } + return static::$loaders[$class]; + } + + /** + * Unregisters all autoloaders that have been registered via the factory. + * This will NOT unregister autoloaders registered outside of the fctory. + * + * @return void + */ + public static function unregisterAutoloaders() + { + foreach (static::getRegisteredAutoloaders() as $class => $autoloader) { + spl_autoload_unregister([$autoloader, 'autoload']); + unset(static::$loaders[$class]); + } + } + + /** + * Unregister a single autoloader by class name + * + * @param string $autoloaderClass + * @return bool + */ + public static function unregisterAutoloader($autoloaderClass) + { + if (! isset(static::$loaders[$autoloaderClass])) { + return false; + } + + $autoloader = static::$loaders[$autoloaderClass]; + spl_autoload_unregister([$autoloader, 'autoload']); + unset(static::$loaders[$autoloaderClass]); + return true; + } + + /** + * Get an instance of the standard autoloader + * + * Used to attempt to resolve autoloader classes, using the + * StandardAutoloader. The instance is marked as a fallback autoloader, to + * allow resolving autoloaders not under the "Laminas" namespace. + * + * @return SplAutoloader + */ + protected static function getStandardAutoloader() + { + if (null !== static::$standardAutoloader) { + return static::$standardAutoloader; + } + + + if (! class_exists(static::STANDARD_AUTOLOADER)) { + // Extract the filename from the classname + $stdAutoloader = substr(strrchr(static::STANDARD_AUTOLOADER, '\\'), 1); + require_once __DIR__ . "/$stdAutoloader.php"; + } + $loader = new StandardAutoloader(); + static::$standardAutoloader = $loader; + return static::$standardAutoloader; + } + + /** + * Checks if the object has this class as one of its parents + * + * @see https://bugs.php.net/bug.php?id=53727 + * @see https://github.com/zendframework/zf2/pull/1807 + * + * @deprecated since laminas 2.3 requires PHP >= 5.3.23 + * + * @param string $className + * @param string $type + * @return bool + */ + protected static function isSubclassOf($className, $type) + { + return is_subclass_of($className, $type); + } +} diff --git a/lib/laminas/laminas-loader/src/ClassMapAutoloader.php b/lib/laminas/laminas-loader/src/ClassMapAutoloader.php new file mode 100644 index 000000000..ae4787c9f --- /dev/null +++ b/lib/laminas/laminas-loader/src/ClassMapAutoloader.php @@ -0,0 +1,220 @@ +setOptions($options); + } + } + + /** + * Configure the autoloader + * + * Proxies to {@link registerAutoloadMaps()}. + * + * @param array|Traversable $options + * @return ClassMapAutoloader + */ + public function setOptions($options) + { + $this->registerAutoloadMaps($options); + return $this; + } + + /** + * Register an autoload map + * + * An autoload map may be either an associative array, or a file returning + * an associative array. + * + * An autoload map should be an associative array containing + * classname/file pairs. + * + * @param string|array $map + * @throws Exception\InvalidArgumentException + * @return ClassMapAutoloader + */ + public function registerAutoloadMap($map) + { + if (is_string($map)) { + $location = $map; + if ($this === ($map = $this->loadMapFromFile($location))) { + return $this; + } + } + + if (! is_array($map)) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(sprintf( + 'Map file provided does not return a map. Map file: "%s"', + (isset($location) && is_string($location) ? $location : 'unexpected type: ' . gettype($map)) + )); + } + + $this->map = $map + $this->map; + + if (isset($location)) { + $this->mapsLoaded[] = $location; + } + + return $this; + } + + /** + * Register many autoload maps at once + * + * @param array $locations + * @throws Exception\InvalidArgumentException + * @return ClassMapAutoloader + */ + public function registerAutoloadMaps($locations) + { + if (! is_array($locations) && ! ($locations instanceof Traversable)) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException('Map list must be an array or implement Traversable'); + } + foreach ($locations as $location) { + $this->registerAutoloadMap($location); + } + return $this; + } + + /** + * Retrieve current autoload map + * + * @return array + */ + public function getAutoloadMap() + { + return $this->map; + } + + /** + * {@inheritDoc} + */ + public function autoload($class) + { + if (isset($this->map[$class])) { + require_once $this->map[$class]; + + return $class; + } + + return false; + } + + /** + * Register the autoloader with spl_autoload registry + * + * @return void + */ + public function register() + { + spl_autoload_register([$this, 'autoload'], true, true); + } + + /** + * Load a map from a file + * + * If the map has been previously loaded, returns the current instance; + * otherwise, returns whatever was returned by calling include() on the + * location. + * + * @param string $location + * @return ClassMapAutoloader|mixed + * @throws Exception\InvalidArgumentException for nonexistent locations + */ + protected function loadMapFromFile($location) + { + if (! file_exists($location)) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(sprintf( + 'Map file provided does not exist. Map file: "%s"', + (is_string($location) ? $location : 'unexpected type: ' . gettype($location)) + )); + } + + if (! $path = static::realPharPath($location)) { + $path = realpath($location); + } + + if (in_array($path, $this->mapsLoaded)) { + // Already loaded this map + return $this; + } + + $map = include $path; + + return $map; + } + + /** + * Resolve the real_path() to a file within a phar. + * + * @see https://bugs.php.net/bug.php?id=52769 + * @param string $path + * @return string + */ + public static function realPharPath($path) + { + if (! preg_match('|^phar:(/{2,3})|', $path, $match)) { + return; + } + + $prefixLength = 5 + strlen($match[1]); + $parts = explode('/', str_replace(['/', '\\'], '/', substr($path, $prefixLength))); + $parts = array_values(array_filter($parts, function ($p) { + return ($p !== '' && $p !== '.'); + })); + + array_walk($parts, function ($value, $key) use (&$parts) { + if ($value === '..') { + unset($parts[$key], $parts[$key - 1]); + $parts = array_values($parts); + } + }); + + if (file_exists($realPath = str_pad('phar:', $prefixLength, '/') . implode('/', $parts))) { + return $realPath; + } + } +} diff --git a/lib/laminas/laminas-loader/src/Exception/BadMethodCallException.php b/lib/laminas/laminas-loader/src/Exception/BadMethodCallException.php new file mode 100644 index 000000000..028c2318c --- /dev/null +++ b/lib/laminas/laminas-loader/src/Exception/BadMethodCallException.php @@ -0,0 +1,16 @@ + path + */ + protected $explicitPaths = []; + + /** + * @var array An array of namespaceName => namespacePath + */ + protected $namespacedPaths = []; + + /** + * @var string Will contain the absolute phar:// path to the executable when packaged as phar file + */ + protected $pharBasePath = ""; + + /** + * @var array An array of supported phar extensions (filled on constructor) + */ + protected $pharExtensions = []; + + /** + * @var array An array of module classes to their containing files + */ + protected $moduleClassMap = []; + + /** + * Constructor + * + * Allow configuration of the autoloader via the constructor. + * + * @param null|array|Traversable $options + */ + public function __construct($options = null) + { + if (extension_loaded('phar')) { + $this->pharBasePath = Phar::running(true); + $this->pharExtensions = [ + 'phar', + 'phar.tar', + 'tar', + ]; + + // ext/zlib enabled -> phar can read gzip & zip compressed files + if (extension_loaded('zlib')) { + $this->pharExtensions[] = 'phar.gz'; + $this->pharExtensions[] = 'phar.tar.gz'; + $this->pharExtensions[] = 'tar.gz'; + + $this->pharExtensions[] = 'phar.zip'; + $this->pharExtensions[] = 'zip'; + } + + // ext/bzip2 enabled -> phar can read bz2 compressed files + if (extension_loaded('bzip2')) { + $this->pharExtensions[] = 'phar.bz2'; + $this->pharExtensions[] = 'phar.tar.bz2'; + $this->pharExtensions[] = 'tar.bz2'; + } + } + + if (null !== $options) { + $this->setOptions($options); + } + } + + /** + * Configure the autoloader + * + * In most cases, $options should be either an associative array or + * Traversable object. + * + * @param array|Traversable $options + * @return ModuleAutoloader + */ + public function setOptions($options) + { + $this->registerPaths($options); + return $this; + } + + /** + * Retrieves the class map for all loaded modules. + * + * @return array + */ + public function getModuleClassMap() + { + return $this->moduleClassMap; + } + + /** + * Sets the class map used to speed up the module autoloading. + * + * @param array $classmap + * @return ModuleAutoloader + */ + public function setModuleClassMap(array $classmap) + { + $this->moduleClassMap = $classmap; + + return $this; + } + + /** + * Autoload a class + * + * @param $class + * @return mixed + * False [if unable to load $class] + * get_class($class) [if $class is successfully loaded] + */ + public function autoload($class) + { + // Limit scope of this autoloader + if (substr($class, -7) !== '\Module') { + return false; + } + + if (isset($this->moduleClassMap[$class])) { + require_once $this->moduleClassMap[$class]; + return $class; + } + + $moduleName = substr($class, 0, -7); + if (isset($this->explicitPaths[$moduleName])) { + $classLoaded = $this->loadModuleFromDir($this->explicitPaths[$moduleName], $class); + if ($classLoaded) { + return $classLoaded; + } + + $classLoaded = $this->loadModuleFromPhar($this->explicitPaths[$moduleName], $class); + if ($classLoaded) { + return $classLoaded; + } + } + + if (count($this->namespacedPaths) >= 1) { + foreach ($this->namespacedPaths as $namespace => $path) { + if (false === strpos($moduleName, $namespace)) { + continue; + } + + $moduleNameBuffer = str_replace($namespace . "\\", "", $moduleName); + $path .= DIRECTORY_SEPARATOR . $moduleNameBuffer . DIRECTORY_SEPARATOR; + + $classLoaded = $this->loadModuleFromDir($path, $class); + if ($classLoaded) { + return $classLoaded; + } + + $classLoaded = $this->loadModuleFromPhar($path, $class); + if ($classLoaded) { + return $classLoaded; + } + } + } + + $moduleClassPath = str_replace('\\', DIRECTORY_SEPARATOR, $moduleName); + + $pharSuffixPattern = null; + if ($this->pharExtensions) { + $pharSuffixPattern = '(' . implode('|', array_map('preg_quote', $this->pharExtensions)) . ')'; + } + + foreach ($this->paths as $path) { + $path = $path . $moduleClassPath; + + if ($path == '.' || substr($path, 0, 2) == './' || substr($path, 0, 2) == '.\\') { + if (! $basePath = $this->pharBasePath) { + $basePath = realpath('.'); + } + + if (false === $basePath) { + $basePath = getcwd(); + } + + $path = rtrim($basePath, '\/\\') . substr($path, 1); + } + + $classLoaded = $this->loadModuleFromDir($path, $class); + if ($classLoaded) { + return $classLoaded; + } + + // No directory with Module.php, searching for phars + if ($pharSuffixPattern) { + foreach (new GlobIterator($path . '.*') as $entry) { + if ($entry->isDir()) { + continue; + } + + if (! preg_match('#.+\.' . $pharSuffixPattern . '$#', $entry->getPathname())) { + continue; + } + + $classLoaded = $this->loadModuleFromPhar($entry->getPathname(), $class); + if ($classLoaded) { + return $classLoaded; + } + } + } + } + + return false; + } + + /** + * loadModuleFromDir + * + * @param string $dirPath + * @param string $class + * @return mixed + * False [if unable to load $class] + * get_class($class) [if $class is successfully loaded] + */ + protected function loadModuleFromDir($dirPath, $class) + { + $modulePath = $dirPath . '/Module.php'; + if (substr($modulePath, 0, 7) === 'phar://') { + $file = new PharFileInfo($modulePath); + } else { + $file = new SplFileInfo($modulePath); + } + + if (($file->isReadable() && $file->isFile())) { + // Found directory with Module.php in it + $absModulePath = $this->pharBasePath ? (string) $file : $file->getRealPath(); + require_once $absModulePath; + if (class_exists($class)) { + $this->moduleClassMap[$class] = $absModulePath; + return $class; + } + } + return false; + } + + /** + * loadModuleFromPhar + * + * @param string $pharPath + * @param string $class + * @return mixed + * False [if unable to load $class] + * get_class($class) [if $class is successfully loaded] + */ + protected function loadModuleFromPhar($pharPath, $class) + { + $pharPath = static::normalizePath($pharPath, false); + $file = new SplFileInfo($pharPath); + if (! $file->isReadable() || ! $file->isFile()) { + return false; + } + + $fileRealPath = $file->getRealPath(); + + // Phase 0: Check for executable phar with Module class in stub + if (strpos($fileRealPath, '.phar') !== false) { + // First see if the stub makes the Module class available + require_once $fileRealPath; + if (class_exists($class)) { + $this->moduleClassMap[$class] = $fileRealPath; + return $class; + } + } + + // Phase 1: Not executable phar, no stub, or stub did not provide Module class; try Module.php directly + $moduleClassFile = 'phar://' . $fileRealPath . '/Module.php'; + $moduleFile = new SplFileInfo($moduleClassFile); + if ($moduleFile->isReadable() && $moduleFile->isFile()) { + require_once $moduleClassFile; + if (class_exists($class)) { + $this->moduleClassMap[$class] = $moduleClassFile; + return $class; + } + } + + // Phase 2: Check for nested module directory within archive + // Checks for /path/to/MyModule.tar/MyModule/Module.php + // (shell-integrated zip/tar utilities wrap directories like this) + $pharBaseName = $this->pharFileToModuleName($fileRealPath); + $moduleClassFile = 'phar://' . $fileRealPath . '/' . $pharBaseName . '/Module.php'; + $moduleFile = new SplFileInfo($moduleClassFile); + if ($moduleFile->isReadable() && $moduleFile->isFile()) { + require_once $moduleClassFile; + if (class_exists($class)) { + $this->moduleClassMap[$class] = $moduleClassFile; + return $class; + } + } + + return false; + } + + /** + * Register the autoloader with spl_autoload registry + * + * @return void + */ + public function register() + { + spl_autoload_register([$this, 'autoload']); + } + + /** + * Unregister the autoloader with spl_autoload registry + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister([$this, 'autoload']); + } + + /** + * registerPaths + * + * @param array|Traversable $paths + * @throws \InvalidArgumentException + * @return ModuleAutoloader + */ + public function registerPaths($paths) + { + if (! is_array($paths) && ! $paths instanceof Traversable) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException( + 'Parameter to \\Laminas\\Loader\\ModuleAutoloader\'s ' + . 'registerPaths method must be an array or ' + . 'implement the Traversable interface' + ); + } + + foreach ($paths as $module => $path) { + if (is_string($module)) { + $this->registerPath($path, $module); + } else { + $this->registerPath($path); + } + } + + return $this; + } + + /** + * registerPath + * + * @param string $path + * @param bool|string $moduleName + * @throws \InvalidArgumentException + * @return ModuleAutoloader + */ + public function registerPath($path, $moduleName = false) + { + if (! is_string($path)) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid path provided; must be a string, received %s', + gettype($path) + )); + } + if ($moduleName) { + if (in_array(substr($moduleName, -2), ['\\*', '\\%'])) { + $this->namespacedPaths[substr($moduleName, 0, -2)] = static::normalizePath($path); + } else { + $this->explicitPaths[$moduleName] = static::normalizePath($path); + } + } else { + $this->paths[] = static::normalizePath($path); + } + return $this; + } + + /** + * getPaths + * + * This is primarily for unit testing, but could have other uses. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Returns the base module name from the path to a phar + * + * @param string $pharPath + * @return string + */ + protected function pharFileToModuleName($pharPath) + { + do { + $pathinfo = pathinfo($pharPath); + $pharPath = $pathinfo['filename']; + } while (isset($pathinfo['extension'])); + return $pathinfo['filename']; + } + + /** + * Normalize a path for insertion in the stack + * + * @param string $path + * @param bool $trailingSlash Whether trailing slash should be included + * @return string + */ + public static function normalizePath($path, $trailingSlash = true) + { + $path = rtrim($path, '/'); + $path = rtrim($path, '\\'); + if ($trailingSlash) { + $path .= DIRECTORY_SEPARATOR; + } + return $path; + } +} diff --git a/lib/laminas/laminas-loader/src/PluginClassLoader.php b/lib/laminas/laminas-loader/src/PluginClassLoader.php new file mode 100644 index 000000000..90a0892e5 --- /dev/null +++ b/lib/laminas/laminas-loader/src/PluginClassLoader.php @@ -0,0 +1,216 @@ + class name pairs + * @var array + */ + protected $plugins = []; + + /** + * Static map allow global seeding of plugin loader + * @var array + */ + protected static $staticMap = []; + + /** + * Constructor + * + * @param null|array|Traversable $map If provided, seeds the loader with a map + */ + public function __construct($map = null) + { + // Merge in static overrides + if (! empty(static::$staticMap)) { + $this->registerPlugins(static::$staticMap); + } + + // Merge in constructor arguments + if ($map !== null) { + $this->registerPlugins($map); + } + } + + /** + * Add a static map of plugins + * + * A null value will clear the static map. + * + * @param null|array|Traversable $map + * @throws Exception\InvalidArgumentException + * @return void + */ + public static function addStaticMap($map) + { + if (null === $map) { + static::$staticMap = []; + return; + } + + if (! is_array($map) && ! $map instanceof Traversable) { + throw new Exception\InvalidArgumentException('Expects an array or Traversable object'); + } + foreach ($map as $key => $value) { + static::$staticMap[$key] = $value; + } + } + + /** + * Register a class to a given short name + * + * @param string $shortName + * @param string $className + * @return PluginClassLoader + */ + public function registerPlugin($shortName, $className) + { + $this->plugins[strtolower($shortName)] = $className; + return $this; + } + + /** + * Register many plugins at once + * + * If $map is a string, assumes that the map is the class name of a + * Traversable object (likely a ShortNameLocator); it will then instantiate + * this class and use it to register plugins. + * + * If $map is an array or Traversable object, it will iterate it to + * register plugin names/classes. + * + * For all other arguments, or if the string $map is not a class or not a + * Traversable class, an exception will be raised. + * + * @param string|array|Traversable $map + * @return PluginClassLoader + * @throws Exception\InvalidArgumentException + */ + public function registerPlugins($map) + { + if (is_string($map)) { + if (! class_exists($map)) { + throw new Exception\InvalidArgumentException('Map class provided is invalid'); + } + $map = new $map; + } + if (is_array($map)) { + $map = new ArrayIterator($map); + } + if (! $map instanceof Traversable) { + throw new Exception\InvalidArgumentException('Map provided is invalid; must be traversable'); + } + + // iterator_apply doesn't work as expected with IteratorAggregate + if ($map instanceof IteratorAggregate) { + $map = $map->getIterator(); + } + + foreach ($map as $name => $class) { + if (is_int($name) || is_numeric($name)) { + if (! is_object($class) && class_exists($class)) { + $class = new $class(); + } + + if ($class instanceof Traversable) { + $this->registerPlugins($class); + continue; + } + } + + $this->registerPlugin($name, $class); + } + + return $this; + } + + /** + * Unregister a short name lookup + * + * @param mixed $shortName + * @return PluginClassLoader + */ + public function unregisterPlugin($shortName) + { + $lookup = strtolower($shortName); + if (array_key_exists($lookup, $this->plugins)) { + unset($this->plugins[$lookup]); + } + return $this; + } + + /** + * Get a list of all registered plugins + * + * @return array|Traversable + */ + public function getRegisteredPlugins() + { + return $this->plugins; + } + + /** + * Whether or not a plugin by a specific name has been registered + * + * @param string $name + * @return bool + */ + public function isLoaded($name) + { + $lookup = strtolower($name); + return isset($this->plugins[$lookup]); + } + + /** + * Return full class name for a named helper + * + * @param string $name + * @return string|false + */ + public function getClassName($name) + { + return $this->load($name); + } + + /** + * Load a helper via the name provided + * + * @param string $name + * @return string|false + */ + public function load($name) + { + if (! $this->isLoaded($name)) { + return false; + } + return $this->plugins[strtolower($name)]; + } + + /** + * Defined by IteratorAggregate + * + * Returns an instance of ArrayIterator, containing a map of + * all plugins + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->plugins); + } +} diff --git a/lib/laminas/laminas-loader/src/PluginClassLocator.php b/lib/laminas/laminas-loader/src/PluginClassLocator.php new file mode 100644 index 000000000..078983f47 --- /dev/null +++ b/lib/laminas/laminas-loader/src/PluginClassLocator.php @@ -0,0 +1,42 @@ + + * spl_autoload_register(array($this, 'autoload')); + * + * + * @return void + */ + public function register(); +} diff --git a/lib/laminas/laminas-loader/src/StandardAutoloader.php b/lib/laminas/laminas-loader/src/StandardAutoloader.php new file mode 100644 index 000000000..5a1f29a1a --- /dev/null +++ b/lib/laminas/laminas-loader/src/StandardAutoloader.php @@ -0,0 +1,327 @@ +setOptions($options); + } + } + + /** + * Configure autoloader + * + * Allows specifying both "namespace" and "prefix" pairs, using the + * following structure: + * + * array( + * 'namespaces' => array( + * 'Laminas' => '/path/to/Laminas/library', + * 'Doctrine' => '/path/to/Doctrine/library', + * ), + * 'prefixes' => array( + * 'Phly_' => '/path/to/Phly/library', + * ), + * 'fallback_autoloader' => true, + * ) + * + * + * @param array|\Traversable $options + * @throws Exception\InvalidArgumentException + * @return StandardAutoloader + */ + public function setOptions($options) + { + if (! is_array($options) && ! ($options instanceof \Traversable)) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException('Options must be either an array or Traversable'); + } + + foreach ($options as $type => $pairs) { + switch ($type) { + case self::AUTOREGISTER_LAMINAS: + if ($pairs) { + $this->registerNamespace('Laminas', dirname(__DIR__)); + } + break; + case self::LOAD_NS: + if (is_array($pairs) || $pairs instanceof \Traversable) { + $this->registerNamespaces($pairs); + } + break; + case self::LOAD_PREFIX: + if (is_array($pairs) || $pairs instanceof \Traversable) { + $this->registerPrefixes($pairs); + } + break; + case self::ACT_AS_FALLBACK: + $this->setFallbackAutoloader($pairs); + break; + default: + // ignore + } + } + return $this; + } + + /** + * Set flag indicating fallback autoloader status + * + * @param bool $flag + * @return StandardAutoloader + */ + public function setFallbackAutoloader($flag) + { + $this->fallbackAutoloaderFlag = (bool) $flag; + return $this; + } + + /** + * Is this autoloader acting as a fallback autoloader? + * + * @return bool + */ + public function isFallbackAutoloader() + { + return $this->fallbackAutoloaderFlag; + } + + /** + * Register a namespace/directory pair + * + * @param string $namespace + * @param string $directory + * @return StandardAutoloader + */ + public function registerNamespace($namespace, $directory) + { + $namespace = rtrim($namespace, self::NS_SEPARATOR) . self::NS_SEPARATOR; + $this->namespaces[$namespace] = $this->normalizeDirectory($directory); + return $this; + } + + /** + * Register many namespace/directory pairs at once + * + * @param array $namespaces + * @throws Exception\InvalidArgumentException + * @return StandardAutoloader + */ + public function registerNamespaces($namespaces) + { + if (! is_array($namespaces) && ! $namespaces instanceof \Traversable) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException('Namespace pairs must be either an array or Traversable'); + } + + foreach ($namespaces as $namespace => $directory) { + $this->registerNamespace($namespace, $directory); + } + return $this; + } + + /** + * Register a prefix/directory pair + * + * @param string $prefix + * @param string $directory + * @return StandardAutoloader + */ + public function registerPrefix($prefix, $directory) + { + $prefix = rtrim($prefix, self::PREFIX_SEPARATOR). self::PREFIX_SEPARATOR; + $this->prefixes[$prefix] = $this->normalizeDirectory($directory); + return $this; + } + + /** + * Register many namespace/directory pairs at once + * + * @param array $prefixes + * @throws Exception\InvalidArgumentException + * @return StandardAutoloader + */ + public function registerPrefixes($prefixes) + { + if (! is_array($prefixes) && ! $prefixes instanceof \Traversable) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException('Prefix pairs must be either an array or Traversable'); + } + + foreach ($prefixes as $prefix => $directory) { + $this->registerPrefix($prefix, $directory); + } + return $this; + } + + /** + * Defined by Autoloadable; autoload a class + * + * @param string $class + * @return false|string + */ + public function autoload($class) + { + $isFallback = $this->isFallbackAutoloader(); + if (false !== strpos($class, self::NS_SEPARATOR)) { + if ($this->loadClass($class, self::LOAD_NS)) { + return $class; + } elseif ($isFallback) { + return $this->loadClass($class, self::ACT_AS_FALLBACK); + } + return false; + } + if (false !== strpos($class, self::PREFIX_SEPARATOR)) { + if ($this->loadClass($class, self::LOAD_PREFIX)) { + return $class; + } elseif ($isFallback) { + return $this->loadClass($class, self::ACT_AS_FALLBACK); + } + return false; + } + if ($isFallback) { + return $this->loadClass($class, self::ACT_AS_FALLBACK); + } + return false; + } + + /** + * Register the autoloader with spl_autoload + * + * @return void + */ + public function register() + { + spl_autoload_register([$this, 'autoload']); + } + + /** + * Transform the class name to a filename + * + * @param string $class + * @param string $directory + * @return string + */ + protected function transformClassNameToFilename($class, $directory) + { + // $class may contain a namespace portion, in which case we need + // to preserve any underscores in that portion. + $matches = []; + preg_match('/(?P.+\\\)?(?P[^\\\]+$)/', $class, $matches); + + $class = (isset($matches['class'])) ? $matches['class'] : ''; + $namespace = (isset($matches['namespace'])) ? $matches['namespace'] : ''; + + return $directory + . str_replace(self::NS_SEPARATOR, '/', $namespace) + . str_replace(self::PREFIX_SEPARATOR, '/', $class) + . '.php'; + } + + /** + * Load a class, based on its type (namespaced or prefixed) + * + * @param string $class + * @param string $type + * @return bool|string + * @throws Exception\InvalidArgumentException + */ + protected function loadClass($class, $type) + { + if (! in_array($type, [self::LOAD_NS, self::LOAD_PREFIX, self::ACT_AS_FALLBACK])) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(); + } + + // Fallback autoloading + if ($type === self::ACT_AS_FALLBACK) { + // create filename + $filename = $this->transformClassNameToFilename($class, ''); + $resolvedName = stream_resolve_include_path($filename); + if ($resolvedName !== false) { + return include $resolvedName; + } + return false; + } + + // Namespace and/or prefix autoloading + foreach ($this->$type as $leader => $path) { + if (0 === strpos($class, $leader)) { + // Trim off leader (namespace or prefix) + $trimmedClass = substr($class, strlen($leader)); + + // create filename + $filename = $this->transformClassNameToFilename($trimmedClass, $path); + if (file_exists($filename)) { + return include $filename; + } + } + } + return false; + } + + /** + * Normalize the directory to include a trailing directory separator + * + * @param string $directory + * @return string + */ + protected function normalizeDirectory($directory) + { + $last = $directory[strlen($directory) - 1]; + if (in_array($last, ['/', '\\'])) { + $directory[strlen($directory) - 1] = DIRECTORY_SEPARATOR; + return $directory; + } + $directory .= DIRECTORY_SEPARATOR; + return $directory; + } +} diff --git a/lib/laminas/laminas-mail/CHANGELOG.md b/lib/laminas/laminas-mail/CHANGELOG.md new file mode 100644 index 000000000..e596e660f --- /dev/null +++ b/lib/laminas/laminas-mail/CHANGELOG.md @@ -0,0 +1,443 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 2.11.1 - 2020-07-28 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#97](https://github.com/laminas/laminas-mail/pull/97) adds code to `Header\ContentType::addParameter()` to ensure the value is trimmed of whitespace, fixing issues whereby filenames might contain a leading or trailing space. + +## 2.11.0 - 2020-06-30 + +### Added + +- [#31](https://github.com/laminas/laminas-mail/pull/31) adds the class `Laminas\Main\Header\ContentDisposition`, which implements `Laminas\Mail\Header\UnstructuredInterface`, and which provides propery encoding and escaping for `Content-Disposition` mail headers. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#31](https://github.com/laminas/laminas-mail/pull/31) provides a fix to ensure that the `Content-Disposition` header is properly encoded. + +## 2.10.2 - 2020-06-30 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#89](https://github.com/laminas/laminas-mail/pull/89) corrects two parameter typehints within the Storage subcomponent to correctly detail what they allow. + +- [#93](https://github.com/laminas/laminas-mail/pull/93) fixes an issue whereby an address containing a `;` character was not getting quoted, causing it to be interpreted as an address separator instead of part of the address. + +## 2.10.1 - 2020-04-21 + +### Added + +- [#81](https://github.com/laminas/laminas-mail/pull/81) adds PHP 7.3 and 7.4 support. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#83](https://github.com/laminas/laminas-mail/pull/83) fixes PHPDocs in `Transport\InMemory` (last message can be `null`). + +- [#84](https://github.com/laminas/laminas-mail/pull/84) fixes PHP 7.4 compatibility. + +- [#82](https://github.com/laminas/laminas-mail/pull/82) fixes numerous issues in `Storage\Maildir`. This storage adapter was not working before and unit tests were disabled. + +- [#75](https://github.com/laminas/laminas-mail/pull/75) fixes how `Laminas\Mail\Header\ListParser::parse()` parses the string with quotes. + +- [#88](https://github.com/laminas/laminas-mail/pull/88) fixes recognising encoding of `Subject` and `GenericHeader` headers. + +## 2.10.0 - 2018-06-07 + +### Added + +- [zendframework/zend-mail#213](https://github.com/zendframework/zend-mail/pull/213) re-adds support for PHP 5.6 and 7.0; Laminas policy is never + to bump the major version of a PHP requirement unless the package is bumping major version. + +- [zendframework/zend-mail#172](https://github.com/zendframework/zend-mail/pull/172) adds the flag `connection_time_limit` to the possible `Laminas\Mail\Transport\Smtp` options. + This flag, when provided as a positive integer, and in conjunction with the `use_complete_quit` flag, will + reconnect to the server after the specified interval. + +- [zendframework/zend-mail#166](https://github.com/zendframework/zend-mail/pull/166) adds functionality for handling `References` and `In-Reply-To` headers. + +- [zendframework/zend-mail#148](https://github.com/zendframework/zend-mail/pull/148) adds the optional constructor argument `$comment` and the method `getComment()` to the class + `Laminas\Mail\Address`. When a comment is present, `toString()` will include it in the representation. + +- [zendframework/zend-mail#148](https://github.com/zendframework/zend-mail/pull/148) adds the method `Laminas\Mail\Address::fromString(string $address, $comment = null) : Address`. + The method can be used to generate an instance from a string containing a `(name)?` value. + The `$comment` argument can be used to associate a comment with the address. + +### Changed + +- [zendframework/zend-mail#196](https://github.com/zendframework/zend-mail/pull/196) updates how the `Headers::fromString()` handles header line continuations + that include a single empty line, ensuring they are concatenated to the + header value. + +- [zendframework/zend-mail#165](https://github.com/zendframework/zend-mail/pull/165) changes the `AbstractAddressList` IDN<->ASCII conversion; it now no longer requires + ext-intl, but instead uses a bundled true/punycode library to accomplish it. This also means that + the conversions will work on any PHP installation. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-mail#211](https://github.com/zendframework/zend-mail/pull/211) fixes how the `ContentType` header class parses the value it receives. Previously, + it was incorrectly splitting the value on semi-colons that were inside quotes; in now correctly + ignores them. + +- [zendframework/zend-mail#204](https://github.com/zendframework/zend-mail/pull/204) fixes `HeaderWrap::mimeDecodeValue()` behavior when handling a multiline UTF-8 + header split across a character. The fix will only work when ext-imap is present, however. + +- [zendframework/zend-mail#164](https://github.com/zendframework/zend-mail/pull/164) fixes the return value from `Laminas\Mail\Protocol\Imap::capability()` when no response is + returned from the server; previously, it returned `false`, but now correctly returns an empty array. + +- [zendframework/zend-mail#148](https://github.com/zendframework/zend-mail/pull/148) fixes how `Laminas\Mail\Header\AbstractAddressList` parses address values, ensuring + that they now retain any address comment discovered to include in the generated `Laminas\Mail\Address` instances. + +- [zendframework/zend-mail#147](https://github.com/zendframework/zend-mail/pull/147) fixes how address lists are parsed, expanding the functionality to allow either + `,` or `;` delimiters (or both in combination). + +## 2.9.0 - 2017-03-01 + +### Added + +- [zendframework/zend-mail#177](https://github.com/zendframework/zend-mail/issues/177) + [zendframework/zend-mail#181](https://github.com/zendframework/zend-mail/pull/181) + [zendframework/zend-mail#192](https://github.com/zendframework/zend-mail/pull/192) + [zendframework/zend-mail#189](https://github.com/zendframework/zend-mail/pull/189) PHP 7.2 support +- [zendframework/zend-mail#73](https://github.com/zendframework/zend-mail/issues/73) + [zendframework/zend-mail#160](https://github.com/zendframework/zend-mail/pull/160) Support for + mails that don't have a `To`, as long as `Cc` or `Bcc` are set. +- [zendframework/zend-mail#161](https://github.com/zendframework/zend-mail/issues/161) removed + useless try-catch that just re-throws. +- [zendframework/zend-mail#134](https://github.com/zendframework/zend-mail/issues/134) simplified + checks for the existence of some string sub-sequences, which were + needlessly performed via regular expressions + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-mail#188](https://github.com/zendframework/zend-mail/pull/188) split strings + before calling `iconv_mime_decode()`, which destroys newlines, rendering + DKIM parsing useless. +- [zendframework/zend-mail#156](https://github.com/zendframework/zend-mail/pull/156) fixed a + regression in which `<` and `>` would appear doubled in message + identifiers. +- [zendframework/zend-mail#143](https://github.com/zendframework/zend-mail/pull/143) fixed parsing + of `<` and `>` being part of the email address comment. + +## 2.8.0 - 2017-06-08 + +### Added + +- [zendframework/zend-mail#117](https://github.com/zendframework/zend-mail/pull/117) adds support + configuring whether or not an SMTP transport should issue a `QUIT` at + `__destruct()` and/or end of script execution. Use the `use_complete_quit` + configuration flag and/or the `setuseCompleteQuit($flag)` method to change + the setting (default is to enable this behavior, which was the previous + behavior). +- [zendframework/zend-mail#128](https://github.com/zendframework/zend-mail/pull/128) adds a + requirement on ext/iconv, as it is used internally. +- [zendframework/zend-mail#132](https://github.com/zendframework/zend-mail/pull/132) bumps minimum + php version to 5.6 +- [zendframework/zend-mail#144](https://github.com/zendframework/zend-mail/pull/144) adds support + for TLS versions 1.1 and 1.2 for all protocols supporting TLS operations. + +### Changed + +- [zendframework/zend-mail#140](https://github.com/zendframework/zend-mail/pull/140) updates the + `Sendmail` transport such that `From` and `Sender` addresses are passed to + `escapeshellarg()` when forming the `-f` argument for the `sendmail` binary. + While malformed addresses should never reach this class, this extra hardening + helps ensure safety in cases where a developer codes their own + `AddressInterface` implementations for these types of addresses. +- [zendframework/zend-mail#141](https://github.com/zendframework/zend-mail/pull/141) updates + `Laminas\Mail\Message::getHeaders()` to throw an exception in a case where the + `$headers` property is not a `Headers` instance. +- [zendframework/zend-mail#150](https://github.com/zendframework/zend-mail/pull/150) updates the + `Smtp` protocol to allow an empty or `none` value for the SSL configuration + value. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-mail#151](https://github.com/zendframework/zend-mail/pull/151) fixes a condition + in the `Sendmail` transport whereby CLI parameters were not properly trimmed. + +## 2.7.3 - 2017-02-14 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-mail#93](https://github.com/zendframework/zend-mail/pull/93) fixes a situation + whereby `getSender()` was unintentionally creating a blank `Sender` header, + instead of returning `null` if none exists, fixing an issue in the SMTP + transport. +- [zendframework/zend-mail#105](https://github.com/zendframework/zend-mail/pull/105) fixes the header + implementation to allow zero (`0`) values for header values. +- [zendframework/zend-mail#116](https://github.com/zendframework/zend-mail/pull/116) fixes how the + `AbstractProtocol` handles `stream_socket_client()` errors, ensuring an + exception is thrown with detailed information regarding the failure. + +## 2.7.2 - 2016-12-19 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Fixes [ZF2016-04](https://framework.zend.com/security/advisory/ZF2016-04). + +## 2.7.1 - 2016-05-09 + +### Added + +- [zendframework/zend-mail#38](https://github.com/zendframework/zend-mail/pull/38) adds support in the + IMAP protocol adapter for fetching items by UID. +- [zendframework/zend-mail#88](https://github.com/zendframework/zend-mail/pull/88) adds and publishes + documentation to https://docs.laminas.dev/laminas-mail/ + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-mail#9](https://github.com/zendframework/zend-mail/pull/9) fixes the + `Laminas\Mail\Header\Sender::fromString()` implementation to more closely follow + the ABNF defined in RFC-5322, specifically to allow addresses in the form + `user@domain` (with no TLD). +- [zendframework/zend-mail#28](https://github.com/zendframework/zend-mail/pull/28) and + [zendframework/zend-mail#87](https://github.com/zendframework/zend-mail/pull/87) fix header value + validation when headers wrap using the sequence `\r\n\t`; prior to this + release, such sequences incorrectly marked a header value invalid. +- [zendframework/zend-mail#37](https://github.com/zendframework/zend-mail/pull/37) ensures that empty + lines do not result in PHP errors when consuming messages from a Courier IMAP + server. +- [zendframework/zend-mail#81](https://github.com/zendframework/zend-mail/pull/81) fixes the validation + in `Laminas\Mail\Address` to also DNS hostnames as well as local addresses. + +## 2.7.0 - 2016-04-11 + +### Added + +- [zendframework/zend-mail#41](https://github.com/zendframework/zend-mail/pull/41) adds support for + IMAP delimiters in the IMAP storage adapter. +- [zendframework/zend-mail#80](https://github.com/zendframework/zend-mail/pull/80) adds: + - `Laminas\Mail\Protocol\SmtpPluginManagerFactory`, for creating and returning an + `SmtpPluginManagerFactory` instance. + - `Laminas\Mail\ConfigProvider`, which maps the `SmtpPluginManager` to the above + factory. + - `Laminas\Mail\Module`, which does the same, for laminas-mvc contexts. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 2.6.2 - 2016-04-11 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-mail#44](https://github.com/zendframework/zend-mail/pull/44) fixes an issue with + decoding of addresses where the full name contains a comma (e.g., "Lastname, + Firstname"). +- [zendframework/zend-mail#45](https://github.com/zendframework/zend-mail/pull/45) ensures that the + message parser allows deserializing message bodies containing multiple EOL + sequences. +- [zendframework/zend-mail#78](https://github.com/zendframework/zend-mail/pull/78) fixes the logic of + `HeaderWrap::canBeEncoded()` to ensure it returns correctly for header lines + containing at least one multibyte character, and particularly when that + character falls at specific locations (per a + [reported bug at php.net](https://bugs.php.net/bug.php?id=53891)). + +## 2.6.1 - 2016-02-24 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-mail#72](https://github.com/zendframework/zend-mail/pull/72) re-implements + `SmtpPluginManager` as a laminas-servicemanager `AbstractPluginManager`, after + reports that making it standalone broke important extensibility use cases + (specifically, replacing existing plugins and/or providing additional plugins + could only be managed with significant code changes). + +## 2.6.0 - 2016-02-18 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-mail#47](https://github.com/zendframework/zend-mail/pull/47) updates the + component to remove the (soft) dependency on laminas-servicemanager, by + altering the `SmtpPluginManager` to implement container-interop's + `ContainerInterface` instead of extending from `AbstractPluginManager`. + Usage remains the same, though developers who were adding services + to the plugin manager will need to instead extend it now. +- [zendframework/zend-mail#70](https://github.com/zendframework/zend-mail/pull/70) updates dependencies + to stable, forwards-compatible versions, and removes unused dependencies. + +## 2.5.2 - 2015-09-10 + +### Added + +- [zendframework/zend-mail#12](https://github.com/zendframework/zend-mail/pull/12) adds support for + simple comments in address lists. +- [zendframework/zend-mail#13](https://github.com/zendframework/zend-mail/pull/13) adds support for + groups in address lists. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-mail#26](https://github.com/zendframework/zend-mail/pull/26) fixes the + `ContentType` header to properly handle parameters with encoded values. +- [zendframework/zend-mail#11](https://github.com/zendframework/zend-mail/pull/11) fixes the + behavior of the `Sender` header, ensuring it can handle domains that do not + contain a TLD, as well as addresses referencing mailboxes (no domain). +- [zendframework/zend-mail#24](https://github.com/zendframework/zend-mail/pull/24) fixes parsing of + mail messages that contain an initial blank line (prior to the headers), a + situation observed in particular with GMail. diff --git a/lib/laminas/laminas-mail/COPYRIGHT.md b/lib/laminas/laminas-mail/COPYRIGHT.md new file mode 100644 index 000000000..0a8cccc06 --- /dev/null +++ b/lib/laminas/laminas-mail/COPYRIGHT.md @@ -0,0 +1 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) diff --git a/lib/laminas/laminas-mail/LICENSE.md b/lib/laminas/laminas-mail/LICENSE.md new file mode 100644 index 000000000..10b40f142 --- /dev/null +++ b/lib/laminas/laminas-mail/LICENSE.md @@ -0,0 +1,26 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Laminas Foundation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/laminas/laminas-mail/README.md b/lib/laminas/laminas-mail/README.md new file mode 100644 index 000000000..3ffca6e7a --- /dev/null +++ b/lib/laminas/laminas-mail/README.md @@ -0,0 +1,13 @@ +# laminas-mail + +[![Build Status](https://travis-ci.com/laminas/laminas-mail.svg?branch=master)](https://travis-ci.com/laminas/laminas-mail) +[![Coverage Status](https://coveralls.io/repos/github/laminas/laminas-mail/badge.svg?branch=master)](https://coveralls.io/github/laminas/laminas-mail?branch=master) + +`Laminas\Mail` provides generalized functionality to compose and send both text and +MIME-compliant multipart email messages. Mail can be sent with `Laminas\Mail` via +the `Mail\Transport\Sendmail`, `Mail\Transport\Smtp` or the `Mail\Transport\File` +transport. Of course, you can also implement your own transport by implementing +the `Mail\Transport\TransportInterface`. + +- File issues at https://github.com/laminas/laminas-mail/issues +- Documentation is at https://docs.laminas.dev/laminas-mail/ diff --git a/lib/laminas/laminas-mail/composer.json b/lib/laminas/laminas-mail/composer.json new file mode 100644 index 000000000..b119f0995 --- /dev/null +++ b/lib/laminas/laminas-mail/composer.json @@ -0,0 +1,75 @@ +{ + "name": "laminas/laminas-mail", + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "license": "BSD-3-Clause", + "keywords": [ + "laminas", + "mail" + ], + "homepage": "https://laminas.dev", + "support": { + "docs": "https://docs.laminas.dev/laminas-mail/", + "issues": "https://github.com/laminas/laminas-mail/issues", + "source": "https://github.com/laminas/laminas-mail", + "rss": "https://github.com/laminas/laminas-mail/releases.atom", + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev" + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" + }, + "laminas": { + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" + } + }, + "require": { + "php": "^5.6 || ^7.0", + "ext-iconv": "*", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mime": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.10.2", + "laminas/laminas-zendframework-bridge": "^1.0", + "true/punycode": "^2.1" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-crypt": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4" + }, + "suggest": { + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + }, + "autoload": { + "psr-4": { + "Laminas\\Mail\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LaminasTest\\Mail\\": "test/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" + }, + "replace": { + "zendframework/zend-mail": "^2.10.0" + } +} diff --git a/lib/laminas/laminas-mail/src/Address.php b/lib/laminas/laminas-mail/src/Address.php new file mode 100644 index 000000000..49f02e076 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Address.php @@ -0,0 +1,166 @@ +.*)<(?P[^>]+)>|(?P.+))$/', $address, $matches)) { + throw new Exception\InvalidArgumentException('Invalid address format'); + } + + $name = null; + if (isset($matches['name'])) { + $name = trim($matches['name']); + } + if (empty($name)) { + $name = null; + } + + if (isset($matches['namedEmail'])) { + $email = $matches['namedEmail']; + } + if (isset($matches['email'])) { + $email = $matches['email']; + } + $email = trim($email); + + return new static($email, $name, $comment); + } + + /** + * Constructor + * + * @param string $email + * @param null|string $name + * @param null|string $comment + * @throws Exception\InvalidArgumentException + */ + public function __construct($email, $name = null, $comment = null) + { + $emailAddressValidator = new EmailAddressValidator(Hostname::ALLOW_DNS | Hostname::ALLOW_LOCAL); + if (! is_string($email) || empty($email)) { + throw new Exception\InvalidArgumentException('Email must be a valid email address'); + } + + if (preg_match("/[\r\n]/", $email)) { + throw new Exception\InvalidArgumentException('CRLF injection detected'); + } + + if (! $emailAddressValidator->isValid($email)) { + $invalidMessages = $emailAddressValidator->getMessages(); + throw new Exception\InvalidArgumentException(array_shift($invalidMessages)); + } + + if (null !== $name) { + if (! is_string($name)) { + throw new Exception\InvalidArgumentException('Name must be a string'); + } + + if (preg_match("/[\r\n]/", $name)) { + throw new Exception\InvalidArgumentException('CRLF injection detected'); + } + + $this->name = $name; + } + + $this->email = $email; + + if (null !== $comment) { + $this->comment = $comment; + } + } + + /** + * Retrieve email + * + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * Retrieve name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Retrieve comment, if any + * + * @return null|string + */ + public function getComment() + { + return $this->comment; + } + + /** + * String representation of address + * + * @return string + */ + public function toString() + { + $string = sprintf('<%s>', $this->getEmail()); + $name = $this->constructName(); + if (null === $name) { + return $string; + } + + return sprintf('%s %s', $name, $string); + } + + /** + * Constructs the name string + * + * If a comment is present, appends the comment (commented using parens) to + * the name before returning it; otherwise, returns just the name. + * + * @return null|string + */ + private function constructName() + { + $name = $this->getName(); + $comment = $this->getComment(); + + if ($comment === null || $comment === '') { + return $name; + } + + $string = sprintf('%s (%s)', $name, $comment); + return trim($string); + } +} diff --git a/lib/laminas/laminas-mail/src/Address/AddressInterface.php b/lib/laminas/laminas-mail/src/Address/AddressInterface.php new file mode 100644 index 000000000..fe30d7bf0 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Address/AddressInterface.php @@ -0,0 +1,33 @@ +createAddress($emailOrAddress, $name); + } + + if (! $emailOrAddress instanceof Address\AddressInterface) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects an email address or %s\Address object as its first argument; received "%s"', + __METHOD__, + __NAMESPACE__, + (is_object($emailOrAddress) ? get_class($emailOrAddress) : gettype($emailOrAddress)) + )); + } + + $email = strtolower($emailOrAddress->getEmail()); + if ($this->has($email)) { + return $this; + } + + $this->addresses[$email] = $emailOrAddress; + return $this; + } + + /** + * Add many addresses at once + * + * If an email key is provided, it will be used as the email, and the value + * as the name. Otherwise, the value is passed as the sole argument to add(), + * and, as such, can be either email strings or Address\AddressInterface objects. + * + * @param array $addresses + * @throws Exception\RuntimeException + * @return AddressList + */ + public function addMany(array $addresses) + { + foreach ($addresses as $key => $value) { + if (is_int($key) || is_numeric($key)) { + $this->add($value); + continue; + } + + if (! is_string($key)) { + throw new Exception\RuntimeException(sprintf( + 'Invalid key type in provided addresses array ("%s")', + (is_object($key) ? get_class($key) : var_export($key, 1)) + )); + } + + $this->add($key, $value); + } + return $this; + } + + /** + * Add an address to the list from any valid string format, such as + * - "Laminas Dev" + * - dev@laminas.com + * + * @param string $address + * @param null|string $comment Comment associated with the address, if any. + * @throws Exception\InvalidArgumentException + * @return AddressList + */ + public function addFromString($address, $comment = null) + { + $this->add(Address::fromString($address, $comment)); + } + + /** + * Merge another address list into this one + * + * @param AddressList $addressList + * @return AddressList + */ + public function merge(AddressList $addressList) + { + foreach ($addressList as $address) { + $this->add($address); + } + return $this; + } + + /** + * Does the email exist in this list? + * + * @param string $email + * @return bool + */ + public function has($email) + { + $email = strtolower($email); + return isset($this->addresses[$email]); + } + + /** + * Get an address by email + * + * @param string $email + * @return bool|Address\AddressInterface + */ + public function get($email) + { + $email = strtolower($email); + if (! isset($this->addresses[$email])) { + return false; + } + + return $this->addresses[$email]; + } + + /** + * Delete an address from the list + * + * @param string $email + * @return bool + */ + public function delete($email) + { + $email = strtolower($email); + if (! isset($this->addresses[$email])) { + return false; + } + + unset($this->addresses[$email]); + return true; + } + + /** + * Return count of addresses + * + * @return int + */ + public function count() + { + return count($this->addresses); + } + + /** + * Rewind iterator + * + * @return mixed the value of the first addresses element, or false if the addresses is + * empty. + * @see addresses + */ + public function rewind() + { + return reset($this->addresses); + } + + /** + * Return current item in iteration + * + * @return Address + */ + public function current() + { + return current($this->addresses); + } + + /** + * Return key of current item of iteration + * + * @return string + */ + public function key() + { + return key($this->addresses); + } + + /** + * Move to next item + * + * @return mixed the addresses value in the next place that's pointed to by the + * internal array pointer, or false if there are no more elements. + * @see addresses + */ + public function next() + { + return next($this->addresses); + } + + /** + * Is the current item of iteration valid? + * + * @return bool + */ + public function valid() + { + $key = key($this->addresses); + return ($key !== null && $key !== false); + } + + /** + * Create an address object + * + * @param string $email + * @param string|null $name + * @return Address + */ + protected function createAddress($email, $name) + { + return new Address($email, $name); + } +} diff --git a/lib/laminas/laminas-mail/src/ConfigProvider.php b/lib/laminas/laminas-mail/src/ConfigProvider.php new file mode 100644 index 000000000..2e7fa83e5 --- /dev/null +++ b/lib/laminas/laminas-mail/src/ConfigProvider.php @@ -0,0 +1,42 @@ + $this->getDependencyConfig(), + ]; + } + + /** + * Retrieve dependency settings for laminas-mail package. + * + * @return array + */ + public function getDependencyConfig() + { + return [ + // Legacy Zend Framework aliases + 'aliases' => [ + \Zend\Mail\Protocol\SmtpPluginManager::class => Protocol\SmtpPluginManager::class, + ], + 'factories' => [ + Protocol\SmtpPluginManager::class => Protocol\SmtpPluginManagerFactory::class, + ], + ]; + } +} diff --git a/lib/laminas/laminas-mail/src/Exception/BadMethodCallException.php b/lib/laminas/laminas-mail/src/Exception/BadMethodCallException.php new file mode 100644 index 000000000..a37a60073 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Exception/BadMethodCallException.php @@ -0,0 +1,17 @@ +setEncoding('UTF-8'); + } + + /** @var AddressList $addressList */ + $addressList = $header->getAddressList(); + foreach ($addresses as $address) { + $addressList->add($address); + } + + return $header; + } + + public function getFieldName() + { + return $this->fieldName; + } + + /** + * Safely convert UTF-8 encoded domain name to ASCII + * @param string $domainName the UTF-8 encoded email + * @return string + */ + protected function idnToAscii($domainName) + { + if (null === self::$punycode) { + self::$punycode = new Punycode(); + } + try { + return self::$punycode->encode($domainName); + } catch (OutOfBoundsException $e) { + return $domainName; + } + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + $emails = []; + $encoding = $this->getEncoding(); + + foreach ($this->getAddressList() as $address) { + $email = $address->getEmail(); + $name = $address->getName(); + + // quote $name if value requires so + if (! empty($name) && (false !== strpos($name, ',') || false !== strpos($name, ';'))) { + // FIXME: what if name contains double quote? + $name = sprintf('"%s"', $name); + } + + if ($format === HeaderInterface::FORMAT_ENCODED + && 'ASCII' !== $encoding + ) { + if (! empty($name)) { + $name = HeaderWrap::mimeEncodeValue($name, $encoding); + } + + if (preg_match('/^(.+)@([^@]+)$/', $email, $matches)) { + $localPart = $matches[1]; + $hostname = $this->idnToAscii($matches[2]); + $email = sprintf('%s@%s', $localPart, $hostname); + } + } + + if (empty($name)) { + $emails[] = $email; + } else { + $emails[] = sprintf('%s <%s>', $name, $email); + } + } + + // Ensure the values are valid before sending them. + if ($format !== HeaderInterface::FORMAT_RAW) { + foreach ($emails as $email) { + HeaderValue::assertValid($email); + } + } + + return implode(',' . Headers::FOLDING, $emails); + } + + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + public function getEncoding() + { + return $this->encoding; + } + + /** + * Set address list for this header + * + * @param AddressList $addressList + */ + public function setAddressList(AddressList $addressList) + { + $this->addressList = $addressList; + } + + /** + * Get address list managed by this header + * + * @return AddressList + */ + public function getAddressList() + { + if (null === $this->addressList) { + $this->setAddressList(new AddressList()); + } + return $this->addressList; + } + + public function toString() + { + $name = $this->getFieldName(); + $value = $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + return (empty($value)) ? '' : sprintf('%s: %s', $name, $value); + } + + /** + * Retrieve comments from value, if any. + * + * Supposed to be private, protected as a workaround for PHP bug 68194 + * + * @param string $value + * @return string + */ + protected static function getComments($value) + { + $matches = []; + preg_match_all( + '/\\( + (?P( + \\\\.| + [^\\\\)] + )+) + \\)/x', + $value, + $matches + ); + return isset($matches['comment']) ? implode(', ', $matches['comment']) : ''; + } + + /** + * Strip all comments from value, if any. + * + * Supposed to be private, protected as a workaround for PHP bug 68194 + * + * @param string $value + * @return string + */ + protected static function stripComments($value) + { + return preg_replace( + '/\\( + ( + \\\\.| + [^\\\\)] + )+ + \\)/x', + '', + $value + ); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/Bcc.php b/lib/laminas/laminas-mail/src/Header/Bcc.php new file mode 100644 index 000000000..ce93b7588 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/Bcc.php @@ -0,0 +1,22 @@ +setDisposition($parts[0]); + + if (isset($parts[1])) { + $values = ListParser::parse(trim($parts[1]), [';', '=']); + $length = count($values); + $continuedValues = []; + + for ($i = 0; $i < $length; $i += 2) { + $value = $values[$i + 1]; + $value = trim($value, "'\" \t\n\r\0\x0B"); + $name = trim($values[$i], "'\" \t\n\r\0\x0B"); + + if (strpos($name, '*')) { + list($name, $count) = explode('*', $name); + if (! isset($continuedValues[$name])) { + $continuedValues[$name] = []; + } + $continuedValues[$name][$count] = $value; + } else { + $header->setParameter($name, $value); + } + } + + foreach ($continuedValues as $name => $values) { + $value = ''; + for ($i = 0; $i < count($values); $i++) { + if (! isset($values[$i])) { + throw new Exception\InvalidArgumentException( + 'Invalid header line for Content-Disposition string - incomplete continuation' + ); + } + $value .= $values[$i]; + } + $header->setParameter($name, $value); + } + } + + return $header; + } + + /** + * @inheritDoc + */ + public function getFieldName() + { + return 'Content-Disposition'; + } + + /** + * @inheritDoc + */ + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + $result = $this->disposition; + if (empty($this->parameters)) { + return $result; + } + + foreach ($this->parameters as $attribute => $value) { + $valueIsEncoded = false; + if (HeaderInterface::FORMAT_ENCODED === $format && ! Mime::isPrintable($value)) { + $value = $this->getEncodedValue($value); + $valueIsEncoded = true; + } + + $line = sprintf('%s="%s"', $attribute, $value); + + if (strlen($line) < self::MAX_PARAMETER_LENGTH) { + $lines = explode(Headers::FOLDING, $result); + + if (count($lines) === 1) { + $existingLineLength = strlen('Content-Disposition: ' . $result); + } else { + $existingLineLength = 1 + strlen($lines[count($lines) - 1]); + } + + if ((2 + $existingLineLength + strlen($line)) <= self::MAX_PARAMETER_LENGTH) { + $result .= '; ' . $line; + } else { + $result .= ';' . Headers::FOLDING . $line; + } + } else { + // Use 'continuation' per RFC 2231 + $maxValueLength = strlen($value); + do { + $maxValueLength = ceil(0.6 * $maxValueLength); + } while ($maxValueLength > self::MAX_PARAMETER_LENGTH); + + if ($valueIsEncoded) { + $encodedLength = strlen($value); + $value = HeaderWrap::mimeDecodeValue($value); + $decodedLength = strlen($value); + $maxValueLength -= ($encodedLength - $decodedLength); + } + + $valueParts = str_split($value, $maxValueLength); + $i = 0; + foreach ($valueParts as $valuePart) { + $attributePart = $attribute . '*' . $i++; + if ($valueIsEncoded) { + $valuePart = $this->getEncodedValue($valuePart); + } + $result .= sprintf(';%s%s="%s"', Headers::FOLDING, $attributePart, $valuePart); + } + } + } + + return $result; + } + + /** + * @param string $value + * @return string + */ + protected function getEncodedValue($value) + { + $configuredEncoding = $this->encoding; + $this->encoding = 'UTF-8'; + $value = HeaderWrap::wrap($value, $this); + $this->encoding = $configuredEncoding; + return $value; + } + + /** + * @inheritDoc + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * @inheritDoc + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @inheritDoc + */ + public function toString() + { + return 'Content-Disposition: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + /** + * Set the content disposition + * Expected values include 'inline', 'attachment' + * + * @param string $disposition + * @return ContentDisposition + */ + public function setDisposition($disposition) + { + $this->disposition = strtolower($disposition); + return $this; + } + + /** + * Retrieve the content disposition + * + * @return string + */ + public function getDisposition() + { + return $this->disposition; + } + + /** + * Add a parameter pair + * + * @param string $name + * @param string $value + * @return ContentDisposition + */ + public function setParameter($name, $value) + { + $name = strtolower($name); + + if (! HeaderValue::isValid($name)) { + throw new Exception\InvalidArgumentException( + 'Invalid content-disposition parameter name detected' + ); + } + // '5' here is for the quotes & equal sign in `name="value"`, + // and the space & semicolon for line folding + if ((strlen($name) + 5) >= self::MAX_PARAMETER_LENGTH) { + throw new Exception\InvalidArgumentException( + 'Invalid content-disposition parameter name detected (too long)' + ); + } + + $this->parameters[$name] = $value; + return $this; + } + + /** + * Get all parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get a parameter by name + * + * @param string $name + * @return null|string + */ + public function getParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + return $this->parameters[$name]; + } + return null; + } + + /** + * Remove a named parameter + * + * @param string $name + * @return bool + */ + public function removeParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + unset($this->parameters[$name]); + return true; + } + return false; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/ContentTransferEncoding.php b/lib/laminas/laminas-mail/src/Header/ContentTransferEncoding.php new file mode 100644 index 000000000..73da55aa3 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/ContentTransferEncoding.php @@ -0,0 +1,114 @@ +setTransferEncoding($value); + + return $header; + } + + public function getFieldName() + { + return 'Content-Transfer-Encoding'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->transferEncoding; + } + + public function setEncoding($encoding) + { + // Header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'Content-Transfer-Encoding: ' . $this->getFieldValue(); + } + + /** + * Set the content transfer encoding + * + * @param string $transferEncoding + * @throws Exception\InvalidArgumentException + * @return $this + */ + public function setTransferEncoding($transferEncoding) + { + // Per RFC 1521, the value of the header is not case sensitive + $transferEncoding = strtolower($transferEncoding); + + if (! in_array($transferEncoding, static::$allowedTransferEncodings)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects one of "'. implode(', ', static::$allowedTransferEncodings) . '"; received "%s"', + __METHOD__, + (string) $transferEncoding + )); + } + $this->transferEncoding = $transferEncoding; + return $this; + } + + /** + * Retrieve the content transfer encoding + * + * @return string + */ + public function getTransferEncoding() + { + return $this->transferEncoding; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/ContentType.php b/lib/laminas/laminas-mail/src/Header/ContentType.php new file mode 100644 index 000000000..3b57df460 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/ContentType.php @@ -0,0 +1,202 @@ +setType($parts[0]); + + if (isset($parts[1])) { + $values = ListParser::parse(trim($parts[1]), [';', '=']); + $length = count($values); + + for ($i = 0; $i < $length; $i += 2) { + $value = $values[$i + 1]; + $value = trim($value, "'\" \t\n\r\0\x0B"); + $header->addParameter($values[$i], $value); + } + } + + return $header; + } + + public function getFieldName() + { + return 'Content-Type'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + $prepared = $this->type; + if (empty($this->parameters)) { + return $prepared; + } + + $values = [$prepared]; + foreach ($this->parameters as $attribute => $value) { + if (HeaderInterface::FORMAT_ENCODED === $format && ! Mime::isPrintable($value)) { + $this->encoding = 'UTF-8'; + $value = HeaderWrap::wrap($value, $this); + $this->encoding = 'ASCII'; + } + + $values[] = sprintf('%s="%s"', $attribute, $value); + } + + return implode(';' . Headers::FOLDING, $values); + } + + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + public function getEncoding() + { + return $this->encoding; + } + + public function toString() + { + return 'Content-Type: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + /** + * Set the content type + * + * @param string $type + * @throws Exception\InvalidArgumentException + * @return ContentType + */ + public function setType($type) + { + if (! preg_match('/^[a-z-]+\/[a-z0-9.+-]+$/i', $type)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a value in the format "type/subtype"; received "%s"', + __METHOD__, + (string) $type + )); + } + $this->type = $type; + return $this; + } + + /** + * Retrieve the content type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Add a parameter pair + * + * @param string $name + * @param string $value + * @return ContentType + * @throws Exception\InvalidArgumentException for parameter names that do not follow RFC 2822 + * @throws Exception\InvalidArgumentException for parameter values that do not follow RFC 2822 + */ + public function addParameter($name, $value) + { + $name = trim(strtolower($name)); + $value = (string) $value; + + if (! HeaderValue::isValid($name)) { + throw new Exception\InvalidArgumentException('Invalid content-type parameter name detected'); + } + if (! HeaderWrap::canBeEncoded($value)) { + throw new Exception\InvalidArgumentException( + 'Parameter value must be composed of printable US-ASCII or UTF-8 characters.' + ); + } + + $this->parameters[$name] = $value; + return $this; + } + + /** + * Get all parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get a parameter by name + * + * @param string $name + * @return null|string + */ + public function getParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + return $this->parameters[$name]; + } + return; + } + + /** + * Remove a named parameter + * + * @param string $name + * @return bool + */ + public function removeParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + unset($this->parameters[$name]); + return true; + } + return false; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/Date.php b/lib/laminas/laminas-mail/src/Header/Date.php new file mode 100644 index 000000000..7032c3f8e --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/Date.php @@ -0,0 +1,69 @@ +value = $value; + } + + public function getFieldName() + { + return 'Date'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->value; + } + + public function setEncoding($encoding) + { + // This header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'Date: ' . $this->getFieldValue(); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php b/lib/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php new file mode 100644 index 000000000..0b3f219ff --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php @@ -0,0 +1,15 @@ +setFieldName($fieldName); + } + + if ($fieldValue !== null) { + $this->setFieldValue($fieldValue); + } + } + + /** + * Set header name + * + * @param string $fieldName + * @return GenericHeader + * @throws Exception\InvalidArgumentException; + */ + public function setFieldName($fieldName) + { + if (! is_string($fieldName) || empty($fieldName)) { + throw new Exception\InvalidArgumentException('Header name must be a string'); + } + + // Pre-filter to normalize valid characters, change underscore to dash + $fieldName = str_replace(' ', '-', ucwords(str_replace(['_', '-'], ' ', $fieldName))); + + if (! HeaderName::isValid($fieldName)) { + throw new Exception\InvalidArgumentException( + 'Header name must be composed of printable US-ASCII characters, except colon.' + ); + } + + $this->fieldName = $fieldName; + return $this; + } + + public function getFieldName() + { + return $this->fieldName; + } + + /** + * Set header value + * + * @param string $fieldValue + * @return GenericHeader + * @throws Exception\InvalidArgumentException; + */ + public function setFieldValue($fieldValue) + { + $fieldValue = (string) $fieldValue; + + if (! HeaderWrap::canBeEncoded($fieldValue)) { + throw new Exception\InvalidArgumentException( + 'Header value must be composed of printable US-ASCII characters and valid folding sequences.' + ); + } + + $this->fieldValue = $fieldValue; + $this->encoding = null; + + return $this; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + if (HeaderInterface::FORMAT_ENCODED === $format) { + return HeaderWrap::wrap($this->fieldValue, $this); + } + + return $this->fieldValue; + } + + public function setEncoding($encoding) + { + if ($encoding === $this->encoding) { + return $this; + } + + if ($encoding === null) { + $this->encoding = null; + return $this; + } + + $encoding = strtoupper($encoding); + if ($encoding === 'UTF-8') { + $this->encoding = $encoding; + return $this; + } + + if ($encoding === 'ASCII' && Mime::isPrintable($this->fieldValue)) { + $this->encoding = $encoding; + return $this; + } + + $this->encoding = null; + + return $this; + } + + public function getEncoding() + { + if (! $this->encoding) { + $this->encoding = Mime::isPrintable($this->fieldValue) ? 'ASCII' : 'UTF-8'; + } + + return $this->encoding; + } + + public function toString() + { + $name = $this->getFieldName(); + if (empty($name)) { + throw new Exception\RuntimeException('Header name is not set, use setFieldName()'); + } + $value = $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + + return $name . ': ' . $value; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/GenericMultiHeader.php b/lib/laminas/laminas-mail/src/Header/GenericMultiHeader.php new file mode 100644 index 000000000..583c3082b --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/GenericMultiHeader.php @@ -0,0 +1,55 @@ +getFieldName(); + $values = [$this->getFieldValue(HeaderInterface::FORMAT_ENCODED)]; + + foreach ($headers as $header) { + if (! $header instanceof static) { + throw new Exception\InvalidArgumentException( + 'This method toStringMultipleHeaders was expecting an array of headers of the same type' + ); + } + $values[] = $header->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + return $name . ': ' . implode(',', $values); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderInterface.php b/lib/laminas/laminas-mail/src/Header/HeaderInterface.php new file mode 100644 index 000000000..3aa74fab3 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderInterface.php @@ -0,0 +1,75 @@ + Bcc::class, + 'cc' => Cc::class, + 'contentdisposition' => ContentDisposition::class, + 'content_disposition' => ContentDisposition::class, + 'content-disposition' => ContentDisposition::class, + 'contenttype' => ContentType::class, + 'content_type' => ContentType::class, + 'content-type' => ContentType::class, + 'contenttransferencoding' => ContentTransferEncoding::class, + 'content_transfer_encoding' => ContentTransferEncoding::class, + 'content-transfer-encoding' => ContentTransferEncoding::class, + 'date' => Date::class, + 'from' => From::class, + 'in-reply-to' => InReplyTo::class, + 'message-id' => MessageId::class, + 'mimeversion' => MimeVersion::class, + 'mime_version' => MimeVersion::class, + 'mime-version' => MimeVersion::class, + 'received' => Received::class, + 'references' => References::class, + 'replyto' => ReplyTo::class, + 'reply_to' => ReplyTo::class, + 'reply-to' => ReplyTo::class, + 'sender' => Sender::class, + 'subject' => Subject::class, + 'to' => To::class, + ]; +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderName.php b/lib/laminas/laminas-mail/src/Header/HeaderName.php new file mode 100644 index 000000000..1ce01e49d --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderName.php @@ -0,0 +1,73 @@ + 32 && $ord < 127 && $ord !== 58) { + $result .= $name[$i]; + } + } + return $result; + } + + /** + * Determine if the header name contains any invalid characters. + * + * @param string $name + * @return bool + */ + public static function isValid($name) + { + $tot = strlen($name); + for ($i = 0; $i < $tot; $i += 1) { + $ord = ord($name[$i]); + if ($ord < 33 || $ord > 126 || $ord === 58) { + return false; + } + } + return true; + } + + /** + * Assert that the header name is valid. + * + * Raises an exception if invalid. + * + * @param string $name + * @throws Exception\RuntimeException + * @return void + */ + public static function assertValid($name) + { + if (! self::isValid($name)) { + throw new Exception\RuntimeException('Invalid header name detected'); + } + } +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderValue.php b/lib/laminas/laminas-mail/src/Header/HeaderValue.php new file mode 100644 index 000000000..8bfbb0c6c --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderValue.php @@ -0,0 +1,116 @@ + 127) { + continue; + } + + if ($ord === 13) { + if ($i + 2 >= $total) { + continue; + } + + $lf = ord($value[$i + 1]); + $sp = ord($value[$i + 2]); + + if ($lf !== 10 || $sp !== 32) { + continue; + } + + $result .= "\r\n "; + $i += 2; + continue; + } + + $result .= $value[$i]; + } + + return $result; + } + + /** + * Determine if the header value contains any invalid characters. + * + * @see http://www.rfc-base.org/txt/rfc-2822.txt (section 2.2) + * @param string $value + * @return bool + */ + public static function isValid($value) + { + $total = strlen($value); + for ($i = 0; $i < $total; $i += 1) { + $ord = ord($value[$i]); + + // bare LF means we aren't valid + if ($ord === 10 || $ord > 127) { + return false; + } + + if ($ord === 13) { + if ($i + 2 >= $total) { + return false; + } + + $lf = ord($value[$i + 1]); + $sp = ord($value[$i + 2]); + + if ($lf !== 10 || ! in_array($sp, [9, 32], true)) { + return false; + } + + // skip over the LF following this + $i += 2; + } + } + + return true; + } + + /** + * Assert that the header value is valid. + * + * Raises an exception if invalid. + * + * @param string $value + * @throws Exception\RuntimeException + * @return void + */ + public static function assertValid($value) + { + if (! self::isValid($value)) { + throw new Exception\RuntimeException('Invalid header value detected'); + } + } +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderWrap.php b/lib/laminas/laminas-mail/src/Header/HeaderWrap.php new file mode 100644 index 000000000..13c255bf7 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderWrap.php @@ -0,0 +1,161 @@ +getEncoding(); + if ($encoding == 'ASCII') { + return wordwrap($value, 78, Headers::FOLDING); + } + return static::mimeEncodeValue($value, $encoding, 78); + } + + /** + * Wrap a structured header line + * + * @param string $value + * @param StructuredInterface $header + * @return string + */ + protected static function wrapStructuredHeader($value, StructuredInterface $header) + { + $delimiter = $header->getDelimiter(); + + $length = strlen($value); + $lines = []; + $temp = ''; + for ($i = 0; $i < $length; $i++) { + $temp .= $value[$i]; + if ($value[$i] == $delimiter) { + $lines[] = $temp; + $temp = ''; + } + } + return implode(Headers::FOLDING, $lines); + } + + /** + * MIME-encode a value + * + * Performs quoted-printable encoding on a value, setting maximum + * line-length to 998. + * + * @param string $value + * @param string $encoding + * @param int $lineLength maximum line-length, by default 998 + * @return string Returns the mime encode value without the last line ending + */ + public static function mimeEncodeValue($value, $encoding, $lineLength = 998) + { + return Mime::encodeQuotedPrintableHeader($value, $encoding, $lineLength, Headers::EOL); + } + + /** + * MIME-decode a value + * + * Performs quoted-printable decoding on a value. + * + * @param string $value + * @return string Returns the mime encode value without the last line ending + */ + public static function mimeDecodeValue($value) + { + // unfold first, because iconv_mime_decode is discarding "\n" with no apparent reason + // making the resulting value no longer valid. + + // see https://tools.ietf.org/html/rfc2822#section-2.2.3 about unfolding + $parts = explode(Headers::FOLDING, $value); + $value = implode(' ', $parts); + + $decodedValue = iconv_mime_decode($value, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8'); + + // imap (unlike iconv) can handle multibyte headers which are splitted across multiple line + if (self::isNotDecoded($value, $decodedValue) && extension_loaded('imap')) { + return array_reduce( + imap_mime_header_decode(imap_utf8($value)), + function ($accumulator, $headerPart) { + return $accumulator . $headerPart->text; + }, + '' + ); + } + + return $decodedValue; + } + + private static function isNotDecoded($originalValue, $value) + { + return 0 === strpos($value, '=?') + && strlen($value) - 2 === strpos($value, '?=') + && false !== strpos($originalValue, $value); + } + + /** + * Test if is possible apply MIME-encoding + * + * @param string $value + * @return bool + */ + public static function canBeEncoded($value) + { + // avoid any wrapping by specifying line length long enough + // "test" -> 4 + // "x-test: =?ISO-8859-1?B?dGVzdA==?=" -> 33 + // 8 +2 +3 +3 -> 16 + $charset = 'UTF-8'; + $lineLength = strlen($value) * 4 + strlen($charset) + 16; + + $preferences = [ + 'scheme' => 'Q', + 'input-charset' => $charset, + 'output-charset' => $charset, + 'line-length' => $lineLength, + ]; + + $encoded = iconv_mime_encode('x-test', $value, $preferences); + + return (false !== $encoded); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/IdentificationField.php b/lib/laminas/laminas-mail/src/Header/IdentificationField.php new file mode 100644 index 000000000..0d00b4a53 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/IdentificationField.php @@ -0,0 +1,143 @@ +setIds($messageIds); + + return $header; + } + + /** + * @param string $id + * @return string + */ + private static function trimMessageId($id) + { + return trim($id, "\t\n\r\0\x0B<>"); + } + + /** + * @return string + */ + public function getFieldName() + { + return $this->fieldName; + } + + /** + * @param bool $format + * @return string + */ + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return implode(Headers::FOLDING, array_map(function ($id) { + return sprintf('<%s>', $id); + }, $this->messageIds)); + } + + /** + * @param string $encoding Ignored; headers of this type MUST always be in + * ASCII. + * @return static This method is a no-op, and implements a fluent interface. + */ + public function setEncoding($encoding) + { + return $this; + } + + /** + * @return string Always returns ASCII + */ + public function getEncoding() + { + return 'ASCII'; + } + + /** + * @return string + */ + public function toString() + { + return sprintf('%s: %s', $this->getFieldName(), $this->getFieldValue()); + } + + /** + * Set the message ids + * + * @param string[] $ids + * @return static This method implements a fluent interface. + */ + public function setIds($ids) + { + foreach ($ids as $id) { + if (! HeaderValue::isValid($id) + || preg_match("/[\r\n]/", $id) + ) { + throw new Exception\InvalidArgumentException('Invalid ID detected'); + } + } + + $this->messageIds = array_map([IdentificationField::class, "trimMessageId"], $ids); + return $this; + } + + /** + * Retrieve the message ids + * + * @return string[] + */ + public function getIds() + { + return $this->messageIds; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/InReplyTo.php b/lib/laminas/laminas-mail/src/Header/InReplyTo.php new file mode 100644 index 000000000..125295de0 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/InReplyTo.php @@ -0,0 +1,15 @@ +setId($value); + + return $header; + } + + public function getFieldName() + { + return 'Message-ID'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->messageId; + } + + public function setEncoding($encoding) + { + // This header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'Message-ID: ' . $this->getFieldValue(); + } + + /** + * Set the message id + * + * @param string|null $id + * @return MessageId + */ + public function setId($id = null) + { + if ($id === null) { + $id = $this->createMessageId(); + } else { + $id = trim($id, '<>'); + } + + if (! HeaderValue::isValid($id) + || preg_match("/[\r\n]/", $id) + ) { + throw new Exception\InvalidArgumentException('Invalid ID detected'); + } + + $this->messageId = sprintf('<%s>', $id); + return $this; + } + + /** + * Retrieve the message id + * + * @return string + */ + public function getId() + { + return $this->messageId; + } + + /** + * Creates the Message-ID + * + * @return string + */ + public function createMessageId() + { + $time = time(); + + if (isset($_SERVER['REMOTE_ADDR'])) { + $user = $_SERVER['REMOTE_ADDR']; + } else { + $user = getmypid(); + } + + $rand = mt_rand(); + + if (isset($_SERVER["SERVER_NAME"])) { + $hostName = $_SERVER["SERVER_NAME"]; + } else { + $hostName = php_uname('n'); + } + + return sha1($time . $user . $rand) . '@' . $hostName; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/MimeVersion.php b/lib/laminas/laminas-mail/src/Header/MimeVersion.php new file mode 100644 index 000000000..02bdac8d0 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/MimeVersion.php @@ -0,0 +1,87 @@ +\d+\.\d+)$/', $value, $matches)) { + $header->setVersion($matches['version']); + } + + return $header; + } + + public function getFieldName() + { + return 'MIME-Version'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->version; + } + + public function setEncoding($encoding) + { + // This header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'MIME-Version: ' . $this->getFieldValue(); + } + + /** + * Set the version string used in this header + * + * @param string $version + * @return MimeVersion + */ + public function setVersion($version) + { + if (! preg_match('/^[1-9]\d*\.\d+$/', $version)) { + throw new Exception\InvalidArgumentException('Invalid MIME-Version value detected'); + } + $this->version = $version; + return $this; + } + + /** + * Retrieve the version string for this header + * + * @return string + */ + public function getVersion() + { + return $this->version; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php b/lib/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php new file mode 100644 index 000000000..efbc804e5 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php @@ -0,0 +1,14 @@ +value = $value; + } + + public function getFieldName() + { + return 'Received'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->value; + } + + public function setEncoding($encoding) + { + // This header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'Received: ' . $this->getFieldValue(); + } + + /** + * Serialize collection of Received headers to string + * + * @param array $headers + * @throws Exception\RuntimeException + * @return string + */ + public function toStringMultipleHeaders(array $headers) + { + $strings = [$this->toString()]; + foreach ($headers as $header) { + if (! $header instanceof Received) { + throw new Exception\RuntimeException( + 'The Received multiple header implementation can only accept an array of Received headers' + ); + } + $strings[] = $header->toString(); + } + return implode(Headers::EOL, $strings); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/References.php b/lib/laminas/laminas-mail/src/Header/References.php new file mode 100644 index 000000000..c13559626 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/References.php @@ -0,0 +1,15 @@ + when a name is present + * 'name' and 'email' capture groups correspond respectively to 'display-name' and 'addr-spec' in the ABNF + * @see https://tools.ietf.org/html/rfc5322#section-3.4 + */ + $hasMatches = preg_match( + '/^(?:(?P.+)\s)?(?(name)<|[^\s]+?)(?(name)>|>?)$/', + $value, + $matches + ); + + if ($hasMatches !== 1) { + throw new Exception\InvalidArgumentException('Invalid header value for Sender string'); + } + + $senderName = trim($matches['name']); + + if (empty($senderName)) { + $senderName = null; + } + + $header->setAddress($matches['email'], $senderName); + + return $header; + } + + public function getFieldName() + { + return 'Sender'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + if (! $this->address instanceof Mail\Address\AddressInterface) { + return ''; + } + + $email = sprintf('<%s>', $this->address->getEmail()); + $name = $this->address->getName(); + + if (! empty($name)) { + if ($format == HeaderInterface::FORMAT_ENCODED) { + $encoding = $this->getEncoding(); + if ('ASCII' !== $encoding) { + $name = HeaderWrap::mimeEncodeValue($name, $encoding); + } + } + $email = sprintf('%s %s', $name, $email); + } + + return $email; + } + + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + public function getEncoding() + { + if (! $this->encoding) { + $this->encoding = Mime::isPrintable($this->getFieldValue(HeaderInterface::FORMAT_RAW)) + ? 'ASCII' + : 'UTF-8'; + } + + return $this->encoding; + } + + public function toString() + { + return 'Sender: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + /** + * Set the address used in this header + * + * @param string|\Laminas\Mail\Address\AddressInterface $emailOrAddress + * @param null|string $name + * @throws Exception\InvalidArgumentException + * @return Sender + */ + public function setAddress($emailOrAddress, $name = null) + { + if (is_string($emailOrAddress)) { + $emailOrAddress = new Mail\Address($emailOrAddress, $name); + } elseif (! $emailOrAddress instanceof Mail\Address\AddressInterface) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string or AddressInterface object; received "%s"', + __METHOD__, + (is_object($emailOrAddress) ? get_class($emailOrAddress) : gettype($emailOrAddress)) + )); + } + $this->address = $emailOrAddress; + return $this; + } + + /** + * Retrieve the internal address from this header + * + * @return \Laminas\Mail\Address\AddressInterface|null + */ + public function getAddress() + { + return $this->address; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/StructuredInterface.php b/lib/laminas/laminas-mail/src/Header/StructuredInterface.php new file mode 100644 index 000000000..396f54745 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/StructuredInterface.php @@ -0,0 +1,19 @@ +setSubject($value); + + return $header; + } + + public function getFieldName() + { + return 'Subject'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + if (HeaderInterface::FORMAT_ENCODED === $format) { + return HeaderWrap::wrap($this->subject, $this); + } + + return $this->subject; + } + + public function setEncoding($encoding) + { + if ($encoding === $this->encoding) { + return $this; + } + + if ($encoding === null) { + $this->encoding = null; + return $this; + } + + $encoding = strtoupper($encoding); + if ($encoding === 'UTF-8') { + $this->encoding = $encoding; + return $this; + } + + if ($encoding === 'ASCII' && Mime::isPrintable($this->subject)) { + $this->encoding = $encoding; + return $this; + } + + $this->encoding = null; + + return $this; + } + + public function getEncoding() + { + if (! $this->encoding) { + $this->encoding = Mime::isPrintable($this->subject) ? 'ASCII' : 'UTF-8'; + } + + return $this->encoding; + } + + public function setSubject($subject) + { + $subject = (string) $subject; + + if (! HeaderWrap::canBeEncoded($subject)) { + throw new Exception\InvalidArgumentException( + 'Subject value must be composed of printable US-ASCII or UTF-8 characters.' + ); + } + + $this->subject = $subject; + $this->encoding = null; + + return $this; + } + + public function toString() + { + return 'Subject: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/To.php b/lib/laminas/laminas-mail/src/Header/To.php new file mode 100644 index 000000000..ea9655ee3 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/To.php @@ -0,0 +1,15 @@ + 2) { + throw new Exception\RuntimeException('Malformed header detected'); + } + continue; + } + + if ($emptyLine > 1) { + throw new Exception\RuntimeException('Malformed header detected'); + } + + // check if a header name is present + if (preg_match('/^[\x21-\x39\x3B-\x7E]+:.*$/', $line)) { + if ($currentLine) { + // a header name was present, then store the current complete line + $headers->addHeaderLine($currentLine); + } + $currentLine = trim($line); + continue; + } + + // continuation: append to current line + // recover the whitespace that break the line (unfolding, rfc2822#section-2.2.3) + if (preg_match('/^\s+.*$/', $line)) { + $currentLine .= ' ' . trim($line); + continue; + } + + // Line does not match header format! + throw new Exception\RuntimeException(sprintf( + 'Line "%s" does not match header format!', + $line + )); + } + if ($currentLine) { + $headers->addHeaderLine($currentLine); + } + return $headers; + } + + /** + * Set an alternate implementation for the PluginClassLoader + * + * @param PluginClassLocator $pluginClassLoader + * @return Headers + */ + public function setPluginClassLoader(PluginClassLocator $pluginClassLoader) + { + $this->pluginClassLoader = $pluginClassLoader; + return $this; + } + + /** + * Return an instance of a PluginClassLocator, lazyload and inject map if necessary + * + * @return PluginClassLocator + */ + public function getPluginClassLoader() + { + if ($this->pluginClassLoader === null) { + $this->pluginClassLoader = new Header\HeaderLoader(); + } + return $this->pluginClassLoader; + } + + /** + * Set the header encoding + * + * @param string $encoding + * @return Headers + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + foreach ($this as $header) { + $header->setEncoding($encoding); + } + return $this; + } + + /** + * Get the header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Add many headers at once + * + * Expects an array (or Traversable object) of type/value pairs. + * + * @param array|Traversable $headers + * @throws Exception\InvalidArgumentException + * @return Headers + */ + public function addHeaders($headers) + { + if (! is_array($headers) && ! $headers instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + 'Expected array or Traversable; received "%s"', + (is_object($headers) ? get_class($headers) : gettype($headers)) + )); + } + + foreach ($headers as $name => $value) { + if (is_int($name)) { + if (is_string($value)) { + $this->addHeaderLine($value); + } elseif (is_array($value) && count($value) == 1) { + $this->addHeaderLine(key($value), current($value)); + } elseif (is_array($value) && count($value) == 2) { + $this->addHeaderLine($value[0], $value[1]); + } elseif ($value instanceof Header\HeaderInterface) { + $this->addHeader($value); + } + } elseif (is_string($name)) { + $this->addHeaderLine($name, $value); + } + } + + return $this; + } + + /** + * Add a raw header line, either in name => value, or as a single string 'name: value' + * + * This method allows for lazy-loading in that the parsing and instantiation of HeaderInterface object + * will be delayed until they are retrieved by either get() or current() + * + * @throws Exception\InvalidArgumentException + * @param string $headerFieldNameOrLine + * @param string $fieldValue optional + * @return Headers + */ + public function addHeaderLine($headerFieldNameOrLine, $fieldValue = null) + { + if (! is_string($headerFieldNameOrLine)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects its first argument to be a string; received "%s"', + __METHOD__, + (is_object($headerFieldNameOrLine) + ? get_class($headerFieldNameOrLine) + : gettype($headerFieldNameOrLine)) + )); + } + + if ($fieldValue === null) { + $headers = $this->loadHeader($headerFieldNameOrLine); + $headers = is_array($headers) ? $headers : [$headers]; + foreach ($headers as $header) { + $this->addHeader($header); + } + } elseif (is_array($fieldValue)) { + foreach ($fieldValue as $i) { + $this->addHeader(Header\GenericMultiHeader::fromString($headerFieldNameOrLine . ':' . $i)); + } + } else { + $this->addHeader(Header\GenericHeader::fromString($headerFieldNameOrLine . ':' . $fieldValue)); + } + + return $this; + } + + /** + * Add a Header\Interface to this container, for raw values see {@link addHeaderLine()} and {@link addHeaders()} + * + * @param Header\HeaderInterface $header + * @return Headers + */ + public function addHeader(Header\HeaderInterface $header) + { + $key = $this->normalizeFieldName($header->getFieldName()); + $this->headersKeys[] = $key; + $this->headers[] = $header; + if ($this->getEncoding() !== 'ASCII') { + $header->setEncoding($this->getEncoding()); + } + return $this; + } + + /** + * Remove a Header from the container + * + * @param string|Header\HeaderInterface field name or specific header instance to remove + * @return bool + */ + public function removeHeader($instanceOrFieldName) + { + if ($instanceOrFieldName instanceof Header\HeaderInterface) { + $indexes = array_keys($this->headers, $instanceOrFieldName, true); + } else { + $key = $this->normalizeFieldName($instanceOrFieldName); + $indexes = array_keys($this->headersKeys, $key, true); + } + + if (! empty($indexes)) { + foreach ($indexes as $index) { + unset($this->headersKeys[$index]); + unset($this->headers[$index]); + } + return true; + } + + return false; + } + + /** + * Clear all headers + * + * Removes all headers from queue + * + * @return Headers + */ + public function clearHeaders() + { + $this->headers = $this->headersKeys = []; + return $this; + } + + /** + * Get all headers of a certain name/type + * + * @param string $name + * @return bool|ArrayIterator|Header\HeaderInterface Returns false if there is no headers with $name in this + * contain, an ArrayIterator if the header is a MultipleHeadersInterface instance and finally returns + * HeaderInterface for the rest of cases. + */ + public function get($name) + { + $key = $this->normalizeFieldName($name); + $results = []; + + foreach (array_keys($this->headersKeys, $key) as $index) { + if ($this->headers[$index] instanceof Header\GenericHeader) { + $results[] = $this->lazyLoadHeader($index); + } else { + $results[] = $this->headers[$index]; + } + } + + switch (count($results)) { + case 0: + return false; + case 1: + if ($results[0] instanceof Header\MultipleHeadersInterface) { + return new ArrayIterator($results); + } else { + return $results[0]; + } + //fall-trough + default: + return new ArrayIterator($results); + } + } + + /** + * Test for existence of a type of header + * + * @param string $name + * @return bool + */ + public function has($name) + { + $name = $this->normalizeFieldName($name); + return in_array($name, $this->headersKeys); + } + + /** + * Advance the pointer for this object as an iterator + * + */ + public function next() + { + next($this->headers); + } + + /** + * Return the current key for this object as an iterator + * + * @return mixed + */ + public function key() + { + return key($this->headers); + } + + /** + * Is this iterator still valid? + * + * @return bool + */ + public function valid() + { + return (current($this->headers) !== false); + } + + /** + * Reset the internal pointer for this object as an iterator + * + */ + public function rewind() + { + reset($this->headers); + } + + /** + * Return the current value for this iterator, lazy loading it if need be + * + * @return Header\HeaderInterface + */ + public function current() + { + $current = current($this->headers); + if ($current instanceof Header\GenericHeader) { + $current = $this->lazyLoadHeader(key($this->headers)); + } + return $current; + } + + /** + * Return the number of headers in this contain, if all headers have not been parsed, actual count could + * increase if MultipleHeader objects exist in the Request/Response. If you need an exact count, iterate + * + * @return int count of currently known headers + */ + public function count() + { + return count($this->headers); + } + + /** + * Render all headers at once + * + * This method handles the normal iteration of headers; it is up to the + * concrete classes to prepend with the appropriate status/request line. + * + * @return string + */ + public function toString() + { + $headers = ''; + foreach ($this as $header) { + if ($str = $header->toString()) { + $headers .= $str . self::EOL; + } + } + + return $headers; + } + + /** + * Return the headers container as an array + * + * @param bool $format Return the values in Mime::Encoded or in Raw format + * @return array + * @todo determine how to produce single line headers, if they are supported + */ + public function toArray($format = Header\HeaderInterface::FORMAT_RAW) + { + $headers = []; + /* @var $header Header\HeaderInterface */ + foreach ($this->headers as $header) { + if ($header instanceof Header\MultipleHeadersInterface) { + $name = $header->getFieldName(); + if (! isset($headers[$name])) { + $headers[$name] = []; + } + $headers[$name][] = $header->getFieldValue($format); + } else { + $headers[$header->getFieldName()] = $header->getFieldValue($format); + } + } + return $headers; + } + + /** + * By calling this, it will force parsing and loading of all headers, after this count() will be accurate + * + * @return bool + */ + public function forceLoading() + { + foreach ($this as $item) { + // $item should now be loaded + } + return true; + } + + /** + * Create Header object from header line + * + * @param string $headerLine + * @return Header\HeaderInterface|Header\HeaderInterface[] + */ + public function loadHeader($headerLine) + { + list($name, ) = Header\GenericHeader::splitHeaderLine($headerLine); + + /** @var HeaderInterface $class */ + $class = $this->getPluginClassLoader()->load($name) ?: Header\GenericHeader::class; + return $class::fromString($headerLine); + } + + /** + * @param $index + * @return mixed + */ + protected function lazyLoadHeader($index) + { + $current = $this->headers[$index]; + + $key = $this->headersKeys[$index]; + + /** @var GenericHeader $class */ + $class = ($this->getPluginClassLoader()->load($key)) ?: Header\GenericHeader::class; + + $encoding = $current->getEncoding(); + $headers = $class::fromString($current->toString()); + if (is_array($headers)) { + $current = array_shift($headers); + $current->setEncoding($encoding); + $this->headers[$index] = $current; + foreach ($headers as $header) { + $header->setEncoding($encoding); + $this->headersKeys[] = $key; + $this->headers[] = $header; + } + return $current; + } + + $current = $headers; + $current->setEncoding($encoding); + $this->headers[$index] = $current; + return $current; + } + + /** + * Normalize a field name + * + * @param string $fieldName + * @return string + */ + protected function normalizeFieldName($fieldName) + { + return str_replace(['-', '_', ' ', '.'], '', strtolower($fieldName)); + } +} diff --git a/lib/laminas/laminas-mail/src/Message.php b/lib/laminas/laminas-mail/src/Message.php new file mode 100644 index 000000000..c6526dd14 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Message.php @@ -0,0 +1,576 @@ +getFrom(); + if (! $from instanceof AddressList) { + return false; + } + return (bool) count($from); + } + + /** + * Set the message encoding + * + * @param string $encoding + * @return Message + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + $this->getHeaders()->setEncoding($encoding); + return $this; + } + + /** + * Get the message encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Compose headers + * + * @param Headers $headers + * @return Message + */ + public function setHeaders(Headers $headers) + { + $this->headers = $headers; + $headers->setEncoding($this->getEncoding()); + return $this; + } + + /** + * Access headers collection + * + * Lazy-loads if not already attached. + * + * @return Headers + */ + public function getHeaders() + { + if (null === $this->headers) { + $this->setHeaders(new Headers()); + $date = Header\Date::fromString('Date: ' . date('r')); + $this->headers->addHeader($date); + } + return $this->headers; + } + + /** + * Set (overwrite) From addresses + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param string|null $name + * @return Message + */ + public function setFrom($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('from'); + return $this->addFrom($emailOrAddressList, $name); + } + + /** + * Add a "From" address + * + * @param string|Address|array|AddressList|Traversable $emailOrAddressOrList + * @param string|null $name + * @return Message + */ + public function addFrom($emailOrAddressOrList, $name = null) + { + $addressList = $this->getFrom(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Retrieve list of From senders + * + * @return AddressList + */ + public function getFrom() + { + return $this->getAddressListFromHeader('from', __NAMESPACE__ . '\Header\From'); + } + + /** + * Overwrite the address list in the To recipients + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param null|string $name + * @return Message + */ + public function setTo($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('to'); + return $this->addTo($emailOrAddressList, $name); + } + + /** + * Add one or more addresses to the To recipients + * + * Appends to the list. + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressOrList + * @param null|string $name + * @return Message + */ + public function addTo($emailOrAddressOrList, $name = null) + { + $addressList = $this->getTo(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Access the address list of the To header + * + * @return AddressList + */ + public function getTo() + { + return $this->getAddressListFromHeader('to', __NAMESPACE__ . '\Header\To'); + } + + /** + * Set (overwrite) CC addresses + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param string|null $name + * @return Message + */ + public function setCc($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('cc'); + return $this->addCc($emailOrAddressList, $name); + } + + /** + * Add a "Cc" address + * + * @param string|Address|array|AddressList|Traversable $emailOrAddressOrList + * @param string|null $name + * @return Message + */ + public function addCc($emailOrAddressOrList, $name = null) + { + $addressList = $this->getCc(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Retrieve list of CC recipients + * + * @return AddressList + */ + public function getCc() + { + return $this->getAddressListFromHeader('cc', __NAMESPACE__ . '\Header\Cc'); + } + + /** + * Set (overwrite) BCC addresses + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param string|null $name + * @return Message + */ + public function setBcc($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('bcc'); + return $this->addBcc($emailOrAddressList, $name); + } + + /** + * Add a "Bcc" address + * + * @param string|Address|array|AddressList|Traversable $emailOrAddressOrList + * @param string|null $name + * @return Message + */ + public function addBcc($emailOrAddressOrList, $name = null) + { + $addressList = $this->getBcc(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Retrieve list of BCC recipients + * + * @return AddressList + */ + public function getBcc() + { + return $this->getAddressListFromHeader('bcc', __NAMESPACE__ . '\Header\Bcc'); + } + + /** + * Overwrite the address list in the Reply-To recipients + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param null|string $name + * @return Message + */ + public function setReplyTo($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('reply-to'); + return $this->addReplyTo($emailOrAddressList, $name); + } + + /** + * Add one or more addresses to the Reply-To recipients + * + * Appends to the list. + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressOrList + * @param null|string $name + * @return Message + */ + public function addReplyTo($emailOrAddressOrList, $name = null) + { + $addressList = $this->getReplyTo(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Access the address list of the Reply-To header + * + * @return AddressList + */ + public function getReplyTo() + { + return $this->getAddressListFromHeader('reply-to', __NAMESPACE__ . '\Header\ReplyTo'); + } + + /** + * setSender + * + * @param mixed $emailOrAddress + * @param mixed $name + * @return Message + */ + public function setSender($emailOrAddress, $name = null) + { + /** @var Sender $header */ + $header = $this->getHeaderByName('sender', __NAMESPACE__ . '\Header\Sender'); + $header->setAddress($emailOrAddress, $name); + return $this; + } + + /** + * Retrieve the sender address, if any + * + * @return null|Address\AddressInterface + */ + public function getSender() + { + $headers = $this->getHeaders(); + if (! $headers->has('sender')) { + return null; + } + + /** @var Sender $header */ + $header = $this->getHeaderByName('sender', __NAMESPACE__ . '\Header\Sender'); + return $header->getAddress(); + } + + /** + * Set the message subject header value + * + * @param string $subject + * @return Message + */ + public function setSubject($subject) + { + $headers = $this->getHeaders(); + if (! $headers->has('subject')) { + $header = new Header\Subject(); + $headers->addHeader($header); + } else { + $header = $headers->get('subject'); + } + $header->setSubject($subject); + $header->setEncoding($this->getEncoding()); + return $this; + } + + /** + * Get the message subject header value + * + * @return null|string + */ + public function getSubject() + { + $headers = $this->getHeaders(); + if (! $headers->has('subject')) { + return; + } + $header = $headers->get('subject'); + return $header->getFieldValue(); + } + + /** + * Set the message body + * + * @param null|string|\Laminas\Mime\Message|object $body + * @throws Exception\InvalidArgumentException + * @return Message + */ + public function setBody($body) + { + if (! is_string($body) && $body !== null) { + if (! is_object($body)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string or object argument; received "%s"', + __METHOD__, + gettype($body) + )); + } + if (! $body instanceof Mime\Message) { + if (! method_exists($body, '__toString')) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects object arguments of type %s or implementing __toString();' + . ' object of type "%s" received', + __METHOD__, + Mime\Message::class, + get_class($body) + )); + } + } + } + $this->body = $body; + + if (! $this->body instanceof Mime\Message) { + return $this; + } + + // Get headers, and set Mime-Version header + $headers = $this->getHeaders(); + $this->getHeaderByName('mime-version', __NAMESPACE__ . '\Header\MimeVersion'); + + // Multipart content headers + if ($this->body->isMultiPart()) { + $mime = $this->body->getMime(); + + /** @var ContentType $header */ + $header = $this->getHeaderByName('content-type', __NAMESPACE__ . '\Header\ContentType'); + $header->setType('multipart/mixed'); + $header->addParameter('boundary', $mime->boundary()); + return $this; + } + + // MIME single part headers + $parts = $this->body->getParts(); + if (! empty($parts)) { + $part = array_shift($parts); + $headers->addHeaders($part->getHeadersArray("\r\n")); + } + return $this; + } + + /** + * Return the currently set message body + * + * @return object|string|Mime\Message + */ + public function getBody() + { + return $this->body; + } + + /** + * Get the string-serialized message body text + * + * @return string + */ + public function getBodyText() + { + if ($this->body instanceof Mime\Message) { + return $this->body->generateMessage(Headers::EOL); + } + + return (string) $this->body; + } + + /** + * Retrieve a header by name + * + * If not found, instantiates one based on $headerClass. + * + * @param string $headerName + * @param string $headerClass + * @return Header\HeaderInterface|\ArrayIterator header instance or collection of headers + */ + protected function getHeaderByName($headerName, $headerClass) + { + $headers = $this->getHeaders(); + if ($headers->has($headerName)) { + $header = $headers->get($headerName); + } else { + $header = new $headerClass(); + $headers->addHeader($header); + } + return $header; + } + + /** + * Clear a header by name + * + * @param string $headerName + */ + protected function clearHeaderByName($headerName) + { + $this->getHeaders()->removeHeader($headerName); + } + + /** + * Retrieve the AddressList from a named header + * + * Used with To, From, Cc, Bcc, and ReplyTo headers. If the header does not + * exist, instantiates it. + * + * @param string $headerName + * @param string $headerClass + * @throws Exception\DomainException + * @return AddressList + */ + protected function getAddressListFromHeader($headerName, $headerClass) + { + $header = $this->getHeaderByName($headerName, $headerClass); + if (! $header instanceof Header\AbstractAddressList) { + throw new Exception\DomainException(sprintf( + 'Cannot grab address list from header of type "%s"; not an AbstractAddressList implementation', + get_class($header) + )); + } + return $header->getAddressList(); + } + + /** + * Update an address list + * + * Proxied to this from addFrom, addTo, addCc, addBcc, and addReplyTo. + * + * @param AddressList $addressList + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressOrList + * @param null|string $name + * @param string $callingMethod + * @throws Exception\InvalidArgumentException + */ + protected function updateAddressList(AddressList $addressList, $emailOrAddressOrList, $name, $callingMethod) + { + if ($emailOrAddressOrList instanceof Traversable) { + foreach ($emailOrAddressOrList as $address) { + $addressList->add($address); + } + return; + } + if (is_array($emailOrAddressOrList)) { + $addressList->addMany($emailOrAddressOrList); + return; + } + if (! is_string($emailOrAddressOrList) && ! $emailOrAddressOrList instanceof Address\AddressInterface) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string, AddressInterface, array, AddressList, or Traversable as its first argument;' + . ' received "%s"', + $callingMethod, + (is_object($emailOrAddressOrList) ? get_class($emailOrAddressOrList) : gettype($emailOrAddressOrList)) + )); + } + + if (is_string($emailOrAddressOrList) && $name === null) { + $addressList->addFromString($emailOrAddressOrList); + return; + } + + $addressList->add($emailOrAddressOrList, $name); + } + + /** + * Serialize to string + * + * @return string + */ + public function toString() + { + $headers = $this->getHeaders(); + return $headers->toString() + . Headers::EOL + . $this->getBodyText(); + } + + /** + * Instantiate from raw message string + * + * @todo Restore body to Mime\Message + * @param string $rawMessage + * @return Message + */ + public static function fromString($rawMessage) + { + $message = new static(); + + /** @var Headers $headers */ + $headers = null; + $content = null; + Mime\Decode::splitMessage($rawMessage, $headers, $content, Headers::EOL); + if ($headers->has('mime-version')) { + // todo - restore body to mime\message + } + $message->setHeaders($headers); + $message->setBody($content); + return $message; + } +} diff --git a/lib/laminas/laminas-mail/src/MessageFactory.php b/lib/laminas/laminas-mail/src/MessageFactory.php new file mode 100644 index 000000000..7290b7508 --- /dev/null +++ b/lib/laminas/laminas-mail/src/MessageFactory.php @@ -0,0 +1,64 @@ + $value) { + $setter = self::getSetterMethod($key); + if (method_exists($message, $setter)) { + $message->{$setter}($value); + } + } + + return $message; + } + + /** + * Generate a setter method name based on a provided key. + * + * @param string $key + * @return string + */ + private static function getSetterMethod($key) + { + return 'set' + . str_replace( + ' ', + '', + ucwords( + strtr( + $key, + [ + '-' => ' ', + '_' => ' ', + ] + ) + ) + ); + } +} diff --git a/lib/laminas/laminas-mail/src/Module.php b/lib/laminas/laminas-mail/src/Module.php new file mode 100644 index 000000000..bed90fdc6 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Module.php @@ -0,0 +1,25 @@ + $provider->getDependencyConfig(), + ]; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/AbstractProtocol.php b/lib/laminas/laminas-mail/src/Protocol/AbstractProtocol.php new file mode 100644 index 000000000..7739e3562 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/AbstractProtocol.php @@ -0,0 +1,354 @@ +validHost = new Validator\ValidatorChain(); + $this->validHost->attach(new Validator\Hostname(Validator\Hostname::ALLOW_ALL)); + + if (! $this->validHost->isValid($host)) { + throw new Exception\RuntimeException(implode(', ', $this->validHost->getMessages())); + } + + $this->host = $host; + $this->port = $port; + } + + /** + * Class destructor to cleanup open resources + * + */ + public function __destruct() + { + $this->_disconnect(); + } + + /** + * Set the maximum log size + * + * @param int $maximumLog Maximum log size + */ + public function setMaximumLog($maximumLog) + { + $this->maximumLog = (int) $maximumLog; + } + + /** + * Get the maximum log size + * + * @return int the maximum log size + */ + public function getMaximumLog() + { + return $this->maximumLog; + } + + /** + * Create a connection to the remote host + * + * Concrete adapters for this class will implement their own unique connect + * scripts, using the _connect() method to create the socket resource. + */ + abstract public function connect(); + + /** + * Retrieve the last client request + * + * @return string + */ + public function getRequest() + { + return $this->request; + } + + /** + * Retrieve the last server response + * + * @return array + */ + public function getResponse() + { + return $this->response; + } + + /** + * Retrieve the transaction log + * + * @return string + */ + public function getLog() + { + return implode('', $this->log); + } + + /** + * Reset the transaction log + * + */ + public function resetLog() + { + $this->log = []; + } + + // @codingStandardsIgnoreStart + /** + * Add the transaction log + * + * @param string $value new transaction + */ + protected function _addLog($value) + { + // @codingStandardsIgnoreEnd + if ($this->maximumLog >= 0 && count($this->log) >= $this->maximumLog) { + array_shift($this->log); + } + + $this->log[] = $value; + } + + // @codingStandardsIgnoreStart + /** + * Connect to the server using the supplied transport and target + * + * An example $remote string may be 'tcp://mail.example.com:25' or 'ssh://hostname.com:2222' + * + * @param string $remote Remote + * @throws Exception\RuntimeException + * @return bool + */ + protected function _connect($remote) + { + // @codingStandardsIgnoreEnd + $errorNum = 0; + $errorStr = ''; + + // open connection + set_error_handler( + function ($error, $message = '') { + throw new Exception\RuntimeException(sprintf('Could not open socket: %s', $message), $error); + }, + E_WARNING + ); + $this->socket = stream_socket_client($remote, $errorNum, $errorStr, self::TIMEOUT_CONNECTION); + restore_error_handler(); + + if ($this->socket === false) { + if ($errorNum == 0) { + $errorStr = 'Could not open socket'; + } + throw new Exception\RuntimeException($errorStr); + } + + if (($result = stream_set_timeout($this->socket, self::TIMEOUT_CONNECTION)) === false) { + throw new Exception\RuntimeException('Could not set stream timeout'); + } + + return $result; + } + + // @codingStandardsIgnoreStart + /** + * Disconnect from remote host and free resource + * + */ + protected function _disconnect() + { + // @codingStandardsIgnoreEnd + if (is_resource($this->socket)) { + fclose($this->socket); + } + } + + // @codingStandardsIgnoreStart + /** + * Send the given request followed by a LINEEND to the server. + * + * @param string $request + * @throws Exception\RuntimeException + * @return int|bool Number of bytes written to remote host + */ + protected function _send($request) + { + // @codingStandardsIgnoreEnd + if (! is_resource($this->socket)) { + throw new Exception\RuntimeException('No connection has been established to ' . $this->host); + } + + $this->request = $request; + + $result = fwrite($this->socket, $request . self::EOL); + + // Save request to internal log + $this->_addLog($request . self::EOL); + + if ($result === false) { + throw new Exception\RuntimeException('Could not send request to ' . $this->host); + } + + return $result; + } + + // @codingStandardsIgnoreStart + /** + * Get a line from the stream. + * + * @param int $timeout Per-request timeout value if applicable + * @throws Exception\RuntimeException + * @return string + */ + protected function _receive($timeout = null) + { + // @codingStandardsIgnoreEnd + if (! is_resource($this->socket)) { + throw new Exception\RuntimeException('No connection has been established to ' . $this->host); + } + + // Adapters may wish to supply per-commend timeouts according to appropriate RFC + if ($timeout !== null) { + stream_set_timeout($this->socket, $timeout); + } + + // Retrieve response + $response = fgets($this->socket, 1024); + + // Save request to internal log + $this->_addLog($response); + + // Check meta data to ensure connection is still valid + $info = stream_get_meta_data($this->socket); + + if (! empty($info['timed_out'])) { + throw new Exception\RuntimeException($this->host . ' has timed out'); + } + + if ($response === false) { + throw new Exception\RuntimeException('Could not read from ' . $this->host); + } + + return $response; + } + + // @codingStandardsIgnoreStart + /** + * Parse server response for successful codes + * + * Read the response from the stream and check for expected return code. + * Throws a Laminas\Mail\Protocol\Exception\ExceptionInterface if an unexpected code is returned. + * + * @param string|array $code One or more codes that indicate a successful response + * @param int $timeout Per-request timeout value if applicable + * @throws Exception\RuntimeException + * @return string Last line of response string + */ + protected function _expect($code, $timeout = null) + { + // @codingStandardsIgnoreEnd + $this->response = []; + $errMsg = ''; + + if (! is_array($code)) { + $code = [$code]; + } + + do { + $this->response[] = $result = $this->_receive($timeout); + list($cmd, $more, $msg) = preg_split('/([\s-]+)/', $result, 2, PREG_SPLIT_DELIM_CAPTURE); + + if ($errMsg !== '') { + $errMsg .= ' ' . $msg; + } elseif ($cmd === null || ! in_array($cmd, $code)) { + $errMsg = $msg; + } + + // The '-' message prefix indicates an information string instead of a response string. + } while (strpos($more, '-') === 0); + + if ($errMsg !== '') { + throw new Exception\RuntimeException($errMsg); + } + + return $msg; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php b/lib/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php new file mode 100644 index 000000000..b8b1aacf6 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php @@ -0,0 +1,15 @@ +connect($host, $port, $ssl); + } + } + + /** + * Public destructor + */ + public function __destruct() + { + $this->logout(); + } + + /** + * Open connection to IMAP server + * + * @param string $host hostname or IP address of IMAP server + * @param int|null $port of IMAP server, default is 143 (993 for ssl) + * @param string|bool $ssl use 'SSL', 'TLS' or false + * @throws Exception\RuntimeException + * @return string welcome message + */ + public function connect($host, $port = null, $ssl = false) + { + $isTls = false; + + if ($ssl) { + $ssl = strtolower($ssl); + } + + switch ($ssl) { + case 'ssl': + $host = 'ssl://' . $host; + if (! $port) { + $port = 993; + } + break; + case 'tls': + $isTls = true; + // break intentionally omitted + default: + if (! $port) { + $port = 143; + } + } + + ErrorHandler::start(); + $this->socket = fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION); + $error = ErrorHandler::stop(); + if (! $this->socket) { + throw new Exception\RuntimeException(sprintf( + 'cannot connect to host %s', + ($error ? sprintf('; error = %s (errno = %d )', $error->getMessage(), $error->getCode()) : '') + ), 0, $error); + } + + if (! $this->assumedNextLine('* OK')) { + throw new Exception\RuntimeException('host doesn\'t allow connection'); + } + + if ($isTls) { + $result = $this->requestAndResponse('STARTTLS'); + $result = $result && stream_socket_enable_crypto($this->socket, true, $this->getCryptoMethod()); + if (! $result) { + throw new Exception\RuntimeException('cannot enable TLS'); + } + } + } + + /** + * get the next line from socket with error checking, but nothing else + * + * @throws Exception\RuntimeException + * @return string next line + */ + protected function nextLine() + { + $line = fgets($this->socket); + if ($line === false) { + throw new Exception\RuntimeException('cannot read - connection closed?'); + } + + return $line; + } + + /** + * get next line and assume it starts with $start. some requests give a simple + * feedback so we can quickly check if we can go on. + * + * @param string $start the first bytes we assume to be in the next line + * @return bool line starts with $start + */ + protected function assumedNextLine($start) + { + $line = $this->nextLine(); + return strpos($line, $start) === 0; + } + + /** + * get next line and split the tag. that's the normal case for a response line + * + * @param string $tag tag of line is returned by reference + * @return string next line + */ + protected function nextTaggedLine(&$tag) + { + $line = $this->nextLine(); + + // separate tag from line + list($tag, $line) = explode(' ', $line, 2); + + return $line; + } + + /** + * split a given line in tokens. a token is literal of any form or a list + * + * @param string $line line to decode + * @return array tokens, literals are returned as string, lists as array + */ + protected function decodeLine($line) + { + $tokens = []; + $stack = []; + + /* + We start to decode the response here. The understood tokens are: + literal + "literal" or also "lit\\er\"al" + {bytes}literal + (literals*) + All tokens are returned in an array. Literals in braces (the last understood + token in the list) are returned as an array of tokens. I.e. the following response: + "foo" baz {3}bar ("f\\\"oo" bar) + would be returned as: + array('foo', 'baz', 'bar', array('f\\\"oo', 'bar')); + + // TODO: add handling of '[' and ']' to parser for easier handling of response text + */ + // replace any trailing including spaces with a single space + $line = rtrim($line) . ' '; + while (($pos = strpos($line, ' ')) !== false) { + $token = substr($line, 0, $pos); + if (! strlen($token)) { + continue; + } + while ($token[0] == '(') { + array_push($stack, $tokens); + $tokens = []; + $token = substr($token, 1); + } + if ($token[0] == '"') { + if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) { + $tokens[] = $matches[1]; + $line = substr($line, strlen($matches[0])); + continue; + } + } + if ($token[0] == '{') { + $endPos = strpos($token, '}'); + $chars = substr($token, 1, $endPos - 1); + if (is_numeric($chars)) { + $token = ''; + while (strlen($token) < $chars) { + $token .= $this->nextLine(); + } + $line = ''; + if (strlen($token) > $chars) { + $line = substr($token, $chars); + $token = substr($token, 0, $chars); + } else { + $line .= $this->nextLine(); + } + $tokens[] = $token; + $line = trim($line) . ' '; + continue; + } + } + if ($stack && $token[strlen($token) - 1] == ')') { + // closing braces are not separated by spaces, so we need to count them + $braces = strlen($token); + $token = rtrim($token, ')'); + // only count braces if more than one + $braces -= strlen($token) + 1; + // only add if token had more than just closing braces + if (rtrim($token) != '') { + $tokens[] = rtrim($token); + } + $token = $tokens; + $tokens = array_pop($stack); + // special handline if more than one closing brace + while ($braces-- > 0) { + $tokens[] = $token; + $token = $tokens; + $tokens = array_pop($stack); + } + } + $tokens[] = $token; + $line = substr($line, $pos + 1); + } + + // maybe the server forgot to send some closing braces + while ($stack) { + $child = $tokens; + $tokens = array_pop($stack); + $tokens[] = $child; + } + + return $tokens; + } + + /** + * read a response "line" (could also be more than one real line if response has {..}) + * and do a simple decode + * + * @param array|string $tokens decoded tokens are returned by reference, if $dontParse + * is true the unparsed line is returned here + * @param string $wantedTag check for this tag for response code. Default '*' is + * continuation tag. + * @param bool $dontParse if true only the unparsed line is returned $tokens + * @return bool if returned tag matches wanted tag + */ + public function readLine(&$tokens = [], $wantedTag = '*', $dontParse = false) + { + $tag = null; // define $tag variable before first use + $line = $this->nextTaggedLine($tag); // get next tag + if (! $dontParse) { + $tokens = $this->decodeLine($line); + } else { + $tokens = $line; + } + + // if tag is wanted tag we might be at the end of a multiline response + return $tag == $wantedTag; + } + + /** + * read all lines of response until given tag is found (last line of response) + * + * @param string $tag the tag of your request + * @param bool $dontParse if true every line is returned unparsed instead of + * the decoded tokens + * @return null|bool|array tokens if success, false if error, null if bad request + */ + public function readResponse($tag, $dontParse = false) + { + $lines = []; + $tokens = null; // define $tokens variable before first use + while (! $this->readLine($tokens, $tag, $dontParse)) { + $lines[] = $tokens; + } + + if ($dontParse) { + // last to chars are still needed for response code + $tokens = [substr($tokens, 0, 2)]; + } + // last line has response code + if ($tokens[0] == 'OK') { + return $lines ? $lines : true; + } elseif ($tokens[0] == 'NO') { + return false; + } + return; + } + + /** + * send a request + * + * @param string $command your request command + * @param array $tokens additional parameters to command, use escapeString() to prepare + * @param string $tag provide a tag otherwise an autogenerated is returned + * @throws Exception\RuntimeException + */ + public function sendRequest($command, $tokens = [], &$tag = null) + { + if (! $tag) { + ++$this->tagCount; + $tag = 'TAG' . $this->tagCount; + } + + $line = $tag . ' ' . $command; + + foreach ($tokens as $token) { + if (is_array($token)) { + if (fwrite($this->socket, $line . ' ' . $token[0] . "\r\n") === false) { + throw new Exception\RuntimeException('cannot write - connection closed?'); + } + if (! $this->assumedNextLine('+ ')) { + throw new Exception\RuntimeException('cannot send literal string'); + } + $line = $token[1]; + } else { + $line .= ' ' . $token; + } + } + + if (fwrite($this->socket, $line . "\r\n") === false) { + throw new Exception\RuntimeException('cannot write - connection closed?'); + } + } + + /** + * send a request and get response at once + * + * @param string $command command as in sendRequest() + * @param array $tokens parameters as in sendRequest() + * @param bool $dontParse if true unparsed lines are returned instead of tokens + * @return mixed response as in readResponse() + */ + public function requestAndResponse($command, $tokens = [], $dontParse = false) + { + $tag = null; // define $tag variable before first use + $this->sendRequest($command, $tokens, $tag); + $response = $this->readResponse($tag, $dontParse); + + return $response; + } + + /** + * escape one or more literals i.e. for sendRequest + * + * @param string|array $string the literal/-s + * @return string|array escape literals, literals with newline ar returned + * as array('{size}', 'string'); + */ + public function escapeString($string) + { + if (func_num_args() < 2) { + if (strpos($string, "\n") !== false) { + return ['{' . strlen($string) . '}', $string]; + } else { + return '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $string) . '"'; + } + } + $result = []; + foreach (func_get_args() as $string) { + $result[] = $this->escapeString($string); + } + return $result; + } + + /** + * escape a list with literals or lists + * + * @param array $list list with literals or lists as PHP array + * @return string escaped list for imap + */ + public function escapeList($list) + { + $result = []; + foreach ($list as $v) { + if (! is_array($v)) { + $result[] = $v; + continue; + } + $result[] = $this->escapeList($v); + } + return '(' . implode(' ', $result) . ')'; + } + + /** + * Login to IMAP server. + * + * @param string $user username + * @param string $password password + * @return bool success + */ + public function login($user, $password) + { + return $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true); + } + + /** + * logout of imap server + * + * @return bool success + */ + public function logout() + { + $result = false; + if ($this->socket) { + try { + $result = $this->requestAndResponse('LOGOUT', [], true); + } catch (Exception\ExceptionInterface $e) { + // ignoring exception + } + fclose($this->socket); + $this->socket = null; + } + return $result; + } + + /** + * Get capabilities from IMAP server + * + * @return array list of capabilities + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function capability() + { + $response = $this->requestAndResponse('CAPABILITY'); + + if (! $response) { + return []; + } + + $capabilities = []; + foreach ($response as $line) { + $capabilities = array_merge($capabilities, $line); + } + return $capabilities; + } + + /** + * Examine and select have the same response. The common code for both + * is in this method + * + * @param string $command can be 'EXAMINE' or 'SELECT' and this is used as command + * @param string $box which folder to change to or examine + * @return bool|array false if error, array with returned information + * otherwise (flags, exists, recent, uidvalidity) + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX') + { + $tag = null; // define $tag variable before first use + $this->sendRequest($command, [$this->escapeString($box)], $tag); + + $result = []; + $tokens = null; // define $tokens variable before first use + while (! $this->readLine($tokens, $tag)) { + if ($tokens[0] == 'FLAGS') { + array_shift($tokens); + $result['flags'] = $tokens; + continue; + } + switch ($tokens[1]) { + case 'EXISTS': + case 'RECENT': + $result[strtolower($tokens[1])] = $tokens[0]; + break; + case '[UIDVALIDITY': + $result['uidvalidity'] = (int) $tokens[2]; + break; + default: + // ignore + } + } + + if ($tokens[0] != 'OK') { + return false; + } + return $result; + } + + /** + * change folder + * + * @param string $box change to this folder + * @return bool|array see examineOrselect() + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function select($box = 'INBOX') + { + return $this->examineOrSelect('SELECT', $box); + } + + /** + * examine folder + * + * @param string $box examine this folder + * @return bool|array see examineOrselect() + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function examine($box = 'INBOX') + { + return $this->examineOrSelect('EXAMINE', $box); + } + + /** + * fetch one or more items of one or more messages + * + * @param string|array $items items to fetch from message(s) as string (if only one item) + * or array of strings + * @param int|array $from message for items or start message if $to !== null + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @param bool $uid set to true if passing a unique id + * @throws Exception\RuntimeException + * @return string|array if only one item of one message is fetched it's returned as string + * if items of one message are fetched it's returned as (name => value) + * if one items of messages are fetched it's returned as (msgno => value) + * if items of messages are fetched it's returned as (msgno => (name => value)) + */ + public function fetch($items, $from, $to = null, $uid = false) + { + if (is_array($from)) { + $set = implode(',', $from); + } elseif ($to === null) { + $set = (int) $from; + } elseif ($to === INF) { + $set = (int) $from . ':*'; + } else { + $set = (int) $from . ':' . (int) $to; + } + + $items = (array) $items; + $itemList = $this->escapeList($items); + + $tag = null; // define $tag variable before first use + $this->sendRequest(($uid ? 'UID ' : '') . 'FETCH', [$set, $itemList], $tag); + + $result = []; + $tokens = null; // define $tokens variable before first use + while (! $this->readLine($tokens, $tag)) { + // ignore other responses + if ($tokens[1] != 'FETCH') { + continue; + } + + // find array key of UID value; try the last elements, or search for it + if ($uid) { + $count = count($tokens[2]); + if ($tokens[2][$count - 2] == 'UID') { + $uidKey = $count - 1; + } else { + $uidKey = array_search('UID', $tokens[2]) + 1; + } + } + + // ignore other messages + if ($to === null && ! is_array($from) && ($uid ? $tokens[2][$uidKey] != $from : $tokens[0] != $from)) { + continue; + } + + // if we only want one item we return that one directly + if (count($items) == 1) { + if ($tokens[2][0] == $items[0]) { + $data = $tokens[2][1]; + } elseif ($uid && $tokens[2][2] == $items[0]) { + $data = $tokens[2][3]; + } else { + // maybe the server send an other field we didn't wanted + $count = count($tokens[2]); + // we start with 2, because 0 was already checked + for ($i = 2; $i < $count; $i += 2) { + if ($tokens[2][$i] != $items[0]) { + continue; + } + $data = $tokens[2][$i + 1]; + break; + } + } + } else { + $data = []; + while (key($tokens[2]) !== null) { + $data[current($tokens[2])] = next($tokens[2]); + next($tokens[2]); + } + } + + // if we want only one message we can ignore everything else and just return + if ($to === null && ! is_array($from) && ($uid ? $tokens[2][$uidKey] == $from : $tokens[0] == $from)) { + // we still need to read all lines + while (! $this->readLine($tokens, $tag)) { + } + return $data; + } + $result[$tokens[0]] = $data; + } + + if ($to === null && ! is_array($from)) { + throw new Exception\RuntimeException('the single id was not found in response'); + } + + return $result; + } + + /** + * get mailbox list + * + * this method can't be named after the IMAP command 'LIST', as list is a reserved keyword + * + * @param string $reference mailbox reference for list + * @param string $mailbox mailbox name match with wildcards + * @return array mailboxes that matched $mailbox as array(globalName => array('delim' => .., 'flags' => ..)) + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function listMailbox($reference = '', $mailbox = '*') + { + $result = []; + $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox)); + if (! $list || $list === true) { + return $result; + } + + foreach ($list as $item) { + if (count($item) != 4 || $item[0] != 'LIST') { + continue; + } + $result[$item[3]] = ['delim' => $item[2], 'flags' => $item[1]]; + } + + return $result; + } + + /** + * set flags + * + * @param array $flags flags to set, add or remove - see $mode + * @param int $from message for items or start message if $to !== null + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given + * @param bool $silent if false the return values are the new flags for the wanted messages + * @return bool|array new flags if $silent is false, else true or false depending on success + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function store(array $flags, $from, $to = null, $mode = null, $silent = true) + { + $item = 'FLAGS'; + if ($mode == '+' || $mode == '-') { + $item = $mode . $item; + } + if ($silent) { + $item .= '.SILENT'; + } + + $flags = $this->escapeList($flags); + $set = (int) $from; + if ($to !== null) { + $set .= ':' . ($to == INF ? '*' : (int) $to); + } + + $result = $this->requestAndResponse('STORE', [$set, $item, $flags], $silent); + + if ($silent) { + return (bool) $result; + } + + $tokens = $result; + $result = []; + foreach ($tokens as $token) { + if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') { + continue; + } + $result[$token[0]] = $token[2][1]; + } + + return $result; + } + + /** + * append a new message to given folder + * + * @param string $folder name of target folder + * @param string $message full message content + * @param array $flags flags for new message + * @param string $date date for new message + * @return bool success + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function append($folder, $message, $flags = null, $date = null) + { + $tokens = []; + $tokens[] = $this->escapeString($folder); + if ($flags !== null) { + $tokens[] = $this->escapeList($flags); + } + if ($date !== null) { + $tokens[] = $this->escapeString($date); + } + $tokens[] = $this->escapeString($message); + + return $this->requestAndResponse('APPEND', $tokens, true); + } + + /** + * copy message set from current folder to other folder + * + * @param string $folder destination folder + * @param $from + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @return bool success + */ + public function copy($folder, $from, $to = null) + { + $set = (int) $from; + if ($to !== null) { + $set .= ':' . ($to == INF ? '*' : (int) $to); + } + + return $this->requestAndResponse('COPY', [$set, $this->escapeString($folder)], true); + } + + /** + * create a new folder (and parent folders if needed) + * + * @param string $folder folder name + * @return bool success + */ + public function create($folder) + { + return $this->requestAndResponse('CREATE', [$this->escapeString($folder)], true); + } + + /** + * rename an existing folder + * + * @param string $old old name + * @param string $new new name + * @return bool success + */ + public function rename($old, $new) + { + return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true); + } + + /** + * remove a folder + * + * @param string $folder folder name + * @return bool success + */ + public function delete($folder) + { + return $this->requestAndResponse('DELETE', [$this->escapeString($folder)], true); + } + + /** + * subscribe to a folder + * + * @param string $folder folder name + * @return bool success + */ + public function subscribe($folder) + { + return $this->requestAndResponse('SUBSCRIBE', [$this->escapeString($folder)], true); + } + + /** + * permanently remove messages + * + * @return bool success + */ + public function expunge() + { + // TODO: parse response? + return $this->requestAndResponse('EXPUNGE'); + } + + /** + * send noop + * + * @return bool success + */ + public function noop() + { + // TODO: parse response + return $this->requestAndResponse('NOOP'); + } + + /** + * do a search request + * + * This method is currently marked as internal as the API might change and is not + * safe if you don't take precautions. + * + * @param array $params + * @return array message ids + */ + public function search(array $params) + { + $response = $this->requestAndResponse('SEARCH', $params); + if (! $response) { + return $response; + } + + foreach ($response as $ids) { + if ($ids[0] == 'SEARCH') { + array_shift($ids); + return $ids; + } + } + return []; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Pop3.php b/lib/laminas/laminas-mail/src/Protocol/Pop3.php new file mode 100644 index 000000000..342644bcd --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Pop3.php @@ -0,0 +1,406 @@ +connect($host, $port, $ssl); + } + } + + /** + * Public destructor + */ + public function __destruct() + { + $this->logout(); + } + + /** + * Open connection to POP3 server + * + * @param string $host hostname or IP address of POP3 server + * @param int|null $port of POP3 server, default is 110 (995 for ssl) + * @param string|bool $ssl use 'SSL', 'TLS' or false + * @throws Exception\RuntimeException + * @return string welcome message + */ + public function connect($host, $port = null, $ssl = false) + { + $isTls = false; + + if ($ssl) { + $ssl = strtolower($ssl); + } + + switch ($ssl) { + case 'ssl': + $host = 'ssl://' . $host; + if (! $port) { + $port = 995; + } + break; + case 'tls': + $isTls = true; + // break intentionally omitted + default: + if (! $port) { + $port = 110; + } + } + + ErrorHandler::start(); + $this->socket = fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION); + $error = ErrorHandler::stop(); + if (! $this->socket) { + throw new Exception\RuntimeException(sprintf( + 'cannot connect to host %s', + ($error ? sprintf('; error = %s (errno = %d )', $error->getMessage(), $error->getCode()) : '') + ), 0, $error); + } + + $welcome = $this->readResponse(); + + strtok($welcome, '<'); + $this->timestamp = strtok('>'); + if (! strpos($this->timestamp, '@')) { + $this->timestamp = null; + } else { + $this->timestamp = '<' . $this->timestamp . '>'; + } + + if ($isTls) { + $this->request('STLS'); + $result = stream_socket_enable_crypto($this->socket, true, $this->getCryptoMethod()); + if (! $result) { + throw new Exception\RuntimeException('cannot enable TLS'); + } + } + + return $welcome; + } + + /** + * Send a request + * + * @param string $request your request without newline + * @throws Exception\RuntimeException + */ + public function sendRequest($request) + { + ErrorHandler::start(); + $result = fputs($this->socket, $request . "\r\n"); + $error = ErrorHandler::stop(); + if (! $result) { + throw new Exception\RuntimeException('send failed - connection closed?', 0, $error); + } + } + + /** + * read a response + * + * @param bool $multiline response has multiple lines and should be read until "." + * @throws Exception\RuntimeException + * @return string response + */ + public function readResponse($multiline = false) + { + ErrorHandler::start(); + $result = fgets($this->socket); + $error = ErrorHandler::stop(); + if (! is_string($result)) { + throw new Exception\RuntimeException('read failed - connection closed?', 0, $error); + } + + $result = trim($result); + if (strpos($result, ' ')) { + list($status, $message) = explode(' ', $result, 2); + } else { + $status = $result; + $message = ''; + } + + if ($status != '+OK') { + throw new Exception\RuntimeException('last request failed'); + } + + if ($multiline) { + $message = ''; + $line = fgets($this->socket); + while ($line && rtrim($line, "\r\n") != '.') { + if ($line[0] == '.') { + $line = substr($line, 1); + } + $message .= $line; + $line = fgets($this->socket); + }; + } + + return $message; + } + + /** + * Send request and get response + * + * @see sendRequest() + * @see readResponse() + * @param string $request request + * @param bool $multiline multiline response? + * @return string result from readResponse() + */ + public function request($request, $multiline = false) + { + $this->sendRequest($request); + return $this->readResponse($multiline); + } + + /** + * End communication with POP3 server (also closes socket) + */ + public function logout() + { + if ($this->socket) { + try { + $this->request('QUIT'); + } catch (Exception\ExceptionInterface $e) { + // ignore error - we're closing the socket anyway + } + + fclose($this->socket); + $this->socket = null; + } + } + + + /** + * Get capabilities from POP3 server + * + * @return array list of capabilities + */ + public function capa() + { + $result = $this->request('CAPA', true); + return explode("\n", $result); + } + + + /** + * Login to POP3 server. Can use APOP + * + * @param string $user username + * @param string $password password + * @param bool $tryApop should APOP be tried? + */ + public function login($user, $password, $tryApop = true) + { + if ($tryApop && $this->timestamp) { + try { + $this->request("APOP $user " . md5($this->timestamp . $password)); + return; + } catch (Exception\ExceptionInterface $e) { + // ignore + } + } + + $this->request("USER $user"); + $this->request("PASS $password"); + } + + + /** + * Make STAT call for message count and size sum + * + * @param int $messages out parameter with count of messages + * @param int $octets out parameter with size in octets of messages + */ + public function status(&$messages, &$octets) + { + $messages = 0; + $octets = 0; + $result = $this->request('STAT'); + + list($messages, $octets) = explode(' ', $result); + } + + + /** + * Make LIST call for size of message(s) + * + * @param int|null $msgno number of message, null for all + * @return int|array size of given message or list with array(num => size) + */ + public function getList($msgno = null) + { + if ($msgno !== null) { + $result = $this->request("LIST $msgno"); + + list(, $result) = explode(' ', $result); + return (int) $result; + } + + $result = $this->request('LIST', true); + $messages = []; + $line = strtok($result, "\n"); + while ($line) { + list($no, $size) = explode(' ', trim($line)); + $messages[(int) $no] = (int) $size; + $line = strtok("\n"); + } + + return $messages; + } + + + /** + * Make UIDL call for getting a uniqueid + * + * @param int|null $msgno number of message, null for all + * @return string|array uniqueid of message or list with array(num => uniqueid) + */ + public function uniqueid($msgno = null) + { + if ($msgno !== null) { + $result = $this->request("UIDL $msgno"); + + list(, $result) = explode(' ', $result); + return $result; + } + + $result = $this->request('UIDL', true); + + $result = explode("\n", $result); + $messages = []; + foreach ($result as $line) { + if (! $line) { + continue; + } + list($no, $id) = explode(' ', trim($line), 2); + $messages[(int) $no] = $id; + } + + return $messages; + } + + + /** + * Make TOP call for getting headers and maybe some body lines + * This method also sets hasTop - before it it's not known if top is supported + * + * The fallback makes normal RETR call, which retrieves the whole message. Additional + * lines are not removed. + * + * @param int $msgno number of message + * @param int $lines number of wanted body lines (empty line is inserted after header lines) + * @param bool $fallback fallback with full retrieve if top is not supported + * @throws Exception\RuntimeException + * @throws Exception\ExceptionInterface + * @return string message headers with wanted body lines + */ + public function top($msgno, $lines = 0, $fallback = false) + { + if ($this->hasTop === false) { + if ($fallback) { + return $this->retrieve($msgno); + } else { + throw new Exception\RuntimeException('top not supported and no fallback wanted'); + } + } + $this->hasTop = true; + + $lines = (! $lines || $lines < 1) ? 0 : (int) $lines; + + try { + $result = $this->request("TOP $msgno $lines", true); + } catch (Exception\ExceptionInterface $e) { + $this->hasTop = false; + if ($fallback) { + $result = $this->retrieve($msgno); + } else { + throw $e; + } + } + + return $result; + } + + /** + * Make a RETR call for retrieving a full message with headers and body + * + * @param int $msgno message number + * @return string message + */ + public function retrieve($msgno) + { + $result = $this->request("RETR $msgno", true); + return $result; + } + + /** + * Make a NOOP call, maybe needed for keeping the server happy + */ + public function noop() + { + $this->request('NOOP'); + } + + /** + * Make a DELE count to remove a message + * + * @param $msgno + */ + public function delete($msgno) + { + $this->request("DELE $msgno"); + } + + /** + * Make RSET call, which rollbacks delete requests + */ + public function undelete() + { + $this->request('RSET'); + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/ProtocolTrait.php b/lib/laminas/laminas-mail/src/Protocol/ProtocolTrait.php new file mode 100644 index 000000000..17655f029 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/ProtocolTrait.php @@ -0,0 +1,30 @@ +secure = 'tls'; + break; + + case 'ssl': + $this->transport = 'ssl'; + $this->secure = 'ssl'; + if ($port === null) { + $port = 465; + } + break; + + case '': + // fall-through + case 'none': + break; + + default: + throw new Exception\InvalidArgumentException($config['ssl'] . ' is unsupported SSL type'); + } + } + + if (array_key_exists('use_complete_quit', $config)) { + $this->setUseCompleteQuit($config['use_complete_quit']); + } + + // If no port has been specified then check the master PHP ini file. Defaults to 25 if the ini setting is null. + if ($port === null) { + if (($port = ini_get('smtp_port')) == '') { + $port = 25; + } + } + + parent::__construct($host, $port); + } + + /** + * Set whether or not send QUIT command + * + * @param bool $useCompleteQuit use complete quit + * @return bool + */ + public function setUseCompleteQuit($useCompleteQuit) + { + return $this->useCompleteQuit = (bool) $useCompleteQuit; + } + + /** + * Whether or not send QUIT command + * + * @return bool + */ + public function useCompleteQuit() + { + return $this->useCompleteQuit; + } + + /** + * Connect to the server with the parameters given in the constructor. + * + * @return bool + */ + public function connect() + { + return $this->_connect($this->transport . '://' . $this->host . ':' . $this->port); + } + + + /** + * Initiate HELO/EHLO sequence and set flag to indicate valid smtp session + * + * @param string $host The client hostname or IP address (default: 127.0.0.1) + * @throws Exception\RuntimeException + */ + public function helo($host = '127.0.0.1') + { + // Respect RFC 2821 and disallow HELO attempts if session is already initiated. + if ($this->sess === true) { + throw new Exception\RuntimeException('Cannot issue HELO to existing session'); + } + + // Validate client hostname + if (! $this->validHost->isValid($host)) { + throw new Exception\RuntimeException(implode(', ', $this->validHost->getMessages())); + } + + // Initiate helo sequence + $this->_expect(220, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + $this->ehlo($host); + + // If a TLS session is required, commence negotiation + if ($this->secure == 'tls') { + $this->_send('STARTTLS'); + $this->_expect(220, 180); + if (! stream_socket_enable_crypto($this->socket, true, $this->getCryptoMethod())) { + throw new Exception\RuntimeException('Unable to connect via TLS'); + } + $this->ehlo($host); + } + + $this->startSession(); + $this->auth(); + } + + /** + * Returns the perceived session status + * + * @return bool + */ + public function hasSession() + { + return $this->sess; + } + + /** + * Send EHLO or HELO depending on capabilities of smtp host + * + * @param string $host The client hostname or IP address (default: 127.0.0.1) + * @throws \Exception|Exception\ExceptionInterface + */ + protected function ehlo($host) + { + // Support for older, less-compliant remote servers. Tries multiple attempts of EHLO or HELO. + try { + $this->_send('EHLO ' . $host); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } catch (Exception\ExceptionInterface $e) { + $this->_send('HELO ' . $host); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + } + + + /** + * Issues MAIL command + * + * @param string $from Sender mailbox + * @throws Exception\RuntimeException + */ + public function mail($from) + { + if ($this->sess !== true) { + throw new Exception\RuntimeException('A valid session has not been started'); + } + + $this->_send('MAIL FROM:<' . $from . '>'); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + + // Set mail to true, clear recipients and any existing data flags as per 4.1.1.2 of RFC 2821 + $this->mail = true; + $this->rcpt = false; + $this->data = false; + } + + + /** + * Issues RCPT command + * + * @param string $to Receiver(s) mailbox + * @throws Exception\RuntimeException + */ + public function rcpt($to) + { + if ($this->mail !== true) { + throw new Exception\RuntimeException('No sender reverse path has been supplied'); + } + + // Set rcpt to true, as per 4.1.1.3 of RFC 2821 + $this->_send('RCPT TO:<' . $to . '>'); + $this->_expect([250, 251], 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + $this->rcpt = true; + } + + + /** + * Issues DATA command + * + * @param string $data + * @throws Exception\RuntimeException + */ + public function data($data) + { + // Ensure recipients have been set + if ($this->rcpt !== true) { // Per RFC 2821 3.3 (page 18) + throw new Exception\RuntimeException('No recipient forward path has been supplied'); + } + + $this->_send('DATA'); + $this->_expect(354, 120); // Timeout set for 2 minutes as per RFC 2821 4.5.3.2 + + if (($fp = fopen("php://temp", "r+")) === false) { + throw new Exception\RuntimeException('cannot fopen'); + } + if (fwrite($fp, $data) === false) { + throw new Exception\RuntimeException('cannot fwrite'); + } + unset($data); + rewind($fp); + + // max line length is 998 char + \r\n = 1000 + while (($line = stream_get_line($fp, 1000, "\n")) !== false) { + $line = rtrim($line, "\r"); + if (isset($line[0]) && $line[0] === '.') { + // Escape lines prefixed with a '.' + $line = '.' . $line; + } + $this->_send($line); + } + fclose($fp); + + $this->_send('.'); + $this->_expect(250, 600); // Timeout set for 10 minutes as per RFC 2821 4.5.3.2 + $this->data = true; + } + + + /** + * Issues the RSET command end validates answer + * + * Can be used to restore a clean smtp communication state when a + * transaction has been cancelled or commencing a new transaction. + */ + public function rset() + { + $this->_send('RSET'); + // MS ESMTP doesn't follow RFC, see https://zendframework.com/issues/browse/ZF-1377 + $this->_expect([250, 220]); + + $this->mail = false; + $this->rcpt = false; + $this->data = false; + } + + /** + * Issues the NOOP command end validates answer + * + * Not used by Laminas\Mail, could be used to keep a connection alive or check if it is still open. + * + */ + public function noop() + { + $this->_send('NOOP'); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + + /** + * Issues the VRFY command end validates answer + * + * Not used by Laminas\Mail. + * + * @param string $user User Name or eMail to verify + */ + public function vrfy($user) + { + $this->_send('VRFY ' . $user); + $this->_expect([250, 251, 252], 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + + /** + * Issues the QUIT command and clears the current session + * + */ + public function quit() + { + if ($this->sess) { + $this->auth = false; + + if ($this->useCompleteQuit()) { + $this->_send('QUIT'); + $this->_expect(221, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + + $this->stopSession(); + } + } + + /** + * Default authentication method + * + * This default method is implemented by AUTH adapters to properly authenticate to a remote host. + * + * @throws Exception\RuntimeException + */ + public function auth() + { + if ($this->auth === true) { + throw new Exception\RuntimeException('Already authenticated for this session'); + } + } + + /** + * Closes connection + * + */ + public function disconnect() + { + $this->_disconnect(); + } + + // @codingStandardsIgnoreStart + /** + * Disconnect from remote host and free resource + */ + protected function _disconnect() + { + // @codingStandardsIgnoreEnd + + // Make sure the session gets closed + $this->quit(); + parent::_disconnect(); + } + + /** + * Start mail session + * + */ + protected function startSession() + { + $this->sess = true; + } + + /** + * Stop mail session + * + */ + protected function stopSession() + { + $this->sess = false; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php new file mode 100644 index 000000000..50099163f --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php @@ -0,0 +1,138 @@ +setUsername($config['username']); + } + if (isset($config['password'])) { + $this->setPassword($config['password']); + } + } + + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); + } + + /** + * Performs CRAM-MD5 authentication with supplied credentials + */ + public function auth() + { + // Ensure AUTH has not already been initiated. + parent::auth(); + + $this->_send('AUTH CRAM-MD5'); + $challenge = $this->_expect(334); + $challenge = base64_decode($challenge); + $digest = $this->hmacMd5($this->getPassword(), $challenge); + $this->_send(base64_encode($this->getUsername() . ' ' . $digest)); + $this->_expect(235); + $this->auth = true; + } + + /** + * Set value for username + * + * @param string $username + * @return Crammd5 + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Get username + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set value for password + * + * @param string $password + * @return Crammd5 + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Get password + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Prepare CRAM-MD5 response to server's ticket + * + * @param string $key Challenge key (usually password) + * @param string $data Challenge data + * @param int $block Length of blocks (deprecated; unused) + * @return string + */ + protected function hmacMd5($key, $data, $block = 64) + { + return Hmac::compute($key, 'md5', $data); + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php new file mode 100644 index 000000000..8be7ba694 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php @@ -0,0 +1,126 @@ +setUsername($config['username']); + } + if (isset($config['password'])) { + $this->setPassword($config['password']); + } + } + + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); + } + + /** + * Perform LOGIN authentication with supplied credentials + * + */ + public function auth() + { + // Ensure AUTH has not already been initiated. + parent::auth(); + + $this->_send('AUTH LOGIN'); + $this->_expect(334); + $this->_send(base64_encode($this->getUsername())); + $this->_expect(334); + $this->_send(base64_encode($this->getPassword())); + $this->_expect(235); + $this->auth = true; + } + + /** + * Set value for username + * + * @param string $username + * @return Login + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Get username + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set value for password + * + * @param string $password + * @return Login + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Get password + * + * @return string + */ + public function getPassword() + { + return $this->password; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php new file mode 100644 index 000000000..8cf6c3724 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php @@ -0,0 +1,124 @@ +setUsername($config['username']); + } + if (isset($config['password'])) { + $this->setPassword($config['password']); + } + } + + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); + } + + /** + * Perform PLAIN authentication with supplied credentials + * + */ + public function auth() + { + // Ensure AUTH has not already been initiated. + parent::auth(); + + $this->_send('AUTH PLAIN'); + $this->_expect(334); + $this->_send(base64_encode("\0" . $this->getUsername() . "\0" . $this->getPassword())); + $this->_expect(235); + $this->auth = true; + } + + /** + * Set value for username + * + * @param string $username + * @return Plain + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Get username + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set value for password + * + * @param string $password + * @return Plain + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Get password + * + * @return string + */ + public function getPassword() + { + return $this->password; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php b/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php new file mode 100644 index 000000000..a0dcf6fd3 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php @@ -0,0 +1,114 @@ + Smtp\Auth\Crammd5::class, + 'cramMd5' => Smtp\Auth\Crammd5::class, + 'CramMd5' => Smtp\Auth\Crammd5::class, + 'cramMD5' => Smtp\Auth\Crammd5::class, + 'CramMD5' => Smtp\Auth\Crammd5::class, + 'login' => Smtp\Auth\Login::class, + 'Login' => Smtp\Auth\Login::class, + 'plain' => Smtp\Auth\Plain::class, + 'Plain' => Smtp\Auth\Plain::class, + 'smtp' => Smtp::class, + 'Smtp' => Smtp::class, + 'SMTP' => Smtp::class, + + // Legacy Zend Framework aliases + \Zend\Mail\Protocol\Smtp\Auth\Crammd5::class => Smtp\Auth\Crammd5::class, + \Zend\Mail\Protocol\Smtp\Auth\Login::class => Smtp\Auth\Login::class, + \Zend\Mail\Protocol\Smtp\Auth\Plain::class => Smtp\Auth\Plain::class, + \Zend\Mail\Protocol\Smtp::class => Smtp::class, + + // v2 normalized FQCNs + 'zendmailprotocolsmtpauthcrammd5' => Smtp\Auth\Crammd5::class, + 'zendmailprotocolsmtpauthlogin' => Smtp\Auth\Login::class, + 'zendmailprotocolsmtpauthplain' => Smtp\Auth\Plain::class, + 'zendmailprotocolsmtp' => Smtp::class, + ]; + + /** + * Service factories + * + * @var array + */ + protected $factories = [ + Smtp\Auth\Crammd5::class => InvokableFactory::class, + Smtp\Auth\Login::class => InvokableFactory::class, + Smtp\Auth\Plain::class => InvokableFactory::class, + Smtp::class => InvokableFactory::class, + + // v2 normalized service names + + 'laminasmailprotocolsmtpauthcrammd5' => InvokableFactory::class, + 'laminasmailprotocolsmtpauthlogin' => InvokableFactory::class, + 'laminasmailprotocolsmtpauthplain' => InvokableFactory::class, + 'laminasmailprotocolsmtp' => InvokableFactory::class, + ]; + + /** + * Plugins must be an instance of the Smtp class + * + * @var string + */ + protected $instanceOf = Smtp::class; + + /** + * Validate a retrieved plugin instance (v3). + * + * @param object $plugin + * @throws InvalidServiceException + */ + public function validate($plugin) + { + if (! $plugin instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + 'Plugin of type %s is invalid; must extend %s', + (is_object($plugin) ? get_class($plugin) : gettype($plugin)), + Smtp::class + )); + } + } + + /** + * Validate a retrieved plugin instance (v2). + * + * @param object $plugin + * @throws Exception\InvalidArgumentException + */ + public function validatePlugin($plugin) + { + try { + $this->validate($plugin); + } catch (InvalidServiceException $e) { + throw new Exception\InvalidArgumentException( + $e->getMessage(), + $e->getCode(), + $e + ); + } + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php b/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php new file mode 100644 index 000000000..7a8834a05 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php @@ -0,0 +1,54 @@ +creationOptions); + } + + /** + * laminas-servicemanager v2 support for invocation options. + * + * @param array $options + * @return void + */ + public function setCreationOptions(array $options) + { + $this->creationOptions = $options; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage.php b/lib/laminas/laminas-mail/src/Storage.php new file mode 100644 index 000000000..81f13197f --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage.php @@ -0,0 +1,23 @@ + true, + 'delete' => false, + 'create' => false, + 'top' => false, + 'fetchPart' => true, + 'flags' => false, + ]; + + /** + * current iteration position + * @var int + */ + protected $iterationPos = 0; + + /** + * maximum iteration position (= message count) + * @var null|int + */ + protected $iterationMax = null; + + /** + * used message class, change it in an extended class to extend the returned message class + * @var string + */ + protected $messageClass = Message::class; + + /** + * Getter for has-properties. The standard has properties + * are: hasFolder, hasUniqueid, hasDelete, hasCreate, hasTop + * + * The valid values for the has-properties are: + * - true if a feature is supported + * - false if a feature is not supported + * - null is it's not yet known or it can't be know if a feature is supported + * + * @param string $var property name + * @throws Exception\InvalidArgumentException + * @return bool supported or not + */ + public function __get($var) + { + if (strpos($var, 'has') === 0) { + $var = strtolower(substr($var, 3)); + return isset($this->has[$var]) ? $this->has[$var] : null; + } + + throw new Exception\InvalidArgumentException($var . ' not found'); + } + + /** + * Get a full list of features supported by the specific mail lib and the server + * + * @return array list of features as array(feature_name => true|false[|null]) + */ + public function getCapabilities() + { + return $this->has; + } + + /** + * Count messages messages in current box/folder + * + * @return int number of messages + * @throws Exception\ExceptionInterface + */ + abstract public function countMessages(); + + /** + * Get a list of messages with number and size + * + * @param int $id number of message + * @return int|array size of given message of list with all messages as array(num => size) + */ + abstract public function getSize($id = 0); + + /** + * Get a message with headers and body + * + * @param $id int number of message + * @return Message + */ + abstract public function getMessage($id); + + /** + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + */ + abstract public function getRawHeader($id, $part = null, $topLines = 0); + + /** + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @return string raw content + */ + abstract public function getRawContent($id, $part = null); + + /** + * Create instance with parameters + * + * @param array $params mail reader specific parameters + * @throws Exception\ExceptionInterface + */ + abstract public function __construct($params); + + /** + * Destructor calls close() and therefore closes the resource. + */ + public function __destruct() + { + $this->close(); + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + */ + abstract public function close(); + + /** + * Keep the resource alive. + */ + abstract public function noop(); + + /** + * delete a message from current box/folder + * + * @param $id + */ + abstract public function removeMessage($id); + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws Exception\ExceptionInterface + */ + abstract public function getUniqueId($id = null); + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @return int message number + * @throws Exception\ExceptionInterface + */ + abstract public function getNumberByUniqueId($id); + + // interface implementations follows + + /** + * Countable::count() + * + * @return int + */ + public function count() + { + return $this->countMessages(); + } + + /** + * ArrayAccess::offsetExists() + * + * @param int $id + * @return bool + */ + public function offsetExists($id) + { + try { + if ($this->getMessage($id)) { + return true; + } + } catch (Exception\ExceptionInterface $e) { + } + + return false; + } + + /** + * ArrayAccess::offsetGet() + * + * @param int $id + * @return \Laminas\Mail\Storage\Message message object + */ + public function offsetGet($id) + { + return $this->getMessage($id); + } + + /** + * ArrayAccess::offsetSet() + * + * @param mixed $id + * @param mixed $value + * @throws Exception\RuntimeException + */ + public function offsetSet($id, $value) + { + throw new Exception\RuntimeException('cannot write mail messages via array access'); + } + + /** + * ArrayAccess::offsetUnset() + * + * @param int $id + * @return bool success + */ + public function offsetUnset($id) + { + return $this->removeMessage($id); + } + + /** + * Iterator::rewind() + * + * Rewind always gets the new count from the storage. Thus if you use + * the interfaces and your scripts take long you should use reset() + * from time to time. + */ + public function rewind() + { + $this->iterationMax = $this->countMessages(); + $this->iterationPos = 1; + } + + /** + * Iterator::current() + * + * @return Message current message + */ + public function current() + { + return $this->getMessage($this->iterationPos); + } + + /** + * Iterator::key() + * + * @return int id of current position + */ + public function key() + { + return $this->iterationPos; + } + + /** + * Iterator::next() + */ + public function next() + { + ++$this->iterationPos; + } + + /** + * Iterator::valid() + * + * @return bool + */ + public function valid() + { + if ($this->iterationMax === null) { + $this->iterationMax = $this->countMessages(); + } + return $this->iterationPos && $this->iterationPos <= $this->iterationMax; + } + + /** + * SeekableIterator::seek() + * + * @param int $pos + * @throws Exception\OutOfBoundsException + */ + public function seek($pos) + { + if ($this->iterationMax === null) { + $this->iterationMax = $this->countMessages(); + } + + if ($pos > $this->iterationMax) { + throw new Exception\OutOfBoundsException('this position does not exist'); + } + $this->iterationPos = $pos; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php b/lib/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php new file mode 100644 index 000000000..a049e2cf7 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php @@ -0,0 +1,15 @@ + \Laminas\Mail\Storage\Folder folder) + * @var array + */ + protected $folders; + + /** + * local name (name of folder in parent folder) + * @var string + */ + protected $localName; + + /** + * global name (absolute name of folder) + * @var string + */ + protected $globalName; + + /** + * folder is selectable if folder is able to hold messages, otherwise it is a parent folder + * @var bool + */ + protected $selectable = true; + + /** + * create a new mail folder instance + * + * @param string $localName name of folder in current subdirectory + * @param string $globalName absolute name of folder + * @param bool $selectable if true folder holds messages, if false it's + * just a parent for subfolders (Default: true) + * @param array $folders init with given instances of Folder as subfolders + */ + public function __construct($localName, $globalName = '', $selectable = true, array $folders = []) + { + $this->localName = $localName; + $this->globalName = $globalName ? $globalName : $localName; + $this->selectable = $selectable; + $this->folders = $folders; + } + + /** + * implements RecursiveIterator::hasChildren() + * + * @return bool current element has children + */ + public function hasChildren() + { + $current = $this->current(); + return $current && $current instanceof Folder && ! $current->isLeaf(); + } + + /** + * implements RecursiveIterator::getChildren() + * + * @return \Laminas\Mail\Storage\Folder same as self::current() + */ + public function getChildren() + { + return $this->current(); + } + + /** + * implements Iterator::valid() + * + * @return bool check if there's a current element + */ + public function valid() + { + return key($this->folders) !== null; + } + + /** + * implements Iterator::next() + */ + public function next() + { + next($this->folders); + } + + /** + * implements Iterator::key() + * + * @return string key/local name of current element + */ + public function key() + { + return key($this->folders); + } + + /** + * implements Iterator::current() + * + * @return \Laminas\Mail\Storage\Folder current folder + */ + public function current() + { + return current($this->folders); + } + + /** + * implements Iterator::rewind() + */ + public function rewind() + { + reset($this->folders); + } + + /** + * get subfolder named $name + * + * @param string $name wanted subfolder + * @throws Exception\InvalidArgumentException + * @return \Laminas\Mail\Storage\Folder folder named $folder + */ + public function __get($name) + { + if (! isset($this->folders[$name])) { + throw new Exception\InvalidArgumentException("no subfolder named $name"); + } + + return $this->folders[$name]; + } + + /** + * add or replace subfolder named $name + * + * @param string $name local name of subfolder + * @param \Laminas\Mail\Storage\Folder $folder instance for new subfolder + */ + public function __set($name, Folder $folder) + { + $this->folders[$name] = $folder; + } + + /** + * remove subfolder named $name + * + * @param string $name local name of subfolder + */ + public function __unset($name) + { + unset($this->folders[$name]); + } + + /** + * magic method for easy output of global name + * + * @return string global name of folder + */ + public function __toString() + { + return (string) $this->getGlobalName(); + } + + /** + * get local name + * + * @return string local name + */ + public function getLocalName() + { + return $this->localName; + } + + /** + * get global name + * + * @return string global name + */ + public function getGlobalName() + { + return $this->globalName; + } + + /** + * is this folder selectable? + * + * @return bool selectable + */ + public function isSelectable() + { + return $this->selectable; + } + + /** + * check if folder has no subfolder + * + * @return bool true if no subfolders + */ + public function isLeaf() + { + return empty($this->folders); + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php b/lib/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php new file mode 100644 index 000000000..d2f17b236 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php @@ -0,0 +1,38 @@ +dirname) || ! is_dir($params->dirname)) { + throw new Exception\InvalidArgumentException('no valid dirname given in params'); + } + + $this->rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + $this->delim = isset($params->delim) ? $params->delim : '.'; + + $this->buildFolderTree(); + $this->selectFolder(! empty($params->folder) ? $params->folder : 'INBOX'); + $this->has['top'] = true; + $this->has['flags'] = true; + } + + /** + * find all subfolders and mbox files for folder structure + * + * Result is save in Storage\Folder instances with the root in $this->rootFolder. + * $parentFolder and $parentGlobalName are only used internally for recursion. + * + * @throws Exception\RuntimeException + */ + protected function buildFolderTree() + { + $this->rootFolder = new Storage\Folder('/', '/', false); + $this->rootFolder->INBOX = new Storage\Folder('INBOX', 'INBOX', true); + + ErrorHandler::start(E_WARNING); + $dh = opendir($this->rootdir); + $error = ErrorHandler::stop(); + if (! $dh) { + throw new Exception\RuntimeException("can't read folders in maildir", 0, $error); + } + $dirs = []; + + while (($entry = readdir($dh)) !== false) { + // maildir++ defines folders must start with . + if ($entry[0] != '.' || $entry == '.' || $entry == '..') { + continue; + } + + if ($this->isMaildir($this->rootdir . $entry)) { + $dirs[] = $entry; + } + } + closedir($dh); + + sort($dirs); + $stack = [null]; + $folderStack = [null]; + $parentFolder = $this->rootFolder; + $parent = '.'; + + foreach ($dirs as $dir) { + do { + if (strpos($dir, $parent) === 0) { + $local = substr($dir, strlen($parent)); + if (strpos($local, $this->delim) !== false) { + throw new Exception\RuntimeException('error while reading maildir'); + } + array_push($stack, $parent); + $parent = $dir . $this->delim; + $folder = new Storage\Folder($local, substr($dir, 1), true); + $parentFolder->$local = $folder; + array_push($folderStack, $parentFolder); + $parentFolder = $folder; + break; + } elseif ($stack) { + $parent = array_pop($stack); + $parentFolder = array_pop($folderStack); + } + } while ($stack); + if (! $stack) { + throw new Exception\RuntimeException('error while reading maildir'); + } + } + } + + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @throws \Laminas\Mail\Storage\Exception\InvalidArgumentException + * @return \Laminas\Mail\Storage\Folder root or wanted folder + */ + public function getFolders($rootFolder = null) + { + if (! $rootFolder || $rootFolder == 'INBOX') { + return $this->rootFolder; + } + + // rootdir is same as INBOX in maildir + if (strpos($rootFolder, 'INBOX' . $this->delim) === 0) { + $rootFolder = substr($rootFolder, 6); + } + $currentFolder = $this->rootFolder; + $subname = trim($rootFolder, $this->delim); + + while ($currentFolder) { + ErrorHandler::start(E_NOTICE); + list($entry, $subname) = explode($this->delim, $subname, 2); + ErrorHandler::stop(); + $currentFolder = $currentFolder->$entry; + if (! $subname) { + break; + } + } + + if ($currentFolder->getGlobalName() != rtrim($rootFolder, $this->delim)) { + throw new Exception\InvalidArgumentException("folder $rootFolder not found"); + } + return $currentFolder; + } + + /** + * select given folder + * + * folder must be selectable! + * + * @param Storage\Folder|string $globalName global name of folder or + * instance for subfolder + * @throws Exception\RuntimeException + */ + public function selectFolder($globalName) + { + $this->currentFolder = (string) $globalName; + + // getting folder from folder tree for validation + $folder = $this->getFolders($this->currentFolder); + + try { + $this->openMaildir($this->rootdir . '.' . $folder->getGlobalName()); + } catch (Exception\ExceptionInterface $e) { + // check what went wrong + if (! $folder->isSelectable()) { + throw new Exception\RuntimeException("{$this->currentFolder} is not selectable", 0, $e); + } + // seems like file has vanished; rebuilding folder tree - but it's still an exception + $this->buildFolderTree(); + throw new Exception\RuntimeException( + 'seems like the maildir has vanished; I have rebuilt the folder tree; ' + . 'search for another folder and try again', + 0, + $e + ); + } + } + + /** + * get Storage\Folder instance for current folder + * + * @return Storage\Folder instance of current folder + */ + public function getCurrentFolder() + { + return $this->currentFolder; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Folder/Mbox.php b/lib/laminas/laminas-mail/src/Storage/Folder/Mbox.php new file mode 100644 index 000000000..e05a9a57a --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Folder/Mbox.php @@ -0,0 +1,213 @@ +filename)) { + throw new Exception\InvalidArgumentException(sprintf('use %s for a single file', Storage\Mbox::class)); + } + + if (! isset($params->dirname) || ! is_dir($params->dirname)) { + throw new Exception\InvalidArgumentException('no valid dirname given in params'); + } + + $this->rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + $this->buildFolderTree($this->rootdir); + $this->selectFolder(! empty($params->folder) ? $params->folder : 'INBOX'); + $this->has['top'] = true; + $this->has['uniqueid'] = false; + } + + /** + * find all subfolders and mbox files for folder structure + * + * Result is save in Storage\Folder instances with the root in $this->rootFolder. + * $parentFolder and $parentGlobalName are only used internally for recursion. + * + * @param string $currentDir call with root dir, also used for recursion. + * @param Storage\Folder|null $parentFolder used for recursion + * @param string $parentGlobalName used for recursion + * @throws Exception\InvalidArgumentException + */ + protected function buildFolderTree($currentDir, $parentFolder = null, $parentGlobalName = '') + { + if (! $parentFolder) { + $this->rootFolder = new Storage\Folder('/', '/', false); + $parentFolder = $this->rootFolder; + } + + ErrorHandler::start(E_WARNING); + $dh = opendir($currentDir); + ErrorHandler::stop(); + if (! $dh) { + throw new Exception\InvalidArgumentException("can't read dir $currentDir"); + } + while (($entry = readdir($dh)) !== false) { + // ignore hidden files for mbox + if ($entry[0] == '.') { + continue; + } + $absoluteEntry = $currentDir . $entry; + $globalName = $parentGlobalName . DIRECTORY_SEPARATOR . $entry; + if (is_file($absoluteEntry) && $this->isMboxFile($absoluteEntry)) { + $parentFolder->$entry = new Storage\Folder($entry, $globalName); + continue; + } + if (! is_dir($absoluteEntry) /* || $entry == '.' || $entry == '..' */) { + continue; + } + $folder = new Storage\Folder($entry, $globalName, false); + $parentFolder->$entry = $folder; + $this->buildFolderTree($absoluteEntry . DIRECTORY_SEPARATOR, $folder, $globalName); + } + + closedir($dh); + } + + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @return Storage\Folder root or wanted folder + * @throws Exception\InvalidArgumentException + */ + public function getFolders($rootFolder = null) + { + if (! $rootFolder) { + return $this->rootFolder; + } + + $currentFolder = $this->rootFolder; + $subname = trim($rootFolder, DIRECTORY_SEPARATOR); + while ($currentFolder) { + ErrorHandler::start(E_NOTICE); + list($entry, $subname) = explode(DIRECTORY_SEPARATOR, $subname, 2); + ErrorHandler::stop(); + $currentFolder = $currentFolder->$entry; + if (! $subname) { + break; + } + } + + if ($currentFolder->getGlobalName() != DIRECTORY_SEPARATOR . trim($rootFolder, DIRECTORY_SEPARATOR)) { + throw new Exception\InvalidArgumentException("folder $rootFolder not found"); + } + return $currentFolder; + } + + /** + * select given folder + * + * folder must be selectable! + * + * @param Storage\Folder|string $globalName global name of folder or + * instance for subfolder + * @throws Exception\RuntimeException + */ + public function selectFolder($globalName) + { + $this->currentFolder = (string) $globalName; + + // getting folder from folder tree for validation + $folder = $this->getFolders($this->currentFolder); + + try { + $this->openMboxFile($this->rootdir . $folder->getGlobalName()); + } catch (Exception\ExceptionInterface $e) { + // check what went wrong + if (! $folder->isSelectable()) { + throw new Exception\RuntimeException("{$this->currentFolder} is not selectable", 0, $e); + } + // seems like file has vanished; rebuilding folder tree - but it's still an exception + $this->buildFolderTree($this->rootdir); + throw new Exception\RuntimeException( + 'seems like the mbox file has vanished; I have rebuilt the folder tree; ' + . 'search for another folder and try again', + 0, + $e + ); + } + } + + /** + * get Storage\Folder instance for current folder + * + * @return Storage\Folder instance of current folder + * @throws Exception\ExceptionInterface + */ + public function getCurrentFolder() + { + return $this->currentFolder; + } + + /** + * magic method for serialize() + * + * with this method you can cache the mbox class + * + * @return array name of variables + */ + public function __sleep() + { + return array_merge(parent::__sleep(), ['currentFolder', 'rootFolder', 'rootdir']); + } + + /** + * magic method for unserialize(), with this method you can cache the mbox class + */ + public function __wakeup() + { + // if cache is stall selectFolder() rebuilds the tree on error + parent::__wakeup(); + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Imap.php b/lib/laminas/laminas-mail/src/Storage/Imap.php new file mode 100644 index 000000000..029e950ec --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Imap.php @@ -0,0 +1,546 @@ + Mail\Storage::FLAG_PASSED, + '\Answered' => Mail\Storage::FLAG_ANSWERED, + '\Seen' => Mail\Storage::FLAG_SEEN, + '\Unseen' => Mail\Storage::FLAG_UNSEEN, + '\Deleted' => Mail\Storage::FLAG_DELETED, + '\Draft' => Mail\Storage::FLAG_DRAFT, + '\Flagged' => Mail\Storage::FLAG_FLAGGED, + ]; + + /** + * IMAP flags to search criteria + * @var array + */ + protected static $searchFlags = [ + '\Recent' => 'RECENT', + '\Answered' => 'ANSWERED', + '\Seen' => 'SEEN', + '\Unseen' => 'UNSEEN', + '\Deleted' => 'DELETED', + '\Draft' => 'DRAFT', + '\Flagged' => 'FLAGGED', + ]; + + /** + * Count messages all messages in current box + * + * @param null $flags + * @throws Exception\RuntimeException + * @throws Protocol\Exception\RuntimeException + * @return int number of messages + */ + public function countMessages($flags = null) + { + if (! $this->currentFolder) { + throw new Exception\RuntimeException('No selected folder to count'); + } + + if ($flags === null) { + return count($this->protocol->search(['ALL'])); + } + + $params = []; + foreach ((array) $flags as $flag) { + if (isset(static::$searchFlags[$flag])) { + $params[] = static::$searchFlags[$flag]; + } else { + $params[] = 'KEYWORD'; + $params[] = $this->protocol->escapeString($flag); + } + } + return count($this->protocol->search($params)); + } + + /** + * get a list of messages with number and size + * + * @param int $id number of message + * @return int|array size of given message of list with all messages as [num => size] + * @throws Protocol\Exception\RuntimeException + */ + public function getSize($id = 0) + { + if ($id) { + return $this->protocol->fetch('RFC822.SIZE', $id); + } + return $this->protocol->fetch('RFC822.SIZE', 1, INF); + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return Message + * @throws Protocol\Exception\RuntimeException + */ + public function getMessage($id) + { + $data = $this->protocol->fetch(['FLAGS', 'RFC822.HEADER'], $id); + $header = $data['RFC822.HEADER']; + + $flags = []; + foreach ($data['FLAGS'] as $flag) { + $flags[] = isset(static::$knownFlags[$flag]) ? static::$knownFlags[$flag] : $flag; + } + + return new $this->messageClass(['handler' => $this, 'id' => $id, 'headers' => $header, 'flags' => $flags]); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws Exception\RuntimeException + * @throws Protocol\Exception\RuntimeException + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + // TODO: toplines + return $this->protocol->fetch('RFC822.HEADER', $id); + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @return string raw content + * @throws Protocol\Exception\RuntimeException + * @throws Exception\RuntimeException + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + return $this->protocol->fetch('RFC822.TEXT', $id); + } + + /** + * create instance with parameters + * + * Supported parameters are + * + * - user username + * - host hostname or ip address of IMAP server [optional, default = 'localhost'] + * - password password for user 'username' [optional, default = ''] + * - port port for IMAP server [optional, default = 110] + * - ssl 'SSL' or 'TLS' for secure sockets + * - folder select this folder [optional, default = 'INBOX'] + * + * @param array|Protocol\Imap $params mail reader specific parameters or configured Imap protocol object + * @throws Exception\RuntimeException + * @throws Exception\InvalidArgumentException + * @throws Protocol\Exception\RuntimeException + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object) $params; + } + + $this->has['flags'] = true; + + if ($params instanceof Protocol\Imap) { + $this->protocol = $params; + try { + $this->selectFolder('INBOX'); + } catch (Exception\ExceptionInterface $e) { + throw new Exception\RuntimeException('cannot select INBOX, is this a valid transport?', 0, $e); + } + return; + } + + if (! isset($params->user)) { + throw new Exception\InvalidArgumentException('need at least user in params'); + } + + $host = isset($params->host) ? $params->host : 'localhost'; + $password = isset($params->password) ? $params->password : ''; + $port = isset($params->port) ? $params->port : null; + $ssl = isset($params->ssl) ? $params->ssl : false; + + $this->protocol = new Protocol\Imap(); + $this->protocol->connect($host, $port, $ssl); + if (! $this->protocol->login($params->user, $password)) { + throw new Exception\RuntimeException('cannot login, user or password wrong'); + } + $this->selectFolder(isset($params->folder) ? $params->folder : 'INBOX'); + } + + /** + * Close resource for mail lib. + * + * If you need to control, when the resource is closed. Otherwise the + * destructor would call this. + */ + public function close() + { + $this->currentFolder = ''; + $this->protocol->logout(); + } + + /** + * Keep the server busy. + * + * @throws Exception\RuntimeException + */ + public function noop() + { + if (! $this->protocol->noop()) { + throw new Exception\RuntimeException('could not do nothing'); + } + } + + /** + * Remove a message from server. + * + * If you're doing that from a web environment you should be careful and + * use a uniqueid as parameter if possible to identify the message. + * + * @param int $id number of message + * @throws Exception\RuntimeException + */ + public function removeMessage($id) + { + if (! $this->protocol->store([Mail\Storage::FLAG_DELETED], $id, null, '+')) { + throw new Exception\RuntimeException('cannot set deleted flag'); + } + // TODO: expunge here or at close? we can handle an error here better and are more fail safe + if (! $this->protocol->expunge()) { + throw new Exception\RuntimeException('message marked as deleted, but could not expunge'); + } + } + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message + * number. + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws Protocol\Exception\RuntimeException + */ + public function getUniqueId($id = null) + { + if ($id) { + return $this->protocol->fetch('UID', $id); + } + + return $this->protocol->fetch('UID', 1, INF); + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should + * use unique ids as parameter and use this method to translate it to + * message number right before calling removeMessage() + * + * @param string $id unique id + * @throws Exception\InvalidArgumentException + * @return int message number + */ + public function getNumberByUniqueId($id) + { + // TODO: use search to find number directly + $ids = $this->getUniqueId(); + foreach ($ids as $k => $v) { + if ($v == $id) { + return $k; + } + } + + throw new Exception\InvalidArgumentException('unique id not found'); + } + + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @throws Exception\RuntimeException + * @throws Exception\InvalidArgumentException + * @throws Protocol\Exception\RuntimeException + * @return Folder root or wanted folder + */ + public function getFolders($rootFolder = null) + { + $folders = $this->protocol->listMailbox((string) $rootFolder); + if (! $folders) { + throw new Exception\InvalidArgumentException('folder not found'); + } + + ksort($folders, SORT_STRING); + $root = new Folder('/', '/', false); + $stack = [null]; + $folderStack = [null]; + $parentFolder = $root; + $parent = ''; + + foreach ($folders as $globalName => $data) { + do { + if (! $parent || strpos($globalName, $parent) === 0) { + $pos = strrpos($globalName, $data['delim']); + if ($pos === false) { + $localName = $globalName; + } else { + $localName = substr($globalName, $pos + 1); + } + $selectable = ! $data['flags'] || ! in_array('\\Noselect', $data['flags']); + + array_push($stack, $parent); + $parent = $globalName . $data['delim']; + $folder = new Folder($localName, $globalName, $selectable); + $parentFolder->$localName = $folder; + array_push($folderStack, $parentFolder); + $parentFolder = $folder; + $this->delimiter = $data['delim']; + break; + } elseif ($stack) { + $parent = array_pop($stack); + $parentFolder = array_pop($folderStack); + } + } while ($stack); + if (! $stack) { + throw new Exception\RuntimeException('error while constructing folder tree'); + } + } + + return $root; + } + + /** + * select given folder + * + * folder must be selectable! + * + * @param Folder|string $globalName global name of folder or instance for subfolder + * @throws Exception\RuntimeException + * @throws Protocol\Exception\RuntimeException + */ + public function selectFolder($globalName) + { + $this->currentFolder = $globalName; + if (! $this->protocol->select($this->currentFolder)) { + $this->currentFolder = ''; + throw new Exception\RuntimeException('cannot change folder, maybe it does not exist'); + } + } + + /** + * get Folder instance for current folder + * + * @return Folder instance of current folder + */ + public function getCurrentFolder() + { + return $this->currentFolder; + } + + /** + * create a new folder + * + * This method also creates parent folders if necessary. Some mail storages + * may restrict, which folder may be used as parent or which chars may be + * used in the folder name + * + * @param string $name global name of folder, local name if $parentFolder + * is set + * @param string|Folder $parentFolder parent folder for new folder, else + * root folder is parent + * @throws Exception\RuntimeException + */ + public function createFolder($name, $parentFolder = null) + { + // TODO: we assume / as the hierarchy delim - need to get that from the folder class! + if ($parentFolder instanceof Folder) { + $folder = $parentFolder->getGlobalName() . '/' . $name; + } elseif ($parentFolder !== null) { + $folder = $parentFolder . '/' . $name; + } else { + $folder = $name; + } + + if (! $this->protocol->create($folder)) { + throw new Exception\RuntimeException('cannot create folder'); + } + } + + /** + * remove a folder + * + * @param string|Folder $name name or instance of folder + * @throws Exception\RuntimeException + */ + public function removeFolder($name) + { + if ($name instanceof Folder) { + $name = $name->getGlobalName(); + } + + if (! $this->protocol->delete($name)) { + throw new Exception\RuntimeException('cannot delete folder'); + } + } + + /** + * rename and/or move folder + * + * The new name has the same restrictions as in createFolder() + * + * @param string|Folder $oldName name or instance of folder + * @param string $newName new global name of folder + * @throws Exception\RuntimeException + */ + public function renameFolder($oldName, $newName) + { + if ($oldName instanceof Folder) { + $oldName = $oldName->getGlobalName(); + } + + if (! $this->protocol->rename($oldName, $newName)) { + throw new Exception\RuntimeException('cannot rename folder'); + } + } + + /** + * append a new message to mail storage + * + * @param string $message message as string or instance of message class + * @param null|string|Folder $folder folder for new message, else current + * folder is taken + * @param null|array $flags set flags for new message, else a default set + * is used + * @throws Exception\RuntimeException + */ + public function appendMessage($message, $folder = null, $flags = null) + { + if ($folder === null) { + $folder = $this->currentFolder; + } + + if ($flags === null) { + $flags = [Mail\Storage::FLAG_SEEN]; + } + + // TODO: handle class instances for $message + if (! $this->protocol->append($folder, $message, $flags)) { + throw new Exception\RuntimeException( + 'cannot create message, please check if the folder exists and your flags' + ); + } + } + + /** + * copy an existing message + * + * @param int $id number of message + * @param string|Folder $folder name or instance of target folder + * @throws Exception\RuntimeException + */ + public function copyMessage($id, $folder) + { + if (! $this->protocol->copy($folder, $id)) { + throw new Exception\RuntimeException('cannot copy message, does the folder exist?'); + } + } + + /** + * move an existing message + * + * NOTE: IMAP has no native move command, thus it's emulated with copy and delete + * + * @param int $id number of message + * @param string|Folder $folder name or instance of target folder + * @throws Exception\RuntimeException + */ + public function moveMessage($id, $folder) + { + $this->copyMessage($id, $folder); + $this->removeMessage($id); + } + + /** + * set flags for message + * + * NOTE: this method can't set the recent flag. + * + * @param int $id number of message + * @param array $flags new flags for message + * @throws Exception\RuntimeException + */ + public function setFlags($id, $flags) + { + if (! $this->protocol->store($flags, $id)) { + throw new Exception\RuntimeException( + 'cannot set flags, have you tried to set the recent flag or special chars?' + ); + } + } + + /** + * get IMAP delimiter + * + * @return string|null + */ + public function delimiter() + { + if (! isset($this->delimiter)) { + $this->getFolders(); + } + return $this->delimiter; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Maildir.php b/lib/laminas/laminas-mail/src/Storage/Maildir.php new file mode 100644 index 000000000..aab9d0c56 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Maildir.php @@ -0,0 +1,416 @@ + Mail\Storage::FLAG_DRAFT, + 'F' => Mail\Storage::FLAG_FLAGGED, + 'P' => Mail\Storage::FLAG_PASSED, + 'R' => Mail\Storage::FLAG_ANSWERED, + 'S' => Mail\Storage::FLAG_SEEN, + 'T' => Mail\Storage::FLAG_DELETED, + ]; + + // TODO: getFlags($id) for fast access if headers are not needed (i.e. just setting flags)? + + /** + * Count messages all messages in current box + * + * @param mixed $flags + * @return int number of messages + */ + public function countMessages($flags = null) + { + if ($flags === null) { + return count($this->files); + } + + $count = 0; + if (! is_array($flags)) { + foreach ($this->files as $file) { + if (isset($file['flaglookup'][$flags])) { + ++$count; + } + } + return $count; + } + + $flags = array_flip($flags); + foreach ($this->files as $file) { + foreach ($flags as $flag => $v) { + if (! isset($file['flaglookup'][$flag])) { + continue 2; + } + } + ++$count; + } + return $count; + } + + /** + * Get one or all fields from file structure. Also checks if message is valid + * + * @param int $id message number + * @param string|null $field wanted field + * @throws Exception\InvalidArgumentException + * @return string|array wanted field or all fields as array + */ + protected function getFileData($id, $field = null) + { + if (! isset($this->files[$id - 1])) { + throw new Exception\InvalidArgumentException('id does not exist'); + } + + if (! $field) { + return $this->files[$id - 1]; + } + + if (! isset($this->files[$id - 1][$field])) { + throw new Exception\InvalidArgumentException('field does not exist'); + } + + return $this->files[$id - 1][$field]; + } + + /** + * Get a list of messages with number and size + * + * @param int|null $id number of message or null for all messages + * @return int|array size of given message of list with all messages as array(num => size) + */ + public function getSize($id = null) + { + if ($id !== null) { + $filedata = $this->getFileData($id); + return isset($filedata['size']) ? $filedata['size'] : filesize($filedata['filename']); + } + + $result = []; + foreach ($this->files as $num => $data) { + $result[$num + 1] = isset($data['size']) ? $data['size'] : filesize($data['filename']); + } + + return $result; + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return \Laminas\Mail\Storage\Message\File + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getMessage($id) + { + // TODO that's ugly, would be better to let the message class decide + if (\trim($this->messageClass, '\\') === Message\File::class + || is_subclass_of($this->messageClass, Message\File::class) + ) { + return new $this->messageClass([ + 'file' => $this->getFileData($id, 'filename'), + 'flags' => $this->getFileData($id, 'flags'), + ]); + } + + return new $this->messageClass([ + 'handler' => $this, + 'id' => $id, + 'headers' => $this->getRawHeader($id), + 'flags' => $this->getFileData($id, 'flags'), + ]); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @throws Exception\RuntimeException + * @return string raw header + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + $fh = fopen($this->getFileData($id, 'filename'), 'r'); + + $content = ''; + while (! feof($fh)) { + $line = fgets($fh); + if (! trim($line)) { + break; + } + $content .= $line; + } + + fclose($fh); + return $content; + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @throws Exception\RuntimeException + * @return string raw content + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + $fh = fopen($this->getFileData($id, 'filename'), 'r'); + + while (! feof($fh)) { + $line = fgets($fh); + if (! trim($line)) { + break; + } + } + + $content = stream_get_contents($fh); + fclose($fh); + return $content; + } + + /** + * Create instance with parameters + * Supported parameters are: + * - dirname dirname of mbox file + * + * @param $params array mail reader specific parameters + * @throws Exception\InvalidArgumentException + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object) $params; + } + + if (! isset($params->dirname) || ! is_dir($params->dirname)) { + throw new Exception\InvalidArgumentException('no valid dirname given in params'); + } + + if (! $this->isMaildir($params->dirname)) { + throw new Exception\InvalidArgumentException('invalid maildir given'); + } + + $this->has['top'] = true; + $this->has['flags'] = true; + $this->openMaildir($params->dirname); + } + + /** + * check if a given dir is a valid maildir + * + * @param string $dirname name of dir + * @return bool dir is valid maildir + */ + protected function isMaildir($dirname) + { + if (file_exists($dirname . '/new') && ! is_dir($dirname . '/new')) { + return false; + } + if (file_exists($dirname . '/tmp') && ! is_dir($dirname . '/tmp')) { + return false; + } + return is_dir($dirname . '/cur'); + } + + /** + * open given dir as current maildir + * + * @param string $dirname name of maildir + * @throws Exception\RuntimeException + */ + protected function openMaildir($dirname) + { + if ($this->files) { + $this->close(); + } + + ErrorHandler::start(E_WARNING); + $dh = opendir($dirname . '/cur/'); + $error = ErrorHandler::stop(); + if (! $dh) { + throw new Exception\RuntimeException('cannot open maildir', 0, $error); + } + $this->getMaildirFiles($dh, $dirname . '/cur/'); + closedir($dh); + + ErrorHandler::start(E_WARNING); + $dh = opendir($dirname . '/new/'); + $error = ErrorHandler::stop(); + if (! $dh) { + throw new Exception\RuntimeException('cannot read recent mails in maildir', 0, $error); + } + + $this->getMaildirFiles($dh, $dirname . '/new/', [Mail\Storage::FLAG_RECENT]); + closedir($dh); + } + + /** + * find all files in opened dir handle and add to maildir files + * + * @param resource $dh dir handle used for search + * @param string $dirname dirname of dir in $dh + * @param array $defaultFlags default flags for given dir + */ + protected function getMaildirFiles($dh, $dirname, $defaultFlags = []) + { + while (($entry = readdir($dh)) !== false) { + if ($entry[0] == '.' || ! is_file($dirname . $entry)) { + continue; + } + + ErrorHandler::start(E_NOTICE); + list($uniq, $info) = explode(':', $entry, 2); + list(, $size) = explode(',', $uniq, 2); + ErrorHandler::stop(); + if ($size && $size[0] == 'S' && $size[1] == '=') { + $size = substr($size, 2); + } + if (! ctype_digit($size)) { + $size = null; + } + + ErrorHandler::start(E_NOTICE); + list($version, $flags) = explode(',', $info, 2); + ErrorHandler::stop(); + if ($version != 2) { + $flags = ''; + } + + $namedFlags = $defaultFlags; + $length = strlen($flags); + for ($i = 0; $i < $length; ++$i) { + $flag = $flags[$i]; + $namedFlags[$flag] = isset(static::$knownFlags[$flag]) ? static::$knownFlags[$flag] : $flag; + } + + $data = [ + 'uniq' => $uniq, + 'flags' => $namedFlags, + 'flaglookup' => array_flip($namedFlags), + 'filename' => $dirname . $entry + ]; + if ($size !== null) { + $data['size'] = (int) $size; + } + $this->files[] = $data; + } + \usort($this->files, function ($a, $b) { + return \strcmp($a['filename'], $b['filename']); + }); + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + * + */ + public function close() + { + $this->files = []; + } + + /** + * Waste some CPU cycles doing nothing. + * + * @return bool always return true + */ + public function noop() + { + return true; + } + + /** + * stub for not supported message deletion + * + * @param $id + * @throws Exception\RuntimeException + */ + public function removeMessage($id) + { + throw new Exception\RuntimeException('maildir is (currently) read-only'); + } + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + */ + public function getUniqueId($id = null) + { + if ($id) { + return $this->getFileData($id, 'uniq'); + } + + $ids = []; + foreach ($this->files as $num => $file) { + $ids[$num + 1] = $file['uniq']; + } + return $ids; + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @throws Exception\InvalidArgumentException + * @return int message number + */ + public function getNumberByUniqueId($id) + { + foreach ($this->files as $num => $file) { + if ($file['uniq'] == $id) { + return $num + 1; + } + } + + throw new Exception\InvalidArgumentException('unique id not found'); + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Mbox.php b/lib/laminas/laminas-mail/src/Storage/Mbox.php new file mode 100644 index 000000000..eb98b59d8 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Mbox.php @@ -0,0 +1,415 @@ + start, 'separator' => headersep, 'end' => end) + * @var array + */ + protected $positions; + + /** + * used message class, change it in an extended class to extend the returned message class + * @var string + */ + protected $messageClass = Message\File::class; + + /** + * end of Line for messages + * + * @var string|null + */ + protected $messageEOL; + + /** + * Count messages all messages in current box + * + * @return int number of messages + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function countMessages() + { + return count($this->positions); + } + + /** + * Get a list of messages with number and size + * + * @param int|null $id number of message or null for all messages + * @return int|array size of given message of list with all messages as array(num => size) + */ + public function getSize($id = 0) + { + if ($id) { + $pos = $this->positions[$id - 1]; + return $pos['end'] - $pos['start']; + } + + $result = []; + foreach ($this->positions as $num => $pos) { + $result[$num + 1] = $pos['end'] - $pos['start']; + } + + return $result; + } + + /** + * Get positions for mail message or throw exception if id is invalid + * + * @param int $id number of message + * @throws Exception\InvalidArgumentException + * @return array positions as in positions + */ + protected function getPos($id) + { + if (! isset($this->positions[$id - 1])) { + throw new Exception\InvalidArgumentException('id does not exist'); + } + + return $this->positions[$id - 1]; + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return \Laminas\Mail\Storage\Message\File + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getMessage($id) + { + // TODO that's ugly, would be better to let the message class decide + if (is_subclass_of($this->messageClass, Message\File::class) + || strtolower($this->messageClass) === strtolower(Message\File::class)) { + // TODO top/body lines + $messagePos = $this->getPos($id); + + $messageClassParams = [ + 'file' => $this->fh, + 'startPos' => $messagePos['start'], + 'endPos' => $messagePos['end'] + ]; + + if (isset($this->messageEOL)) { + $messageClassParams['EOL'] = $this->messageEOL; + } + + return new $this->messageClass($messageClassParams); + } + + $bodyLines = 0; // TODO: need a way to change that + + $message = $this->getRawHeader($id); + // file pointer is after headers now + if ($bodyLines) { + $message .= "\n"; + while ($bodyLines-- && ftell($this->fh) < $this->positions[$id - 1]['end']) { + $message .= fgets($this->fh); + } + } + + return new $this->messageClass(['handler' => $this, 'id' => $id, 'headers' => $message]); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + $messagePos = $this->getPos($id); + // TODO: toplines + return stream_get_contents($this->fh, $messagePos['separator'] - $messagePos['start'], $messagePos['start']); + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @return string raw content + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + $messagePos = $this->getPos($id); + return stream_get_contents($this->fh, $messagePos['end'] - $messagePos['separator'], $messagePos['separator']); + } + + /** + * Create instance with parameters + * Supported parameters are: + * - filename filename of mbox file + * + * @param $params array mail reader specific parameters + * @throws Exception\InvalidArgumentException + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object) $params; + } + + if (! isset($params->filename)) { + throw new Exception\InvalidArgumentException('no valid filename given in params'); + } + + if (isset($params->messageEOL)) { + $this->messageEOL = (string) $params->messageEOL; + } + + $this->openMboxFile($params->filename); + $this->has['top'] = true; + $this->has['uniqueid'] = false; + } + + /** + * check if given file is a mbox file + * + * if $file is a resource its file pointer is moved after the first line + * + * @param resource|string $file stream resource of name of file + * @param bool $fileIsString file is string or resource + * @return bool file is mbox file + */ + protected function isMboxFile($file, $fileIsString = true) + { + if ($fileIsString) { + ErrorHandler::start(E_WARNING); + $file = fopen($file, 'r'); + ErrorHandler::stop(); + if (! $file) { + return false; + } + } else { + fseek($file, 0); + } + + $result = false; + + $line = fgets($file) ?: ''; + if (strpos($line, 'From ') === 0) { + $result = true; + } + + if ($fileIsString) { + ErrorHandler::start(E_WARNING); + fclose($file); + ErrorHandler::stop(); + } + + return $result; + } + + /** + * open given file as current mbox file + * + * @param string $filename filename of mbox file + * @throws Exception\RuntimeException + * @throws Exception\InvalidArgumentException + */ + protected function openMboxFile($filename) + { + if ($this->fh) { + $this->close(); + } + + if (is_dir($filename)) { + throw new Exception\InvalidArgumentException('file is not a valid mbox file'); + } + + ErrorHandler::start(); + $this->fh = fopen($filename, 'r'); + $error = ErrorHandler::stop(); + if (! $this->fh) { + throw new Exception\RuntimeException('cannot open mbox file', 0, $error); + } + $this->filename = $filename; + $this->filemtime = filemtime($this->filename); + + if (! $this->isMboxFile($this->fh, false)) { + ErrorHandler::start(E_WARNING); + fclose($this->fh); + $error = ErrorHandler::stop(); + throw new Exception\InvalidArgumentException('file is not a valid mbox format', 0, $error); + } + + $messagePos = ['start' => ftell($this->fh), 'separator' => 0, 'end' => 0]; + while (($line = fgets($this->fh)) !== false) { + if (strpos($line, 'From ') === 0) { + $messagePos['end'] = ftell($this->fh) - strlen($line) - 2; // + newline + if (! $messagePos['separator']) { + $messagePos['separator'] = $messagePos['end']; + } + $this->positions[] = $messagePos; + $messagePos = ['start' => ftell($this->fh), 'separator' => 0, 'end' => 0]; + } + if (! $messagePos['separator'] && ! trim($line)) { + $messagePos['separator'] = ftell($this->fh); + } + } + + $messagePos['end'] = ftell($this->fh); + if (! $messagePos['separator']) { + $messagePos['separator'] = $messagePos['end']; + } + $this->positions[] = $messagePos; + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + * + */ + public function close() + { + ErrorHandler::start(E_WARNING); + fclose($this->fh); + ErrorHandler::stop(); + $this->positions = []; + } + + + /** + * Waste some CPU cycles doing nothing. + * + * @return bool always return true + */ + public function noop() + { + return true; + } + + + /** + * stub for not supported message deletion + * + * @param $id + * @throws Exception\RuntimeException + */ + public function removeMessage($id) + { + throw new Exception\RuntimeException('mbox is read-only'); + } + + /** + * get unique id for one or all messages + * + * Mbox does not support unique ids (yet) - it's always the same as the message number. + * That shouldn't be a problem, because we can't change mbox files. Therefor the message + * number is save enough. + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getUniqueId($id = null) + { + if ($id) { + // check if id exists + $this->getPos($id); + return $id; + } + + $range = range(1, $this->countMessages()); + return array_combine($range, $range); + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @return int message number + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getNumberByUniqueId($id) + { + // check if id exists + $this->getPos($id); + return $id; + } + + /** + * magic method for serialize() + * + * with this method you can cache the mbox class + * + * @return array name of variables + */ + public function __sleep() + { + return ['filename', 'positions', 'filemtime']; + } + + /** + * magic method for unserialize() + * + * with this method you can cache the mbox class + * for cache validation the mtime of the mbox file is used + * + * @throws Exception\RuntimeException + */ + public function __wakeup() + { + ErrorHandler::start(); + $filemtime = filemtime($this->filename); + ErrorHandler::stop(); + if ($this->filemtime != $filemtime) { + $this->close(); + $this->openMboxFile($this->filename); + } else { + ErrorHandler::start(); + $this->fh = fopen($this->filename, 'r'); + $error = ErrorHandler::stop(); + if (! $this->fh) { + throw new Exception\RuntimeException('cannot open mbox file', 0, $error); + } + } + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Message.php b/lib/laminas/laminas-mail/src/Storage/Message.php new file mode 100644 index 000000000..04fca5e63 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Message.php @@ -0,0 +1,86 @@ +flags = array_combine($params['flags'], $params['flags']); + } + + parent::__construct($params); + } + + /** + * return toplines as found after headers + * + * @return string toplines + */ + public function getTopLines() + { + return $this->topLines; + } + + /** + * check if flag is set + * + * @param mixed $flag a flag name, use constants defined in \Laminas\Mail\Storage + * @return bool true if set, otherwise false + */ + public function hasFlag($flag) + { + return isset($this->flags[$flag]); + } + + /** + * get all set flags + * + * @return array array with flags, key and value are the same for easy lookup + */ + public function getFlags() + { + return $this->flags; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Message/File.php b/lib/laminas/laminas-mail/src/Storage/Message/File.php new file mode 100644 index 000000000..9034eb7e3 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Message/File.php @@ -0,0 +1,70 @@ +flags = array_combine($params['flags'], $params['flags']); + } + + parent::__construct($params); + } + + /** + * return toplines as found after headers + * + * @return string toplines + */ + public function getTopLines() + { + return $this->topLines; + } + + /** + * check if flag is set + * + * @param mixed $flag a flag name, use constants defined in \Laminas\Mail\Storage + * @return bool true if set, otherwise false + */ + public function hasFlag($flag) + { + return isset($this->flags[$flag]); + } + + /** + * get all set flags + * + * @return array array with flags, key and value are the same for easy lookup + */ + public function getFlags() + { + return $this->flags; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Message/MessageInterface.php b/lib/laminas/laminas-mail/src/Storage/Message/MessageInterface.php new file mode 100644 index 000000000..6f924efdd --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Message/MessageInterface.php @@ -0,0 +1,34 @@ + value) or string, if a content part is found it's used as toplines + * - noToplines ignore content found after headers in param 'headers' + * - content content as string + * - strict strictly parse raw content + * + * @param array $params full message with or without headers + * @throws Exception\InvalidArgumentException + */ + public function __construct(array $params) + { + if (isset($params['handler'])) { + if (! $params['handler'] instanceof AbstractStorage) { + throw new Exception\InvalidArgumentException('handler is not a valid mail handler'); + } + if (! isset($params['id'])) { + throw new Exception\InvalidArgumentException('need a message id with a handler'); + } + + $this->mail = $params['handler']; + $this->messageNum = $params['id']; + } + + $params['strict'] = isset($params['strict']) ? $params['strict'] : false; + + if (isset($params['raw'])) { + Mime\Decode::splitMessage( + $params['raw'], + $this->headers, + $this->content, + Mime\Mime::LINEEND, + $params['strict'] + ); + } elseif (isset($params['headers'])) { + if (is_array($params['headers'])) { + $this->headers = new Headers(); + $this->headers->addHeaders($params['headers']); + } else { + if (empty($params['noToplines'])) { + Mime\Decode::splitMessage($params['headers'], $this->headers, $this->topLines); + } else { + $this->headers = Headers::fromString($params['headers']); + } + } + + if (isset($params['content'])) { + $this->content = $params['content']; + } + } + } + + /** + * Check if part is a multipart message + * + * @return bool if part is multipart + */ + public function isMultipart() + { + try { + return stripos($this->contentType, 'multipart/') === 0; + } catch (Exception\ExceptionInterface $e) { + return false; + } + } + + + /** + * Body of part + * + * If part is multipart the raw content of this part with all sub parts is returned + * + * @throws Exception\RuntimeException + * @return string body + */ + public function getContent() + { + if ($this->content !== null) { + return $this->content; + } + + if ($this->mail) { + return $this->mail->getRawContent($this->messageNum); + } + + throw new Exception\RuntimeException('no content'); + } + + /** + * Return size of part + * + * Quite simple implemented currently (not decoding). Handle with care. + * + * @return int size + */ + public function getSize() + { + return strlen($this->getContent()); + } + + + /** + * Cache content and split in parts if multipart + * + * @throws Exception\RuntimeException + * @return null + */ + protected function cacheContent() + { + // caching content if we can't fetch parts + if ($this->content === null && $this->mail) { + $this->content = $this->mail->getRawContent($this->messageNum); + } + + if (! $this->isMultipart()) { + return; + } + + // split content in parts + $boundary = $this->getHeaderField('content-type', 'boundary'); + if (! $boundary) { + throw new Exception\RuntimeException('no boundary found in content type to split message'); + } + $parts = Mime\Decode::splitMessageStruct($this->content, $boundary); + if ($parts === null) { + return; + } + $counter = 1; + foreach ($parts as $part) { + $this->parts[$counter++] = new static(['headers' => $part['header'], 'content' => $part['body']]); + } + } + + /** + * Get part of multipart message + * + * @param int $num number of part starting with 1 for first part + * @throws Exception\RuntimeException + * @return Part wanted part + */ + public function getPart($num) + { + if (isset($this->parts[$num])) { + return $this->parts[$num]; + } + + if (! $this->mail && $this->content === null) { + throw new Exception\RuntimeException('part not found'); + } + + if ($this->mail && $this->mail->hasFetchPart) { + // TODO: fetch part + // return + } + + $this->cacheContent(); + + if (! isset($this->parts[$num])) { + throw new Exception\RuntimeException('part not found'); + } + + return $this->parts[$num]; + } + + /** + * Count parts of a multipart part + * + * @return int number of sub-parts + */ + public function countParts() + { + if ($this->countParts) { + return $this->countParts; + } + + $this->countParts = count($this->parts); + if ($this->countParts) { + return $this->countParts; + } + + if ($this->mail && $this->mail->hasFetchPart) { + // TODO: fetch part + // return + } + + $this->cacheContent(); + + $this->countParts = count($this->parts); + return $this->countParts; + } + + /** + * Access headers collection + * + * Lazy-loads if not already attached. + * + * @return Headers + * @throws Exception\RuntimeException + */ + public function getHeaders() + { + if (null === $this->headers) { + if ($this->mail) { + $part = $this->mail->getRawHeader($this->messageNum); + $this->headers = Headers::fromString($part); + } else { + $this->headers = new Headers(); + } + } + if (! $this->headers instanceof Headers) { + throw new Exception\RuntimeException( + '$this->headers must be an instance of Headers' + ); + } + + return $this->headers; + } + + /** + * Get a header in specified format + * + * Internally headers that occur more than once are saved as array, all other as string. If $format + * is set to string implode is used to concat the values (with Mime::LINEEND as delim). + * + * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes + * @param string $format change type of return value to 'string' or 'array' + * @throws Exception\InvalidArgumentException + * @return string|array|HeaderInterface|\ArrayIterator value of header in wanted or internal format + */ + public function getHeader($name, $format = null) + { + $header = $this->getHeaders()->get($name); + if ($header === false) { + $lowerName = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $name)); + $header = $this->getHeaders()->get($lowerName); + if ($header === false) { + throw new Exception\InvalidArgumentException( + "Header with Name $name or $lowerName not found" + ); + } + } + + switch ($format) { + case 'string': + if ($header instanceof HeaderInterface) { + $return = $header->getFieldValue(HeaderInterface::FORMAT_RAW); + } else { + $return = ''; + foreach ($header as $h) { + $return .= $h->getFieldValue(HeaderInterface::FORMAT_RAW) + . Mime\Mime::LINEEND; + } + $return = trim($return, Mime\Mime::LINEEND); + } + break; + case 'array': + if ($header instanceof HeaderInterface) { + $return = [$header->getFieldValue()]; + } else { + $return = []; + foreach ($header as $h) { + $return[] = $h->getFieldValue(HeaderInterface::FORMAT_RAW); + } + } + break; + default: + $return = $header; + } + + return $return; + } + + /** + * Get a specific field from a header like content type or all fields as array + * + * If the header occurs more than once, only the value from the first header + * is returned. + * + * Throws an Exception if the requested header does not exist. If + * the specific header field does not exist, returns null. + * + * @param string $name name of header, like in getHeader() + * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned + * @param string $firstName key name for the first part + * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) + * @throws \Laminas\Mime\Exception\RuntimeException + */ + public function getHeaderField($name, $wantedPart = '0', $firstName = '0') + { + return Mime\Decode::splitHeaderField(current($this->getHeader($name, 'array')), $wantedPart, $firstName); + } + + /** + * Getter for mail headers - name is matched in lowercase + * + * This getter is short for Part::getHeader($name, 'string') + * + * @see Part::getHeader() + * + * @param string $name header name + * @return string value of header + * @throws Exception\ExceptionInterface + */ + public function __get($name) + { + return $this->getHeader($name, 'string'); + } + + /** + * Isset magic method proxy to hasHeader + * + * This method is short syntax for Part::hasHeader($name); + * + * @see Part::hasHeader + * + * @param string + * @return bool + */ + public function __isset($name) + { + return $this->getHeaders()->has($name); + } + + /** + * magic method to get content of part + * + * @return string content + */ + public function __toString() + { + return $this->getContent(); + } + + /** + * implements RecursiveIterator::hasChildren() + * + * @return bool current element has children/is multipart + */ + public function hasChildren() + { + $current = $this->current(); + return $current && $current instanceof Part && $current->isMultipart(); + } + + /** + * implements RecursiveIterator::getChildren() + * + * @return Part same as self::current() + */ + public function getChildren() + { + return $this->current(); + } + + /** + * implements Iterator::valid() + * + * @return bool check if there's a current element + */ + public function valid() + { + if ($this->countParts === null) { + $this->countParts(); + } + return $this->iterationPos && $this->iterationPos <= $this->countParts; + } + + /** + * implements Iterator::next() + */ + public function next() + { + ++$this->iterationPos; + } + + /** + * implements Iterator::key() + * + * @return string key/number of current part + */ + public function key() + { + return $this->iterationPos; + } + + /** + * implements Iterator::current() + * + * @return Part current part + */ + public function current() + { + return $this->getPart($this->iterationPos); + } + + /** + * implements Iterator::rewind() + */ + public function rewind() + { + $this->countParts(); + $this->iterationPos = 1; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php b/lib/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php new file mode 100644 index 000000000..d6b387b0c --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php @@ -0,0 +1,15 @@ +fh = fopen($params['file'], 'r'); + } else { + $this->fh = $params['file']; + } + if (! $this->fh) { + throw new Exception\RuntimeException('could not open file'); + } + if (isset($params['startPos'])) { + fseek($this->fh, $params['startPos']); + } + $header = ''; + $endPos = isset($params['endPos']) ? $params['endPos'] : null; + while (($endPos === null || ftell($this->fh) < $endPos) && trim($line = fgets($this->fh))) { + $header .= $line; + } + + if (isset($params['EOL'])) { + $this->headers = Headers::fromString($header, $params['EOL']); + } else { + $this->headers = Headers::fromString($header); + } + + $this->contentPos[0] = ftell($this->fh); + if ($endPos !== null) { + $this->contentPos[1] = $endPos; + } else { + fseek($this->fh, 0, SEEK_END); + $this->contentPos[1] = ftell($this->fh); + } + if (! $this->isMultipart()) { + return; + } + + $boundary = $this->getHeaderField('content-type', 'boundary'); + if (! $boundary) { + throw new Exception\RuntimeException('no boundary found in content type to split message'); + } + + $part = []; + $pos = $this->contentPos[0]; + fseek($this->fh, $pos); + while (! feof($this->fh) && ($endPos === null || $pos < $endPos)) { + $line = fgets($this->fh); + if ($line === false) { + if (feof($this->fh)) { + break; + } + throw new Exception\RuntimeException('error reading file'); + } + + $lastPos = $pos; + $pos = ftell($this->fh); + $line = trim($line); + + if ($line == '--' . $boundary) { + if ($part) { + // not first part + $part[1] = $lastPos; + $this->partPos[] = $part; + } + $part = [$pos]; + } elseif ($line == '--' . $boundary . '--') { + $part[1] = $lastPos; + $this->partPos[] = $part; + break; + } + } + $this->countParts = count($this->partPos); + } + + /** + * Body of part + * + * If part is multipart the raw content of this part with all sub parts is returned + * + * @param resource $stream Optional + * @return string body + */ + public function getContent($stream = null) + { + fseek($this->fh, $this->contentPos[0]); + if ($stream !== null) { + return stream_copy_to_stream($this->fh, $stream, $this->contentPos[1] - $this->contentPos[0]); + } + $length = $this->contentPos[1] - $this->contentPos[0]; + return $length < 1 ? '' : fread($this->fh, $length); + } + + /** + * Return size of part + * + * Quite simple implemented currently (not decoding). Handle with care. + * + * @return int size + */ + public function getSize() + { + return $this->contentPos[1] - $this->contentPos[0]; + } + + /** + * Get part of multipart message + * + * @param int $num number of part starting with 1 for first part + * @throws Exception\RuntimeException + * @return Part wanted part + */ + public function getPart($num) + { + --$num; + if (! isset($this->partPos[$num])) { + throw new Exception\RuntimeException('part not found'); + } + + return new static(['file' => $this->fh, 'startPos' => $this->partPos[$num][0], + 'endPos' => $this->partPos[$num][1]]); + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Part/PartInterface.php b/lib/laminas/laminas-mail/src/Storage/Part/PartInterface.php new file mode 100644 index 000000000..54bc6990c --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Part/PartInterface.php @@ -0,0 +1,123 @@ + firstPart, partname => value] + * @throws Exception\ExceptionInterface + */ + public function getHeaderField($name, $wantedPart = '0', $firstName = '0'); + + /** + * Getter for mail headers - name is matched in lowercase + * + * This getter is short for PartInterface::getHeader($name, 'string') + * + * @see PartInterface::getHeader() + * @param string $name header name + * @return string value of header + * @throws Exception\ExceptionInterface + */ + public function __get($name); + + /** + * magic method to get content of part + * + * @return string content + */ + public function __toString(); +} diff --git a/lib/laminas/laminas-mail/src/Storage/Pop3.php b/lib/laminas/laminas-mail/src/Storage/Pop3.php new file mode 100644 index 000000000..7e799ae9b --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Pop3.php @@ -0,0 +1,278 @@ +protocol->status($count, $octets); + return (int) $count; + } + + /** + * get a list of messages with number and size + * + * @param int $id number of message + * @return int|array size of given message of list with all messages as array(num => size) + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function getSize($id = 0) + { + $id = $id ? $id : null; + return $this->protocol->getList($id); + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return \Laminas\Mail\Storage\Message + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function getMessage($id) + { + $bodyLines = 0; + $message = $this->protocol->top($id, $bodyLines, true); + + return new $this->messageClass(['handler' => $this, 'id' => $id, 'headers' => $message, + 'noToplines' => $bodyLines < 1]); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + return $this->protocol->top($id, 0, true); + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @return string raw content + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + $content = $this->protocol->retrieve($id); + // TODO: find a way to avoid decoding the headers + $headers = null; // "Declare" variable since it's passed by reference + $body = null; // "Declare" variable before first usage. + Mime\Decode::splitMessage($content, $headers, $body); + return $body; + } + + /** + * create instance with parameters + * Supported parameters are + * - host hostname or ip address of POP3 server + * - user username + * - password password for user 'username' [optional, default = ''] + * - port port for POP3 server [optional, default = 110] + * - ssl 'SSL' or 'TLS' for secure sockets + * + * @param array|Protocol\Pop3 $params mail reader specific parameters or configured Pop3 protocol object + * @throws \Laminas\Mail\Storage\Exception\InvalidArgumentException + * @throws \Laminas\Mail\Protocol\Exception\RuntimeException + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object) $params; + } + + $this->has['fetchPart'] = false; + $this->has['top'] = null; + $this->has['uniqueid'] = null; + + if ($params instanceof Protocol\Pop3) { + $this->protocol = $params; + return; + } + + if (! isset($params->user)) { + throw new Exception\InvalidArgumentException('need at least user in params'); + } + + $host = isset($params->host) ? $params->host : 'localhost'; + $password = isset($params->password) ? $params->password : ''; + $port = isset($params->port) ? $params->port : null; + $ssl = isset($params->ssl) ? $params->ssl : false; + + $this->protocol = new Protocol\Pop3(); + $this->protocol->connect($host, $port, $ssl); + $this->protocol->login($params->user, $password); + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + */ + public function close() + { + $this->protocol->logout(); + } + + /** + * Keep the server busy. + * + * @throws \Laminas\Mail\Protocol\Exception\RuntimeException + */ + public function noop() + { + $this->protocol->noop(); + } + + /** + * Remove a message from server. If you're doing that from a web environment + * you should be careful and use a uniqueid as parameter if possible to + * identify the message. + * + * @param int $id number of message + * @throws \Laminas\Mail\Protocol\Exception\RuntimeException + */ + public function removeMessage($id) + { + $this->protocol->delete($id); + } + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getUniqueId($id = null) + { + if (! $this->hasUniqueid) { + if ($id) { + return $id; + } + $count = $this->countMessages(); + if ($count < 1) { + return []; + } + $range = range(1, $count); + return array_combine($range, $range); + } + + return $this->protocol->uniqueid($id); + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @throws Exception\InvalidArgumentException + * @return int message number + */ + public function getNumberByUniqueId($id) + { + if (! $this->hasUniqueid) { + return $id; + } + + $ids = $this->getUniqueId(); + foreach ($ids as $k => $v) { + if ($v == $id) { + return $k; + } + } + + throw new Exception\InvalidArgumentException('unique id not found'); + } + + /** + * Special handling for hasTop and hasUniqueid. The headers of the first message is + * retrieved if Top wasn't needed/tried yet. + * + * @see AbstractStorage::__get() + * @param string $var + * @return string + */ + public function __get($var) + { + $result = parent::__get($var); + if ($result !== null) { + return $result; + } + + if (strtolower($var) == 'hastop') { + if ($this->protocol->hasTop === null) { + // need to make a real call, because not all server are honest in their capas + try { + $this->protocol->top(1, 0, false); + } catch (MailException\ExceptionInterface $e) { + // ignoring error + } + } + $this->has['top'] = $this->protocol->hasTop; + return $this->protocol->hasTop; + } + + if (strtolower($var) == 'hasuniqueid') { + $id = null; + try { + $id = $this->protocol->uniqueid(1); + } catch (MailException\ExceptionInterface $e) { + // ignoring error + } + $this->has['uniqueid'] = (bool) $id; + return $this->has['uniqueid']; + } + + return $result; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Writable/Maildir.php b/lib/laminas/laminas-mail/src/Storage/Writable/Maildir.php new file mode 100644 index 000000000..b8c301746 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Writable/Maildir.php @@ -0,0 +1,953 @@ +create) + && isset($params->dirname) + && ! file_exists($params->dirname . DIRECTORY_SEPARATOR . 'cur') + ) { + self::initMaildir($params->dirname); + } + + parent::__construct($params); + } + + /** + * create a new folder + * + * This method also creates parent folders if necessary. Some mail storages may restrict, which folder + * may be used as parent or which chars may be used in the folder name + * + * @param string $name global name of folder, local name if $parentFolder is set + * @param string|\Laminas\Mail\Storage\Folder $parentFolder parent of new folder, else root folder is parent + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + * @return string only used internally (new created maildir) + */ + public function createFolder($name, $parentFolder = null) + { + if ($parentFolder instanceof Folder) { + $folder = $parentFolder->getGlobalName() . $this->delim . $name; + } elseif ($parentFolder !== null) { + $folder = rtrim($parentFolder, $this->delim) . $this->delim . $name; + } else { + $folder = $name; + } + + $folder = trim($folder, $this->delim); + + // first we check if we try to create a folder that does exist + $exists = null; + try { + $exists = $this->getFolders($folder); + } catch (MailException\ExceptionInterface $e) { + // ok + } + if ($exists) { + throw new StorageException\RuntimeException('folder already exists'); + } + + if (strpos($folder, $this->delim . $this->delim) !== false) { + throw new StorageException\RuntimeException('invalid name - folder parts may not be empty'); + } + + if (strpos($folder, 'INBOX' . $this->delim) === 0) { + $folder = substr($folder, 6); + } + + $fulldir = $this->rootdir . '.' . $folder; + + // check if we got tricked and would create a dir outside of the rootdir or not as direct child + if (strpos($folder, DIRECTORY_SEPARATOR) !== false || strpos($folder, '/') !== false + || dirname($fulldir) . DIRECTORY_SEPARATOR != $this->rootdir + ) { + throw new StorageException\RuntimeException('invalid name - no directory separator allowed in folder name'); + } + + // has a parent folder? + $parent = null; + if (strpos($folder, $this->delim)) { + // let's see if the parent folder exists + $parent = substr($folder, 0, strrpos($folder, $this->delim)); + try { + $this->getFolders($parent); + } catch (MailException\ExceptionInterface $e) { + // does not - create parent folder + $this->createFolder($parent); + } + } + + ErrorHandler::start(); + if (! mkdir($fulldir) || ! mkdir($fulldir . DIRECTORY_SEPARATOR . 'cur')) { + $error = ErrorHandler::stop(); + throw new StorageException\RuntimeException( + 'error while creating new folder, may be created incompletely', + 0, + $error + ); + } + ErrorHandler::stop(); + + mkdir($fulldir . DIRECTORY_SEPARATOR . 'new'); + mkdir($fulldir . DIRECTORY_SEPARATOR . 'tmp'); + + $localName = $parent ? substr($folder, strlen($parent) + 1) : $folder; + $this->getFolders($parent)->$localName = new Folder($localName, $folder, true); + + return $fulldir; + } + + /** + * remove a folder + * + * @param string|Folder $name name or instance of folder + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function removeFolder($name) + { + // TODO: This could fail in the middle of the task, which is not optimal. + // But there is no defined standard way to mark a folder as removed and there is no atomar fs-op + // to remove a directory. Also moving the folder to a/the trash folder is not possible, as + // all parent folders must be created. What we could do is add a dash to the front of the + // directory name and it should be ignored as long as other processes obey the standard. + + if ($name instanceof Folder) { + $name = $name->getGlobalName(); + } + + $name = trim($name, $this->delim); + if (strpos($name, 'INBOX' . $this->delim) === 0) { + $name = substr($name, 6); + } + + // check if folder exists and has no children + if (! $this->getFolders($name)->isLeaf()) { + throw new StorageException\RuntimeException('delete children first'); + } + + if ($name == 'INBOX' || $name == DIRECTORY_SEPARATOR || $name == '/') { + throw new StorageException\RuntimeException('wont delete INBOX'); + } + + if ($name == $this->getCurrentFolder()) { + throw new StorageException\RuntimeException('wont delete selected folder'); + } + + foreach (['tmp', 'new', 'cur', '.'] as $subdir) { + $dir = $this->rootdir . '.' . $name . DIRECTORY_SEPARATOR . $subdir; + if (! file_exists($dir)) { + continue; + } + $dh = opendir($dir); + if (! $dh) { + throw new StorageException\RuntimeException("error opening $subdir"); + } + while (($entry = readdir($dh)) !== false) { + if ($entry == '.' || $entry == '..') { + continue; + } + if (! unlink($dir . DIRECTORY_SEPARATOR . $entry)) { + throw new StorageException\RuntimeException("error cleaning $subdir"); + } + } + closedir($dh); + if ($subdir !== '.') { + if (! rmdir($dir)) { + throw new StorageException\RuntimeException("error removing $subdir"); + } + } + } + + if (! rmdir($this->rootdir . '.' . $name)) { + // at least we should try to make it a valid maildir again + mkdir($this->rootdir . '.' . $name . DIRECTORY_SEPARATOR . 'cur'); + throw new StorageException\RuntimeException("error removing maindir"); + } + + $parent = strpos($name, $this->delim) ? substr($name, 0, strrpos($name, $this->delim)) : null; + $localName = $parent ? substr($name, strlen($parent) + 1) : $name; + unset($this->getFolders($parent)->$localName); + } + + /** + * rename and/or move folder + * + * The new name has the same restrictions as in createFolder() + * + * @param string|\Laminas\Mail\Storage\Folder $oldName name or instance of folder + * @param string $newName new global name of folder + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function renameFolder($oldName, $newName) + { + // TODO: This is also not atomar and has similar problems as removeFolder() + + if ($oldName instanceof Folder) { + $oldName = $oldName->getGlobalName(); + } + + $oldName = trim($oldName, $this->delim); + if (strpos($oldName, 'INBOX' . $this->delim) === 0) { + $oldName = substr($oldName, 6); + } + + $newName = trim($newName, $this->delim); + if (strpos($newName, 'INBOX' . $this->delim) === 0) { + $newName = substr($newName, 6); + } + + if (strpos($newName, $oldName . $this->delim) === 0) { + throw new StorageException\RuntimeException('new folder cannot be a child of old folder'); + } + + // check if folder exists and has no children + $folder = $this->getFolders($oldName); + + if ($oldName == 'INBOX' || $oldName == DIRECTORY_SEPARATOR || $oldName == '/') { + throw new StorageException\RuntimeException('wont rename INBOX'); + } + + if ($oldName == $this->getCurrentFolder()) { + throw new StorageException\RuntimeException('wont rename selected folder'); + } + + $newdir = $this->createFolder($newName); + + if (! $folder->isLeaf()) { + foreach ($folder as $k => $v) { + $this->renameFolder($v->getGlobalName(), $newName . $this->delim . $k); + } + } + + $olddir = $this->rootdir . '.' . $folder; + foreach (['tmp', 'new', 'cur'] as $subdir) { + $subdir = DIRECTORY_SEPARATOR . $subdir; + if (! file_exists($olddir . $subdir)) { + continue; + } + // using copy or moving files would be even better - but also much slower + if (! rename($olddir . $subdir, $newdir . $subdir)) { + throw new StorageException\RuntimeException('error while moving ' . $subdir); + } + } + // create a dummy if removing fails - otherwise we can't read it next time + mkdir($olddir . DIRECTORY_SEPARATOR . 'cur'); + $this->removeFolder($oldName); + } + + /** + * create a uniqueid for maildir filename + * + * This is nearly the format defined in the maildir standard. The microtime() call should already + * create a uniqueid, the pid is for multicore/-cpu machine that manage to call this function at the + * exact same time, and uname() gives us the hostname for multiple machines accessing the same storage. + * + * If someone disables posix we create a random number of the same size, so this method should also + * work on Windows - if you manage to get maildir working on Windows. + * Microtime could also be disabled, although I've never seen it. + * + * @return string new uniqueid + */ + protected function createUniqueId() + { + $id = ''; + $id .= microtime(true); + $id .= '.' . getmypid(); + $id .= '.' . php_uname('n'); + + return $id; + } + + /** + * open a temporary maildir file + * + * makes sure tmp/ exists and create a file with a unique name + * you should close the returned filehandle! + * + * @param string $folder name of current folder without leading . + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + * @return array array('dirname' => dir of maildir folder, 'uniq' => unique id, 'filename' => name of create file + * 'handle' => file opened for writing) + */ + protected function createTmpFile($folder = 'INBOX') + { + if ($folder == 'INBOX') { + $tmpdir = $this->rootdir . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + } else { + $tmpdir = $this->rootdir . '.' . $folder . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + } + if (! file_exists($tmpdir)) { + if (! mkdir($tmpdir)) { + throw new StorageException\RuntimeException('problems creating tmp dir'); + } + } + + // we should retry to create a unique id if a file with the same name exists + // to avoid a script timeout we only wait 1 second (instead of 2) and stop + // after a defined retry count + // if you change this variable take into account that it can take up to $maxTries seconds + // normally we should have a valid unique name after the first try, we're just following the "standard" here + $maxTries = 5; + for ($i = 0; $i < $maxTries; ++$i) { + $uniq = $this->createUniqueId(); + if (! file_exists($tmpdir . $uniq)) { + // here is the race condition! - as defined in the standard + // to avoid having a long time between stat()ing the file and creating it we're opening it here + // to mark the filename as taken + $fh = fopen($tmpdir . $uniq, 'w'); + if (! $fh) { + throw new StorageException\RuntimeException('could not open temp file'); + } + break; + } + sleep(1); + } + + if (! $fh) { + throw new StorageException\RuntimeException( + "tried {$maxTries} unique ids for a temp file, but all were taken - giving up" + ); + } + + return ['dirname' => $this->rootdir . '.' . $folder, + 'uniq' => $uniq, + 'filename' => $tmpdir . $uniq, + 'handle' => $fh]; + } + + /** + * create an info string for filenames with given flags + * + * @param array $flags wanted flags, with the reference you'll get the set + * flags with correct key (= char for flag) + * @return string info string for version 2 filenames including the leading colon + * @throws StorageException\InvalidArgumentException + */ + protected function getInfoString(&$flags) + { + // accessing keys is easier, faster and it removes duplicated flags + $wantedFlags = array_flip($flags); + if (isset($wantedFlags[Storage::FLAG_RECENT])) { + throw new StorageException\InvalidArgumentException('recent flag may not be set'); + } + + $info = ':2,'; + $flags = []; + foreach (Storage\Maildir::$knownFlags as $char => $flag) { + if (! isset($wantedFlags[$flag])) { + continue; + } + $info .= $char; + $flags[$char] = $flag; + unset($wantedFlags[$flag]); + } + + if (! empty($wantedFlags)) { + $wantedFlags = implode(', ', array_keys($wantedFlags)); + throw new StorageException\InvalidArgumentException('unknown flag(s): ' . $wantedFlags); + } + + return $info; + } + + /** + * append a new message to mail storage + * + * @param string|resource $message message as string or stream resource. + * @param null|string|Folder $folder folder for new message, else current + * folder is taken. + * @param null|array $flags set flags for new message, else a default set + * is used. + * @param bool $recent handle this mail as if recent flag has been set, + * should only be used in delivery. + * @throws StorageException\RuntimeException + */ + public function appendMessage($message, $folder = null, $flags = null, $recent = false) + { + if ($this->quota && $this->checkQuota()) { + throw new StorageException\RuntimeException('storage is over quota!'); + } + + if ($folder === null) { + $folder = $this->currentFolder; + } + + if (! ($folder instanceof Folder)) { + $folder = $this->getFolders($folder); + } + + if ($flags === null) { + $flags = [Storage::FLAG_SEEN]; + } + $info = $this->getInfoString($flags); + $tempFile = $this->createTmpFile($folder->getGlobalName()); + + // TODO: handle class instances for $message + if (is_resource($message) && get_resource_type($message) == 'stream') { + stream_copy_to_stream($message, $tempFile['handle']); + } else { + fwrite($tempFile['handle'], $message); + } + fclose($tempFile['handle']); + + // we're adding the size to the filename for maildir++ + $size = filesize($tempFile['filename']); + if ($size !== false) { + $info = ',S=' . $size . $info; + } + $newFilename = $tempFile['dirname'] . DIRECTORY_SEPARATOR; + $newFilename .= $recent ? 'new' : 'cur'; + $newFilename .= DIRECTORY_SEPARATOR . $tempFile['uniq'] . $info; + + // we're throwing any exception after removing our temp file and saving it to this variable instead + $exception = null; + + if (! link($tempFile['filename'], $newFilename)) { + $exception = new StorageException\RuntimeException('cannot link message file to final dir'); + } + + ErrorHandler::start(E_WARNING); + unlink($tempFile['filename']); + ErrorHandler::stop(); + + if ($exception) { + throw $exception; + } + + $this->files[] = ['uniq' => $tempFile['uniq'], + 'flags' => $flags, + 'filename' => $newFilename]; + if ($this->quota) { + $this->addQuotaEntry((int) $size, 1); + } + } + + /** + * copy an existing message + * + * @param int $id number of message + * @param string|\Laminas\Mail\Storage\Folder $folder name or instance of targer folder + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function copyMessage($id, $folder) + { + if ($this->quota && $this->checkQuota()) { + throw new StorageException\RuntimeException('storage is over quota!'); + } + + if (! ($folder instanceof Folder)) { + $folder = $this->getFolders($folder); + } + + $filedata = $this->getFileData($id); + $oldFile = $filedata['filename']; + $flags = $filedata['flags']; + + // copied message can't be recent + while (($key = array_search(Storage::FLAG_RECENT, $flags)) !== false) { + unset($flags[$key]); + } + $info = $this->getInfoString($flags); + + // we're creating the copy as temp file before moving to cur/ + $tempFile = $this->createTmpFile($folder->getGlobalName()); + // we don't write directly to the file + fclose($tempFile['handle']); + + // we're adding the size to the filename for maildir++ + $size = filesize($oldFile); + if ($size !== false) { + $info = ',S=' . $size . $info; + } + + $newFile = $tempFile['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $tempFile['uniq'] . $info; + + // we're throwing any exception after removing our temp file and saving it to this variable instead + $exception = null; + + if (! copy($oldFile, $tempFile['filename'])) { + $exception = new StorageException\RuntimeException('cannot copy message file'); + } elseif (! link($tempFile['filename'], $newFile)) { + $exception = new StorageException\RuntimeException('cannot link message file to final dir'); + } + + ErrorHandler::start(E_WARNING); + unlink($tempFile['filename']); + ErrorHandler::stop(); + + if ($exception) { + throw $exception; + } + + if ($folder->getGlobalName() == $this->currentFolder + || ($this->currentFolder == 'INBOX' && $folder->getGlobalName() == '/') + ) { + $this->files[] = ['uniq' => $tempFile['uniq'], + 'flags' => $flags, + 'filename' => $newFile]; + } + + if ($this->quota) { + $this->addQuotaEntry((int) $size, 1); + } + } + + /** + * move an existing message + * + * @param int $id number of message + * @param string|\Laminas\Mail\Storage\Folder $folder name or instance of targer folder + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function moveMessage($id, $folder) + { + if (! ($folder instanceof Folder)) { + $folder = $this->getFolders($folder); + } + + if ($folder->getGlobalName() == $this->currentFolder + || ($this->currentFolder == 'INBOX' && $folder->getGlobalName() == '/') + ) { + throw new StorageException\RuntimeException('target is current folder'); + } + + $filedata = $this->getFileData($id); + $oldFile = $filedata['filename']; + $flags = $filedata['flags']; + + // moved message can't be recent + while (($key = array_search(Storage::FLAG_RECENT, $flags)) !== false) { + unset($flags[$key]); + } + $info = $this->getInfoString($flags); + + // reserving a new name + $tempFile = $this->createTmpFile($folder->getGlobalName()); + fclose($tempFile['handle']); + + // we're adding the size to the filename for maildir++ + $size = filesize($oldFile); + if ($size !== false) { + $info = ',S=' . $size . $info; + } + + $newFile = $tempFile['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $tempFile['uniq'] . $info; + + // we're throwing any exception after removing our temp file and saving it to this variable instead + $exception = null; + + if (! rename($oldFile, $newFile)) { + $exception = new StorageException\RuntimeException('cannot move message file'); + } + + ErrorHandler::start(E_WARNING); + unlink($tempFile['filename']); + ErrorHandler::stop(); + + if ($exception) { + throw $exception; + } + + unset($this->files[$id - 1]); + // remove the gap + $this->files = array_values($this->files); + } + + /** + * set flags for message + * + * NOTE: this method can't set the recent flag. + * + * @param int $id number of message + * @param array $flags new flags for message + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function setFlags($id, $flags) + { + $info = $this->getInfoString($flags); + $filedata = $this->getFileData($id); + + // NOTE: double dirname to make sure we always move to cur. if recent + // flag has been set (message is in new) it will be moved to cur. + $newFilename = dirname(dirname($filedata['filename'])) + . DIRECTORY_SEPARATOR + . 'cur' + . DIRECTORY_SEPARATOR + . "$filedata[uniq]$info"; + + ErrorHandler::start(); + $test = rename($filedata['filename'], $newFilename); + $error = ErrorHandler::stop(); + if (! $test) { + throw new StorageException\RuntimeException('cannot rename file', 0, $error); + } + + $filedata['flags'] = $flags; + $filedata['filename'] = $newFilename; + + $this->files[$id - 1] = $filedata; + } + + /** + * stub for not supported message deletion + * + * @param $id + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function removeMessage($id) + { + $filename = $this->getFileData($id, 'filename'); + + if ($this->quota) { + $size = filesize($filename); + } + + ErrorHandler::start(); + $test = unlink($filename); + $error = ErrorHandler::stop(); + if (! $test) { + throw new StorageException\RuntimeException('cannot remove message', 0, $error); + } + unset($this->files[$id - 1]); + // remove the gap + $this->files = array_values($this->files); + if ($this->quota) { + $this->addQuotaEntry(0 - (int) $size, -1); + } + } + + /** + * enable/disable quota and set a quota value if wanted or needed + * + * You can enable/disable quota with true/false. If you don't have + * a MDA or want to enforce a quota value you can also set this value + * here. Use array('size' => SIZE_QUOTA, 'count' => MAX_MESSAGE) do + * define your quota. Order of these fields does matter! + * + * @param bool|array $value new quota value + */ + public function setQuota($value) + { + $this->quota = $value; + } + + /** + * get currently set quota + * + * @see \Laminas\Mail\Storage\Writable\Maildir::setQuota() + * @param bool $fromStorage + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + * @return bool|array + */ + public function getQuota($fromStorage = false) + { + if ($fromStorage) { + ErrorHandler::start(E_WARNING); + $fh = fopen($this->rootdir . 'maildirsize', 'r'); + $error = ErrorHandler::stop(); + if (! $fh) { + throw new StorageException\RuntimeException('cannot open maildirsize', 0, $error); + } + $definition = fgets($fh); + fclose($fh); + $definition = explode(',', trim($definition)); + $quota = []; + foreach ($definition as $member) { + $key = $member[strlen($member) - 1]; + if ($key == 'S' || $key == 'C') { + $key = $key == 'C' ? 'count' : 'size'; + } + $quota[$key] = substr($member, 0, -1); + } + return $quota; + } + + return $this->quota; + } + + /** + * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating maildirsize" + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + * @return array + */ + protected function calculateMaildirsize() + { + $timestamps = []; + $messages = 0; + $totalSize = 0; + + if (is_array($this->quota)) { + $quota = $this->quota; + } else { + try { + $quota = $this->getQuota(true); + } catch (StorageException\ExceptionInterface $e) { + throw new StorageException\RuntimeException('no quota definition found', 0, $e); + } + } + + $folders = new RecursiveIteratorIterator($this->getFolders(), RecursiveIteratorIterator::SELF_FIRST); + foreach ($folders as $folder) { + $subdir = $folder->getGlobalName(); + if ($subdir == 'INBOX') { + $subdir = ''; + } else { + $subdir = '.' . $subdir; + } + if ($subdir == 'Trash') { + continue; + } + + foreach (['cur', 'new'] as $subsubdir) { + $dirname = $this->rootdir . $subdir . DIRECTORY_SEPARATOR . $subsubdir . DIRECTORY_SEPARATOR; + if (! file_exists($dirname)) { + continue; + } + // NOTE: we are using mtime instead of "the latest timestamp". The latest would be atime + // and as we are accessing the directory it would make the whole calculation useless. + $timestamps[$dirname] = filemtime($dirname); + + $dh = opendir($dirname); + // NOTE: Should have been checked in constructor. Not throwing an exception here, quotas will + // therefore not be fully enforced, but next request will fail anyway, if problem persists. + if (! $dh) { + continue; + } + + while (($entry = readdir()) !== false) { + if ($entry[0] == '.' || ! is_file($dirname . $entry)) { + continue; + } + + if (strpos($entry, ',S=')) { + strtok($entry, '='); + $filesize = strtok(':'); + if (is_numeric($filesize)) { + $totalSize += $filesize; + ++$messages; + continue; + } + } + $size = filesize($dirname . $entry); + if ($size === false) { + // ignore, as we assume file got removed + continue; + } + $totalSize += $size; + ++$messages; + } + } + } + + $tmp = $this->createTmpFile(); + $fh = $tmp['handle']; + $definition = []; + foreach ($quota as $type => $value) { + if ($type == 'size' || $type == 'count') { + $type = $type == 'count' ? 'C' : 'S'; + } + $definition[] = $value . $type; + } + $definition = implode(',', $definition); + fwrite($fh, "$definition\n"); + fwrite($fh, "$totalSize $messages\n"); + fclose($fh); + rename($tmp['filename'], $this->rootdir . 'maildirsize'); + foreach ($timestamps as $dir => $timestamp) { + if ($timestamp < filemtime($dir)) { + unlink($this->rootdir . 'maildirsize'); + break; + } + } + + return ['size' => $totalSize, + 'count' => $messages, + 'quota' => $quota]; + } + + /** + * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating the quota for a Maildir++" + * @param bool $forceRecalc + * @return array + */ + protected function calculateQuota($forceRecalc = false) + { + $fh = null; + $totalSize = 0; + $messages = 0; + $maildirsize = ''; + if (! $forceRecalc + && file_exists($this->rootdir . 'maildirsize') + && filesize($this->rootdir . 'maildirsize') < 5120 + ) { + $fh = fopen($this->rootdir . 'maildirsize', 'r'); + } + if ($fh) { + $maildirsize = fread($fh, 5120); + if (strlen($maildirsize) >= 5120) { + fclose($fh); + $fh = null; + $maildirsize = ''; + } + } + if (! $fh) { + $result = $this->calculateMaildirsize(); + $totalSize = $result['size']; + $messages = $result['count']; + $quota = $result['quota']; + } else { + $maildirsize = explode("\n", $maildirsize); + if (is_array($this->quota)) { + $quota = $this->quota; + } else { + $definition = explode(',', $maildirsize[0]); + $quota = []; + foreach ($definition as $member) { + $key = $member[strlen($member) - 1]; + if ($key == 'S' || $key == 'C') { + $key = $key == 'C' ? 'count' : 'size'; + } + $quota[$key] = substr($member, 0, -1); + } + } + unset($maildirsize[0]); + foreach ($maildirsize as $line) { + list($size, $count) = explode(' ', trim($line)); + $totalSize += $size; + $messages += $count; + } + } + + $overQuota = false; + $overQuota = $overQuota || (isset($quota['size']) && $totalSize > $quota['size']); + $overQuota = $overQuota || (isset($quota['count']) && $messages > $quota['count']); + // NOTE: $maildirsize equals false if it wasn't set (AKA we recalculated) or it's only + // one line, because $maildirsize[0] gets unsetted. + // Also we're using local time to calculate the 15 minute offset. Touching a file just for known the + // local time of the file storage isn't worth the hassle. + if ($overQuota && ($maildirsize || filemtime($this->rootdir . 'maildirsize') > time() - 900)) { + $result = $this->calculateMaildirsize(); + $totalSize = $result['size']; + $messages = $result['count']; + $quota = $result['quota']; + $overQuota = false; + $overQuota = $overQuota || (isset($quota['size']) && $totalSize > $quota['size']); + $overQuota = $overQuota || (isset($quota['count']) && $messages > $quota['count']); + } + + if ($fh) { + // TODO is there a safe way to keep the handle open for writing? + fclose($fh); + } + + return ['size' => $totalSize, + 'count' => $messages, + 'quota' => $quota, + 'over_quota' => $overQuota]; + } + + protected function addQuotaEntry($size, $count = 1) + { + if (! file_exists($this->rootdir . 'maildirsize')) { + // TODO: should get file handler from calculateQuota + } + $size = (int) $size; + $count = (int) $count; + file_put_contents($this->rootdir . 'maildirsize', "$size $count\n", FILE_APPEND); + } + + /** + * check if storage is currently over quota + * + * @see calculateQuota() + * @param bool $detailedResponse return known data of quota and current size and message count + * @param bool $forceRecalc + * @return bool|array over quota state or detailed response + */ + public function checkQuota($detailedResponse = false, $forceRecalc = false) + { + $result = $this->calculateQuota($forceRecalc); + return $detailedResponse ? $result : $result['over_quota']; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php b/lib/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php new file mode 100644 index 000000000..84f3b991d --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php @@ -0,0 +1,92 @@ +from; + } + + /** + * Set MAIL FROM + * + * @param string $from + */ + public function setFrom($from) + { + $this->from = (string) $from; + } + + /** + * Get RCPT TO + * + * @return string|null + */ + public function getTo() + { + return $this->to; + } + + /** + * Set RCPT TO + * + * @param string $to + */ + public function setTo($to) + { + $this->to = $to; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/Exception/DomainException.php b/lib/laminas/laminas-mail/src/Transport/Exception/DomainException.php new file mode 100644 index 000000000..9269e13eb --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/Exception/DomainException.php @@ -0,0 +1,18 @@ + File::class, + 'inmemory' => InMemory::class, + 'memory' => InMemory::class, + 'null' => InMemory::class, + 'sendmail' => Sendmail::class, + 'smtp' => Smtp::class, + ]; + + /** + * @param array $spec + * @return TransportInterface + * @throws Exception\InvalidArgumentException + * @throws Exception\DomainException + */ + public static function create($spec = []) + { + if ($spec instanceof Traversable) { + $spec = ArrayUtils::iteratorToArray($spec); + } + + if (! is_array($spec)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects an array or Traversable argument; received "%s"', + __METHOD__, + (is_object($spec) ? get_class($spec) : gettype($spec)) + )); + } + + $type = isset($spec['type']) ? $spec['type'] : 'sendmail'; + + $normalizedType = strtolower($type); + + if (isset(static::$classMap[$normalizedType])) { + $type = static::$classMap[$normalizedType]; + } + + if (! class_exists($type)) { + throw new Exception\DomainException(sprintf( + '%s expects the "type" attribute to resolve to an existing class; received "%s"', + __METHOD__, + $type + )); + } + + $transport = new $type; + + if (! $transport instanceof TransportInterface) { + throw new Exception\DomainException(sprintf( + '%s expects the "type" attribute to resolve to a valid %s instance; received "%s"', + __METHOD__, + TransportInterface::class, + $type + )); + } + + if ($transport instanceof Smtp && isset($spec['options'])) { + $transport->setOptions(new SmtpOptions($spec['options'])); + } + + if ($transport instanceof File && isset($spec['options'])) { + $transport->setOptions(new FileOptions($spec['options'])); + } + + return $transport; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/File.php b/lib/laminas/laminas-mail/src/Transport/File.php new file mode 100644 index 000000000..3b4b9783f --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/File.php @@ -0,0 +1,96 @@ +setOptions($options); + } + + /** + * @return FileOptions + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets options + * + * @param FileOptions $options + */ + public function setOptions(FileOptions $options) + { + $this->options = $options; + } + + /** + * Saves e-mail message to a file + * + * @param Message $message + * @throws Exception\RuntimeException on not writable target directory or + * on file_put_contents() failure + */ + public function send(Message $message) + { + $options = $this->options; + $filename = call_user_func($options->getCallback(), $this); + $file = $options->getPath() . DIRECTORY_SEPARATOR . $filename; + $email = $message->toString(); + + if (false === file_put_contents($file, $email)) { + throw new Exception\RuntimeException(sprintf( + 'Unable to write mail to file (directory "%s")', + $options->getPath() + )); + } + + $this->lastFile = $file; + } + + /** + * Get the name of the last file written to + * + * @return string + */ + public function getLastFile() + { + return $this->lastFile; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/FileOptions.php b/lib/laminas/laminas-mail/src/Transport/FileOptions.php new file mode 100644 index 000000000..2307a84d1 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/FileOptions.php @@ -0,0 +1,95 @@ +path = $path; + return $this; + } + + /** + * Get path + * + * If none is set, uses value from sys_get_temp_dir() + * + * @return string + */ + public function getPath() + { + if (null === $this->path) { + $this->setPath(sys_get_temp_dir()); + } + return $this->path; + } + + /** + * Set callback used to generate a file name + * + * @param callable $callback + * @throws \Laminas\Mail\Exception\InvalidArgumentException + * @return FileOptions + */ + public function setCallback($callback) + { + if (! is_callable($callback)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a valid callback; received "%s"', + __METHOD__, + (is_object($callback) ? get_class($callback) : gettype($callback)) + )); + } + $this->callback = $callback; + return $this; + } + + /** + * Get callback used to generate a file name + * + * @return callable + */ + public function getCallback() + { + if (null === $this->callback) { + $this->setCallback(function () { + return 'LaminasMail_' . time() . '_' . mt_rand() . '.eml'; + }); + } + return $this->callback; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/InMemory.php b/lib/laminas/laminas-mail/src/Transport/InMemory.php new file mode 100644 index 000000000..c21917b88 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/InMemory.php @@ -0,0 +1,46 @@ +lastMessage = $message; + } + + /** + * Get the last message sent. + * + * @return null|Message + */ + public function getLastMessage() + { + return $this->lastMessage; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/Null.php b/lib/laminas/laminas-mail/src/Transport/Null.php new file mode 100644 index 000000000..4f4ff3c5f --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/Null.php @@ -0,0 +1,34 @@ +setParameters($parameters); + } + $this->callable = [$this, 'mailHandler']; + } + + /** + * Set sendmail parameters + * + * Used to populate the additional_parameters argument to mail() + * + * @param null|string|array|Traversable $parameters + * @throws \Laminas\Mail\Transport\Exception\InvalidArgumentException + * @return Sendmail + */ + public function setParameters($parameters) + { + if ($parameters === null || is_string($parameters)) { + $this->parameters = $parameters; + return $this; + } + + if (! is_array($parameters) && ! $parameters instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string, array, or Traversable object of parameters; received "%s"', + __METHOD__, + (is_object($parameters) ? get_class($parameters) : gettype($parameters)) + )); + } + + $string = ''; + foreach ($parameters as $param) { + $string .= ' ' . $param; + } + + $this->parameters = trim($string); + return $this; + } + + /** + * Set callback to use for mail + * + * Primarily for testing purposes, but could be used to curry arguments. + * + * @param callable $callable + * @throws \Laminas\Mail\Transport\Exception\InvalidArgumentException + * @return Sendmail + */ + public function setCallable($callable) + { + if (! is_callable($callable)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a callable argument; received "%s"', + __METHOD__, + (is_object($callable) ? get_class($callable) : gettype($callable)) + )); + } + $this->callable = $callable; + return $this; + } + + /** + * Send a message + * + * @param \Laminas\Mail\Message $message + */ + public function send(Mail\Message $message) + { + $to = $this->prepareRecipients($message); + $subject = $this->prepareSubject($message); + $body = $this->prepareBody($message); + $headers = $this->prepareHeaders($message); + $params = $this->prepareParameters($message); + + // On *nix platforms, we need to replace \r\n with \n + // sendmail is not an SMTP server, it is a unix command - it expects LF + if (! $this->isWindowsOs()) { + $to = str_replace("\r\n", "\n", $to); + $subject = str_replace("\r\n", "\n", $subject); + $body = str_replace("\r\n", "\n", $body); + $headers = str_replace("\r\n", "\n", $headers); + } + + call_user_func($this->callable, $to, $subject, $body, $headers, $params); + } + + /** + * Prepare recipients list + * + * @param \Laminas\Mail\Message $message + * @throws \Laminas\Mail\Transport\Exception\RuntimeException + * @return string + */ + protected function prepareRecipients(Mail\Message $message) + { + $headers = $message->getHeaders(); + + $hasTo = $headers->has('to'); + if (! $hasTo && ! $headers->has('cc') && ! $headers->has('bcc')) { + throw new Exception\RuntimeException( + 'Invalid email; contains no at least one of "To", "Cc", and "Bcc" header' + ); + } + + if (! $hasTo) { + return ''; + } + + /** @var Mail\Header\To $to */ + $to = $headers->get('to'); + $list = $to->getAddressList(); + if (0 == count($list)) { + throw new Exception\RuntimeException('Invalid "To" header; contains no addresses'); + } + + // If not on Windows, return normal string + if (! $this->isWindowsOs()) { + return $to->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + // Otherwise, return list of emails + $addresses = []; + foreach ($list as $address) { + $addresses[] = $address->getEmail(); + } + $addresses = implode(', ', $addresses); + return $addresses; + } + + /** + * Prepare the subject line string + * + * @param \Laminas\Mail\Message $message + * @return string + */ + protected function prepareSubject(Mail\Message $message) + { + $headers = $message->getHeaders(); + if (! $headers->has('subject')) { + return; + } + $header = $headers->get('subject'); + return $header->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + /** + * Prepare the body string + * + * @param \Laminas\Mail\Message $message + * @return string + */ + protected function prepareBody(Mail\Message $message) + { + if (! $this->isWindowsOs()) { + // *nix platforms can simply return the body text + return $message->getBodyText(); + } + + // On windows, lines beginning with a full stop need to be fixed + $text = $message->getBodyText(); + $text = str_replace("\n.", "\n..", $text); + return $text; + } + + /** + * Prepare the textual representation of headers + * + * @param \Laminas\Mail\Message $message + * @return string + */ + protected function prepareHeaders(Mail\Message $message) + { + // On Windows, simply return verbatim + if ($this->isWindowsOs()) { + return $message->getHeaders()->toString(); + } + + // On *nix platforms, strip the "to" header + $headers = clone $message->getHeaders(); + $headers->removeHeader('To'); + $headers->removeHeader('Subject'); + + /** @var Mail\Header\From $from Sanitize the From header*/ + $from = $headers->get('From'); + if ($from) { + foreach ($from->getAddressList() as $address) { + if (strpos($address->getEmail(), '\\"') !== false) { + throw new Exception\RuntimeException('Potential code injection in From header'); + } + } + } + return $headers->toString(); + } + + /** + * Prepare additional_parameters argument + * + * Basically, overrides the MAIL FROM envelope with either the Sender or + * From address. + * + * @param \Laminas\Mail\Message $message + * @return string + */ + protected function prepareParameters(Mail\Message $message) + { + if ($this->isWindowsOs()) { + return; + } + + $parameters = (string) $this->parameters; + + $sender = $message->getSender(); + if ($sender instanceof AddressInterface) { + $parameters .= ' -f' . \escapeshellarg($sender->getEmail()); + return $parameters; + } + + $from = $message->getFrom(); + if (count($from)) { + $from->rewind(); + $sender = $from->current(); + $parameters .= ' -f' . \escapeshellarg($sender->getEmail()); + return $parameters; + } + + return $parameters; + } + + /** + * Send mail using PHP native mail() + * + * @param string $to + * @param string $subject + * @param string $message + * @param string $headers + * @param $parameters + * @throws \Laminas\Mail\Transport\Exception\RuntimeException + */ + public function mailHandler($to, $subject, $message, $headers, $parameters) + { + set_error_handler([$this, 'handleMailErrors']); + if ($parameters === null) { + $result = mail($to, $subject, $message, $headers); + } else { + $result = mail($to, $subject, $message, $headers, $parameters); + } + restore_error_handler(); + + if ($this->errstr !== null || ! $result) { + $errstr = $this->errstr; + if (empty($errstr)) { + $errstr = 'Unknown error'; + } + throw new Exception\RuntimeException('Unable to send mail: ' . $errstr); + } + } + + /** + * Temporary error handler for PHP native mail(). + * + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param string $errline + * @param array $errcontext + * @return bool always true + */ + public function handleMailErrors($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null) + { + $this->errstr = $errstr; + return true; + } + + /** + * Is this a windows OS? + * + * @return bool + */ + protected function isWindowsOs() + { + if (! $this->operatingSystem) { + $this->operatingSystem = strtoupper(substr(PHP_OS, 0, 3)); + } + return ($this->operatingSystem == 'WIN'); + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/Smtp.php b/lib/laminas/laminas-mail/src/Transport/Smtp.php new file mode 100644 index 000000000..52ec0e489 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/Smtp.php @@ -0,0 +1,406 @@ +setOptions($options); + } + + /** + * Set options + * + * @param SmtpOptions $options + * @return Smtp + */ + public function setOptions(SmtpOptions $options) + { + $this->options = $options; + return $this; + } + + /** + * Get options + * + * @return SmtpOptions + */ + public function getOptions() + { + return $this->options; + } + + /** + * Set options + * + * @param Envelope $envelope + */ + public function setEnvelope(Envelope $envelope) + { + $this->envelope = $envelope; + } + + /** + * Get envelope + * + * @return Envelope|null + */ + public function getEnvelope() + { + return $this->envelope; + } + + /** + * Set plugin manager for obtaining SMTP protocol connection + * + * @param Protocol\SmtpPluginManager $plugins + * @throws Exception\InvalidArgumentException + * @return Smtp + */ + public function setPluginManager(Protocol\SmtpPluginManager $plugins) + { + $this->plugins = $plugins; + return $this; + } + + /** + * Get plugin manager for loading SMTP protocol connection + * + * @return Protocol\SmtpPluginManager + */ + public function getPluginManager() + { + if (null === $this->plugins) { + $this->setPluginManager(new Protocol\SmtpPluginManager(new ServiceManager())); + } + return $this->plugins; + } + + /** + * Set the automatic disconnection when destruct + * + * @param bool $flag + * @return Smtp + */ + public function setAutoDisconnect($flag) + { + $this->autoDisconnect = (bool) $flag; + return $this; + } + + /** + * Get the automatic disconnection value + * + * @return bool + */ + public function getAutoDisconnect() + { + return $this->autoDisconnect; + } + + /** + * Return an SMTP connection + * + * @param string $name + * @param array|null $options + * @return Protocol\Smtp + */ + public function plugin($name, array $options = null) + { + return $this->getPluginManager()->get($name, $options); + } + + /** + * Class destructor to ensure all open connections are closed + */ + public function __destruct() + { + if (! $this->getConnection() instanceof Protocol\Smtp) { + return; + } + + try { + $this->getConnection()->quit(); + } catch (ProtocolException\ExceptionInterface $e) { + // ignore + } + + if ($this->autoDisconnect) { + $this->getConnection()->disconnect(); + } + } + + /** + * Sets the connection protocol instance + * + * @param Protocol\AbstractProtocol $connection + */ + public function setConnection(Protocol\AbstractProtocol $connection) + { + $this->connection = $connection; + if (($connection instanceof Protocol\Smtp) + && ($this->getOptions()->getConnectionTimeLimit() !== null) + ) { + $connection->setUseCompleteQuit(false); + } + } + + /** + * Gets the connection protocol instance + * + * @return Protocol\Smtp + */ + public function getConnection() + { + $timeLimit = $this->getOptions()->getConnectionTimeLimit(); + if ($timeLimit !== null + && $this->connectedTime !== null + && ((time() - $this->connectedTime) > $timeLimit) + ) { + $this->connection = null; + } + return $this->connection; + } + + /** + * Disconnect the connection protocol instance + * + * @return void + */ + public function disconnect() + { + if ($this->getConnection() instanceof Protocol\Smtp) { + $this->getConnection()->disconnect(); + $this->connectedTime = null; + } + } + + /** + * Send an email via the SMTP connection protocol + * + * The connection via the protocol adapter is made just-in-time to allow a + * developer to add a custom adapter if required before mail is sent. + * + * @param Message $message + * @throws Exception\RuntimeException + */ + public function send(Message $message) + { + // If sending multiple messages per session use existing adapter + $connection = $this->getConnection(); + + if (! ($connection instanceof Protocol\Smtp) || ! $connection->hasSession()) { + $connection = $this->connect(); + } else { + // Reset connection to ensure reliable transaction + $connection->rset(); + } + + // Prepare message + $from = $this->prepareFromAddress($message); + $recipients = $this->prepareRecipients($message); + $headers = $this->prepareHeaders($message); + $body = $this->prepareBody($message); + + if ((count($recipients) == 0) && (! empty($headers) || ! empty($body))) { + // Per RFC 2821 3.3 (page 18) + throw new Exception\RuntimeException( + sprintf( + '%s transport expects at least one recipient if the message has at least one header or body', + __CLASS__ + ) + ); + } + + // Set sender email address + $connection->mail($from); + + // Set recipient forward paths + foreach ($recipients as $recipient) { + $connection->rcpt($recipient); + } + + // Issue DATA command to client + $connection->data($headers . Headers::EOL . $body); + } + + /** + * Retrieve email address for envelope FROM + * + * @param Message $message + * @throws Exception\RuntimeException + * @return string + */ + protected function prepareFromAddress(Message $message) + { + if ($this->getEnvelope() && $this->getEnvelope()->getFrom()) { + return $this->getEnvelope()->getFrom(); + } + + $sender = $message->getSender(); + if ($sender instanceof Address\AddressInterface) { + return $sender->getEmail(); + } + + $from = $message->getFrom(); + if (! count($from)) { + // Per RFC 2822 3.6 + throw new Exception\RuntimeException(sprintf( + '%s transport expects either a Sender or at least one From address in the Message; none provided', + __CLASS__ + )); + } + + $from->rewind(); + $sender = $from->current(); + return $sender->getEmail(); + } + + /** + * Prepare array of email address recipients + * + * @param Message $message + * @return array + */ + protected function prepareRecipients(Message $message) + { + if ($this->getEnvelope() && $this->getEnvelope()->getTo()) { + return (array) $this->getEnvelope()->getTo(); + } + + $recipients = []; + foreach ($message->getTo() as $address) { + $recipients[] = $address->getEmail(); + } + foreach ($message->getCc() as $address) { + $recipients[] = $address->getEmail(); + } + foreach ($message->getBcc() as $address) { + $recipients[] = $address->getEmail(); + } + + $recipients = array_unique($recipients); + return $recipients; + } + + /** + * Prepare header string from message + * + * @param Message $message + * @return string + */ + protected function prepareHeaders(Message $message) + { + $headers = clone $message->getHeaders(); + $headers->removeHeader('Bcc'); + return $headers->toString(); + } + + /** + * Prepare body string from message + * + * @param Message $message + * @return string + */ + protected function prepareBody(Message $message) + { + return $message->getBodyText(); + } + + /** + * Lazy load the connection + * + * @return Protocol\Smtp + */ + protected function lazyLoadConnection() + { + // Check if authentication is required and determine required class + $options = $this->getOptions(); + $config = $options->getConnectionConfig(); + $config['host'] = $options->getHost(); + $config['port'] = $options->getPort(); + + $this->setConnection($this->plugin($options->getConnectionClass(), $config)); + + return $this->connect(); + } + + /** + * Connect the connection, and pass it helo + * + * @return Protocol\Smtp + */ + protected function connect() + { + if (! $this->connection instanceof Protocol\Smtp) { + return $this->lazyLoadConnection(); + } + + $this->connection->connect(); + + $this->connectedTime = time(); + + $this->connection->helo($this->getOptions()->getName()); + + return $this->connection; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/SmtpOptions.php b/lib/laminas/laminas-mail/src/Transport/SmtpOptions.php new file mode 100644 index 000000000..0f14aaacd --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/SmtpOptions.php @@ -0,0 +1,209 @@ +name; + } + + /** + * Set the local client hostname or IP + * + * @todo hostname/IP validation + * @param string $name + * @throws \Laminas\Mail\Exception\InvalidArgumentException + * @return SmtpOptions + */ + public function setName($name) + { + if (! is_string($name) && $name !== null) { + throw new Exception\InvalidArgumentException(sprintf( + 'Name must be a string or null; argument of type "%s" provided', + (is_object($name) ? get_class($name) : gettype($name)) + )); + } + $this->name = $name; + return $this; + } + + /** + * Get connection class + * + * This should be either the class Laminas\Mail\Protocol\Smtp or a class + * extending it -- typically a class in the Laminas\Mail\Protocol\Smtp\Auth + * namespace. + * + * @return string + */ + public function getConnectionClass() + { + return $this->connectionClass; + } + + /** + * Set connection class + * + * @param string $connectionClass the value to be set + * @throws \Laminas\Mail\Exception\InvalidArgumentException + * @return SmtpOptions + */ + public function setConnectionClass($connectionClass) + { + if (! is_string($connectionClass) && $connectionClass !== null) { + throw new Exception\InvalidArgumentException(sprintf( + 'Connection class must be a string or null; argument of type "%s" provided', + (is_object($connectionClass) ? get_class($connectionClass) : gettype($connectionClass)) + )); + } + $this->connectionClass = $connectionClass; + return $this; + } + + /** + * Get connection configuration array + * + * @return array + */ + public function getConnectionConfig() + { + return $this->connectionConfig; + } + + /** + * Set connection configuration array + * + * @param array $connectionConfig + * @return SmtpOptions + */ + public function setConnectionConfig(array $connectionConfig) + { + $this->connectionConfig = $connectionConfig; + return $this; + } + + /** + * Get the host name + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the SMTP host + * + * @todo hostname/IP validation + * @param string $host + * @return SmtpOptions + */ + public function setHost($host) + { + $this->host = (string) $host; + return $this; + } + + /** + * Get the port the SMTP server runs on + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Set the port the SMTP server runs on + * + * @param int $port + * @throws \Laminas\Mail\Exception\InvalidArgumentException + * @return SmtpOptions + */ + public function setPort($port) + { + $port = (int) $port; + if ($port < 1) { + throw new Exception\InvalidArgumentException(sprintf( + 'Port must be greater than 1; received "%d"', + $port + )); + } + $this->port = $port; + return $this; + } + + /** + * @return int|null + */ + public function getConnectionTimeLimit() + { + return $this->connectionTimeLimit; + } + + /** + * @param int|null $seconds + * @return self + */ + public function setConnectionTimeLimit($seconds) + { + $this->connectionTimeLimit = $seconds === null + ? null + : (int) $seconds; + + return $this; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/TransportInterface.php b/lib/laminas/laminas-mail/src/Transport/TransportInterface.php new file mode 100644 index 000000000..fe7719ca3 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/TransportInterface.php @@ -0,0 +1,25 @@ + array(name => value), 'body' => content), null if no parts found + * @throws Exception\RuntimeException + */ + public static function splitMessageStruct($message, $boundary, $EOL = Mime::LINEEND) + { + $parts = static::splitMime($message, $boundary); + if (! $parts) { + return; + } + $result = []; + $headers = null; // "Declare" variable before the first usage "for reading" + $body = null; // "Declare" variable before the first usage "for reading" + foreach ($parts as $part) { + static::splitMessage($part, $headers, $body, $EOL); + $result[] = [ + 'header' => $headers, + 'body' => $body, + ]; + } + return $result; + } + + /** + * split a message in header and body part, if no header or an + * invalid header is found $headers is empty + * + * The charset of the returned headers depend on your iconv settings. + * + * @param string|Headers $message raw message with header and optional content + * @param Headers $headers output param, headers container + * @param string $body output param, content of message + * @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND} + * @param bool $strict enable strict mode for parsing message + * @return null + */ + public static function splitMessage($message, &$headers, &$body, $EOL = Mime::LINEEND, $strict = false) + { + if ($message instanceof Headers) { + $message = $message->toString(); + } + // check for valid header at first line + $firstlinePos = strpos($message, "\n"); + $firstline = $firstlinePos === false ? $message : substr($message, 0, $firstlinePos); + if (! preg_match('%^[^\s]+[^:]*:%', $firstline)) { + $headers = new Headers(); + // TODO: we're ignoring \r for now - is this function fast enough and is it safe to assume noone needs \r? + $body = str_replace(["\r", "\n"], ['', $EOL], $message); + return; + } + + // see @Laminas-372, pops the first line off a message if it doesn't contain a header + if (! $strict) { + $parts = explode(':', $firstline, 2); + if (count($parts) != 2) { + $message = substr($message, strpos($message, $EOL) + 1); + } + } + + // @todo splitMime removes "\r" sequences, which breaks valid mime + // messages as returned by many mail servers + $headersEOL = $EOL; + + // find an empty line between headers and body + // default is set new line + // @todo Maybe this is too much "magic"; we should be more strict here + if (strpos($message, $EOL . $EOL)) { + list($headers, $body) = explode($EOL . $EOL, $message, 2); + // next is the standard new line + } elseif ($EOL != "\r\n" && strpos($message, "\r\n\r\n")) { + list($headers, $body) = explode("\r\n\r\n", $message, 2); + $headersEOL = "\r\n"; // Headers::fromString will fail with incorrect EOL + // next is the other "standard" new line + } elseif ($EOL != "\n" && strpos($message, "\n\n")) { + list($headers, $body) = explode("\n\n", $message, 2); + $headersEOL = "\n"; + // at last resort find anything that looks like a new line + } else { + ErrorHandler::start(E_NOTICE | E_WARNING); + list($headers, $body) = preg_split("%([\r\n]+)\\1%U", $message, 2); + ErrorHandler::stop(); + } + + $headers = Headers::fromString($headers, $headersEOL); + } + + /** + * split a content type in its different parts + * + * @param string $type content-type + * @param string $wantedPart the wanted part, else an array with all parts is returned + * @return string|array wanted part or all parts as array('type' => content-type, partname => value) + */ + public static function splitContentType($type, $wantedPart = null) + { + return static::splitHeaderField($type, $wantedPart, 'type'); + } + + /** + * split a header field like content type in its different parts + * + * @param string $field header field + * @param string $wantedPart the wanted part, else an array with all parts is returned + * @param string $firstName key name for the first part + * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) + * @throws Exception\RuntimeException + */ + public static function splitHeaderField($field, $wantedPart = null, $firstName = '0') + { + $wantedPart = strtolower($wantedPart); + $firstName = strtolower($firstName); + + // special case - a bit optimized + if ($firstName === $wantedPart) { + $field = strtok($field, ';'); + return $field[0] == '"' ? substr($field, 1, -1) : $field; + } + + $field = $firstName . '=' . $field; + if (! preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) { + throw new Exception\RuntimeException('not a valid header field'); + } + + if ($wantedPart) { + foreach ($matches[1] as $key => $name) { + if (strcasecmp($name, $wantedPart)) { + continue; + } + if ($matches[2][$key][0] != '"') { + return $matches[2][$key]; + } + return substr($matches[2][$key], 1, -1); + } + return; + } + + $split = []; + foreach ($matches[1] as $key => $name) { + $name = strtolower($name); + if ($matches[2][$key][0] == '"') { + $split[$name] = substr($matches[2][$key], 1, -1); + } else { + $split[$name] = $matches[2][$key]; + } + } + + return $split; + } + + /** + * decode a quoted printable encoded string + * + * The charset of the returned string depends on your iconv settings. + * + * @param string $string encoded string + * @return string decoded string + */ + public static function decodeQuotedPrintable($string) + { + return iconv_mime_decode($string, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8'); + } +} diff --git a/lib/laminas/laminas-mime/src/Exception/ExceptionInterface.php b/lib/laminas/laminas-mime/src/Exception/ExceptionInterface.php new file mode 100644 index 000000000..5217cb08e --- /dev/null +++ b/lib/laminas/laminas-mime/src/Exception/ExceptionInterface.php @@ -0,0 +1,13 @@ +parts; + } + + /** + * Sets the given array of Laminas\Mime\Part as the array for the message + * + * @param array $parts + * @return self + */ + public function setParts($parts) + { + $this->parts = $parts; + return $this; + } + + /** + * Append a new Laminas\Mime\Part to the current message + * + * @param \Laminas\Mime\Part $part + * @throws Exception\InvalidArgumentException + * @return self + */ + public function addPart(Part $part) + { + foreach ($this->getParts() as $key => $row) { + if ($part == $row) { + throw new Exception\InvalidArgumentException(sprintf( + 'Provided part %s already defined.', + $part->getId() + )); + } + } + + $this->parts[] = $part; + return $this; + } + + /** + * Check if message needs to be sent as multipart + * MIME message or if it has only one part. + * + * @return bool + */ + public function isMultiPart() + { + return (count($this->parts) > 1); + } + + /** + * Set Laminas\Mime\Mime object for the message + * + * This can be used to set the boundary specifically or to use a subclass of + * Laminas\Mime for generating the boundary. + * + * @param \Laminas\Mime\Mime $mime + * @return self + */ + public function setMime(Mime $mime) + { + $this->mime = $mime; + return $this; + } + + /** + * Returns the Laminas\Mime\Mime object in use by the message + * + * If the object was not present, it is created and returned. Can be used to + * determine the boundary used in this message. + * + * @return \Laminas\Mime\Mime + */ + public function getMime() + { + if ($this->mime === null) { + $this->mime = new Mime(); + } + + return $this->mime; + } + + /** + * Generate MIME-compliant message from the current configuration + * + * This can be a multipart message if more than one MIME part was added. If + * only one part is present, the content of this part is returned. If no + * part had been added, an empty string is returned. + * + * Parts are separated by the mime boundary as defined in Laminas\Mime\Mime. If + * {@link setMime()} has been called before this method, the Laminas\Mime\Mime + * object set by this call will be used. Otherwise, a new Laminas\Mime\Mime object + * is generated and used. + * + * @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND} + * @return string + */ + public function generateMessage($EOL = Mime::LINEEND) + { + if (! $this->isMultiPart()) { + if (empty($this->parts)) { + return ''; + } + $part = current($this->parts); + $body = $part->getContent($EOL); + } else { + $mime = $this->getMime(); + + $boundaryLine = $mime->boundaryLine($EOL); + $body = 'This is a message in Mime Format. If you see this, ' + . "your mail reader does not support this format." . $EOL; + + foreach (array_keys($this->parts) as $p) { + $body .= $boundaryLine + . $this->getPartHeaders($p, $EOL) + . $EOL + . $this->getPartContent($p, $EOL); + } + + $body .= $mime->mimeEnd($EOL); + } + + return trim($body); + } + + /** + * Get the headers of a given part as an array + * + * @param int $partnum + * @return array + */ + public function getPartHeadersArray($partnum) + { + return $this->parts[$partnum]->getHeadersArray(); + } + + /** + * Get the headers of a given part as a string + * + * @param int $partnum + * @param string $EOL + * @return string + */ + public function getPartHeaders($partnum, $EOL = Mime::LINEEND) + { + return $this->parts[$partnum]->getHeaders($EOL); + } + + /** + * Get the (encoded) content of a given part as a string + * + * @param int $partnum + * @param string $EOL + * @return string + */ + public function getPartContent($partnum, $EOL = Mime::LINEEND) + { + return $this->parts[$partnum]->getContent($EOL); + } + + /** + * Explode MIME multipart string into separate parts + * + * Parts consist of the header and the body of each MIME part. + * + * @param string $body + * @param string $boundary + * @throws Exception\RuntimeException + * @return array + */ + // @codingStandardsIgnoreStart + protected static function _disassembleMime($body, $boundary) + { + // @codingStandardsIgnoreEnd + $start = 0; + $res = []; + // find every mime part limiter and cut out the + // string before it. + // the part before the first boundary string is discarded: + $p = strpos($body, '--' . $boundary."\n", $start); + if ($p === false) { + // no parts found! + return []; + } + + // position after first boundary line + $start = $p + 3 + strlen($boundary); + + while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) { + $res[] = substr($body, $start, $p - $start); + $start = $p + 3 + strlen($boundary); + } + + // no more parts, find end boundary + $p = strpos($body, '--' . $boundary . '--', $start); + if ($p === false) { + throw new Exception\RuntimeException('Not a valid Mime Message: End Missing'); + } + + // the remaining part also needs to be parsed: + $res[] = substr($body, $start, $p - $start); + return $res; + } + + /** + * Decodes a MIME encoded string and returns a Laminas\Mime\Message object with + * all the MIME parts set according to the given string + * + * @param string $message + * @param string $boundary Multipart boundary; if omitted, $message will be + * treated as a single part. + * @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND} + * @throws Exception\RuntimeException + * @return Message + */ + public static function createFromMessage($message, $boundary = null, $EOL = Mime::LINEEND) + { + if ($boundary) { + $parts = Decode::splitMessageStruct($message, $boundary, $EOL); + } else { + Decode::splitMessage($message, $headers, $body, $EOL); + $parts = [[ + 'header' => $headers, + 'body' => $body, + ]]; + } + + $res = new static(); + foreach ($parts as $part) { + // now we build a new MimePart for the current Message Part: + $properties = []; + foreach ($part['header'] as $header) { + /** @var \Laminas\Mail\Header\HeaderInterface $header */ + /** + * @todo check for characterset and filename + */ + + $fieldName = $header->getFieldName(); + $fieldValue = $header->getFieldValue(); + switch (strtolower($fieldName)) { + case 'content-type': + $properties['type'] = $fieldValue; + break; + case 'content-transfer-encoding': + $properties['encoding'] = $fieldValue; + break; + case 'content-id': + $properties['id'] = trim($fieldValue, '<>'); + break; + case 'content-disposition': + $properties['disposition'] = $fieldValue; + break; + case 'content-description': + $properties['description'] = $fieldValue; + break; + case 'content-location': + $properties['location'] = $fieldValue; + break; + case 'content-language': + $properties['language'] = $fieldValue; + break; + default: + // Ignore unknown header + break; + } + } + + $body = $part['body']; + + if (isset($properties['encoding'])) { + switch ($properties['encoding']) { + case 'quoted-printable': + $body = quoted_printable_decode($body); + break; + case 'base64': + $body = base64_decode($body); + break; + } + } + + $newPart = new Part($body); + foreach ($properties as $key => $value) { + $newPart->$key = $value; + } + $res->addPart($newPart); + } + + return $res; + } +} diff --git a/lib/laminas/laminas-mime/src/Mime.php b/lib/laminas/laminas-mime/src/Mime.php new file mode 100644 index 000000000..20004023f --- /dev/null +++ b/lib/laminas/laminas-mime/src/Mime.php @@ -0,0 +1,398 @@ +[\x21\x23-\x26\x2a\x2b\x2d\x5e\5f\60\x7b-\x7ea-zA-Z0-9]+)\?(?P[\x21\x23-\x26\x2a\x2b\x2d\x5e\5f\60\x7b-\x7ea-zA-Z0-9]+)\?(?P[\x21-\x3e\x40-\x7e]+)#'; + // @codingStandardsIgnoreEnd + + protected $boundary; + protected static $makeUnique = 0; + + // lookup-Tables for QuotedPrintable + public static $qpKeys = [ + "\x00","\x01","\x02","\x03","\x04","\x05","\x06","\x07", + "\x08","\x09","\x0A","\x0B","\x0C","\x0D","\x0E","\x0F", + "\x10","\x11","\x12","\x13","\x14","\x15","\x16","\x17", + "\x18","\x19","\x1A","\x1B","\x1C","\x1D","\x1E","\x1F", + "\x7F","\x80","\x81","\x82","\x83","\x84","\x85","\x86", + "\x87","\x88","\x89","\x8A","\x8B","\x8C","\x8D","\x8E", + "\x8F","\x90","\x91","\x92","\x93","\x94","\x95","\x96", + "\x97","\x98","\x99","\x9A","\x9B","\x9C","\x9D","\x9E", + "\x9F","\xA0","\xA1","\xA2","\xA3","\xA4","\xA5","\xA6", + "\xA7","\xA8","\xA9","\xAA","\xAB","\xAC","\xAD","\xAE", + "\xAF","\xB0","\xB1","\xB2","\xB3","\xB4","\xB5","\xB6", + "\xB7","\xB8","\xB9","\xBA","\xBB","\xBC","\xBD","\xBE", + "\xBF","\xC0","\xC1","\xC2","\xC3","\xC4","\xC5","\xC6", + "\xC7","\xC8","\xC9","\xCA","\xCB","\xCC","\xCD","\xCE", + "\xCF","\xD0","\xD1","\xD2","\xD3","\xD4","\xD5","\xD6", + "\xD7","\xD8","\xD9","\xDA","\xDB","\xDC","\xDD","\xDE", + "\xDF","\xE0","\xE1","\xE2","\xE3","\xE4","\xE5","\xE6", + "\xE7","\xE8","\xE9","\xEA","\xEB","\xEC","\xED","\xEE", + "\xEF","\xF0","\xF1","\xF2","\xF3","\xF4","\xF5","\xF6", + "\xF7","\xF8","\xF9","\xFA","\xFB","\xFC","\xFD","\xFE", + "\xFF" + ]; + + public static $qpReplaceValues = [ + "=00","=01","=02","=03","=04","=05","=06","=07", + "=08","=09","=0A","=0B","=0C","=0D","=0E","=0F", + "=10","=11","=12","=13","=14","=15","=16","=17", + "=18","=19","=1A","=1B","=1C","=1D","=1E","=1F", + "=7F","=80","=81","=82","=83","=84","=85","=86", + "=87","=88","=89","=8A","=8B","=8C","=8D","=8E", + "=8F","=90","=91","=92","=93","=94","=95","=96", + "=97","=98","=99","=9A","=9B","=9C","=9D","=9E", + "=9F","=A0","=A1","=A2","=A3","=A4","=A5","=A6", + "=A7","=A8","=A9","=AA","=AB","=AC","=AD","=AE", + "=AF","=B0","=B1","=B2","=B3","=B4","=B5","=B6", + "=B7","=B8","=B9","=BA","=BB","=BC","=BD","=BE", + "=BF","=C0","=C1","=C2","=C3","=C4","=C5","=C6", + "=C7","=C8","=C9","=CA","=CB","=CC","=CD","=CE", + "=CF","=D0","=D1","=D2","=D3","=D4","=D5","=D6", + "=D7","=D8","=D9","=DA","=DB","=DC","=DD","=DE", + "=DF","=E0","=E1","=E2","=E3","=E4","=E5","=E6", + "=E7","=E8","=E9","=EA","=EB","=EC","=ED","=EE", + "=EF","=F0","=F1","=F2","=F3","=F4","=F5","=F6", + "=F7","=F8","=F9","=FA","=FB","=FC","=FD","=FE", + "=FF" + ]; + // @codingStandardsIgnoreStart + public static $qpKeysString = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"; + // @codingStandardsIgnoreEnd + + /** + * Check if the given string is "printable" + * + * Checks that a string contains no unprintable characters. If this returns + * false, encode the string for secure delivery. + * + * @param string $str + * @return bool + */ + public static function isPrintable($str) + { + return (strcspn($str, static::$qpKeysString) == strlen($str)); + } + + /** + * Encode a given string with the QUOTED_PRINTABLE mechanism and wrap the lines. + * + * @param string $str + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param string $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeQuotedPrintable( + $str, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND + ) { + $out = ''; + $str = self::_encodeQuotedPrintable($str); + + // Split encoded text into separate lines + $initialPtr = 0; + $strLength = strlen($str); + while ($initialPtr < $strLength) { + $continueAt = $strLength - $initialPtr; + + if ($continueAt > $lineLength) { + $continueAt = $lineLength; + } + + $chunk = substr($str, $initialPtr, $continueAt); + + // Ensure we are not splitting across an encoded character + $endingMarkerPos = strrpos($chunk, '='); + if ($endingMarkerPos !== false && $endingMarkerPos >= strlen($chunk) - 2) { + $chunk = substr($chunk, 0, $endingMarkerPos); + $continueAt = $endingMarkerPos; + } + + if (ord($chunk[0]) == 0x2E) { // 0x2E is a dot + $chunk = '=2E' . substr($chunk, 1); + } + + // copied from swiftmailer https://git.io/vAXU1 + switch (ord(substr($chunk, strlen($chunk) - 1))) { + case 0x09: // Horizontal Tab + $chunk = substr_replace($chunk, '=09', strlen($chunk) - 1, 1); + break; + case 0x20: // Space + $chunk = substr_replace($chunk, '=20', strlen($chunk) - 1, 1); + break; + } + + // Add string and continue + $out .= $chunk . '=' . $lineEnd; + $initialPtr += $continueAt; + } + + $out = rtrim($out, $lineEnd); + $out = rtrim($out, '='); + return $out; + } + + /** + * Converts a string into quoted printable format. + * + * @param string $str + * @return string + */ + // @codingStandardsIgnoreStart + private static function _encodeQuotedPrintable($str) + { + // @codingStandardsIgnoreEnd + $str = str_replace('=', '=3D', $str); + $str = str_replace(static::$qpKeys, static::$qpReplaceValues, $str); + $str = rtrim($str); + return $str; + } + + /** + * Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers. + * + * Mail headers depend on an extended quoted printable algorithm otherwise + * a range of bugs can occur. + * + * @param string $str + * @param string $charset + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param string $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeQuotedPrintableHeader( + $str, + $charset, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND + ) { + // Reduce line-length by the length of the required delimiter, charsets and encoding + $prefix = sprintf('=?%s?Q?', $charset); + $lineLength = $lineLength - strlen($prefix) - 3; + + $str = self::_encodeQuotedPrintable($str); + + // Mail-Header required chars have to be encoded also: + $str = str_replace(['?', ',', ' ', '_'], ['=3F', '=2C', '=20', '=5F'], $str); + + // initialize first line, we need it anyways + $lines = [0 => '']; + + // Split encoded text into separate lines + $tmp = ''; + while (strlen($str) > 0) { + $currentLine = max(count($lines) - 1, 0); + $token = static::getNextQuotedPrintableToken($str); + $substr = substr($str, strlen($token)); + $str = (false === $substr) ? '' : $substr; + + $tmp .= $token; + if ($token === '=20') { + // only if we have a single char token or space, we can append the + // tempstring it to the current line or start a new line if necessary. + $lineLimitReached = (strlen($lines[$currentLine] . $tmp) > $lineLength); + $noCurrentLine = ($lines[$currentLine] === ''); + if ($noCurrentLine && $lineLimitReached) { + $lines[$currentLine] = $tmp; + $lines[$currentLine + 1] = ''; + } elseif ($lineLimitReached) { + $lines[$currentLine + 1] = $tmp; + } else { + $lines[$currentLine] .= $tmp; + } + $tmp = ''; + } + // don't forget to append the rest to the last line + if (strlen($str) === 0) { + $lines[$currentLine] .= $tmp; + } + } + + // assemble the lines together by pre- and appending delimiters, charset, encoding. + for ($i = 0, $count = count($lines); $i < $count; $i++) { + $lines[$i] = " " . $prefix . $lines[$i] . "?="; + } + $str = trim(implode($lineEnd, $lines)); + return $str; + } + + /** + * Retrieves the first token from a quoted printable string. + * + * @param string $str + * @return string + */ + private static function getNextQuotedPrintableToken($str) + { + if (0 === strpos($str, '=')) { + $token = substr($str, 0, 3); + } else { + $token = substr($str, 0, 1); + } + return $token; + } + + /** + * Encode a given string in mail header compatible base64 encoding. + * + * @param string $str + * @param string $charset + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param string $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeBase64Header( + $str, + $charset, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND + ) { + $prefix = '=?' . $charset . '?B?'; + $suffix = '?='; + $remainingLength = $lineLength - strlen($prefix) - strlen($suffix); + + $encodedValue = static::encodeBase64($str, $remainingLength, $lineEnd); + $encodedValue = str_replace($lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue); + $encodedValue = $prefix . $encodedValue . $suffix; + return $encodedValue; + } + + /** + * Encode a given string in base64 encoding and break lines + * according to the maximum linelength. + * + * @param string $str + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param string $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeBase64( + $str, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND + ) { + $lineLength = $lineLength - ($lineLength % 4); + return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd)); + } + + /** + * Constructor + * + * @param null|string $boundary + * @access public + */ + public function __construct($boundary = null) + { + // This string needs to be somewhat unique + if ($boundary === null) { + $this->boundary = '=_' . md5(microtime(1) . static::$makeUnique++); + } else { + $this->boundary = $boundary; + } + } + + /** + * Encode the given string with the given encoding. + * + * @param string $str + * @param string $encoding + * @param string $EOL EOL string; defaults to {@link LINEEND} + * @return string + */ + public static function encode($str, $encoding, $EOL = self::LINEEND) + { + switch ($encoding) { + case self::ENCODING_BASE64: + return static::encodeBase64($str, self::LINELENGTH, $EOL); + + case self::ENCODING_QUOTEDPRINTABLE: + return static::encodeQuotedPrintable($str, self::LINELENGTH, $EOL); + + default: + /** + * @todo 7Bit and 8Bit is currently handled the same way. + */ + return $str; + } + } + + /** + * Return a MIME boundary + * + * @access public + * @return string + */ + public function boundary() + { + return $this->boundary; + } + + /** + * Return a MIME boundary line + * + * @param string $EOL Defaults to {@link LINEEND} + * @access public + * @return string + */ + public function boundaryLine($EOL = self::LINEEND) + { + return $EOL . '--' . $this->boundary . $EOL; + } + + /** + * Return MIME ending + * + * @param string $EOL Defaults to {@link LINEEND} + * @access public + * @return string + */ + public function mimeEnd($EOL = self::LINEEND) + { + return $EOL . '--' . $this->boundary . '--' . $EOL; + } + + /** + * Detect MIME charset + * + * Extract parts according to https://tools.ietf.org/html/rfc2047#section-2 + * + * @param string $str + * @return string + */ + public static function mimeDetectCharset($str) + { + if (preg_match(self::CHARSET_REGEX, $str, $matches)) { + return strtoupper($matches['charset']); + } + + return 'ASCII'; + } +} diff --git a/lib/laminas/laminas-mime/src/Part.php b/lib/laminas/laminas-mime/src/Part.php new file mode 100644 index 000000000..1760e016c --- /dev/null +++ b/lib/laminas/laminas-mime/src/Part.php @@ -0,0 +1,483 @@ +setContent($content); + } + + /** + * @todo error checking for setting $type + * @todo error checking for setting $encoding + */ + + /** + * Set type + * @param string $type + * @return self + */ + public function setType($type = Mime::TYPE_OCTETSTREAM) + { + $this->type = $type; + return $this; + } + + /** + * Get type + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set encoding + * @param string $encoding + * @return self + */ + public function setEncoding($encoding = Mime::ENCODING_8BIT) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get encoding + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Set id + * @param string $id + * @return self + */ + public function setId($id) + { + $this->id = $id; + return $this; + } + + /** + * Get id + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Set disposition + * @param string $disposition + * @return self + */ + public function setDisposition($disposition) + { + $this->disposition = $disposition; + return $this; + } + + /** + * Get disposition + * @return string + */ + public function getDisposition() + { + return $this->disposition; + } + + /** + * Set description + * @param string $description + * @return self + */ + public function setDescription($description) + { + $this->description = $description; + return $this; + } + + /** + * Get description + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set filename + * @param string $fileName + * @return self + */ + public function setFileName($fileName) + { + $this->filename = $fileName; + return $this; + } + + /** + * Get filename + * @return string + */ + public function getFileName() + { + return $this->filename; + } + + /** + * Set charset + * @param string $type + * @return self + */ + public function setCharset($charset) + { + $this->charset = $charset; + return $this; + } + + /** + * Get charset + * @return string + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Set boundary + * @param string $boundary + * @return self + */ + public function setBoundary($boundary) + { + $this->boundary = $boundary; + return $this; + } + + /** + * Get boundary + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + /** + * Set location + * @param string $location + * @return self + */ + public function setLocation($location) + { + $this->location = $location; + return $this; + } + + /** + * Get location + * @return string + */ + public function getLocation() + { + return $this->location; + } + + /** + * Set language + * @param string $language + * @return self + */ + public function setLanguage($language) + { + $this->language = $language; + return $this; + } + + /** + * Get language + * @return string + */ + public function getLanguage() + { + return $this->language; + } + + /** + * Set content + * @param mixed $content String or Stream containing the content + * @throws Exception\InvalidArgumentException + * @return self + */ + public function setContent($content) + { + if (! is_string($content) && ! is_resource($content)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Content must be string or resource; received "%s"', + is_object($content) ? get_class($content) : gettype($content) + )); + } + $this->content = $content; + if (is_resource($content)) { + $this->isStream = true; + } + + return $this; + } + + /** + * Set isStream + * @param bool $isStream + * @return self + */ + public function setIsStream($isStream = false) + { + $this->isStream = (bool) $isStream; + return $this; + } + + /** + * Get isStream + * @return bool + */ + public function getIsStream() + { + return $this->isStream; + } + + /** + * Set filters + * @param array $filters + * @return self + */ + public function setFilters($filters = []) + { + $this->filters = $filters; + return $this; + } + + /** + * Get Filters + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * check if this part can be read as a stream. + * if true, getEncodedStream can be called, otherwise + * only getContent can be used to fetch the encoded + * content of the part + * + * @return bool + */ + public function isStream() + { + return $this->isStream; + } + + /** + * if this was created with a stream, return a filtered stream for + * reading the content. very useful for large file attachments. + * + * @param string $EOL + * @return resource + * @throws Exception\RuntimeException if not a stream or unable to append filter + */ + public function getEncodedStream($EOL = Mime::LINEEND) + { + if (! $this->isStream) { + throw new Exception\RuntimeException('Attempt to get a stream from a string part'); + } + + //stream_filter_remove(); // ??? is that right? + switch ($this->encoding) { + case Mime::ENCODING_QUOTEDPRINTABLE: + if (array_key_exists(Mime::ENCODING_QUOTEDPRINTABLE, $this->filters)) { + stream_filter_remove($this->filters[Mime::ENCODING_QUOTEDPRINTABLE]); + } + $filter = stream_filter_append( + $this->content, + 'convert.quoted-printable-encode', + STREAM_FILTER_READ, + [ + 'line-length' => 76, + 'line-break-chars' => $EOL + ] + ); + $this->filters[Mime::ENCODING_QUOTEDPRINTABLE] = $filter; + if (! is_resource($filter)) { + throw new Exception\RuntimeException('Failed to append quoted-printable filter'); + } + break; + case Mime::ENCODING_BASE64: + if (array_key_exists(Mime::ENCODING_BASE64, $this->filters)) { + stream_filter_remove($this->filters[Mime::ENCODING_BASE64]); + } + $filter = stream_filter_append( + $this->content, + 'convert.base64-encode', + STREAM_FILTER_READ, + [ + 'line-length' => 76, + 'line-break-chars' => $EOL + ] + ); + $this->filters[Mime::ENCODING_BASE64] = $filter; + if (! is_resource($filter)) { + throw new Exception\RuntimeException('Failed to append base64 filter'); + } + break; + default: + } + return $this->content; + } + + /** + * Get the Content of the current Mime Part in the given encoding. + * + * @param string $EOL + * @return string + */ + public function getContent($EOL = Mime::LINEEND) + { + if ($this->isStream) { + $encodedStream = $this->getEncodedStream($EOL); + $encodedStreamContents = stream_get_contents($encodedStream); + $streamMetaData = stream_get_meta_data($encodedStream); + + if (isset($streamMetaData['seekable']) && $streamMetaData['seekable']) { + rewind($encodedStream); + } + + return $encodedStreamContents; + } + return Mime::encode($this->content, $this->encoding, $EOL); + } + + /** + * Get the RAW unencoded content from this part + * @return string + */ + public function getRawContent() + { + if ($this->isStream) { + return stream_get_contents($this->content); + } + return $this->content; + } + + /** + * Create and return the array of headers for this MIME part + * + * @access public + * @param string $EOL + * @return array + */ + public function getHeadersArray($EOL = Mime::LINEEND) + { + $headers = []; + + $contentType = $this->type; + if ($this->charset) { + $contentType .= '; charset=' . $this->charset; + } + + if ($this->boundary) { + $contentType .= ';' . $EOL + . " boundary=\"" . $this->boundary . '"'; + } + + $headers[] = ['Content-Type', $contentType]; + + if ($this->encoding) { + $headers[] = ['Content-Transfer-Encoding', $this->encoding]; + } + + if ($this->id) { + $headers[] = ['Content-ID', '<' . $this->id . '>']; + } + + if ($this->disposition) { + $disposition = $this->disposition; + if ($this->filename) { + $disposition .= '; filename="' . $this->filename . '"'; + } + $headers[] = ['Content-Disposition', $disposition]; + } + + if ($this->description) { + $headers[] = ['Content-Description', $this->description]; + } + + if ($this->location) { + $headers[] = ['Content-Location', $this->location]; + } + + if ($this->language) { + $headers[] = ['Content-Language', $this->language]; + } + + return $headers; + } + + /** + * Return the headers for this part as a string + * + * @param string $EOL + * @return String + */ + public function getHeaders($EOL = Mime::LINEEND) + { + $res = ''; + foreach ($this->getHeadersArray($EOL) as $header) { + $res .= $header[0] . ': ' . $header[1] . $EOL; + } + + return $res; + } +} diff --git a/lib/laminas/laminas-servicemanager/CHANGELOG.md b/lib/laminas/laminas-servicemanager/CHANGELOG.md new file mode 100644 index 000000000..6b96ad87f --- /dev/null +++ b/lib/laminas/laminas-servicemanager/CHANGELOG.md @@ -0,0 +1,696 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 3.5.2 - 2021-01-17 + + +----- + +### Release Notes for [3.5.2](https://github.com/laminas/laminas-servicemanager/milestone/6) + +3.5.x bugfix release (patch) + +### 3.5.2 + +- Total issues resolved: **0** +- Total pull requests resolved: **1** +- Total contributors: **1** + +#### Bug + + - [75: backported testConfigureInvokablesTakePrecedenceOverFactories to 3.5.x](https://github.com/laminas/laminas-servicemanager/pull/75) thanks to @driehle + +## 3.5.1 - 2020-12-01 + +### Release Notes for [3.5.1](https://github.com/laminas/laminas-servicemanager/milestone/4) + +This is a full revert of release `3.5.0`. + +`3.5.0` contained multiple backwards-incompatible (BC) breakages that were not +supposed to be released in a minor version, and therefore had to be reverted. + +Due to unfortunate branch naming issues when migrating from the old `develop` +branch, `develop` was renamed `3.5.x`, but contained multiple BC breaks that +were fundamentally incompatible with the `3.0.0` and newer features. + +Due to the quick response time, in order to not break further downstream systems +that yet have to receive the update, the `3.5.0` release was deleted, so that +it does not appear in your system if you accidentally run `composer update`. + +Thanks to @fabiang for promptly detecting the issue and reporting it +at https://github.com/laminas/laminas-servicemanager/issues/59 + +### 3.5.1 + +- Total issues resolved: **1** +- Total pull requests resolved: **0** +- Total contributors: **1** + +#### BC Break,Bug + + - [59: `ContainerInterface` reference was changed in a minor release](https://github.com/laminas/laminas-servicemanager/issues/59) thanks to @fabiang + +## 3.4.1 - 2020-05-11 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#28](https://github.com/laminas/laminas-servicemanager/pull/28) provides updates to the `ConfigAbstractFactory` to ensure it works correctly under PHP 7.4 when the `config` service is represented by an `ArrayObject` or `ArrayAccess` implementation. + +- [#35](https://github.com/laminas/laminas-servicemanager/pull/35) updates two exception messagers from the `ConfigAbstractFactory` to be more clear about what caused them. + +## 3.4.0 - 2018-12-22 + +### Added + +- [zendframework/zend-servicemanager#275](https://github.com/zendframework/zend-servicemanager/pull/275) Enables + plugin managers to accept as a creation context PSR Containers not + implementing Interop interface + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#268](https://github.com/zendframework/zend-servicemanager/pull/268) Fixes + ReflectionBasedAbstractFactory trying to instantiate classes with private + constructors + +## 3.3.2 - 2018-01-29 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#243](https://github.com/zendframework/zend-servicemanager/pull/243) provides + a fix to the `ReflectionBasedAbstractFactory` to resolve type-hinted arguments + with default values to their default values if no matching type is found in + the container. + +- [zendframework/zend-servicemanager#233](https://github.com/zendframework/zend-servicemanager/pull/233) fixes a + number of parameter annotations to reflect the actual types used. + +## 3.3.1 - 2017-11-27 + +### Added + +- [zendframework/zend-servicemanager#201](https://github.com/zendframework/zend-servicemanager/pull/201) and + [zendframework/zend-servicemanager#202](https://github.com/zendframework/zend-servicemanager/pull/202) add + support for PHP versions 7.1 and 7.2. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#206](https://github.com/zendframework/zend-servicemanager/pull/206) fixes an + issue where by callables in `Class::method` notation were not being honored + under PHP 5.6. + +## 3.3.0 - 2017-03-01 + +### Added + +- [zendframework/zend-servicemanager#180](https://github.com/zendframework/zend-servicemanager/pull/180) adds + explicit support for PSR-11 (ContainerInterface) by requiring + container-interop at a minimum version of 1.2.0, and adding a requirement on + psr/container 1.0. `Laminas\ServiceManager\ServiceLocatorInterface` now + explicitly extends the `ContainerInterface` from both projects. + + Factory interfaces still typehint against the container-interop variant, as + changing the typehint would break backwards compatibility. Users can + duck-type most of these interfaces, however, by creating callables or + invokables that typehint against psr/container instead. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 3.2.1 - 2017-02-15 + +### Added + +- [zendframework/zend-servicemanager#176](https://github.com/zendframework/zend-servicemanager/pull/176) adds + the options `-i` or `--ignore-unresolved` to the shipped + `generate-deps-for-config-factory` command. This flag allows it to build + configuration for classes resolved by the `ConfigAbstractFactory` that + typehint on interfaces, which was previously unsupported. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#174](https://github.com/zendframework/zend-servicemanager/pull/174) updates + the `ConfigAbstractFactory` to allow the `config` service to be either an + `array` or an `ArrayObject`; previously, only `array` was supported. + +## 3.2.0 - 2016-12-19 + +### Added + +- [zendframework/zend-servicemanager#146](https://github.com/zendframework/zend-servicemanager/pull/146) adds + `Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory`, which enables a + configuration-based approach to providing class dependencies when all + dependencies are services known to the `ServiceManager`. Please see + [the documentation](docs/book/config-abstract-factory.md) for details. +- [zendframework/zend-servicemanager#154](https://github.com/zendframework/zend-servicemanager/pull/154) adds + `Laminas\ServiceManager\Tool\ConfigDumper`, which will introspect a given class + to determine dependencies, and then create configuration for + `Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory`, merging it with + the provided configuration file. It also adds a vendor binary, + `generate-deps-for-config-factory`, for generating these from the command + line. +- [zendframework/zend-servicemanager#154](https://github.com/zendframework/zend-servicemanager/pull/154) adds + `Laminas\ServiceManager\Tool\FactoryCreator`, which will introspect a given class + and generate a factory for it. It also adds a vendor binary, + `generate-factory-for-class`, for generating these from the command line. +- [zendframework/zend-servicemanager#153](https://github.com/zendframework/zend-servicemanager/pull/153) adds + `Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory`. This + class may be used as either a mapped factory or an abstract factory, and will + use reflection in order to determine which dependencies to use from the + container when instantiating the requested service, with the following rules: + - Scalar values are not allowed, unless they have default values associated. + - Values named `$config` type-hinted against `array` will be injected with the + `config` service, if present. + - All other array values will be provided an empty array. + - Class/interface typehints will be pulled from the container. +- [zendframework/zend-servicemanager#150](https://github.com/zendframework/zend-servicemanager/pull/150) adds + a "cookbook" section to the documentation, with an initial document detailing + the pros and cons of abstract factory usage. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#106](https://github.com/zendframework/zend-servicemanager/pull/106) adds + detection of multiple attempts to register the same instance or named abstract + factory, using a previous instance when detected. You may still use multiple + discrete instances, however. + +## 3.1.2 - 2016-12-19 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#167](https://github.com/zendframework/zend-servicemanager/pull/167) fixes + how exception codes are provided to ServiceNotCreatedException. Previously, + the code was provided as-is. However, some PHP internal exception classes, + notably PDOException, can sometimes return other values (such as strings), + which can lead to fatal errors when instantiating the new exception. The + patch provided casts exception codes to integers to prevent these errors. + +## 3.1.1 - 2016-07-15 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#136](https://github.com/zendframework/zend-servicemanager/pull/136) removes + several imports to classes in subnamespaces within the `ServiceManager` + classfile, removing potential name resolution conflicts that occurred in edge + cases when testing. + +## 3.1.0 - 2016-06-01 + +### Added + +- [zendframework/zend-servicemanager#103](https://github.com/zendframework/zend-servicemanager/pull/103) Allowing + installation of `ocramius/proxy-manager` `^2.0` together with + `zendframework/zend-servicemanager`. +- [zendframework/zend-servicemanager#103](https://github.com/zendframework/zend-servicemanager/pull/103) Disallowing + test failures when running tests against PHP `7.0.*`. +- [zendframework/zend-servicemanager#113](https://github.com/zendframework/zend-servicemanager/pull/113) Improved performance + when dealing with registering aliases and factories via `ServiceManager#setFactory()` and + `ServiceManager#setAlias()` +- [zendframework/zend-servicemanager#120](https://github.com/zendframework/zend-servicemanager/pull/120) The + `laminas/laminas-servicemanager` component now provides a + `container-interop/container-interop-implementation` implementation + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#97](https://github.com/zendframework/zend-servicemanager/pull/97) Typo corrections + in the delegator factories documentation. +- [zendframework/zend-servicemanager#98](https://github.com/zendframework/zend-servicemanager/pull/98) Using coveralls ^1.0 + for tracking test code coverage changes. + +## 3.0.3 - 2016-02-02 + +### Added + +- [zendframework/zend-servicemanager#89](https://github.com/zendframework/zend-servicemanager/pull/89) adds + cyclic alias detection to the `ServiceManager`; it now raises a + `Laminas\ServiceManager\Exception\CyclicAliasException` when one is detected, + detailing the cycle detected. +- [zendframework/zend-servicemanager#95](https://github.com/zendframework/zend-servicemanager/pull/95) adds + GitHub Pages publication automation, and moves the documentation to + https://docs.laminas.dev/laminas-servicemanager/ +- [zendframework/zend-servicemanager#93](https://github.com/zendframework/zend-servicemanager/pull/93) adds + `Laminas\ServiceManager\Test\CommonPluginManagerTrait`, which can be used to + validate that a plugin manager instance is ready for version 3. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#90](https://github.com/zendframework/zend-servicemanager/pull/90) fixes + several examples in the configuration chapter of the documentation, ensuring + that the signatures are correct. +- [zendframework/zend-servicemanager#92](https://github.com/zendframework/zend-servicemanager/pull/92) ensures + that alias resolution is skipped during configuration if no aliases are + present, and forward-ports the test from [zendframework/zend-servicemanager#81](https://github.com/zendframework/zend-servicemanager/pull/81) + to validate v2/v3 compatibility for plugin managers. + +## 3.0.2 - 2016-01-24 + +### Added + +- [zendframework/zend-servicemanager#64](https://github.com/zendframework/zend-servicemanager/pull/64) performance optimizations + when dealing with alias resolution during service manager instantiation + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#62](https://github.com/zendframework/zend-servicemanager/pull/62) + [zendframework/zend-servicemanager#64](https://github.com/zendframework/zend-servicemanager/pull/64) corrected benchmark assets signature +- [zendframework/zend-servicemanager#72](https://github.com/zendframework/zend-servicemanager/pull/72) corrected link to the Proxy Pattern Wikipedia + page in the documentation +- [zendframework/zend-servicemanager#78](https://github.com/zendframework/zend-servicemanager/issues/78) + [zendframework/zend-servicemanager#79](https://github.com/zendframework/zend-servicemanager/pull/79) creation context was not being correctly passed + to abstract factories when using plugin managers +- [zendframework/zend-servicemanager#82](https://github.com/zendframework/zend-servicemanager/pull/82) corrected migration guide in the DocBlock of + the `InitializerInterface` + +## 3.0.1 - 2016-01-19 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- [zendframework/zend-servicemanager#68](https://github.com/zendframework/zend-servicemanager/pull/68) removes + the dependency on laminas-stdlib by inlining the `ArrayUtils::merge()` routine + as a private method of `Laminas\ServiceManager\Config`. + +### Fixed + +- Nothing. + +## 3.0.0 - 2016-01-11 + +First stable release of version 3 of laminas-servicemanager. + +Documentation is now available at http://laminas-servicemanager.rtfd.org + +### Added + +- You can now map multiple key names to the same factory. It was previously + possible in Laminas but it was not enforced by the `FactoryInterface` interface. + Now the interface receives the `$requestedName` as the *second* parameter + (previously, it was the third). + + Example: + + ```php + $sm = new \Laminas\ServiceManager\ServiceManager([ + 'factories' => [ + MyClassA::class => MyFactory::class, + MyClassB::class => MyFactory::class, + 'MyClassC' => 'MyFactory' // This is equivalent as using ::class + ], + ]); + + $sm->get(MyClassA::class); // MyFactory will receive MyClassA::class as second parameter + ``` + +- Writing a plugin manager has been simplified. If you have simple needs, you no + longer need to implement the complete `validate` method. + + In versions 2.x, if your plugin manager only allows creating instances that + implement `Laminas\Validator\ValidatorInterface`, you needed to write the + following code: + + ```php + class MyPluginManager extends AbstractPluginManager + { + public function validate($instance) + { + if ($instance instanceof \Laminas\Validator\ValidatorInterface) { + return; + } + + throw new InvalidServiceException(sprintf( + 'Plugin manager "%s" expected an instance of type "%s", but "%s" was received', + __CLASS__, + \Laminas\Validator\ValidatorInterface::class, + is_object($instance) ? get_class($instance) : gettype($instance) + )); + } + } + ``` + + In version 3, this becomes: + + ```php + use Laminas\ServiceManager\AbstractPluginManager; + use Laminas\Validator\ValidatorInterface; + + class MyPluginManager extends AbstractPluginManager + { + protected $instanceOf = ValidatorInterface::class; + } + ``` + + Of course, you can still override the `validate` method if your logic is more + complex. + + To aid migration, `validate()` will check for a `validatePlugin()` method (which + was required in v2), and proxy to it if found, after emitting an + `E_USER_DEPRECATED` notice prompting you to rename the method. + +- A new method, `configure()`, was added, allowing full configuration of the + `ServiceManager` instance at once. Each of the various configuration methods — + `setAlias()`, `setInvokableClass()`, etc. — now proxy to this method. + +- A new method, `mapLazyService($name, $class = null)`, was added, to allow + mapping a lazy service, and as an analog to the other various service + definition methods. + +### Deprecated + +- Nothing + +### Removed + +- Peering has been removed. It was a complex and rarely used feature that was + misunderstood most of the time. + +- Integration with `Laminas\Di` has been removed. It may be re-integrated later. + +- `MutableCreationOptionsInterface` has been removed, as options can now be + passed directly through factories. + +- `ServiceLocatorAwareInterface` and its associated trait has been removed. It + was an anti-pattern, and you are encouraged to inject your dependencies in + factories instead of injecting the whole service locator. + +### Changed/Fixed + +v3 of the ServiceManager component is a completely rewritten, more efficient +implementation of the service locator pattern. It includes a number of breaking +changes, outlined in this section. + +- You no longer need a `Laminas\ServiceManager\Config` object to configure the + service manager; you can pass the configuration array directly instead. + + In version 2.x: + + ```php + $config = new \Laminas\ServiceManager\Config([ + 'factories' => [...] + ]); + + $sm = new \Laminas\ServiceManager\ServiceManager($config); + ``` + + In Laminas 3.x: + + ```php + $sm = new \Laminas\ServiceManager\ServiceManager([ + 'factories' => [...] + ]); + ``` + + `Config` and `ConfigInterface` still exist, however, but primarily for the + purposes of codifying and aggregating configuration to use. + +- `ConfigInterface` has two important changes: + - `configureServiceManager()` now **must** return the updated service manager + instance. + - A new method, `toArray()`, was added, to allow pulling the configuration in + order to pass to a ServiceManager or plugin manager's constructor or + `configure()` method. + +- Interfaces for `FactoryInterface`, `DelegatorFactoryInterface` and + `AbstractFactoryInterface` have changed. All are now directly invokable. This + allows a number of performance optimization internally. + + Additionally, all signatures that accepted a "canonical name" argument now + remove it. + + Most of the time, rewriting a factory to match the new interface implies + replacing the method name by `__invoke`, and removing the canonical name + argument if present. + + For instance, here is a simple version 2.x factory: + + ```php + class MyFactory implements FactoryInterface + { + function createService(ServiceLocatorInterface $sl) + { + // ... + } + } + ``` + + The equivalent version 3 factory: + + ```php + class MyFactory implements FactoryInterface + { + function __invoke(ServiceLocatorInterface $sl, $requestedName) + { + // ... + } + } + ``` + + Note another change in the above: factories also receive a second parameter, + enforced through the interface, that allows you to easily map multiple service + names to the same factory. + + To provide forwards compatibility, the original interfaces have been retained, + but extend the new interfaces (which are under new namespaces). You can implement + the new methods in your existing v2 factories in order to make them forwards + compatible with v3. + +- The for `AbstractFactoryInterface` interface renames the method `canCreateServiceWithName()` + to `canCreate()`, and merges the `$name` and `$requestedName` arguments. + +- Plugin managers will now receive the parent service locator instead of itself + in factories. In version 2.x, you needed to call the method + `getServiceLocator()` to retrieve the parent (application) service locator. + This was confusing, and not IDE friendly as this method was not enforced + through the interface. + + In version 2.x, if a factory was set to a service name defined in a plugin manager: + + ```php + class MyFactory implements FactoryInterface + { + function createService(ServiceLocatorInterface $sl) + { + // $sl is actually a plugin manager + + $parentLocator = $sl->getServiceLocator(); + + // ... + } + } + ``` + + In version 3: + + ```php + class MyFactory implements FactoryInterface + { + function __invoke(ServiceLocatorInterface $sl, $requestedName) + { + // $sl is already the main, parent service locator. If you need to + // retrieve the plugin manager again, you can retrieve it through the + // servicelocator: + $pluginManager = $sl->get(MyPluginManager::class); + // ... + } + } + ``` + + In practice, this should reduce code, as dependencies often come from the main + service locator, and not the plugin manager itself. + + To assist in migration, the method `getServiceLocator()` was added to `ServiceManager` + to ensure that existing factories continue to work; the method emits an `E_USER_DEPRECATED` + message to signal developers to update their factories. + +- `PluginManager` now enforces the need for the main service locator in its + constructor. In v2.x, people often forgot to set the parent locator, which led + to bugs in factories trying to fetch dependencies from the parent locator. + Additionally, plugin managers now pull dependencies from the parent locator by + default; if you need to pull a peer plugin, your factories will now need to + pull the corresponding plugin manager first. + + If you omit passing a service locator to the constructor, your plugin manager + will continue to work, but will emit a deprecation notice indicatin you + should update your initialization code. + +- It's so fast now that your app will fly! + +## 2.7.0 - 2016-01-11 + +### Added + +- [zendframework/zend-servicemanager#60](https://github.com/zendframework/zend-servicemanager/pull/60) adds + forward compatibility features for `AbstractPluingManager` and introduces + `InvokableFactory` to help forward migration to version 3. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#46](https://github.com/zendframework/zend-servicemanager/pull/46) updates + the exception hierarchy to inherit from the container-interop exceptions. + This ensures that all exceptions thrown by the component follow the + recommendations of that project. +- [zendframework/zend-servicemanager#52](https://github.com/zendframework/zend-servicemanager/pull/52) fixes + the exception message thrown by `ServiceManager::setFactory()` to remove + references to abstract factories. + +## 2.6.0 - 2015-07-23 + +### Added + +- [zendframework/zend-servicemanager#4](https://github.com/zendframework/zend-servicemanager/pull/4) updates the + `ServiceManager` to [implement the container-interop interface](https://github.com/container-interop/container-interop), + allowing interoperability with applications that consume that interface. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-servicemanager#3](https://github.com/zendframework/zend-servicemanager/pull/3) properly updates the + codebase to PHP 5.5, by taking advantage of the default closure binding + (`$this` in a closure is the invoking object when created within a method). It + also removes several `@requires PHP 5.4.0` annotations. diff --git a/lib/laminas/laminas-servicemanager/COPYRIGHT.md b/lib/laminas/laminas-servicemanager/COPYRIGHT.md new file mode 100644 index 000000000..0a8cccc06 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/COPYRIGHT.md @@ -0,0 +1 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) diff --git a/lib/laminas/laminas-servicemanager/LICENSE.md b/lib/laminas/laminas-servicemanager/LICENSE.md new file mode 100644 index 000000000..10b40f142 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/LICENSE.md @@ -0,0 +1,26 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Laminas Foundation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/laminas/laminas-servicemanager/README.md b/lib/laminas/laminas-servicemanager/README.md new file mode 100644 index 000000000..7fd95af76 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/README.md @@ -0,0 +1,28 @@ +# laminas-servicemanager + +Master: +[![Build Status](https://travis-ci.com/laminas/laminas-servicemanager.svg?branch=master)](https://travis-ci.com/laminas/laminas-servicemanager) +[![Coverage Status](https://coveralls.io/repos/github/laminas/laminas-servicemanager/badge.svg?branch=master)](https://coveralls.io/github/laminas/laminas-servicemanager?branch=master) +Develop: +[![Build Status](https://travis-ci.com/laminas/laminas-servicemanager.svg?branch=develop)](https://travis-ci.com/laminas/laminas-servicemanager) +[![Coverage Status](https://coveralls.io/repos/github/laminas/laminas-servicemanager/badge.svg?branch=develop)](https://coveralls.io/github/laminas/laminas-servicemanager?branch=develop) + +The Service Locator design pattern is implemented by the `Laminas\ServiceManager` +component. The Service Locator is a service/object locator, tasked with +retrieving other objects. + +- File issues at https://github.com/laminas/laminas-servicemanager/issues +- [Online documentation](https://docs.laminas.dev/laminas-servicemanager) +- [Documentation source files](docs/book/) + +## Benchmarks + +We provide scripts for benchmarking laminas-servicemanager using the +[PHPBench](https://github.com/phpbench/phpbench) framework; these can be +found in the `benchmarks/` directory. + +To execute the benchmarks you can run the following command: + +```bash +$ vendor/bin/phpbench run --report=aggregate +``` diff --git a/lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory b/lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory new file mode 100644 index 000000000..c8c8683b0 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory @@ -0,0 +1,26 @@ +#!/usr/bin/env php +has('config')) { + return false; + } + $config = $container->get('config'); + if (! isset($config[self::class])) { + return false; + } + $dependencies = $config[self::class]; + + return is_array($dependencies) && array_key_exists($requestedName, $dependencies); + } + + /** + * {@inheritDoc} + */ + public function __invoke(\Interop\Container\ContainerInterface $container, $requestedName, array $options = null) + { + if (! $container->has('config')) { + throw new ServiceNotCreatedException('Cannot find a config array in the container'); + } + + $config = $container->get('config'); + + if (! (is_array($config) || $config instanceof ArrayObject)) { + throw new ServiceNotCreatedException('Config must be an array or an instance of ArrayObject'); + } + if (! isset($config[self::class])) { + throw new ServiceNotCreatedException('Cannot find a `' . self::class . '` key in the config array'); + } + + + $dependencies = $config[self::class]; + + if (! is_array($dependencies) + || ! array_key_exists($requestedName, $dependencies) + || ! is_array($dependencies[$requestedName]) + ) { + throw new ServiceNotCreatedException('Service dependencies config must exist and be an array'); + } + + $serviceDependencies = $dependencies[$requestedName]; + + if ($serviceDependencies !== array_values(array_map('strval', $serviceDependencies))) { + $problem = json_encode(array_map('gettype', $serviceDependencies)); + throw new ServiceNotCreatedException( + 'Service dependencies config must be an array of strings, ' . $problem . ' given' + ); + } + + $arguments = array_map([$container, 'get'], $serviceDependencies); + + return new $requestedName(...$arguments); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php b/lib/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php new file mode 100644 index 000000000..ef542d2d1 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php @@ -0,0 +1,246 @@ + + * 'service_manager' => [ + * 'abstract_factories' => [ + * ReflectionBasedAbstractFactory::class, + * ], + * ], + * + * + * Or as a factory, mapping a class name to it: + * + * + * 'service_manager' => [ + * 'factories' => [ + * MyClassWithDependencies::class => ReflectionBasedAbstractFactory::class, + * ], + * ], + * + * + * The latter approach is more explicit, and also more performant. + * + * The factory has the following constraints/features: + * + * - A parameter named `$config` typehinted as an array will receive the + * application "config" service (i.e., the merged configuration). + * - Parameters type-hinted against array, but not named `$config` will + * be injected with an empty array. + * - Scalar parameters will result in an exception being thrown, unless + * a default value is present; if the default is present, that will be used. + * - If a service cannot be found for a given typehint, the factory will + * raise an exception detailing this. + * - Some services provided by Laminas components do not have + * entries based on their class name (for historical reasons); the + * factory allows defining a map of these class/interface names to the + * corresponding service name to allow them to resolve. + * + * `$options` passed to the factory are ignored in all cases, as we cannot + * make assumptions about which argument(s) they might replace. + * + * Based on the LazyControllerAbstractFactory from laminas-mvc. + */ +class ReflectionBasedAbstractFactory implements AbstractFactoryInterface +{ + /** + * Maps known classes/interfaces to the service that provides them; only + * required for those services with no entry based on the class/interface + * name. + * + * Extend the class if you wish to add to the list. + * + * Example: + * + * + * [ + * \Laminas\Filter\FilterPluginManager::class => 'FilterManager', + * \Laminas\Validator\ValidatorPluginManager::class => 'ValidatorManager', + * ] + * + * + * @var string[] + */ + protected $aliases = []; + + /** + * Constructor. + * + * Allows overriding the internal list of aliases. These should be of the + * form `class name => well-known service name`; see the documentation for + * the `$aliases` property for details on what is accepted. + * + * @param string[] $aliases + */ + public function __construct(array $aliases = []) + { + if (! empty($aliases)) { + $this->aliases = $aliases; + } + } + + /** + * {@inheritDoc} + * + * @return DispatchableInterface + */ + public function __invoke(ContainerInterface $container, $requestedName, array $options = null) + { + $reflectionClass = new ReflectionClass($requestedName); + + if (null === ($constructor = $reflectionClass->getConstructor())) { + return new $requestedName(); + } + + $reflectionParameters = $constructor->getParameters(); + + if (empty($reflectionParameters)) { + return new $requestedName(); + } + + $resolver = $container->has('config') + ? $this->resolveParameterWithConfigService($container, $requestedName) + : $this->resolveParameterWithoutConfigService($container, $requestedName); + + $parameters = array_map($resolver, $reflectionParameters); + + return new $requestedName(...$parameters); + } + + /** + * {@inheritDoc} + */ + public function canCreate(ContainerInterface $container, $requestedName) + { + return class_exists($requestedName) && $this->canCallConstructor($requestedName); + } + + private function canCallConstructor($requestedName) + { + $constructor = (new ReflectionClass($requestedName))->getConstructor(); + + return $constructor === null || $constructor->isPublic(); + } + + /** + * Resolve a parameter to a value. + * + * Returns a callback for resolving a parameter to a value, but without + * allowing mapping array `$config` arguments to the `config` service. + * + * @param ContainerInterface $container + * @param string $requestedName + * @return callable + */ + private function resolveParameterWithoutConfigService(ContainerInterface $container, $requestedName) + { + /** + * @param ReflectionParameter $parameter + * @return mixed + * @throws ServiceNotFoundException If type-hinted parameter cannot be + * resolved to a service in the container. + */ + return function (ReflectionParameter $parameter) use ($container, $requestedName) { + return $this->resolveParameter($parameter, $container, $requestedName); + }; + } + + /** + * Returns a callback for resolving a parameter to a value, including mapping 'config' arguments. + * + * Unlike resolveParameter(), this version will detect `$config` array + * arguments and have them return the 'config' service. + * + * @param ContainerInterface $container + * @param string $requestedName + * @return callable + */ + private function resolveParameterWithConfigService(ContainerInterface $container, $requestedName) + { + /** + * @param ReflectionParameter $parameter + * @return mixed + * @throws ServiceNotFoundException If type-hinted parameter cannot be + * resolved to a service in the container. + */ + return function (ReflectionParameter $parameter) use ($container, $requestedName) { + if ($parameter->isArray() && $parameter->getName() === 'config') { + return $container->get('config'); + } + return $this->resolveParameter($parameter, $container, $requestedName); + }; + } + + /** + * Logic common to all parameter resolution. + * + * @param ReflectionParameter $parameter + * @param ContainerInterface $container + * @param string $requestedName + * @return mixed + * @throws ServiceNotFoundException If type-hinted parameter cannot be + * resolved to a service in the container. + */ + private function resolveParameter(ReflectionParameter $parameter, ContainerInterface $container, $requestedName) + { + if ($parameter->isArray()) { + return []; + } + + if (! $parameter->getClass()) { + if (! $parameter->isDefaultValueAvailable()) { + throw new ServiceNotFoundException(sprintf( + 'Unable to create service "%s"; unable to resolve parameter "%s" ' + . 'to a class, interface, or array type', + $requestedName, + $parameter->getName() + )); + } + + return $parameter->getDefaultValue(); + } + + $type = $parameter->getClass()->getName(); + $type = isset($this->aliases[$type]) ? $this->aliases[$type] : $type; + + if ($container->has($type)) { + return $container->get($type); + } + + if (! $parameter->isOptional()) { + throw new ServiceNotFoundException(sprintf( + 'Unable to create service "%s"; unable to resolve parameter "%s" using type hint "%s"', + $requestedName, + $parameter->getName(), + $type + )); + } + + // Type not available in container, but the value is optional and has a + // default defined. + return $parameter->getDefaultValue(); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php b/lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php new file mode 100644 index 000000000..c378e7202 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php @@ -0,0 +1,59 @@ +toArray(); + } + + parent::__construct($config); + + if (! $configInstanceOrParentLocator instanceof ContainerInterface) { + trigger_error(sprintf( + '%s now expects a %s instance representing the parent container; please update your code', + __METHOD__, + ContainerInterface::class + ), E_USER_DEPRECATED); + } + + $this->creationContext = $configInstanceOrParentLocator instanceof ContainerInterface + ? $configInstanceOrParentLocator + : $this; + } + + /** + * Override configure() to validate service instances. + * + * If an instance passed in the `services` configuration is invalid for the + * plugin manager, this method will raise an InvalidServiceException. + * + * {@inheritDoc} + * @throws InvalidServiceException + */ + public function configure(array $config) + { + if (isset($config['services'])) { + foreach ($config['services'] as $service) { + $this->validate($service); + } + } + + parent::configure($config); + + return $this; + } + + /** + * {@inheritDoc} + * + * @param string $name Service name of plugin to retrieve. + * @param null|array $options Options to use when creating the instance. + * @return mixed + * @throws Exception\ServiceNotFoundException if the manager does not have + * a service definition for the instance, and the service is not + * auto-invokable. + * @throws InvalidServiceException if the plugin created is invalid for the + * plugin context. + */ + public function get($name, array $options = null) + { + if (! $this->has($name)) { + if (! $this->autoAddInvokableClass || ! class_exists($name)) { + throw new Exception\ServiceNotFoundException(sprintf( + 'A plugin by the name "%s" was not found in the plugin manager %s', + $name, + get_class($this) + )); + } + + $this->setFactory($name, Factory\InvokableFactory::class); + } + + $instance = empty($options) ? parent::get($name) : $this->build($name, $options); + $this->validate($instance); + return $instance; + } + + /** + * {@inheritDoc} + */ + public function validate($instance) + { + if (method_exists($this, 'validatePlugin')) { + trigger_error(sprintf( + '%s::validatePlugin() has been deprecated as of 3.0; please define validate() instead', + get_class($this) + ), E_USER_DEPRECATED); + $this->validatePlugin($instance); + return; + } + + if (empty($this->instanceOf) || $instance instanceof $this->instanceOf) { + return; + } + + throw new InvalidServiceException(sprintf( + 'Plugin manager "%s" expected an instance of type "%s", but "%s" was received', + __CLASS__, + $this->instanceOf, + is_object($instance) ? get_class($instance) : gettype($instance) + )); + } + + /** + * Implemented for backwards compatibility only. + * + * Returns the creation context. + * + * @deprecated since 3.0.0. The creation context should be passed during + * instantiation instead. + * @param ContainerInterface $container + * @return void + */ + public function setServiceLocator(ContainerInterface $container) + { + trigger_error(sprintf( + 'Usage of %s is deprecated since v3.0.0; please pass the container to the constructor instead', + __METHOD__ + ), E_USER_DEPRECATED); + $this->creationContext = $container; + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Config.php b/lib/laminas/laminas-servicemanager/src/Config.php new file mode 100644 index 000000000..8893afa3d --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Config.php @@ -0,0 +1,120 @@ + true, + 'aliases' => true, + 'delegators' => true, + 'factories' => true, + 'initializers' => true, + 'invokables' => true, + 'lazy_services' => true, + 'services' => true, + 'shared' => true, + ]; + + /** + * @var array + */ + protected $config = [ + 'abstract_factories' => [], + 'aliases' => [], + 'delegators' => [], + 'factories' => [], + 'initializers' => [], + 'invokables' => [], + 'lazy_services' => [], + 'services' => [], + 'shared' => [], + ]; + + /** + * @param array $config + */ + public function __construct(array $config = []) + { + // Only merge keys we're interested in + foreach (array_keys($config) as $key) { + if (! isset($this->allowedKeys[$key])) { + unset($config[$key]); + } + } + $this->config = $this->merge($this->config, $config); + } + + /** + * @inheritdoc + */ + public function configureServiceManager(ServiceManager $serviceManager) + { + return $serviceManager->configure($this->config); + } + + /** + * @inheritdoc + */ + public function toArray() + { + return $this->config; + } + + /** + * Copy paste from https://github.com/laminas/laminas-stdlib/commit/26fcc32a358aa08de35625736095cb2fdaced090 + * to keep compatibility with previous version + * + * @link https://github.com/zendframework/zend-servicemanager/pull/68 + */ + private function merge(array $a, array $b) + { + foreach ($b as $key => $value) { + if ($value instanceof MergeReplaceKeyInterface) { + $a[$key] = $value->getData(); + } elseif (isset($a[$key]) || array_key_exists($key, $a)) { + if ($value instanceof MergeRemoveKey) { + unset($a[$key]); + } elseif (is_int($key)) { + $a[] = $value; + } elseif (is_array($value) && is_array($a[$key])) { + $a[$key] = $this->merge($a[$key], $value); + } else { + $a[$key] = $value; + } + } else { + if (! $value instanceof MergeRemoveKey) { + $a[$key] = $value; + } + } + } + return $a; + } +} diff --git a/lib/laminas/laminas-servicemanager/src/ConfigInterface.php b/lib/laminas/laminas-servicemanager/src/ConfigInterface.php new file mode 100644 index 000000000..37598d22c --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/ConfigInterface.php @@ -0,0 +1,47 @@ + $reference) { + $map[] = '"' . $alias . '" => "' . $reference . '"'; + } + + return "[\n" . implode("\n", $map) . "\n]"; + } + + /** + * @param string[][] $detectedCycles + * + * @return string + */ + private static function printCycles(array $detectedCycles) + { + return "[\n" . implode("\n", array_map([__CLASS__, 'printCycle'], $detectedCycles)) . "\n]"; + } + + /** + * @param string[] $detectedCycle + * + * @return string + */ + private static function printCycle(array $detectedCycle) + { + $fullCycle = array_keys($detectedCycle); + $fullCycle[] = reset($fullCycle); + + return implode( + ' => ', + array_map( + function ($cycle) { + return '"' . $cycle . '"'; + }, + $fullCycle + ) + ); + } + + /** + * @param bool[][] $detectedCycles + * + * @return bool[][] de-duplicated + */ + private static function deDuplicateDetectedCycles(array $detectedCycles) + { + $detectedCyclesByHash = []; + + foreach ($detectedCycles as $detectedCycle) { + $cycleAliases = array_keys($detectedCycle); + + sort($cycleAliases); + + $hash = serialize(array_values($cycleAliases)); + + $detectedCyclesByHash[$hash] = isset($detectedCyclesByHash[$hash]) + ? $detectedCyclesByHash[$hash] + : $detectedCycle; + } + + return array_values($detectedCyclesByHash); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php b/lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php new file mode 100644 index 000000000..f6ad4855c --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php @@ -0,0 +1,18 @@ +proxyFactory = $proxyFactory; + $this->servicesMap = $servicesMap; + } + + /** + * {@inheritDoc} + * + * @return \ProxyManager\Proxy\VirtualProxyInterface + */ + public function __invoke(ContainerInterface $container, $name, callable $callback, array $options = null) + { + if (isset($this->servicesMap[$name])) { + $initializer = function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($callback) { + $proxy->setProxyInitializer(null); + $wrappedInstance = $callback(); + + return true; + }; + + return $this->proxyFactory->createProxy($this->servicesMap[$name], $initializer); + } + + throw new Exception\ServiceNotFoundException( + sprintf('The requested service "%s" was not found in the provided services map', $name) + ); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/PsrContainerDecorator.php b/lib/laminas/laminas-servicemanager/src/PsrContainerDecorator.php new file mode 100644 index 000000000..cac04f5f8 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/PsrContainerDecorator.php @@ -0,0 +1,52 @@ +container = $container; + } + + /** + * {@inheritdoc} + */ + public function get($id) + { + return $this->container->get($id); + } + + /** + * {@inheritdoc} + */ + public function has($id) + { + return $this->container->has($id); + } + + /** + * @return PsrContainerInterface + */ + public function getContainer() + { + return $this->container; + } +} diff --git a/lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php b/lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php new file mode 100644 index 000000000..d6ed8e847 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php @@ -0,0 +1,35 @@ + [ + * MyService::class => true, // will be shared, even if "sharedByDefault" is false + * MyOtherService::class => false // won't be shared, even if "sharedByDefault" is true + * ] + * + * @var boolean[] + */ + protected $shared = []; + + /** + * Should the services be shared by default? + * + * @var bool + */ + protected $sharedByDefault = true; + + /** + * Service manager was already configured? + * + * @var bool + */ + protected $configured = false; + + /** + * Cached abstract factories from string. + * + * @var array + */ + private $cachedAbstractFactories = []; + + /** + * Constructor. + * + * See {@see \Laminas\ServiceManager\ServiceManager::configure()} for details + * on what $config accepts. + * + * @param array $config + */ + public function __construct(array $config = []) + { + $this->creationContext = $this; + $this->configure($config); + } + + /** + * Implemented for backwards compatibility with previous plugin managers only. + * + * Returns the creation context. + * + * @deprecated since 3.0.0. Factories using 3.0 should use the container + * instance passed to the factory instead. + * @return ContainerInterface + */ + public function getServiceLocator() + { + trigger_error(sprintf( + 'Usage of %s is deprecated since v3.0.0; please use the container passed to the factory instead', + __METHOD__ + ), E_USER_DEPRECATED); + return $this->creationContext; + } + + /** + * {@inheritDoc} + */ + public function get($name) + { + $requestedName = $name; + + // We start by checking if we have cached the requested service (this + // is the fastest method). + if (isset($this->services[$requestedName])) { + return $this->services[$requestedName]; + } + + $name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name; + + // Next, if the alias should be shared, and we have cached the resolved + // service, use it. + if ($requestedName !== $name + && (! isset($this->shared[$requestedName]) || $this->shared[$requestedName]) + && isset($this->services[$name]) + ) { + $this->services[$requestedName] = $this->services[$name]; + return $this->services[$name]; + } + + // At this point, we need to create the instance; we use the resolved + // name for that. + $object = $this->doCreate($name); + + // Cache it for later, if it is supposed to be shared. + if (($this->sharedByDefault && ! isset($this->shared[$name])) + || (isset($this->shared[$name]) && $this->shared[$name]) + ) { + $this->services[$name] = $object; + } + + // Also do so for aliases; this allows sharing based on service name used. + if ($requestedName !== $name + && (($this->sharedByDefault && ! isset($this->shared[$requestedName])) + || (isset($this->shared[$requestedName]) && $this->shared[$requestedName])) + ) { + $this->services[$requestedName] = $object; + } + + return $object; + } + + /** + * {@inheritDoc} + */ + public function build($name, array $options = null) + { + // We never cache when using "build" + $name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name; + return $this->doCreate($name, $options); + } + + /** + * {@inheritDoc} + */ + public function has($name) + { + $name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name; + $found = isset($this->services[$name]) || isset($this->factories[$name]); + + if ($found) { + return $found; + } + + // Check abstract factories + foreach ($this->abstractFactories as $abstractFactory) { + if ($abstractFactory->canCreate($this->creationContext, $name)) { + return true; + } + } + + return false; + } + + /** + * Indicate whether or not the instance is immutable. + * + * @param bool $flag + */ + public function setAllowOverride($flag) + { + $this->allowOverride = (bool) $flag; + } + + /** + * Retrieve the flag indicating immutability status. + * + * @return bool + */ + public function getAllowOverride() + { + return $this->allowOverride; + } + + /** + * Configure the service manager + * + * Valid top keys are: + * + * - services: service name => service instance pairs + * - invokables: service name => class name pairs for classes that do not + * have required constructor arguments; internally, maps the class to an + * InvokableFactory instance, and creates an alias if the service name + * and class name do not match. + * - factories: service name => factory pairs; factories may be any + * callable, string name resolving to an invokable class, or string name + * resolving to a FactoryInterface instance. + * - abstract_factories: an array of abstract factories; these may be + * instances of AbstractFactoryInterface, or string names resolving to + * classes that implement that interface. + * - delegators: service name => list of delegator factories for the given + * service; each item in the list may be a callable, a string name + * resolving to an invokable class, or a string name resolving to a class + * implementing DelegatorFactoryInterface. + * - shared: service name => flag pairs; the flag is a boolean indicating + * whether or not the service is shared. + * - aliases: alias => service name pairs. + * - lazy_services: lazy service configuration; can contain the keys: + * - class_map: service name => class name pairs. + * - proxies_namespace: string namespace to use for generated proxy + * classes. + * - proxies_target_dir: directory in which to write generated proxy + * classes; uses system temporary by default. + * - write_proxy_files: boolean indicating whether generated proxy + * classes should be written; defaults to boolean false. + * - shared_by_default: boolean, indicating if services in this instance + * should be shared by default. + * + * @param array $config + * @return self + * @throws ContainerModificationsNotAllowedException if the allow + * override flag has been toggled off, and a service instance + * exists for a given service. + */ + public function configure(array $config) + { + $this->validateOverrides($config); + + if (isset($config['services'])) { + $this->services = $config['services'] + $this->services; + } + + if (isset($config['invokables']) && ! empty($config['invokables'])) { + $aliases = $this->createAliasesForInvokables($config['invokables']); + $factories = $this->createFactoriesForInvokables($config['invokables']); + + if (! empty($aliases)) { + $config['aliases'] = (isset($config['aliases'])) + ? array_merge($config['aliases'], $aliases) + : $aliases; + } + + $config['factories'] = (isset($config['factories'])) + ? array_merge($config['factories'], $factories) + : $factories; + } + + if (isset($config['factories'])) { + $this->factories = $config['factories'] + $this->factories; + } + + if (isset($config['delegators'])) { + $this->delegators = array_merge_recursive($this->delegators, $config['delegators']); + } + + if (isset($config['shared'])) { + $this->shared = $config['shared'] + $this->shared; + } + + if (isset($config['aliases'])) { + $this->configureAliases($config['aliases']); + } elseif (! $this->configured && ! empty($this->aliases)) { + $this->resolveAliases($this->aliases); + } + + if (isset($config['shared_by_default'])) { + $this->sharedByDefault = $config['shared_by_default']; + } + + // If lazy service configuration was provided, reset the lazy services + // delegator factory. + if (isset($config['lazy_services']) && ! empty($config['lazy_services'])) { + $this->lazyServices = array_merge_recursive($this->lazyServices, $config['lazy_services']); + $this->lazyServicesDelegator = null; + } + + // For abstract factories and initializers, we always directly + // instantiate them to avoid checks during service construction. + if (isset($config['abstract_factories'])) { + $this->resolveAbstractFactories($config['abstract_factories']); + } + + if (isset($config['initializers'])) { + $this->resolveInitializers($config['initializers']); + } + + $this->configured = true; + + return $this; + } + + /** + * @param string[] $aliases + * + * @return void + */ + private function configureAliases(array $aliases) + { + if (! $this->configured) { + $this->aliases = $aliases + $this->aliases; + + $this->resolveAliases($this->aliases); + + return; + } + + // Performance optimization. If there are no collisions, then we don't need to recompute loops + $intersecting = $this->aliases && \array_intersect_key($this->aliases, $aliases); + $this->aliases = $this->aliases ? \array_merge($this->aliases, $aliases) : $aliases; + + if ($intersecting) { + $this->resolveAliases($this->aliases); + + return; + } + + $this->resolveAliases($aliases); + $this->resolveNewAliasesWithPreviouslyResolvedAliases($aliases); + } + + /** + * Add an alias. + * + * @param string $alias + * @param string $target + */ + public function setAlias($alias, $target) + { + $this->configure(['aliases' => [$alias => $target]]); + } + + /** + * Add an invokable class mapping. + * + * @param string $name Service name + * @param null|string $class Class to which to map; if omitted, $name is + * assumed. + */ + public function setInvokableClass($name, $class = null) + { + $this->configure(['invokables' => [$name => $class ?: $name]]); + } + + /** + * Specify a factory for a given service name. + * + * @param string $name Service name + * @param string|callable|Factory\FactoryInterface $factory Factory to which + * to map. + */ + public function setFactory($name, $factory) + { + $this->configure(['factories' => [$name => $factory]]); + } + + /** + * Create a lazy service mapping to a class. + * + * @param string $name Service name to map + * @param null|string $class Class to which to map; if not provided, $name + * will be used for the mapping. + */ + public function mapLazyService($name, $class = null) + { + $this->configure(['lazy_services' => ['class_map' => [$name => $class ?: $name]]]); + } + + /** + * Add an abstract factory for resolving services. + * + * @param string|Factory\AbstractFactoryInterface $factory Service name + */ + public function addAbstractFactory($factory) + { + $this->configure(['abstract_factories' => [$factory]]); + } + + /** + * Add a delegator for a given service. + * + * @param string $name Service name + * @param string|callable|Factory\DelegatorFactoryInterface $factory Delegator + * factory to assign. + */ + public function addDelegator($name, $factory) + { + $this->configure(['delegators' => [$name => [$factory]]]); + } + + /** + * Add an initializer. + * + * @param string|callable|Initializer\InitializerInterface $initializer + */ + public function addInitializer($initializer) + { + $this->configure(['initializers' => [$initializer]]); + } + + /** + * Map a service. + * + * @param string $name Service name + * @param array|object $service + */ + public function setService($name, $service) + { + $this->configure(['services' => [$name => $service]]); + } + + /** + * Add a service sharing rule. + * + * @param string $name Service name + * @param boolean $flag Whether or not the service should be shared. + */ + public function setShared($name, $flag) + { + $this->configure(['shared' => [$name => (bool) $flag]]); + } + + /** + * Instantiate abstract factories for to avoid checks during service construction. + * + * @param string[]|Factory\AbstractFactoryInterface[] $abstractFactories + * + * @return void + */ + private function resolveAbstractFactories(array $abstractFactories) + { + foreach ($abstractFactories as $abstractFactory) { + if (is_string($abstractFactory) && class_exists($abstractFactory)) { + //Cached string + if (! isset($this->cachedAbstractFactories[$abstractFactory])) { + $this->cachedAbstractFactories[$abstractFactory] = new $abstractFactory(); + } + + $abstractFactory = $this->cachedAbstractFactories[$abstractFactory]; + } + + if ($abstractFactory instanceof Factory\AbstractFactoryInterface) { + $abstractFactoryObjHash = spl_object_hash($abstractFactory); + $this->abstractFactories[$abstractFactoryObjHash] = $abstractFactory; + continue; + } + + // Error condition; let's find out why. + + // If we still have a string, we have a class name that does not resolve + if (is_string($abstractFactory)) { + throw new InvalidArgumentException( + sprintf( + 'An invalid abstract factory was registered; resolved to class "%s" ' . + 'which does not exist; please provide a valid class name resolving ' . + 'to an implementation of %s', + $abstractFactory, + AbstractFactoryInterface::class + ) + ); + } + + // Otherwise, we have an invalid type. + throw new InvalidArgumentException( + sprintf( + 'An invalid abstract factory was registered. Expected an instance of "%s", ' . + 'but "%s" was received', + AbstractFactoryInterface::class, + (is_object($abstractFactory) ? get_class($abstractFactory) : gettype($abstractFactory)) + ) + ); + } + } + + /** + * Instantiate initializers for to avoid checks during service construction. + * + * @param string[]|Initializer\InitializerInterface[]|callable[] $initializers + * + * @return void + */ + private function resolveInitializers(array $initializers) + { + foreach ($initializers as $initializer) { + if (is_string($initializer) && class_exists($initializer)) { + $initializer = new $initializer(); + } + + if (is_callable($initializer)) { + $this->initializers[] = $initializer; + continue; + } + + // Error condition; let's find out why. + + if (is_string($initializer)) { + throw new InvalidArgumentException( + sprintf( + 'An invalid initializer was registered; resolved to class or function "%s" ' . + 'which does not exist; please provide a valid function name or class ' . + 'name resolving to an implementation of %s', + $initializer, + Initializer\InitializerInterface::class + ) + ); + } + + // Otherwise, we have an invalid type. + throw new InvalidArgumentException( + sprintf( + 'An invalid initializer was registered. Expected a callable, or an instance of ' . + '(or string class name resolving to) "%s", ' . + 'but "%s" was received', + Initializer\InitializerInterface::class, + (is_object($initializer) ? get_class($initializer) : gettype($initializer)) + ) + ); + } + } + + /** + * Resolve aliases to their canonical service names. + * + * @param string[] $aliases + * + * @return void + */ + private function resolveAliases(array $aliases) + { + foreach ($aliases as $alias => $service) { + $visited = []; + $name = $alias; + + while (isset($this->aliases[$name])) { + if (isset($visited[$name])) { + throw CyclicAliasException::fromAliasesMap($aliases); + } + + $visited[$name] = true; + $name = $this->aliases[$name]; + } + + $this->resolvedAliases[$alias] = $name; + } + } + + /** + * Rewrites the map of aliases by resolving the given $aliases with the existing resolved ones. + * This is mostly done for performance reasons. + * + * @param string[] $aliases + * + * @return void + */ + private function resolveNewAliasesWithPreviouslyResolvedAliases(array $aliases) + { + foreach ($this->resolvedAliases as $name => $target) { + if (isset($aliases[$target])) { + $this->resolvedAliases[$name] = $this->resolvedAliases[$target]; + } + } + } + + /** + * Get a factory for the given service name + * + * @param string $name + * @return callable + * @throws ServiceNotFoundException + */ + private function getFactory($name) + { + $factory = isset($this->factories[$name]) ? $this->factories[$name] : null; + + $lazyLoaded = false; + if (is_string($factory) && class_exists($factory)) { + $factory = new $factory(); + $lazyLoaded = true; + } + + if (is_callable($factory)) { + if ($lazyLoaded) { + $this->factories[$name] = $factory; + } + // PHP 5.6 fails on 'class::method' callables unless we explode them: + if (PHP_MAJOR_VERSION < 7 + && is_string($factory) && strpos($factory, '::') !== false + ) { + $factory = explode('::', $factory); + } + return $factory; + } + + // Check abstract factories + foreach ($this->abstractFactories as $abstractFactory) { + if ($abstractFactory->canCreate($this->creationContext, $name)) { + return $abstractFactory; + } + } + + throw new ServiceNotFoundException(sprintf( + 'Unable to resolve service "%s" to a factory; are you certain you provided it during configuration?', + $name + )); + } + + /** + * @param string $name + * @param null|array $options + * @return object + */ + private function createDelegatorFromName($name, array $options = null) + { + $creationCallback = function () use ($name, $options) { + // Code is inlined for performance reason, instead of abstracting the creation + $factory = $this->getFactory($name); + return $factory($this->creationContext, $name, $options); + }; + + foreach ($this->delegators[$name] as $index => $delegatorFactory) { + $delegatorFactory = $this->delegators[$name][$index]; + + if ($delegatorFactory === Proxy\LazyServiceFactory::class) { + $delegatorFactory = $this->createLazyServiceDelegatorFactory(); + } + + if (is_string($delegatorFactory) && class_exists($delegatorFactory)) { + $delegatorFactory = new $delegatorFactory(); + } + + if (! is_callable($delegatorFactory)) { + if (is_string($delegatorFactory)) { + throw new ServiceNotCreatedException(sprintf( + 'An invalid delegator factory was registered; resolved to class or function "%s" ' + . 'which does not exist; please provide a valid function name or class name resolving ' + . 'to an implementation of %s', + $delegatorFactory, + DelegatorFactoryInterface::class + )); + } + + throw new ServiceNotCreatedException(sprintf( + 'A non-callable delegator, "%s", was provided; expected a callable or instance of "%s"', + is_object($delegatorFactory) ? get_class($delegatorFactory) : gettype($delegatorFactory), + DelegatorFactoryInterface::class + )); + } + + $this->delegators[$name][$index] = $delegatorFactory; + + $creationCallback = function () use ($delegatorFactory, $name, $creationCallback, $options) { + return $delegatorFactory($this->creationContext, $name, $creationCallback, $options); + }; + } + + return $creationCallback($this->creationContext, $name, $creationCallback, $options); + } + + /** + * Create a new instance with an already resolved name + * + * This is a highly performance sensitive method, do not modify if you have not benchmarked it carefully + * + * @param string $resolvedName + * @param null|array $options + * @return mixed + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + */ + private function doCreate($resolvedName, array $options = null) + { + try { + if (! isset($this->delegators[$resolvedName])) { + // Let's create the service by fetching the factory + $factory = $this->getFactory($resolvedName); + $object = $factory($this->creationContext, $resolvedName, $options); + } else { + $object = $this->createDelegatorFromName($resolvedName, $options); + } + } catch (ContainerException $exception) { + throw $exception; + } catch (Exception $exception) { + throw new ServiceNotCreatedException(sprintf( + 'Service with name "%s" could not be created. Reason: %s', + $resolvedName, + $exception->getMessage() + ), (int) $exception->getCode(), $exception); + } + + foreach ($this->initializers as $initializer) { + $initializer($this->creationContext, $object); + } + + return $object; + } + + /** + * Create the lazy services delegator factory. + * + * Creates the lazy services delegator factory based on the lazy_services + * configuration present. + * + * @return Proxy\LazyServiceFactory + * @throws ServiceNotCreatedException when the lazy service class_map + * configuration is missing + */ + private function createLazyServiceDelegatorFactory() + { + if ($this->lazyServicesDelegator) { + return $this->lazyServicesDelegator; + } + + if (! isset($this->lazyServices['class_map'])) { + throw new ServiceNotCreatedException('Missing "class_map" config key in "lazy_services"'); + } + + $factoryConfig = new ProxyConfiguration(); + + if (isset($this->lazyServices['proxies_namespace'])) { + $factoryConfig->setProxiesNamespace($this->lazyServices['proxies_namespace']); + } + + if (isset($this->lazyServices['proxies_target_dir'])) { + $factoryConfig->setProxiesTargetDir($this->lazyServices['proxies_target_dir']); + } + + if (! isset($this->lazyServices['write_proxy_files']) || ! $this->lazyServices['write_proxy_files']) { + $factoryConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + } else { + $factoryConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy( + new FileLocator($factoryConfig->getProxiesTargetDir()) + )); + } + + spl_autoload_register($factoryConfig->getProxyAutoloader()); + + $this->lazyServicesDelegator = new Proxy\LazyServiceFactory( + new LazyLoadingValueHolderFactory($factoryConfig), + $this->lazyServices['class_map'] + ); + + return $this->lazyServicesDelegator; + } + + /** + * Create aliases for invokable classes. + * + * If an invokable service name does not match the class it maps to, this + * creates an alias to the class (which will later be mapped as an + * invokable factory). + * + * @param array $invokables + * @return array + */ + private function createAliasesForInvokables(array $invokables) + { + $aliases = []; + foreach ($invokables as $name => $class) { + if ($name === $class) { + continue; + } + $aliases[$name] = $class; + } + return $aliases; + } + + /** + * Create invokable factories for invokable classes. + * + * If an invokable service name does not match the class it maps to, this + * creates an invokable factory entry for the class name; otherwise, it + * creates an invokable factory for the entry name. + * + * @param array $invokables + * @return array + */ + private function createFactoriesForInvokables(array $invokables) + { + $factories = []; + foreach ($invokables as $name => $class) { + if ($name === $class) { + $factories[$name] = Factory\InvokableFactory::class; + continue; + } + + $factories[$class] = Factory\InvokableFactory::class; + } + return $factories; + } + + /** + * Determine if one or more services already exist in the container. + * + * If the allow override flag is true or it's first time configured, + * this method does nothing. + * + * Otherwise, it checks against each of the following service types, + * if present, and validates that none are defining services that + * already exist; if they do, it raises an exception indicating + * modification is not allowed. + * + * @param array $config + * @throws ContainerModificationsNotAllowedException if any services + * provided already have instances available. + */ + private function validateOverrides(array $config) + { + if ($this->allowOverride || ! $this->configured) { + return; + } + + if (isset($config['services'])) { + $this->validateOverrideSet(array_keys($config['services']), 'service'); + } + + if (isset($config['aliases'])) { + $this->validateOverrideSet(array_keys($config['aliases']), 'alias'); + } + + if (isset($config['invokables'])) { + $this->validateOverrideSet(array_keys($config['invokables']), 'invokable class'); + } + + if (isset($config['factories'])) { + $this->validateOverrideSet(array_keys($config['factories']), 'factory'); + } + + if (isset($config['delegators'])) { + $this->validateOverrideSet(array_keys($config['delegators']), 'delegator'); + } + + if (isset($config['shared'])) { + $this->validateOverrideSet(array_keys($config['shared']), 'sharing rule'); + } + + if (isset($config['lazy_services']['class_map'])) { + $this->validateOverrideSet(array_keys($config['lazy_services']['class_map']), 'lazy service'); + } + } + + /** + * Determine if one or more services already exist for a given type. + * + * Loops through the provided service names, checking if any have current + * service instances; if not, it returns, but otherwise, it raises an + * exception indicating modification is not allowed. + * + * @param string[] $services + * @param string $type Type of service being checked. + * @throws ContainerModificationsNotAllowedException if any services + * provided already have instances available. + */ + private function validateOverrideSet(array $services, $type) + { + $detected = []; + foreach ($services as $service) { + if (isset($this->services[$service])) { + $detected[] = $service; + } + } + + if (empty($detected)) { + return; + } + + throw new ContainerModificationsNotAllowedException(sprintf( + 'An updated/new %s is not allowed, as the container does not allow ' + . 'changes for services with existing instances; the following ' + . 'already exist in the container: %s', + $type, + implode(', ', $detected) + )); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php b/lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php new file mode 100644 index 000000000..a736cb05c --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php @@ -0,0 +1,115 @@ +getPluginManager(); + $reflection = new ReflectionProperty($manager, 'instanceOf'); + $reflection->setAccessible(true); + $this->assertEquals($this->getInstanceOf(), $reflection->getValue($manager), 'instanceOf does not match'); + } + + public function testShareByDefaultAndSharedByDefault() + { + $manager = $this->getPluginManager(); + $reflection = new ReflectionClass($manager); + $shareByDefault = $sharedByDefault = true; + + foreach ($reflection->getProperties() as $prop) { + if ($prop->getName() == 'shareByDefault') { + $prop->setAccessible(true); + $shareByDefault = $prop->getValue($manager); + } + if ($prop->getName() == 'sharedByDefault') { + $prop->setAccessible(true); + $sharedByDefault = $prop->getValue($manager); + } + } + + $this->assertTrue( + $shareByDefault == $sharedByDefault, + 'Values of shareByDefault and sharedByDefault do not match' + ); + } + + public function testRegisteringInvalidElementRaisesException() + { + $this->expectException($this->getServiceNotFoundException()); + $this->getPluginManager()->setService('test', $this); + } + + public function testLoadingInvalidElementRaisesException() + { + $manager = $this->getPluginManager(); + $manager->setInvokableClass('test', get_class($this)); + $this->expectException($this->getServiceNotFoundException()); + $manager->get('test'); + } + + /** + * @dataProvider aliasProvider + */ + public function testPluginAliasesResolve($alias, $expected) + { + $this->assertInstanceOf($expected, $this->getPluginManager()->get($alias), "Alias '$alias' does not resolve'"); + } + + public function aliasProvider() + { + $manager = $this->getPluginManager(); + $reflection = new ReflectionProperty($manager, 'aliases'); + $reflection->setAccessible(true); + $data = []; + foreach ($reflection->getValue($manager) as $alias => $expected) { + $data[] = [$alias, $expected]; + } + return $data; + } + + protected function getServiceNotFoundException() + { + $manager = $this->getPluginManager(); + if (method_exists($manager, 'configure')) { + return InvalidServiceException::class; + } + return $this->getV2InvalidPluginException(); + } + + /** + * Returns the plugin manager to test + * @return \Laminas\ServiceManager\AbstractPluginManager + */ + abstract protected function getPluginManager(); + + /** + * Returns the FQCN of the exception thrown under v2 by `validatePlugin()` + * @return mixed + */ + abstract protected function getV2InvalidPluginException(); + + /** + * Returns the value the instanceOf property has been set to + * @return string + */ + abstract protected function getInstanceOf(); +} diff --git a/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php new file mode 100644 index 000000000..949b9930d --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php @@ -0,0 +1,259 @@ +container = $container; + } + + /** + * @param array $config + * @param string $className + * @param bool $ignoreUnresolved + * @return array + * @throws InvalidArgumentException for invalid $className + */ + public function createDependencyConfig(array $config, $className, $ignoreUnresolved = false) + { + $this->validateClassName($className); + + $reflectionClass = new ReflectionClass($className); + + // class is an interface; do nothing + if ($reflectionClass->isInterface()) { + return $config; + } + + // class has no constructor, treat it as an invokable + if (! $reflectionClass->getConstructor()) { + return $this->createInvokable($config, $className); + } + + $constructorArguments = $reflectionClass->getConstructor()->getParameters(); + $constructorArguments = array_filter( + $constructorArguments, + function (ReflectionParameter $argument) { + return ! $argument->isOptional(); + } + ); + + // has no required parameters, treat it as an invokable + if (empty($constructorArguments)) { + return $this->createInvokable($config, $className); + } + + $classConfig = []; + + foreach ($constructorArguments as $constructorArgument) { + $argumentType = $constructorArgument->getClass(); + if (is_null($argumentType)) { + if ($ignoreUnresolved) { + // don't throw an exception, just return the previous config + return $config; + } + // don't throw an exception if the class is an already defined service + if ($this->container && $this->container->has($className)) { + return $config; + } + throw new InvalidArgumentException(sprintf( + 'Cannot create config for constructor argument "%s", ' + . 'it has no type hint, or non-class/interface type hint', + $constructorArgument->getName() + )); + } + $argumentName = $argumentType->getName(); + $config = $this->createDependencyConfig($config, $argumentName, $ignoreUnresolved); + $classConfig[] = $argumentName; + } + + $config[ConfigAbstractFactory::class][$className] = $classConfig; + + return $config; + } + + /** + * @param $className + * @throws InvalidArgumentException if class name is not a string or does + * not exist. + */ + private function validateClassName($className) + { + if (! is_string($className)) { + throw new InvalidArgumentException('Class name must be a string, ' . gettype($className) . ' given'); + } + + if (! class_exists($className) && ! interface_exists($className)) { + throw new InvalidArgumentException('Cannot find class or interface with name ' . $className); + } + } + + /** + * @param array $config + * @param string $className + * @return array + */ + private function createInvokable(array $config, $className) + { + $config[ConfigAbstractFactory::class][$className] = []; + return $config; + } + + /** + * @param array $config + * @return array + * @throws InvalidArgumentException if ConfigAbstractFactory configuration + * value is not an array. + */ + public function createFactoryMappingsFromConfig(array $config) + { + if (! array_key_exists(ConfigAbstractFactory::class, $config)) { + return $config; + } + + if (! is_array($config[ConfigAbstractFactory::class])) { + throw new InvalidArgumentException( + 'Config key for ' . ConfigAbstractFactory::class . ' should be an array, ' . gettype( + $config[ConfigAbstractFactory::class] + ) . ' given' + ); + } + + foreach ($config[ConfigAbstractFactory::class] as $className => $dependency) { + $config = $this->createFactoryMappings($config, $className); + } + return $config; + } + + /** + * @param array $config + * @param string $className + * @return array + */ + public function createFactoryMappings(array $config, $className) + { + $this->validateClassName($className); + + if (array_key_exists('service_manager', $config) + && array_key_exists('factories', $config['service_manager']) + && array_key_exists($className, $config['service_manager']['factories']) + ) { + return $config; + } + + $config['service_manager']['factories'][$className] = ConfigAbstractFactory::class; + return $config; + } + + /** + * @param array $config + * @return string + */ + public function dumpConfigFile(array $config) + { + $prepared = $this->prepareConfig($config); + return sprintf( + self::CONFIG_TEMPLATE, + get_class($this), + date('Y-m-d H:i:s'), + $prepared + ); + } + + /** + * @param array|Traversable $config + * @param int $indentLevel + * @return string + */ + private function prepareConfig($config, $indentLevel = 1) + { + $indent = str_repeat(' ', $indentLevel * 4); + $entries = []; + foreach ($config as $key => $value) { + $key = $this->createConfigKey($key); + $entries[] = sprintf( + '%s%s%s,', + $indent, + $key ? sprintf('%s => ', $key) : '', + $this->createConfigValue($value, $indentLevel) + ); + } + + $outerIndent = str_repeat(' ', ($indentLevel - 1) * 4); + + return sprintf( + "[\n%s\n%s]", + implode("\n", $entries), + $outerIndent + ); + } + + /** + * @param string|int|null $key + * @return null|string + */ + private function createConfigKey($key) + { + if (is_string($key) && class_exists($key)) { + return sprintf('\\%s::class', $key); + } + + if (is_int($key)) { + return null; + } + + return sprintf("'%s'", $key); + } + + /** + * @param mixed $value + * @param int $indentLevel + * @return string + */ + private function createConfigValue($value, $indentLevel) + { + if (is_array($value) || $value instanceof Traversable) { + return $this->prepareConfig($value, $indentLevel + 1); + } + + if (is_string($value) && class_exists($value)) { + return sprintf('\\%s::class', $value); + } + + return var_export($value, true); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php new file mode 100644 index 000000000..d2b6589a1 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php @@ -0,0 +1,229 @@ +Usage: + + %s [-h|--help|help] [-i|--ignore-unresolved] + +Arguments: + + -h|--help|help This usage message + -i|--ignore-unresolved Ignore classes with unresolved direct dependencies. + Path to a config file for which to generate + configuration. If the file does not exist, it will + be created. If it does exist, it must return an + array, and the file will be updated with new + configuration. + Name of the class to reflect and for which to + generate dependency configuration. + +Reads the provided configuration file (creating it if it does not exist), +and injects it with ConfigAbstractFactory dependency configuration for +the provided class name, writing the changes back to the file. +EOH; + + /** + * @var ConsoleHelper + */ + private $helper; + + /** + * @var string + */ + private $scriptName; + + /** + * @param string $scriptName + */ + public function __construct($scriptName = self::DEFAULT_SCRIPT_NAME, ConsoleHelper $helper = null) + { + $this->scriptName = $scriptName; + $this->helper = $helper ?: new ConsoleHelper(); + } + + /** + * @param array $args Argument list, minus script name + * @return int Exit status + */ + public function __invoke(array $args) + { + $arguments = $this->parseArgs($args); + + switch ($arguments->command) { + case self::COMMAND_HELP: + $this->help(); + return 0; + case self::COMMAND_ERROR: + $this->helper->writeErrorMessage($arguments->message); + $this->help(STDERR); + return 1; + case self::COMMAND_DUMP: + // fall-through + default: + break; + } + + $dumper = new ConfigDumper(); + try { + $config = $dumper->createDependencyConfig( + $arguments->config, + $arguments->class, + $arguments->ignoreUnresolved + ); + } catch (Exception\InvalidArgumentException $e) { + $this->helper->writeErrorMessage(sprintf( + 'Unable to create config for "%s": %s', + $arguments->class, + $e->getMessage() + )); + $this->help(STDERR); + return 1; + } + + file_put_contents($arguments->configFile, $dumper->dumpConfigFile($config)); + + $this->helper->writeLine(sprintf( + '[DONE] Changes written to %s', + $arguments->configFile + )); + return 0; + } + + /** + * @param array $args + * @return \stdClass + */ + private function parseArgs(array $args) + { + if (! count($args)) { + return $this->createHelpArgument(); + } + + $arg1 = array_shift($args); + + if (in_array($arg1, ['-h', '--help', 'help'], true)) { + return $this->createHelpArgument(); + } + + $ignoreUnresolved = false; + if (in_array($arg1, ['-i', '--ignore-unresolved'], true)) { + $ignoreUnresolved = true; + $arg1 = array_shift($args); + } + + if (! count($args)) { + return $this->createErrorArgument('Missing class name'); + } + + $configFile = $arg1; + switch (file_exists($configFile)) { + case true: + $config = require $configFile; + + if (! is_array($config)) { + return $this->createErrorArgument(sprintf( + 'Configuration at path "%s" does not return an array.', + $configFile + )); + } + + break; + case false: + // fall-through + default: + if (! is_writable(dirname($configFile))) { + return $this->createErrorArgument(sprintf( + 'Cannot create configuration at path "%s"; not writable.', + $configFile + )); + } + + $config = []; + break; + } + + $class = array_shift($args); + + if (! class_exists($class)) { + return $this->createErrorArgument(sprintf( + 'Class "%s" does not exist or could not be autoloaded.', + $class + )); + } + + return $this->createArguments(self::COMMAND_DUMP, $configFile, $config, $class, $ignoreUnresolved); + } + + /** + * @param resource $resource Defaults to STDOUT + * @return void + */ + private function help($resource = STDOUT) + { + $this->helper->writeLine(sprintf( + self::HELP_TEMPLATE, + $this->scriptName + ), true, $resource); + } + + /** + * @param string $command + * @param string $configFile File from which config originates, and to + * which it will be written. + * @param array $config Parsed configuration. + * @param string $class Name of class to reflect. + * @param bool $ignoreUnresolved If to ignore classes with unresolved direct dependencies. + * @return \stdClass + */ + private function createArguments($command, $configFile, $config, $class, $ignoreUnresolved) + { + return (object) [ + 'command' => $command, + 'configFile' => $configFile, + 'config' => $config, + 'class' => $class, + 'ignoreUnresolved' => $ignoreUnresolved, + ]; + } + + /** + * @param string $message + * @return \stdClass + */ + private function createErrorArgument($message) + { + return (object) [ + 'command' => self::COMMAND_ERROR, + 'message' => $message, + ]; + } + + /** + * @return \stdClass + */ + private function createHelpArgument() + { + return (object) [ + 'command' => self::COMMAND_HELP, + ]; + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php b/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php new file mode 100644 index 000000000..017a52de5 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php @@ -0,0 +1,143 @@ +getClassName($className); + + return sprintf( + self::FACTORY_TEMPLATE, + str_replace('\\' . $class, '', $className), + $className, + $class, + $class, + $class, + $this->createArgumentString($className) + ); + } + + /** + * @param $className + * @return string + */ + private function getClassName($className) + { + $class = substr($className, strrpos($className, '\\') + 1); + return $class; + } + + /** + * @param string $className + * @return array + */ + private function getConstructorParameters($className) + { + $reflectionClass = new ReflectionClass($className); + + if (! $reflectionClass || ! $reflectionClass->getConstructor()) { + return []; + } + + $constructorParameters = $reflectionClass->getConstructor()->getParameters(); + + if (empty($constructorParameters)) { + return []; + } + + $constructorParameters = array_filter( + $constructorParameters, + function (ReflectionParameter $argument) { + if ($argument->isOptional()) { + return false; + } + + if (null === $argument->getClass()) { + throw new InvalidArgumentException(sprintf( + 'Cannot identify type for constructor argument "%s"; ' + . 'no type hint, or non-class/interface type hint', + $argument->getName() + )); + } + + return true; + } + ); + + if (empty($constructorParameters)) { + return []; + } + + return array_map(function (ReflectionParameter $parameter) { + return $parameter->getClass()->getName(); + }, $constructorParameters); + } + + /** + * @param string $className + * @return string + */ + private function createArgumentString($className) + { + $arguments = array_map(function ($dependency) { + return sprintf('$container->get(\\%s::class)', $dependency); + }, $this->getConstructorParameters($className)); + + switch (count($arguments)) { + case 0: + return ''; + case 1: + return array_shift($arguments); + default: + $argumentPad = str_repeat(' ', 12); + $closePad = str_repeat(' ', 8); + return sprintf( + "\n%s%s\n%s", + $argumentPad, + implode(",\n" . $argumentPad, $arguments), + $closePad + ); + } + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php b/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php new file mode 100644 index 000000000..da851c420 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php @@ -0,0 +1,150 @@ +Usage: + + %s [-h|--help|help] + +Arguments: + + -h|--help|help This usage message + Name of the class to reflect and for which to generate + a factory. + +Generates to STDOUT a factory for creating the specified class; this may then +be added to your application, and configured as a factory for the class. +EOH; + + /** + * @var ConsoleHelper + */ + private $helper; + + /** + * @var string + */ + private $scriptName; + + /** + * @param string $scriptName + * @param ConsoleHelper $helper + */ + public function __construct($scriptName = self::DEFAULT_SCRIPT_NAME, ConsoleHelper $helper = null) + { + $this->scriptName = $scriptName; + $this->helper = $helper ?: new ConsoleHelper(); + } + + /** + * @param array $args Argument list, minus script name + * @return int Exit status + */ + public function __invoke(array $args) + { + $arguments = $this->parseArgs($args); + + switch ($arguments->command) { + case self::COMMAND_HELP: + $this->help(); + return 0; + case self::COMMAND_ERROR: + $this->helper->writeErrorMessage($arguments->message); + $this->help(STDERR); + return 1; + case self::COMMAND_DUMP: + // fall-through + default: + break; + } + + $generator = new FactoryCreator(); + try { + $factory = $generator->createFactory($arguments->class); + } catch (Exception\InvalidArgumentException $e) { + $this->helper->writeErrorMessage(sprintf( + 'Unable to create factory for "%s": %s', + $arguments->class, + $e->getMessage() + )); + $this->help(STDERR); + return 1; + } + + $this->helper->write($factory, false); + return 0; + } + + /** + * @param array $args + * @return \stdClass + */ + private function parseArgs(array $args) + { + if (! count($args)) { + return $this->createArguments(self::COMMAND_HELP); + } + + $arg1 = array_shift($args); + + if (in_array($arg1, ['-h', '--help', 'help'], true)) { + return $this->createArguments(self::COMMAND_HELP); + } + + $class = $arg1; + + if (! class_exists($class)) { + return $this->createArguments(self::COMMAND_ERROR, null, sprintf( + 'Class "%s" does not exist or could not be autoloaded.', + $class + )); + } + + return $this->createArguments(self::COMMAND_DUMP, $class); + } + + /** + * @param resource $resource Defaults to STDOUT + * @return void + */ + private function help($resource = STDOUT) + { + $this->helper->writeLine(sprintf( + self::HELP_TEMPLATE, + $this->scriptName + ), true, $resource); + } + + /** + * @param string $command + * @param string|null $class Name of class to reflect. + * @param string|null $error Error message, if any. + * @return \stdClass + */ + private function createArguments($command, $class = null, $error = null) + { + return (object) [ + 'command' => $command, + 'class' => $class, + 'message' => $error, + ]; + } +} diff --git a/lib/laminas/laminas-stdlib/CHANGELOG.md b/lib/laminas/laminas-stdlib/CHANGELOG.md new file mode 100644 index 000000000..57b79833f --- /dev/null +++ b/lib/laminas/laminas-stdlib/CHANGELOG.md @@ -0,0 +1,385 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 3.2.1 - 2018-08-28 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-stdlib#92](https://github.com/zendframework/zend-stdlib/pull/92) fixes serialization of `SplPriorityQueue` by ensuring its `$serial` + property is also serialized. + +- [zendframework/zend-stdlib#91](https://github.com/zendframework/zend-stdlib/pull/91) fixes behavior in the `ArrayObject` implementation that was not + compatible with PHP 7.3. + +## 3.2.0 - 2018-04-30 + +### Added + +- [zendframework/zend-stdlib#87](https://github.com/zendframework/zend-stdlib/pull/87) adds support for PHP 7.2. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- [zendframework/zend-stdlib#87](https://github.com/zendframework/zend-stdlib/pull/87) removes support for HHVM. + +### Fixed + +- Nothing. + +## 3.1.1 - 2018-04-12 + +### Added + +- Nothing. + +### Changed + +- [zendframework/zend-stdlib#67](https://github.com/zendframework/zend-stdlib/pull/67) changes the typehint of the `$content` property + of the `Message` class to indicate it is a string. All known implementations + already assumed this. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-stdlib#60](https://github.com/zendframework/zend-stdlib/pull/60) fixes an issue whereby calling `remove()` would + incorrectly re-calculate the maximum priority stored in the queue. + +- [zendframework/zend-stdlib#60](https://github.com/zendframework/zend-stdlib/pull/60) fixes an infinite loop condition that can occur when + inserting an item at 0 priority. + +## 3.1.0 - 2016-09-13 + +### Added + +- [zendframework/zend-stdlib#63](https://github.com/zendframework/zend-stdlib/pull/63) adds a new + `Laminas\Stdlib\ConsoleHelper` class, providing minimal support for writing + output to `STDOUT` and `STDERR`, with optional colorization, when the console + supports that feature. + +### Deprecated + +- [zendframework/zend-stdlib#38](https://github.com/zendframework/zend-stdlib/pull/38) deprecates + `Laminas\Stdlib\JsonSerializable`, as all supported version of PHP now support + it. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 3.0.1 - 2016-04-12 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-stdlib#59](https://github.com/zendframework/zend-stdlib/pull/59) fixes a notice + when defining the `Laminas\Json\Json::GLOB_BRACE` constant on systems using + non-gcc glob implementations. + +## 3.0.0 - 2016-02-03 + +### Added + +- [zendframework/zend-stdlib#51](https://github.com/zendframework/zend-stdlib/pull/51) adds PHP 7 as a + supported PHP version. +- [zendframework/zend-stdlib#51](https://github.com/zendframework/zend-stdlib/pull/51) adds a migration + document from v2 to v3. Hint: if you use hydrators, you need to be using + laminas-hydrator instead! +- [zendframework/zend-stdlib#51](https://github.com/zendframework/zend-stdlib/pull/51) adds automated + documentation builds to gh-pages. + +### Deprecated + +- Nothing. + +### Removed + +- [zendframework/zend-stdlib#33](https://github.com/zendframework/zend-stdlib/pull/33) - removed + deprecated classes + - *All Hydrator classes* see zendframework/zend-stdlib#22. + - `Laminas\Stdlib\CallbackHandler` see zendframework/zend-stdlib#35 +- [zendframework/zend-stdlib#37](https://github.com/zendframework/zend-stdlib/pull/37) - removed + deprecated classes and polyfills: + - `Laminas\Stdlib\DateTime`; this had been deprecated since 2.5, and only + existed as a polyfill for the `createFromISO8601()` support, now standard + in all PHP versions we support. + - `Laminas\Stdlib\Exception\InvalidCallbackException`, which was unused since zendframework/zend-stdlib#33. + - `Laminas\Stdlib\Guard\GuardUtils`, which duplicated `Laminas\Stdlib\Guard\AllGuardsTrait` + to allow usage with pre-PHP 5.4 versions. + - `src/compatibility/autoload.php`, which has been dprecated since 2.5. +- [zendframework/zend-stdlib#37](https://github.com/zendframework/zend-stdlib/pull/37) - removed + unneeded dependencies: + - laminas-config (used only in testing ArrayUtils, and the test was redundant) + - laminas-serializer (no longer used) +- [zendframework/zend-stdlib#51](https://github.com/zendframework/zend-stdlib/pull/51) removes the + documentation for hydrators, as those are part of the laminas-hydrator + component. + +### Fixed + +- Nothing. + +## 2.7.4 - 2015-10-15 + +### Added + +- Nothing. + +### Deprecated + +- [zendframework/zend-stdlib#35](https://github.com/zendframework/zend-stdlib/pull/35) deprecates + `Laminas\Stdlib\CallbackHandler`, as the one component that used it, + laminas-eventmanager, will no longer depend on it starting in v3. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 2.7.3 - 2015-09-24 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-stdlib#27](https://github.com/zendframework/zend-stdlib/pull/27) fixes a race + condition in the `FastPriorityQueue::remove()` logic that occurs when removing + items iteratively from the same priority of a queue. + +## 2.7.2 - 2015-09-23 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-stdlib#26](https://github.com/zendframework/zend-stdlib/pull/26) fixes a subtle + inheritance issue with deprecation in the hydrators, and updates the + `HydratorInterface` to also extend the laminas-hydrator `HydratorInterface` to + ensure LSP is preserved. + +## 2.7.1 - 2015-09-22 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-stdlib#24](https://github.com/zendframework/zend-stdlib/pull/24) fixes an import in + `FastPriorityQueue` to alias `SplPriorityQueue` in order to disambiguate with + the local override present in the component. + +## 2.7.0 - 2015-09-22 + +### Added + +- [zendframework/zend-stdlib#19](https://github.com/zendframework/zend-stdlib/pull/19) adds a new + `FastPriorityQueue` implementation. It follows the same signature as + `SplPriorityQueue`, but uses a performance-optimized algorithm: + + - inserts are 2x faster than `SplPriorityQueue` and 3x faster than the + `Laminas\Stdlib\PriorityQueue` implementation. + - extracts are 4x faster than `SplPriorityQueue` and 4-5x faster than the + `Laminas\Stdlib\PriorityQueue` implementation. + + The intention is to use this as a drop-in replacement in the + `laminas-eventmanager` component to provide performance benefits. + +### Deprecated + +- [zendframework/zend-stdlib#20](https://github.com/zendframework/zend-stdlib/pull/20) deprecates *all + hydrator* classes, in favor of the new [laminas-hydrator](https://github.com/laminas/laminas-hydrator) + component. All classes were updated to extend their laminas-hydrator equivalents, + and marked as `@deprecated`, indicating the equivalent class from the other + repository. + + Users *should* immediately start changing their code to use the laminas-hydrator + equivalents; in most cases, this can be as easy as removing the `Stdlib` + namespace from import statements or hydrator configuration. Hydrators will be + removed entirely from laminas-stdlib in v3.0, and all future updates to hydrators + will occur in the laminas-hydrator library. + + Changes with backwards compatibility implications: + + - Users implementing `Laminas\Stdlib\Hydrator\HydratorAwareInterface` will need to + update their `setHydrator()` implementation to typehint on + `Laminas\Hydrator\HydratorInterface`. This can be done by changing the import + statement for that interface as follows: + + ```php + // Replace this: + use Laminas\Stdlib\Hydrator\HydratorInterface; + // with this: + use Laminas\Hydrator\HydratorInterface; + ``` + + If you are not using imports, change the typehint within the signature itself: + + ```php + // Replace this: + public function setHydrator(\Laminas\Stdlib\Hydrator\HydratorInterface $hydrator) + // with this: + public function setHydrator(\Laminas\Hydrator\HydratorInterface $hydrator) + ``` + + If you are using `Laminas\Stdlib\Hydrator\HydratorAwareTrait`, no changes are + necessary, unless you override that method. + + - If you were catching hydrator-generated exceptions, these were previously in + the `Laminas\Stdlib\Exception` namespace. You will need to update your code to + catch exceptions in the `Laminas\Hydrator\Exception` namespace. + + - Users who *do* migrate to laminas-hydrator may end up in a situation where + their code will not work with existing libraries that are still type-hinting + on the laminas-stdlib interfaces. We will be attempting to address that ASAP, + but the deprecation within laminas-stdlib is necessary as a first step. + + In the meantime, you can write hydrators targeting laminas-stdlib still in + order to guarantee compatibility. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 2.6.0 - 2015-07-21 + +### Added + +- [zendframework/zend-stdlib#13](https://github.com/zendframework/zend-stdlib/pull/13) adds + `Laminas\Stdlib\Hydrator\Iterator`, which provides mechanisms for hydrating + objects when iterating a traversable. This allows creating generic collection + resultsets; the original idea was pulled from + [PhlyMongo](https://github.com/phly/PhlyMongo), where it was used to hydrate + collections retrieved from MongoDB. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 2.5.2 - 2015-07-21 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-stdlib#9](https://github.com/zendframework/zend-stdlib/pull/9) fixes an issue with + count incrementation during insert in PriorityList, ensuring that incrementation only + occurs when the item inserted was not previously present in the list. + +## 2.4.4 - 2015-07-21 + +### Added + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [zendframework/zend-stdlib#9](https://github.com/zendframework/zend-stdlib/pull/9) fixes an issue with + count incrementation during insert in PriorityList, ensuring that incrementation only + occurs when the item inserted was not previously present in the list. diff --git a/lib/laminas/laminas-stdlib/COPYRIGHT.md b/lib/laminas/laminas-stdlib/COPYRIGHT.md new file mode 100644 index 000000000..c4fc4fee1 --- /dev/null +++ b/lib/laminas/laminas-stdlib/COPYRIGHT.md @@ -0,0 +1,2 @@ +Copyright (c) 2019, Laminas Foundation. +All rights reserved. (https://getlaminas.org/) diff --git a/lib/laminas/laminas-stdlib/LICENSE.md b/lib/laminas/laminas-stdlib/LICENSE.md new file mode 100644 index 000000000..09f53edc1 --- /dev/null +++ b/lib/laminas/laminas-stdlib/LICENSE.md @@ -0,0 +1,27 @@ +Copyright (c) 2019, Laminas Foundation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Laminas Foundation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/laminas/laminas-stdlib/README.md b/lib/laminas/laminas-stdlib/README.md new file mode 100644 index 000000000..103bb939e --- /dev/null +++ b/lib/laminas/laminas-stdlib/README.md @@ -0,0 +1,29 @@ +# laminas-stdlib + +[![Build Status](https://travis-ci.org/laminas/laminas-stdlib.svg?branch=master)](https://travis-ci.org/laminas/laminas-stdlib) +[![Coverage Status](https://coveralls.io/repos/github/laminas/laminas-stdlib/badge.svg?branch=master)](https://coveralls.io/github/laminas/laminas-stdlib?branch=master) + +`Laminas\Stdlib` is a set of components that implements general purpose utility +class for different scopes like: + +- array utilities functions; +- general messaging systems; +- string wrappers; +- etc. + +--- + +- File issues at https://github.com/laminas/laminas-stdlib/issues +- Documentation is at https://docs.laminas.dev/laminas-stdlib/ + +## Benchmarks + +We provide scripts for benchmarking laminas-stdlib using the +[PHPBench](https://github.com/phpbench/phpbench) framework; these can be +found in the `benchmark/` directory. + +To execute the benchmarks you can run the following command: + +```bash +$ vendor/bin/phpbench run --report=aggregate +``` diff --git a/lib/laminas/laminas-stdlib/composer.json b/lib/laminas/laminas-stdlib/composer.json new file mode 100644 index 000000000..b3745908e --- /dev/null +++ b/lib/laminas/laminas-stdlib/composer.json @@ -0,0 +1,60 @@ +{ + "name": "laminas/laminas-stdlib", + "description": "SPL extensions, array utilities, error handlers, and more", + "license": "BSD-3-Clause", + "keywords": [ + "laminas", + "stdlib" + ], + "homepage": "https://laminas.dev", + "support": { + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "source": "https://github.com/laminas/laminas-stdlib", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev" + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" + } + }, + "require": { + "php": "^5.6 || ^7.0", + "laminas/laminas-zendframework-bridge": "^1.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpbench/phpbench": "^0.13", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" + }, + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LaminasTest\\Stdlib\\": "test/", + "LaminasBench\\Stdlib\\": "benchmark/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" + }, + "replace": { + "zendframework/zend-stdlib": "self.version" + } +} diff --git a/lib/laminas/laminas-stdlib/src/AbstractOptions.php b/lib/laminas/laminas-stdlib/src/AbstractOptions.php new file mode 100644 index 000000000..c2b113e84 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/AbstractOptions.php @@ -0,0 +1,177 @@ +setFromArray($options); + } + } + + /** + * Set one or more configuration properties + * + * @param array|Traversable|AbstractOptions $options + * @throws Exception\InvalidArgumentException + * @return AbstractOptions Provides fluent interface + */ + public function setFromArray($options) + { + if ($options instanceof self) { + $options = $options->toArray(); + } + + if (! is_array($options) && ! $options instanceof Traversable) { + throw new Exception\InvalidArgumentException( + sprintf( + 'Parameter provided to %s must be an %s, %s or %s', + __METHOD__, + 'array', + 'Traversable', + 'Laminas\Stdlib\AbstractOptions' + ) + ); + } + + foreach ($options as $key => $value) { + $this->__set($key, $value); + } + + return $this; + } + + /** + * Cast to array + * + * @return array + */ + public function toArray() + { + $array = []; + $transform = function ($letters) { + $letter = array_shift($letters); + return '_' . strtolower($letter); + }; + foreach ($this as $key => $value) { + if ($key === '__strictMode__') { + continue; + } + $normalizedKey = preg_replace_callback('/([A-Z])/', $transform, $key); + $array[$normalizedKey] = $value; + } + return $array; + } + + /** + * Set a configuration property + * + * @see ParameterObject::__set() + * @param string $key + * @param mixed $value + * @throws Exception\BadMethodCallException + * @return void + */ + public function __set($key, $value) + { + $setter = 'set' . str_replace('_', '', $key); + + if (is_callable([$this, $setter])) { + $this->{$setter}($value); + + return; + } + + if ($this->__strictMode__) { + throw new Exception\BadMethodCallException(sprintf( + 'The option "%s" does not have a callable "%s" ("%s") setter method which must be defined', + $key, + 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))), + $setter + )); + } + } + + /** + * Get a configuration property + * + * @see ParameterObject::__get() + * @param string $key + * @throws Exception\BadMethodCallException + * @return mixed + */ + public function __get($key) + { + $getter = 'get' . str_replace('_', '', $key); + + if (is_callable([$this, $getter])) { + return $this->{$getter}(); + } + + throw new Exception\BadMethodCallException(sprintf( + 'The option "%s" does not have a callable "%s" getter method which must be defined', + $key, + 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))) + )); + } + + /** + * Test if a configuration property is null + * @see ParameterObject::__isset() + * @param string $key + * @return bool + */ + public function __isset($key) + { + $getter = 'get' . str_replace('_', '', $key); + + return method_exists($this, $getter) && null !== $this->__get($key); + } + + /** + * Set a configuration property to NULL + * + * @see ParameterObject::__unset() + * @param string $key + * @throws Exception\InvalidArgumentException + * @return void + */ + public function __unset($key) + { + try { + $this->__set($key, null); + } catch (Exception\BadMethodCallException $e) { + throw new Exception\InvalidArgumentException( + 'The class property $' . $key . ' cannot be unset as' + . ' NULL is an invalid value for it', + 0, + $e + ); + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArrayObject.php b/lib/laminas/laminas-stdlib/src/ArrayObject.php new file mode 100644 index 000000000..9bbb07411 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArrayObject.php @@ -0,0 +1,433 @@ +setFlags($flags); + $this->storage = $input; + $this->setIteratorClass($iteratorClass); + $this->protectedProperties = array_keys(get_object_vars($this)); + } + + /** + * Returns whether the requested key exists + * + * @param mixed $key + * @return bool + */ + public function __isset($key) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + return $this->offsetExists($key); + } + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); + } + + return isset($this->$key); + } + + /** + * Sets the value at the specified key to value + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + return $this->offsetSet($key, $value); + } + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); + } + $this->$key = $value; + } + + /** + * Unsets the value at the specified key + * + * @param mixed $key + * @return void + */ + public function __unset($key) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + return $this->offsetUnset($key); + } + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); + } + unset($this->$key); + } + + /** + * Returns the value at the specified key by reference + * + * @param mixed $key + * @return mixed + */ + public function &__get($key) + { + $ret = null; + if ($this->flag == self::ARRAY_AS_PROPS) { + $ret =& $this->offsetGet($key); + + return $ret; + } + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); + } + + return $this->$key; + } + + /** + * Appends the value + * + * @param mixed $value + * @return void + */ + public function append($value) + { + $this->storage[] = $value; + } + + /** + * Sort the entries by value + * + * @return void + */ + public function asort() + { + asort($this->storage); + } + + /** + * Get the number of public properties in the ArrayObject + * + * @return int + */ + public function count() + { + return count($this->storage); + } + + /** + * Exchange the array for another one. + * + * @param array|ArrayObject $data + * @return array + */ + public function exchangeArray($data) + { + if (! is_array($data) && ! is_object($data)) { + throw new Exception\InvalidArgumentException( + 'Passed variable is not an array or object, using empty array instead' + ); + } + + if (is_object($data) && ($data instanceof self || $data instanceof \ArrayObject)) { + $data = $data->getArrayCopy(); + } + if (! is_array($data)) { + $data = (array) $data; + } + + $storage = $this->storage; + + $this->storage = $data; + + return $storage; + } + + /** + * Creates a copy of the ArrayObject. + * + * @return array + */ + public function getArrayCopy() + { + return $this->storage; + } + + /** + * Gets the behavior flags. + * + * @return int + */ + public function getFlags() + { + return $this->flag; + } + + /** + * Create a new iterator from an ArrayObject instance + * + * @return \Iterator + */ + public function getIterator() + { + $class = $this->iteratorClass; + + return new $class($this->storage); + } + + /** + * Gets the iterator classname for the ArrayObject. + * + * @return string + */ + public function getIteratorClass() + { + return $this->iteratorClass; + } + + /** + * Sort the entries by key + * + * @return void + */ + public function ksort() + { + ksort($this->storage); + } + + /** + * Sort an array using a case insensitive "natural order" algorithm + * + * @return void + */ + public function natcasesort() + { + natcasesort($this->storage); + } + + /** + * Sort entries using a "natural order" algorithm + * + * @return void + */ + public function natsort() + { + natsort($this->storage); + } + + /** + * Returns whether the requested key exists + * + * @param mixed $key + * @return bool + */ + public function offsetExists($key) + { + return isset($this->storage[$key]); + } + + /** + * Returns the value at the specified key + * + * @param mixed $key + * @return mixed + */ + public function &offsetGet($key) + { + $ret = null; + if (! $this->offsetExists($key)) { + return $ret; + } + $ret =& $this->storage[$key]; + + return $ret; + } + + /** + * Sets the value at the specified key to value + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->storage[$key] = $value; + } + + /** + * Unsets the value at the specified key + * + * @param mixed $key + * @return void + */ + public function offsetUnset($key) + { + if ($this->offsetExists($key)) { + unset($this->storage[$key]); + } + } + + /** + * Serialize an ArrayObject + * + * @return string + */ + public function serialize() + { + return serialize(get_object_vars($this)); + } + + /** + * Sets the behavior flags + * + * @param int $flags + * @return void + */ + public function setFlags($flags) + { + $this->flag = $flags; + } + + /** + * Sets the iterator classname for the ArrayObject + * + * @param string $class + * @return void + */ + public function setIteratorClass($class) + { + if (class_exists($class)) { + $this->iteratorClass = $class; + + return ; + } + + if (strpos($class, '\\') === 0) { + $class = '\\' . $class; + if (class_exists($class)) { + $this->iteratorClass = $class; + + return ; + } + } + + throw new Exception\InvalidArgumentException('The iterator class does not exist'); + } + + /** + * Sort the entries with a user-defined comparison function and maintain key association + * + * @param callable $function + * @return void + */ + public function uasort($function) + { + if (is_callable($function)) { + uasort($this->storage, $function); + } + } + + /** + * Sort the entries by keys using a user-defined comparison function + * + * @param callable $function + * @return void + */ + public function uksort($function) + { + if (is_callable($function)) { + uksort($this->storage, $function); + } + } + + /** + * Unserialize an ArrayObject + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + $ar = unserialize($data); + $this->protectedProperties = array_keys(get_object_vars($this)); + + $this->setFlags($ar['flag']); + $this->exchangeArray($ar['storage']); + $this->setIteratorClass($ar['iteratorClass']); + + foreach ($ar as $k => $v) { + switch ($k) { + case 'flag': + $this->setFlags($v); + break; + case 'storage': + $this->exchangeArray($v); + break; + case 'iteratorClass': + $this->setIteratorClass($v); + break; + case 'protectedProperties': + break; + default: + $this->__set($k, $v); + } + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArraySerializableInterface.php b/lib/laminas/laminas-stdlib/src/ArraySerializableInterface.php new file mode 100644 index 000000000..d0d95eef1 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArraySerializableInterface.php @@ -0,0 +1,27 @@ +getArrayCopy(); + return new ArrayIterator(array_reverse($array)); + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArrayUtils.php b/lib/laminas/laminas-stdlib/src/ArrayUtils.php new file mode 100644 index 000000000..13eb25d0d --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArrayUtils.php @@ -0,0 +1,313 @@ + 0; + } + + /** + * Test whether an array contains one or more integer keys + * + * @param mixed $value + * @param bool $allowEmpty Should an empty array() return true + * @return bool + */ + public static function hasIntegerKeys($value, $allowEmpty = false) + { + if (! is_array($value)) { + return false; + } + + if (! $value) { + return $allowEmpty; + } + + return count(array_filter(array_keys($value), 'is_int')) > 0; + } + + /** + * Test whether an array contains one or more numeric keys. + * + * A numeric key can be one of the following: + * - an integer 1, + * - a string with a number '20' + * - a string with negative number: '-1000' + * - a float: 2.2120, -78.150999 + * - a string with float: '4000.99999', '-10.10' + * + * @param mixed $value + * @param bool $allowEmpty Should an empty array() return true + * @return bool + */ + public static function hasNumericKeys($value, $allowEmpty = false) + { + if (! is_array($value)) { + return false; + } + + if (! $value) { + return $allowEmpty; + } + + return count(array_filter(array_keys($value), 'is_numeric')) > 0; + } + + /** + * Test whether an array is a list + * + * A list is a collection of values assigned to continuous integer keys + * starting at 0 and ending at count() - 1. + * + * For example: + * + * $list = array('a', 'b', 'c', 'd'); + * $list = array( + * 0 => 'foo', + * 1 => 'bar', + * 2 => array('foo' => 'baz'), + * ); + * + * + * @param mixed $value + * @param bool $allowEmpty Is an empty list a valid list? + * @return bool + */ + public static function isList($value, $allowEmpty = false) + { + if (! is_array($value)) { + return false; + } + + if (! $value) { + return $allowEmpty; + } + + return (array_values($value) === $value); + } + + /** + * Test whether an array is a hash table. + * + * An array is a hash table if: + * + * 1. Contains one or more non-integer keys, or + * 2. Integer keys are non-continuous or misaligned (not starting with 0) + * + * For example: + * + * $hash = array( + * 'foo' => 15, + * 'bar' => false, + * ); + * $hash = array( + * 1995 => 'Birth of PHP', + * 2009 => 'PHP 5.3.0', + * 2012 => 'PHP 5.4.0', + * ); + * $hash = array( + * 'formElement, + * 'options' => array( 'debug' => true ), + * ); + * + * + * @param mixed $value + * @param bool $allowEmpty Is an empty array() a valid hash table? + * @return bool + */ + public static function isHashTable($value, $allowEmpty = false) + { + if (! is_array($value)) { + return false; + } + + if (! $value) { + return $allowEmpty; + } + + return (array_values($value) !== $value); + } + + /** + * Checks if a value exists in an array. + * + * Due to "foo" == 0 === TRUE with in_array when strict = false, an option + * has been added to prevent this. When $strict = 0/false, the most secure + * non-strict check is implemented. if $strict = -1, the default in_array + * non-strict behaviour is used. + * + * @param mixed $needle + * @param array $haystack + * @param int|bool $strict + * @return bool + */ + public static function inArray($needle, array $haystack, $strict = false) + { + if (! $strict) { + if (is_int($needle) || is_float($needle)) { + $needle = (string) $needle; + } + if (is_string($needle)) { + foreach ($haystack as &$h) { + if (is_int($h) || is_float($h)) { + $h = (string) $h; + } + } + } + } + return in_array($needle, $haystack, $strict); + } + + /** + * Convert an iterator to an array. + * + * Converts an iterator to an array. The $recursive flag, on by default, + * hints whether or not you want to do so recursively. + * + * @param array|Traversable $iterator The array or Traversable object to convert + * @param bool $recursive Recursively check all nested structures + * @throws Exception\InvalidArgumentException if $iterator is not an array or a Traversable object + * @return array + */ + public static function iteratorToArray($iterator, $recursive = true) + { + if (! is_array($iterator) && ! $iterator instanceof Traversable) { + throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable object'); + } + + if (! $recursive) { + if (is_array($iterator)) { + return $iterator; + } + + return iterator_to_array($iterator); + } + + if (method_exists($iterator, 'toArray')) { + return $iterator->toArray(); + } + + $array = []; + foreach ($iterator as $key => $value) { + if (is_scalar($value)) { + $array[$key] = $value; + continue; + } + + if ($value instanceof Traversable) { + $array[$key] = static::iteratorToArray($value, $recursive); + continue; + } + + if (is_array($value)) { + $array[$key] = static::iteratorToArray($value, $recursive); + continue; + } + + $array[$key] = $value; + } + + return $array; + } + + /** + * Merge two arrays together. + * + * If an integer key exists in both arrays and preserveNumericKeys is false, the value + * from the second array will be appended to the first array. If both values are arrays, they + * are merged together, else the value of the second array overwrites the one of the first array. + * + * @param array $a + * @param array $b + * @param bool $preserveNumericKeys + * @return array + */ + public static function merge(array $a, array $b, $preserveNumericKeys = false) + { + foreach ($b as $key => $value) { + if ($value instanceof MergeReplaceKeyInterface) { + $a[$key] = $value->getData(); + } elseif (isset($a[$key]) || array_key_exists($key, $a)) { + if ($value instanceof MergeRemoveKey) { + unset($a[$key]); + } elseif (! $preserveNumericKeys && is_int($key)) { + $a[] = $value; + } elseif (is_array($value) && is_array($a[$key])) { + $a[$key] = static::merge($a[$key], $value, $preserveNumericKeys); + } else { + $a[$key] = $value; + } + } else { + if (! $value instanceof MergeRemoveKey) { + $a[$key] = $value; + } + } + } + + return $a; + } + + /** + * @deprecated Since 3.2.0; use the native array_filter methods + * + * @param array $data + * @param callable $callback + * @param null|int $flag + * @return array + * @throws Exception\InvalidArgumentException + */ + public static function filter(array $data, $callback, $flag = null) + { + if (! is_callable($callback)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Second parameter of %s must be callable', + __METHOD__ + )); + } + + return array_filter($data, $callback, $flag); + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php new file mode 100644 index 000000000..8c9d56e69 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php @@ -0,0 +1,13 @@ +data = $data; + } + + /** + * {@inheritDoc} + */ + public function getData() + { + return $this->data; + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php new file mode 100644 index 000000000..54c244382 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php @@ -0,0 +1,20 @@ +message`, + * `message`) + * - Write output to a specified stream, optionally with colorization. + * - Write a line of output to a specified stream, optionally with + * colorization, using the system EOL sequence.. + * - Write an error message to STDERR. + * + * Colorization will only occur when expected sequences are discovered, and + * then, only if the console terminal allows it. + * + * Essentially, provides the bare minimum to allow you to provide messages to + * the current console. + */ +class ConsoleHelper +{ + const COLOR_GREEN = "\033[32m"; + const COLOR_RED = "\033[31m"; + const COLOR_RESET = "\033[0m"; + + const HIGHLIGHT_INFO = 'info'; + const HIGHLIGHT_ERROR = 'error'; + + private $highlightMap = [ + self::HIGHLIGHT_INFO => self::COLOR_GREEN, + self::HIGHLIGHT_ERROR => self::COLOR_RED, + ]; + + /** + * @var string Exists only for testing. + */ + private $eol = PHP_EOL; + + /** + * @var resource Exists only for testing. + */ + private $stderr = STDERR; + + /** + * @var bool + */ + private $supportsColor; + + /** + * @param resource $resource + */ + public function __construct($resource = STDOUT) + { + $this->supportsColor = $this->detectColorCapabilities($resource); + } + + /** + * Colorize a string for use with the terminal. + * + * Takes strings formatted as `string` and formats them per the + * $highlightMap; if color support is disabled, simply removes the formatting + * tags. + * + * @param string $string + * @return string + */ + public function colorize($string) + { + $reset = $this->supportsColor ? self::COLOR_RESET : ''; + foreach ($this->highlightMap as $key => $color) { + $pattern = sprintf('#<%s>(.*?)#s', $key, $key); + $color = $this->supportsColor ? $color : ''; + $string = preg_replace($pattern, $color . '$1' . $reset, $string); + } + return $string; + } + + /** + * @param string $string + * @param bool $colorize Whether or not to colorize the string + * @param resource $resource Defaults to STDOUT + * @return void + */ + public function write($string, $colorize = true, $resource = STDOUT) + { + if ($colorize) { + $string = $this->colorize($string); + } + + $string = $this->formatNewlines($string); + + fwrite($resource, $string); + } + + /** + * @param string $string + * @param bool $colorize Whether or not to colorize the line + * @param resource $resource Defaults to STDOUT + * @return void + */ + public function writeLine($string, $colorize = true, $resource = STDOUT) + { + $this->write($string . $this->eol, $colorize, $resource); + } + + /** + * Emit an error message. + * + * Wraps the message in ``, and passes it to `writeLine()`, + * using STDERR as the resource; emits an additional empty line when done, + * also to STDERR. + * + * @param string $message + * @return void + */ + public function writeErrorMessage($message) + { + $this->writeLine(sprintf('%s', $message), true, $this->stderr); + $this->writeLine('', false, $this->stderr); + } + + /** + * @param resource $resource + * @return bool + */ + private function detectColorCapabilities($resource = STDOUT) + { + if ('\\' === DIRECTORY_SEPARATOR) { + // Windows + return false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && posix_isatty($resource); + } + + /** + * Ensure newlines are appropriate for the current terminal. + * + * @param string + * @return string + */ + private function formatNewlines($string) + { + $string = str_replace($this->eol, "\0PHP_EOL\0", $string); + $string = preg_replace("/(\r\n|\n|\r)/", $this->eol, $string); + return str_replace("\0PHP_EOL\0", $this->eol, $string); + } +} diff --git a/lib/laminas/laminas-stdlib/src/DispatchableInterface.php b/lib/laminas/laminas-stdlib/src/DispatchableInterface.php new file mode 100644 index 000000000..62b813af2 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/DispatchableInterface.php @@ -0,0 +1,21 @@ +values[$priority][] = $value; + if (! isset($this->priorities[$priority])) { + $this->priorities[$priority] = $priority; + $this->maxPriority = $this->maxPriority === null ? $priority : max($priority, $this->maxPriority); + } + ++$this->count; + } + + /** + * Extract an element in the queue according to the priority and the + * order of insertion + * + * @return mixed + */ + public function extract() + { + if (! $this->valid()) { + return false; + } + $value = $this->current(); + $this->nextAndRemove(); + return $value; + } + + /** + * Remove an item from the queue + * + * This is different than {@link extract()}; its purpose is to dequeue an + * item. + * + * Note: this removes the first item matching the provided item found. If + * the same item has been added multiple times, it will not remove other + * instances. + * + * @param mixed $datum + * @return bool False if the item was not found, true otherwise. + */ + public function remove($datum) + { + $currentIndex = $this->index; + $currentSubIndex = $this->subIndex; + $currentPriority = $this->maxPriority; + + $this->rewind(); + while ($this->valid()) { + if (current($this->values[$this->maxPriority]) === $datum) { + $index = key($this->values[$this->maxPriority]); + unset($this->values[$this->maxPriority][$index]); + + // The `next()` method advances the internal array pointer, so we need to use the `reset()` function, + // otherwise we would lose all elements before the place the pointer points. + reset($this->values[$this->maxPriority]); + + $this->index = $currentIndex; + $this->subIndex = $currentSubIndex; + + // If the array is empty we need to destroy the unnecessary priority, + // otherwise we would end up with an incorrect value of `$this->count` + // {@see \Laminas\Stdlib\FastPriorityQueue::nextAndRemove()}. + if (empty($this->values[$this->maxPriority])) { + unset($this->values[$this->maxPriority]); + unset($this->priorities[$this->maxPriority]); + if ($this->maxPriority === $currentPriority) { + $this->subIndex = 0; + } + } + + $this->maxPriority = empty($this->priorities) ? null : max($this->priorities); + --$this->count; + return true; + } + $this->next(); + } + return false; + } + + /** + * Get the total number of elements in the queue + * + * @return integer + */ + public function count() + { + return $this->count; + } + + /** + * Get the current element in the queue + * + * @return mixed + */ + public function current() + { + switch ($this->extractFlag) { + case self::EXTR_DATA: + return current($this->values[$this->maxPriority]); + case self::EXTR_PRIORITY: + return $this->maxPriority; + case self::EXTR_BOTH: + return [ + 'data' => current($this->values[$this->maxPriority]), + 'priority' => $this->maxPriority + ]; + } + } + + /** + * Get the index of the current element in the queue + * + * @return integer + */ + public function key() + { + return $this->index; + } + + /** + * Set the iterator pointer to the next element in the queue + * removing the previous element + */ + protected function nextAndRemove() + { + $key = key($this->values[$this->maxPriority]); + + if (false === next($this->values[$this->maxPriority])) { + unset($this->priorities[$this->maxPriority]); + unset($this->values[$this->maxPriority]); + $this->maxPriority = empty($this->priorities) ? null : max($this->priorities); + $this->subIndex = -1; + } else { + unset($this->values[$this->maxPriority][$key]); + } + ++$this->index; + ++$this->subIndex; + --$this->count; + } + + /** + * Set the iterator pointer to the next element in the queue + * without removing the previous element + */ + public function next() + { + if (false === next($this->values[$this->maxPriority])) { + unset($this->subPriorities[$this->maxPriority]); + reset($this->values[$this->maxPriority]); + $this->maxPriority = empty($this->subPriorities) ? null : max($this->subPriorities); + $this->subIndex = -1; + } + ++$this->index; + ++$this->subIndex; + } + + /** + * Check if the current iterator is valid + * + * @return boolean + */ + public function valid() + { + return isset($this->values[$this->maxPriority]); + } + + /** + * Rewind the current iterator + */ + public function rewind() + { + $this->subPriorities = $this->priorities; + $this->maxPriority = empty($this->priorities) ? 0 : max($this->priorities); + $this->index = 0; + $this->subIndex = 0; + } + + /** + * Serialize to an array + * + * Array will be priority => data pairs + * + * @return array + */ + public function toArray() + { + $array = []; + foreach (clone $this as $item) { + $array[] = $item; + } + return $array; + } + + /** + * Serialize + * + * @return string + */ + public function serialize() + { + $clone = clone $this; + $clone->setExtractFlags(self::EXTR_BOTH); + + $data = []; + foreach ($clone as $item) { + $data[] = $item; + } + + return serialize($data); + } + + /** + * Deserialize + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + foreach (unserialize($data) as $item) { + $this->insert($item['data'], $item['priority']); + } + } + + /** + * Set the extract flag + * + * @param integer $flag + */ + public function setExtractFlags($flag) + { + switch ($flag) { + case self::EXTR_DATA: + case self::EXTR_PRIORITY: + case self::EXTR_BOTH: + $this->extractFlag = $flag; + break; + default: + throw new Exception\InvalidArgumentException("The extract flag specified is not valid"); + } + } + + /** + * Check if the queue is empty + * + * @return boolean + */ + public function isEmpty() + { + return empty($this->values); + } + + /** + * Does the queue contain the given datum? + * + * @param mixed $datum + * @return bool + */ + public function contains($datum) + { + foreach ($this->values as $values) { + if (in_array($datum, $values)) { + return true; + } + } + return false; + } + + /** + * Does the queue have an item with the given priority? + * + * @param int $priority + * @return bool + */ + public function hasPriority($priority) + { + return isset($this->values[$priority]); + } +} diff --git a/lib/laminas/laminas-stdlib/src/Glob.php b/lib/laminas/laminas-stdlib/src/Glob.php new file mode 100644 index 000000000..f25d0b9d5 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/Glob.php @@ -0,0 +1,201 @@ + GLOB_MARK, + self::GLOB_NOSORT => GLOB_NOSORT, + self::GLOB_NOCHECK => GLOB_NOCHECK, + self::GLOB_NOESCAPE => GLOB_NOESCAPE, + self::GLOB_BRACE => defined('GLOB_BRACE') ? GLOB_BRACE : 0, + self::GLOB_ONLYDIR => GLOB_ONLYDIR, + self::GLOB_ERR => GLOB_ERR, + ]; + + $globFlags = 0; + + foreach ($flagMap as $internalFlag => $globFlag) { + if ($flags & $internalFlag) { + $globFlags |= $globFlag; + } + } + } else { + $globFlags = 0; + } + + ErrorHandler::start(); + $res = glob($pattern, $globFlags); + $err = ErrorHandler::stop(); + if ($res === false) { + throw new Exception\RuntimeException("glob('{$pattern}', {$globFlags}) failed", 0, $err); + } + return $res; + } + + /** + * Expand braces manually, then use the system glob. + * + * @param string $pattern + * @param int $flags + * @return array + * @throws Exception\RuntimeException + */ + protected static function fallbackGlob($pattern, $flags) + { + if (! $flags & self::GLOB_BRACE) { + return static::systemGlob($pattern, $flags); + } + + $flags &= ~self::GLOB_BRACE; + $length = strlen($pattern); + $paths = []; + + if ($flags & self::GLOB_NOESCAPE) { + $begin = strpos($pattern, '{'); + } else { + $begin = 0; + + while (true) { + if ($begin === $length) { + $begin = false; + break; + } elseif ($pattern[$begin] === '\\' && ($begin + 1) < $length) { + $begin++; + } elseif ($pattern[$begin] === '{') { + break; + } + + $begin++; + } + } + + if ($begin === false) { + return static::systemGlob($pattern, $flags); + } + + $next = static::nextBraceSub($pattern, $begin + 1, $flags); + + if ($next === null) { + return static::systemGlob($pattern, $flags); + } + + $rest = $next; + + while ($pattern[$rest] !== '}') { + $rest = static::nextBraceSub($pattern, $rest + 1, $flags); + + if ($rest === null) { + return static::systemGlob($pattern, $flags); + } + } + + $p = $begin + 1; + + while (true) { + $subPattern = substr($pattern, 0, $begin) + . substr($pattern, $p, $next - $p) + . substr($pattern, $rest + 1); + + $result = static::fallbackGlob($subPattern, $flags | self::GLOB_BRACE); + + if ($result) { + $paths = array_merge($paths, $result); + } + + if ($pattern[$next] === '}') { + break; + } + + $p = $next + 1; + $next = static::nextBraceSub($pattern, $p, $flags); + } + + return array_unique($paths); + } + + /** + * Find the end of the sub-pattern in a brace expression. + * + * @param string $pattern + * @param int $begin + * @param int $flags + * @return int|null + */ + protected static function nextBraceSub($pattern, $begin, $flags) + { + $length = strlen($pattern); + $depth = 0; + $current = $begin; + + while ($current < $length) { + if (! $flags & self::GLOB_NOESCAPE && $pattern[$current] === '\\') { + if (++$current === $length) { + break; + } + + $current++; + } else { + if (($pattern[$current] === '}' && $depth-- === 0) || ($pattern[$current] === ',' && $depth === 0)) { + break; + } elseif ($pattern[$current++] === '{') { + $depth++; + } + } + } + + return ($current < $length ? $current : null); + } +} diff --git a/lib/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php b/lib/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php new file mode 100644 index 000000000..e701c176b --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php @@ -0,0 +1,19 @@ +metadata[$spec] = $value; + return $this; + } + if (! is_array($spec) && ! $spec instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + 'Expected a string, array, or Traversable argument in first position; received "%s"', + (is_object($spec) ? get_class($spec) : gettype($spec)) + )); + } + foreach ($spec as $key => $value) { + $this->metadata[$key] = $value; + } + return $this; + } + + /** + * Retrieve all metadata or a single metadatum as specified by key + * + * @param null|string|int $key + * @param null|mixed $default + * @throws Exception\InvalidArgumentException + * @return mixed + */ + public function getMetadata($key = null, $default = null) + { + if (null === $key) { + return $this->metadata; + } + + if (! is_scalar($key)) { + throw new Exception\InvalidArgumentException('Non-scalar argument provided for key'); + } + + if (array_key_exists($key, $this->metadata)) { + return $this->metadata[$key]; + } + + return $default; + } + + /** + * Set message content + * + * @param mixed $value + * @return Message + */ + public function setContent($value) + { + $this->content = $value; + return $this; + } + + /** + * Get message content + * + * @return mixed + */ + public function getContent() + { + return $this->content; + } + + /** + * @return string + */ + public function toString() + { + $request = ''; + foreach ($this->getMetadata() as $key => $value) { + $request .= sprintf( + "%s: %s\r\n", + (string) $key, + (string) $value + ); + } + $request .= "\r\n" . $this->getContent(); + return $request; + } +} diff --git a/lib/laminas/laminas-stdlib/src/MessageInterface.php b/lib/laminas/laminas-stdlib/src/MessageInterface.php new file mode 100644 index 000000000..76f089a02 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/MessageInterface.php @@ -0,0 +1,43 @@ +exchangeArray($values); + } + + /** + * Populate from query string + * + * @param string $string + * @return void + */ + public function fromString($string) + { + $array = []; + parse_str($string, $array); + $this->fromArray($array); + } + + /** + * Serialize to native PHP array + * + * @return array + */ + public function toArray() + { + return $this->getArrayCopy(); + } + + /** + * Serialize to query string + * + * @return string + */ + public function toString() + { + return http_build_query($this->toArray()); + } + + /** + * Retrieve by key + * + * Returns null if the key does not exist. + * + * @param string $name + * @return mixed + */ + public function offsetGet($name) + { + if ($this->offsetExists($name)) { + return parent::offsetGet($name); + } + return; + } + + /** + * @param string $name + * @param mixed $default optional default value + * @return mixed + */ + public function get($name, $default = null) + { + if ($this->offsetExists($name)) { + return parent::offsetGet($name); + } + return $default; + } + + /** + * @param string $name + * @param mixed $value + * @return Parameters + */ + public function set($name, $value) + { + $this[$name] = $value; + return $this; + } +} diff --git a/lib/laminas/laminas-stdlib/src/ParametersInterface.php b/lib/laminas/laminas-stdlib/src/ParametersInterface.php new file mode 100644 index 000000000..0c62df3a5 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ParametersInterface.php @@ -0,0 +1,85 @@ +items[$name])) { + $this->count++; + } + + $this->sorted = false; + + $this->items[$name] = [ + 'data' => $value, + 'priority' => (int) $priority, + 'serial' => $this->serial++, + ]; + } + + /** + * @param string $name + * @param int $priority + * + * @return $this + * + * @throws \Exception + */ + public function setPriority($name, $priority) + { + if (! isset($this->items[$name])) { + throw new \Exception("item $name not found"); + } + + $this->items[$name]['priority'] = (int) $priority; + $this->sorted = false; + + return $this; + } + + /** + * Remove a item. + * + * @param string $name + * @return void + */ + public function remove($name) + { + if (isset($this->items[$name])) { + $this->count--; + } + + unset($this->items[$name]); + } + + /** + * Remove all items. + * + * @return void + */ + public function clear() + { + $this->items = []; + $this->serial = 0; + $this->count = 0; + $this->sorted = false; + } + + /** + * Get a item. + * + * @param string $name + * @return mixed + */ + public function get($name) + { + if (! isset($this->items[$name])) { + return; + } + + return $this->items[$name]['data']; + } + + /** + * Sort all items. + * + * @return void + */ + protected function sort() + { + if (! $this->sorted) { + uasort($this->items, [$this, 'compare']); + $this->sorted = true; + } + } + + /** + * Compare the priority of two items. + * + * @param array $item1, + * @param array $item2 + * @return int + */ + protected function compare(array $item1, array $item2) + { + return ($item1['priority'] === $item2['priority']) + ? ($item1['serial'] > $item2['serial'] ? -1 : 1) * $this->isLIFO + : ($item1['priority'] > $item2['priority'] ? -1 : 1); + } + + /** + * Get/Set serial order mode + * + * @param bool|null $flag + * + * @return bool + */ + public function isLIFO($flag = null) + { + if ($flag !== null) { + $isLifo = $flag === true ? 1 : -1; + + if ($isLifo !== $this->isLIFO) { + $this->isLIFO = $isLifo; + $this->sorted = false; + } + } + + return 1 === $this->isLIFO; + } + + /** + * {@inheritDoc} + */ + public function rewind() + { + $this->sort(); + reset($this->items); + } + + /** + * {@inheritDoc} + */ + public function current() + { + $this->sorted || $this->sort(); + $node = current($this->items); + + return $node ? $node['data'] : false; + } + + /** + * {@inheritDoc} + */ + public function key() + { + $this->sorted || $this->sort(); + return key($this->items); + } + + /** + * {@inheritDoc} + */ + public function next() + { + $node = next($this->items); + + return $node ? $node['data'] : false; + } + + /** + * {@inheritDoc} + */ + public function valid() + { + return current($this->items) !== false; + } + + /** + * @return self + */ + public function getIterator() + { + return clone $this; + } + + /** + * {@inheritDoc} + */ + public function count() + { + return $this->count; + } + + /** + * Return list as array + * + * @param int $flag + * + * @return array + */ + public function toArray($flag = self::EXTR_DATA) + { + $this->sort(); + + if ($flag == self::EXTR_BOTH) { + return $this->items; + } + + return array_map( + function ($item) use ($flag) { + return ($flag == PriorityList::EXTR_PRIORITY) ? $item['priority'] : $item['data']; + }, + $this->items + ); + } +} diff --git a/lib/laminas/laminas-stdlib/src/PriorityQueue.php b/lib/laminas/laminas-stdlib/src/PriorityQueue.php new file mode 100644 index 000000000..ab4b1b65a --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/PriorityQueue.php @@ -0,0 +1,300 @@ +items[] = [ + 'data' => $data, + 'priority' => $priority, + ]; + $this->getQueue()->insert($data, $priority); + return $this; + } + + /** + * Remove an item from the queue + * + * This is different than {@link extract()}; its purpose is to dequeue an + * item. + * + * This operation is potentially expensive, as it requires + * re-initialization and re-population of the inner queue. + * + * Note: this removes the first item matching the provided item found. If + * the same item has been added multiple times, it will not remove other + * instances. + * + * @param mixed $datum + * @return bool False if the item was not found, true otherwise. + */ + public function remove($datum) + { + $found = false; + foreach ($this->items as $key => $item) { + if ($item['data'] === $datum) { + $found = true; + break; + } + } + if ($found) { + unset($this->items[$key]); + $this->queue = null; + + if (! $this->isEmpty()) { + $queue = $this->getQueue(); + foreach ($this->items as $item) { + $queue->insert($item['data'], $item['priority']); + } + } + return true; + } + return false; + } + + /** + * Is the queue empty? + * + * @return bool + */ + public function isEmpty() + { + return (0 === $this->count()); + } + + /** + * How many items are in the queue? + * + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * Peek at the top node in the queue, based on priority. + * + * @return mixed + */ + public function top() + { + return $this->getIterator()->top(); + } + + /** + * Extract a node from the inner queue and sift up + * + * @return mixed + */ + public function extract() + { + return $this->getQueue()->extract(); + } + + /** + * Retrieve the inner iterator + * + * SplPriorityQueue acts as a heap, which typically implies that as items + * are iterated, they are also removed. This does not work for situations + * where the queue may be iterated multiple times. As such, this class + * aggregates the values, and also injects an SplPriorityQueue. This method + * retrieves the inner queue object, and clones it for purposes of + * iteration. + * + * @return SplPriorityQueue + */ + public function getIterator() + { + $queue = $this->getQueue(); + return clone $queue; + } + + /** + * Serialize the data structure + * + * @return string + */ + public function serialize() + { + return serialize($this->items); + } + + /** + * Unserialize a string into a PriorityQueue object + * + * Serialization format is compatible with {@link Laminas\Stdlib\SplPriorityQueue} + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + foreach (unserialize($data) as $item) { + $this->insert($item['data'], $item['priority']); + } + } + + /** + * Serialize to an array + * + * By default, returns only the item data, and in the order registered (not + * sorted). You may provide one of the EXTR_* flags as an argument, allowing + * the ability to return priorities or both data and priority. + * + * @param int $flag + * @return array + */ + public function toArray($flag = self::EXTR_DATA) + { + switch ($flag) { + case self::EXTR_BOTH: + return $this->items; + case self::EXTR_PRIORITY: + return array_map(function ($item) { + return $item['priority']; + }, $this->items); + case self::EXTR_DATA: + default: + return array_map(function ($item) { + return $item['data']; + }, $this->items); + } + } + + /** + * Specify the internal queue class + * + * Please see {@link getIterator()} for details on the necessity of an + * internal queue class. The class provided should extend SplPriorityQueue. + * + * @param string $class + * @return PriorityQueue + */ + public function setInternalQueueClass($class) + { + $this->queueClass = (string) $class; + return $this; + } + + /** + * Does the queue contain the given datum? + * + * @param mixed $datum + * @return bool + */ + public function contains($datum) + { + foreach ($this->items as $item) { + if ($item['data'] === $datum) { + return true; + } + } + return false; + } + + /** + * Does the queue have an item with the given priority? + * + * @param int $priority + * @return bool + */ + public function hasPriority($priority) + { + foreach ($this->items as $item) { + if ($item['priority'] === $priority) { + return true; + } + } + return false; + } + + /** + * Get the inner priority queue instance + * + * @throws Exception\DomainException + * @return SplPriorityQueue + */ + protected function getQueue() + { + if (null === $this->queue) { + $this->queue = new $this->queueClass(); + if (! $this->queue instanceof \SplPriorityQueue) { + throw new Exception\DomainException(sprintf( + 'PriorityQueue expects an internal queue of type SplPriorityQueue; received "%s"', + get_class($this->queue) + )); + } + } + return $this->queue; + } + + /** + * Add support for deep cloning + * + * @return void + */ + public function __clone() + { + if (null !== $this->queue) { + $this->queue = clone $this->queue; + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/Request.php b/lib/laminas/laminas-stdlib/src/Request.php new file mode 100644 index 000000000..a593a480f --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/Request.php @@ -0,0 +1,14 @@ +serial--]; + } + parent::insert($datum, $priority); + } + + /** + * Serialize to an array + * + * Array will be priority => data pairs + * + * @return array + */ + public function toArray() + { + $array = []; + foreach (clone $this as $item) { + $array[] = $item; + } + return $array; + } + + /** + * Serialize + * + * @return string + */ + public function serialize() + { + $clone = clone $this; + $clone->setExtractFlags(self::EXTR_BOTH); + + $data = []; + foreach ($clone as $item) { + $data[] = $item; + } + + return serialize($data); + } + + /** + * Deserialize + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + $this->serial = PHP_INT_MAX; + foreach (unserialize($data) as $item) { + $this->serial--; + $this->insert($item['data'], $item['priority']); + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/SplQueue.php b/lib/laminas/laminas-stdlib/src/SplQueue.php new file mode 100644 index 000000000..9eb2abb7f --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/SplQueue.php @@ -0,0 +1,54 @@ +toArray()); + } + + /** + * Unserialize + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + foreach (unserialize($data) as $item) { + $this->push($item); + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/SplStack.php b/lib/laminas/laminas-stdlib/src/SplStack.php new file mode 100644 index 000000000..404203dd4 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/SplStack.php @@ -0,0 +1,54 @@ +toArray()); + } + + /** + * Unserialize + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + foreach (unserialize($data) as $item) { + $this->unshift($item); + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringUtils.php b/lib/laminas/laminas-stdlib/src/StringUtils.php new file mode 100644 index 000000000..a52218c31 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringUtils.php @@ -0,0 +1,186 @@ +setEncoding($encoding, $convertEncoding); + return $wrapper; + } + } + + throw new Exception\RuntimeException( + 'No wrapper found supporting "' . $encoding . '"' + . (($convertEncoding !== null) ? ' and "' . $convertEncoding . '"' : '') + ); + } + + /** + * Get a list of all known single-byte character encodings + * + * @return string[] + */ + public static function getSingleByteEncodings() + { + return static::$singleByteEncodings; + } + + /** + * Check if a given encoding is a known single-byte character encoding + * + * @param string $encoding + * @return bool + */ + public static function isSingleByteEncoding($encoding) + { + return in_array(strtoupper($encoding), static::$singleByteEncodings); + } + + /** + * Check if a given string is valid UTF-8 encoded + * + * @param string $str + * @return bool + */ + public static function isValidUtf8($str) + { + return is_string($str) && ($str === '' || preg_match('/^./su', $str) == 1); + } + + /** + * Is PCRE compiled with Unicode support? + * + * @return bool + */ + public static function hasPcreUnicodeSupport() + { + if (static::$hasPcreUnicodeSupport === null) { + ErrorHandler::start(); + static::$hasPcreUnicodeSupport = defined('PREG_BAD_UTF8_OFFSET_ERROR') && preg_match('/\pL/u', 'a') == 1; + ErrorHandler::stop(); + } + return static::$hasPcreUnicodeSupport; + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php b/lib/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php new file mode 100644 index 000000000..a3125f065 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php @@ -0,0 +1,268 @@ +convertEncoding = $convertEncodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Get the defined character encoding to work with + * + * @return string + * @throws Exception\LogicException If no encoding was defined + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Get the defined character encoding to convert to + * + * @return string|null + */ + public function getConvertEncoding() + { + return $this->convertEncoding; + } + + /** + * Convert a string from defined character encoding to the defined convert encoding + * + * @param string $str + * @param bool $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $from = $reverse ? $convertEncoding : $encoding; + $to = $reverse ? $encoding : $convertEncoding; + throw new Exception\RuntimeException(sprintf( + 'Converting from "%s" to "%s" isn\'t supported by this string wrapper', + $from, + $to + )); + } + + /** + * Wraps a string to a given number of characters + * + * @param string $string + * @param int $width + * @param string $break + * @param bool $cut + * @return string|false + */ + public function wordWrap($string, $width = 75, $break = "\n", $cut = false) + { + $string = (string) $string; + if ($string === '') { + return ''; + } + + $break = (string) $break; + if ($break === '') { + throw new Exception\InvalidArgumentException('Break string cannot be empty'); + } + + $width = (int) $width; + if ($width === 0 && $cut) { + throw new Exception\InvalidArgumentException('Cannot force cut when width is zero'); + } + + if (StringUtils::isSingleByteEncoding($this->getEncoding())) { + return wordwrap($string, $width, $break, $cut); + } + + $stringWidth = $this->strlen($string); + $breakWidth = $this->strlen($break); + + $result = ''; + $lastStart = $lastSpace = 0; + + for ($current = 0; $current < $stringWidth; $current++) { + $char = $this->substr($string, $current, 1); + + $possibleBreak = $char; + if ($breakWidth !== 1) { + $possibleBreak = $this->substr($string, $current, $breakWidth); + } + + if ($possibleBreak === $break) { + $result .= $this->substr($string, $lastStart, $current - $lastStart + $breakWidth); + $current += $breakWidth - 1; + $lastStart = $lastSpace = $current + 1; + continue; + } + + if ($char === ' ') { + if ($current - $lastStart >= $width) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $current + 1; + } + + $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $lastStart < $lastSpace) { + $result .= $this->substr($string, $lastStart, $lastSpace - $lastStart) . $break; + $lastStart = $lastSpace = $lastSpace + 1; + continue; + } + } + + if ($lastStart !== $current) { + $result .= $this->substr($string, $lastStart, $current - $lastStart); + } + + return $result; + } + + /** + * Pad a string to a certain length with another string + * + * @param string $input + * @param int $padLength + * @param string $padString + * @param int $padType + * @return string + */ + public function strPad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT) + { + if (StringUtils::isSingleByteEncoding($this->getEncoding())) { + return str_pad($input, $padLength, $padString, $padType); + } + + $lengthOfPadding = $padLength - $this->strlen($input); + if ($lengthOfPadding <= 0) { + return $input; + } + + $padStringLength = $this->strlen($padString); + if ($padStringLength === 0) { + return $input; + } + + $repeatCount = floor($lengthOfPadding / $padStringLength); + + if ($padType === STR_PAD_BOTH) { + $repeatCountLeft = $repeatCountRight = ($repeatCount - $repeatCount % 2) / 2; + + $lastStringLength = $lengthOfPadding - 2 * $repeatCountLeft * $padStringLength; + $lastStringLeftLength = $lastStringRightLength = floor($lastStringLength / 2); + $lastStringRightLength += $lastStringLength % 2; + + $lastStringLeft = $this->substr($padString, 0, $lastStringLeftLength); + $lastStringRight = $this->substr($padString, 0, $lastStringRightLength); + + return str_repeat($padString, $repeatCountLeft) . $lastStringLeft + . $input + . str_repeat($padString, $repeatCountRight) . $lastStringRight; + } + + $lastString = $this->substr($padString, 0, $lengthOfPadding % $padStringLength); + + if ($padType === STR_PAD_LEFT) { + return str_repeat($padString, $repeatCount) . $lastString . $input; + } + + return $input . str_repeat($padString, $repeatCount) . $lastString; + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/Iconv.php b/lib/laminas/laminas-stdlib/src/StringWrapper/Iconv.php new file mode 100644 index 000000000..126b04854 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/Iconv.php @@ -0,0 +1,288 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return iconv_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return iconv_strpos($haystack, $needle, $offset, $this->getEncoding()); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param bool $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + + // automatically add "//IGNORE" to not stop converting on invalid characters + // invalid characters triggers a notice anyway + return iconv($fromEncoding, $toEncoding . '//IGNORE', $str); + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/Intl.php b/lib/laminas/laminas-stdlib/src/StringWrapper/Intl.php new file mode 100644 index 000000000..217bd4fc3 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/Intl.php @@ -0,0 +1,87 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return mb_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return mb_strpos($haystack, $needle, $offset, $this->getEncoding()); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param bool $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + return mb_convert_encoding($str, $toEncoding, $fromEncoding); + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/Native.php b/lib/laminas/laminas-stdlib/src/StringWrapper/Native.php new file mode 100644 index 000000000..0c3734666 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/Native.php @@ -0,0 +1,133 @@ +convertEncoding = $encodingUpper; + } + + if ($convertEncoding !== null) { + if ($encodingUpper !== strtoupper($convertEncoding)) { + throw new Exception\InvalidArgumentException( + 'Wrapper doesn\'t support to convert between character encodings' + ); + } + + $this->convertEncoding = $encodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Returns the length of the given string + * + * @param string $str + * @return int|false + */ + public function strlen($str) + { + return strlen($str); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return substr($str, $offset, $length); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return strpos($haystack, $needle, $offset); + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php b/lib/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php new file mode 100644 index 000000000..5c10365be --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php @@ -0,0 +1,110 @@ +getBody(), "\n")); +$validatorVersion = getVersionFromString('IanaVersion', file_get_contents(Laminas_HOSTNAME_VALIDATOR_FILE)); + +if ($checkOnly && $ianaVersion > $validatorVersion) { + printf( + 'TLDs must be updated, please run `php bin/update_hostname_validator.php` and push your changes%s', + PHP_EOL + ); + exit(1); +} + +if ($checkOnly) { + printf('TLDs are up-to-date%s', PHP_EOL); + exit(0); +} + +foreach (file(Laminas_HOSTNAME_VALIDATOR_FILE) as $line) { + // Replace old version number with new one + if (preg_match('/\*\s+IanaVersion\s+\d+/', $line, $matches)) { + $newFileContent[] = sprintf(" * IanaVersion %s\n", $ianaVersion); + continue; + } + + if ($insertDone === $insertFinish) { + // Outside of $validTlds definition; keep line as-is + $newFileContent[] = $line; + } + + if ($insertFinish) { + continue; + } + + if ($insertDone) { + // Detect where the $validTlds declaration ends + if (preg_match('/^\s+\];\s*$/', $line)) { + $newFileContent[] = $line; + $insertFinish = true; + } + + continue; + } + + // Detect where the $validTlds declaration begins + if (preg_match('/^\s+protected\s+\$validTlds\s+=\s+\[\s*$/', $line)) { + $newFileContent = array_merge($newFileContent, getNewValidTlds($response->getBody())); + $insertDone = true; + } +} + +if (! $insertDone) { + printf('Error: cannot find line with "protected $validTlds"%s', PHP_EOL); + exit(1); +} + +if (!$insertFinish) { + printf('Error: cannot find end of $validTlds declaration%s', PHP_EOL); + exit(1); +} + +if (false === @file_put_contents(Laminas_HOSTNAME_VALIDATOR_FILE, $newFileContent)) { + printf('Error: cannot write info file "%s"%s', Laminas_HOSTNAME_VALIDATOR_FILE, PHP_EOL); + exit(1); +} + +printf('Validator TLD file updated.%s', PHP_EOL); +exit(0); + +/** + * Get Official TLDs + * + * @return \Laminas\Http\Response + * @throws Exception + */ +function getOfficialTLDs() +{ + $client = new Client(); + $client->setOptions([ + 'adapter' => 'Laminas\Http\Client\Adapter\Curl', + ]); + $client->setUri(IANA_URL); + $client->setMethod('GET'); + + $response = $client->send(); + if (! $response->isSuccess()) { + throw new \Exception(sprintf("Error: cannot get '%s'%s", IANA_URL, PHP_EOL)); + } + return $response; +} + +/** + * Extract the first match of a string like + * "Version 2015072300" from the given string + * + * @param string $prefix + * @param string $string + * @return string + * @throws Exception + */ +function getVersionFromString($prefix, $string) +{ + $matches = []; + if (! preg_match(sprintf('/%s\s+((\d+)?)/', $prefix), $string, $matches)) { + throw new Exception('Error: cannot get last update date'); + } + + return $matches[1]; +} + +/** + * Extract new Valid TLDs from a string containing one per line. + * + * @param string $string + * @return array + */ +function getNewValidTlds($string) +{ + $decodePunycode = getPunycodeDecoder(); + + // Get new TLDs from the list previously fetched + $newValidTlds = []; + foreach (preg_grep('/^[^#]/', preg_split("#\r?\n#", $string)) as $line) { + $newValidTlds []= sprintf( + "%s'%s',\n", + str_repeat(' ', 8), + $decodePunycode(strtolower($line)) + ); + } + + return $newValidTlds; +} + +/** + * Retrieve and return a punycode decoder. + * + * TLDs are puny encoded. + * + * We need a decodePunycode function to translate TLDs to UTF-8: + * + * - use idn_to_utf8 if available + * - otherwise, use Hostname::decodePunycode() + * + * @return callable + */ +function getPunycodeDecoder() +{ + if (function_exists('idn_to_utf8')) { + return function ($domain) { + return idn_to_utf8($domain, 0, INTL_IDNA_VARIANT_UTS46); + }; + } + + $hostnameValidator = new Hostname(); + $reflection = new ReflectionClass(get_class($hostnameValidator)); + $decodePunyCode = $reflection->getMethod('decodePunycode'); + $decodePunyCode->setAccessible(true); + + return function ($encode) use ($hostnameValidator, $decodePunyCode) { + if (strpos($encode, 'xn--') === 0) { + return $decodePunyCode->invokeArgs($hostnameValidator, [substr($encode, 4)]); + } + return $encode; + }; +} diff --git a/lib/laminas/laminas-validator/composer.json b/lib/laminas/laminas-validator/composer.json new file mode 100644 index 000000000..ccaca5493 --- /dev/null +++ b/lib/laminas/laminas-validator/composer.json @@ -0,0 +1,86 @@ +{ + "name": "laminas/laminas-validator", + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "license": "BSD-3-Clause", + "keywords": [ + "laminas", + "validator" + ], + "homepage": "https://laminas.dev", + "support": { + "docs": "https://docs.laminas.dev/laminas-validator/", + "issues": "https://github.com/laminas/laminas-validator/issues", + "source": "https://github.com/laminas/laminas-validator", + "rss": "https://github.com/laminas/laminas-validator/releases.atom", + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev" + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" + }, + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, + "require": { + "php": "^5.6 || ^7.0", + "container-interop/container-interop": "^1.1", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0" + }, + "require-dev": { + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.5", + "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "psr/http-message": "^1.0" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" + }, + "autoload": { + "psr-4": { + "Laminas\\Validator\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LaminasTest\\Validator\\": "test/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" + }, + "replace": { + "zendframework/zend-validator": "self.version" + } +} diff --git a/lib/laminas/laminas-validator/src/AbstractValidator.php b/lib/laminas/laminas-validator/src/AbstractValidator.php new file mode 100644 index 000000000..7d8c51243 --- /dev/null +++ b/lib/laminas/laminas-validator/src/AbstractValidator.php @@ -0,0 +1,573 @@ + [], // Array of validation failure messages + 'messageTemplates' => [], // Array of validation failure message templates + 'messageVariables' => [], // Array of additional variables available for validation failure messages + 'translator' => null, // Translation object to used -> Translator\TranslatorInterface + 'translatorTextDomain' => null, // Translation text domain + 'translatorEnabled' => true, // Is translation enabled? + 'valueObscured' => false, // Flag indicating whether or not value should be obfuscated + // in error messages + ]; + + /** + * Abstract constructor for all validators + * A validator should accept following parameters: + * - nothing f.e. Validator() + * - one or multiple scalar values f.e. Validator($first, $second, $third) + * - an array f.e. Validator(array($first => 'first', $second => 'second', $third => 'third')) + * - an instance of Traversable f.e. Validator($config_instance) + * + * @param array|Traversable $options + */ + public function __construct($options = null) + { + // The abstract constructor allows no scalar values + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if (isset($this->messageTemplates)) { + $this->abstractOptions['messageTemplates'] = $this->messageTemplates; + } + + if (isset($this->messageVariables)) { + $this->abstractOptions['messageVariables'] = $this->messageVariables; + } + + if (is_array($options)) { + $this->setOptions($options); + } + } + + /** + * Returns an option + * + * @param string $option Option to be returned + * @return mixed Returned option + * @throws Exception\InvalidArgumentException + */ + public function getOption($option) + { + if (array_key_exists($option, $this->abstractOptions)) { + return $this->abstractOptions[$option]; + } + + if (isset($this->options) && array_key_exists($option, $this->options)) { + return $this->options[$option]; + } + + throw new Exception\InvalidArgumentException("Invalid option '$option'"); + } + + /** + * Returns all available options + * + * @return array Array with all available options + */ + public function getOptions() + { + $result = $this->abstractOptions; + if (isset($this->options)) { + $result += $this->options; + } + return $result; + } + + /** + * Sets one or multiple options + * + * @param array|Traversable $options Options to set + * @throws Exception\InvalidArgumentException If $options is not an array or Traversable + * @return AbstractValidator Provides fluid interface + */ + public function setOptions($options = []) + { + if (! is_array($options) && ! $options instanceof Traversable) { + throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable'); + } + + foreach ($options as $name => $option) { + $fname = 'set' . ucfirst($name); + $fname2 = 'is' . ucfirst($name); + if (($name !== 'setOptions') && method_exists($this, $name)) { + $this->{$name}($option); + } elseif (($fname !== 'setOptions') && method_exists($this, $fname)) { + $this->{$fname}($option); + } elseif (method_exists($this, $fname2)) { + $this->{$fname2}($option); + } elseif (isset($this->options)) { + $this->options[$name] = $option; + } else { + $this->abstractOptions[$name] = $option; + } + } + + return $this; + } + + /** + * Returns array of validation failure messages + * + * @return array + */ + public function getMessages() + { + return array_unique($this->abstractOptions['messages'], SORT_REGULAR); + } + + /** + * Invoke as command + * + * @param mixed $value + * @return bool + */ + public function __invoke($value) + { + return $this->isValid($value); + } + + /** + * Returns an array of the names of variables that are used in constructing validation failure messages + * + * @return array + */ + public function getMessageVariables() + { + return array_keys($this->abstractOptions['messageVariables']); + } + + /** + * Returns the message templates from the validator + * + * @return array + */ + public function getMessageTemplates() + { + return $this->abstractOptions['messageTemplates']; + } + + /** + * Sets the validation failure message template for a particular key + * + * @param string $messageString + * @param string $messageKey OPTIONAL + * @return AbstractValidator Provides a fluent interface + * @throws Exception\InvalidArgumentException + */ + public function setMessage($messageString, $messageKey = null) + { + if ($messageKey === null) { + $keys = array_keys($this->abstractOptions['messageTemplates']); + foreach ($keys as $key) { + $this->setMessage($messageString, $key); + } + return $this; + } + + if (! isset($this->abstractOptions['messageTemplates'][$messageKey])) { + throw new Exception\InvalidArgumentException("No message template exists for key '$messageKey'"); + } + + $this->abstractOptions['messageTemplates'][$messageKey] = $messageString; + return $this; + } + + /** + * Sets validation failure message templates given as an array, where the array keys are the message keys, + * and the array values are the message template strings. + * + * @param array $messages + * @return AbstractValidator + */ + public function setMessages(array $messages) + { + foreach ($messages as $key => $message) { + $this->setMessage($message, $key); + } + return $this; + } + + /** + * Magic function returns the value of the requested property, if and only if it is the value or a + * message variable. + * + * @param string $property + * @return mixed + * @throws Exception\InvalidArgumentException + */ + public function __get($property) + { + if ($property == 'value') { + return $this->value; + } + + if (array_key_exists($property, $this->abstractOptions['messageVariables'])) { + $result = $this->abstractOptions['messageVariables'][$property]; + if (is_array($result)) { + return $this->{key($result)}[current($result)]; + } + return $this->{$result}; + } + + if (isset($this->messageVariables) && array_key_exists($property, $this->messageVariables)) { + $result = $this->{$this->messageVariables[$property]}; + if (is_array($result)) { + return $this->{key($result)}[current($result)]; + } + return $this->{$result}; + } + + throw new Exception\InvalidArgumentException("No property exists by the name '$property'"); + } + + /** + * Constructs and returns a validation failure message with the given message key and value. + * + * Returns null if and only if $messageKey does not correspond to an existing template. + * + * If a translator is available and a translation exists for $messageKey, + * the translation will be used. + * + * @param string $messageKey + * @param string|array|object $value + * @return string + */ + protected function createMessage($messageKey, $value) + { + if (! isset($this->abstractOptions['messageTemplates'][$messageKey])) { + return; + } + + $message = $this->abstractOptions['messageTemplates'][$messageKey]; + + $message = $this->translateMessage($messageKey, $message); + + if (is_object($value) && + ! in_array('__toString', get_class_methods($value)) + ) { + $value = get_class($value) . ' object'; + } elseif (is_array($value)) { + $value = var_export($value, 1); + } else { + $value = (string) $value; + } + + if ($this->isValueObscured()) { + $value = str_repeat('*', strlen($value)); + } + + $message = str_replace('%value%', (string) $value, $message); + foreach ($this->abstractOptions['messageVariables'] as $ident => $property) { + if (is_array($property)) { + $value = $this->{key($property)}[current($property)]; + if (is_array($value)) { + $value = '[' . implode(', ', $value) . ']'; + } + } else { + $value = $this->$property; + } + $message = str_replace("%$ident%", (string) $value, $message); + } + + $length = self::getMessageLength(); + if (($length > -1) && (strlen($message) > $length)) { + $message = substr($message, 0, ($length - 3)) . '...'; + } + + return $message; + } + + /** + * @param string $messageKey + * @param string $value OPTIONAL + * @return void + */ + protected function error($messageKey, $value = null) + { + if ($messageKey === null) { + $keys = array_keys($this->abstractOptions['messageTemplates']); + $messageKey = current($keys); + } + + if ($value === null) { + $value = $this->value; + } + + $this->abstractOptions['messages'][$messageKey] = $this->createMessage($messageKey, $value); + } + + /** + * Returns the validation value + * + * @return mixed Value to be validated + */ + protected function getValue() + { + return $this->value; + } + + /** + * Sets the value to be validated and clears the messages and errors arrays + * + * @param mixed $value + * @return void + */ + protected function setValue($value) + { + $this->value = $value; + $this->abstractOptions['messages'] = []; + } + + /** + * Set flag indicating whether or not value should be obfuscated in messages + * + * @param bool $flag + * @return AbstractValidator + */ + public function setValueObscured($flag) + { + $this->abstractOptions['valueObscured'] = (bool) $flag; + return $this; + } + + /** + * Retrieve flag indicating whether or not value should be obfuscated in + * messages + * + * @return bool + */ + public function isValueObscured() + { + return $this->abstractOptions['valueObscured']; + } + + /** + * Set translation object + * + * @param Translator\TranslatorInterface|null $translator + * @param string $textDomain (optional) + * @return AbstractValidator + * @throws Exception\InvalidArgumentException + */ + public function setTranslator(Translator\TranslatorInterface $translator = null, $textDomain = null) + { + $this->abstractOptions['translator'] = $translator; + if (null !== $textDomain) { + $this->setTranslatorTextDomain($textDomain); + } + return $this; + } + + /** + * Return translation object + * + * @return Translator\TranslatorInterface|null + */ + public function getTranslator() + { + if (! $this->isTranslatorEnabled()) { + return; + } + + if (null === $this->abstractOptions['translator']) { + $this->abstractOptions['translator'] = self::getDefaultTranslator(); + } + + return $this->abstractOptions['translator']; + } + + /** + * Does this validator have its own specific translator? + * + * @return bool + */ + public function hasTranslator() + { + return (bool) $this->abstractOptions['translator']; + } + + /** + * Set translation text domain + * + * @param string $textDomain + * @return AbstractValidator + */ + public function setTranslatorTextDomain($textDomain = 'default') + { + $this->abstractOptions['translatorTextDomain'] = $textDomain; + return $this; + } + + /** + * Return the translation text domain + * + * @return string + */ + public function getTranslatorTextDomain() + { + if (null === $this->abstractOptions['translatorTextDomain']) { + $this->abstractOptions['translatorTextDomain'] = + self::getDefaultTranslatorTextDomain(); + } + return $this->abstractOptions['translatorTextDomain']; + } + + /** + * Set default translation object for all validate objects + * + * @param Translator\TranslatorInterface|null $translator + * @param string $textDomain (optional) + * @return void + * @throws Exception\InvalidArgumentException + */ + public static function setDefaultTranslator(Translator\TranslatorInterface $translator = null, $textDomain = null) + { + static::$defaultTranslator = $translator; + if (null !== $textDomain) { + self::setDefaultTranslatorTextDomain($textDomain); + } + } + + /** + * Get default translation object for all validate objects + * + * @return Translator\TranslatorInterface|null + */ + public static function getDefaultTranslator() + { + return static::$defaultTranslator; + } + + /** + * Is there a default translation object set? + * + * @return bool + */ + public static function hasDefaultTranslator() + { + return (bool) static::$defaultTranslator; + } + + /** + * Set default translation text domain for all validate objects + * + * @param string $textDomain + * @return void + */ + public static function setDefaultTranslatorTextDomain($textDomain = 'default') + { + static::$defaultTranslatorTextDomain = $textDomain; + } + + /** + * Get default translation text domain for all validate objects + * + * @return string + */ + public static function getDefaultTranslatorTextDomain() + { + return static::$defaultTranslatorTextDomain; + } + + /** + * Indicate whether or not translation should be enabled + * + * @param bool $flag + * @return AbstractValidator + */ + public function setTranslatorEnabled($flag = true) + { + $this->abstractOptions['translatorEnabled'] = (bool) $flag; + return $this; + } + + /** + * Is translation enabled? + * + * @return bool + */ + public function isTranslatorEnabled() + { + return $this->abstractOptions['translatorEnabled']; + } + + /** + * Returns the maximum allowed message length + * + * @return int + */ + public static function getMessageLength() + { + return static::$messageLength; + } + + /** + * Sets the maximum allowed message length + * + * @param int $length + */ + public static function setMessageLength($length = -1) + { + static::$messageLength = $length; + } + + /** + * Translate a validation message + * + * @param string $messageKey + * @param string $message + * @return string + */ + protected function translateMessage($messageKey, $message) + { + $translator = $this->getTranslator(); + if (! $translator) { + return $message; + } + + return $translator->translate($message, $this->getTranslatorTextDomain()); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode.php b/lib/laminas/laminas-validator/src/Barcode.php new file mode 100644 index 000000000..65e27ed7c --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode.php @@ -0,0 +1,186 @@ + "The input failed checksum validation", + self::INVALID_CHARS => "The input contains invalid characters", + self::INVALID_LENGTH => "The input should have a length of %length% characters", + self::INVALID => "Invalid type given. String expected", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'length' => ['options' => 'length'], + ]; + + protected $options = [ + 'adapter' => null, // Barcode adapter Laminas\Validator\Barcode\AbstractAdapter + 'options' => null, // Options for this adapter + 'length' => null, + 'useChecksum' => null, + ]; + + /** + * Constructor for barcodes + * + * @param array|string $options Options to use + */ + public function __construct($options = null) + { + if ($options === null) { + $options = []; + } + + if (! is_array($options) && ! ($options instanceof Traversable)) { + $options = ['adapter' => $options]; + } + + if (array_key_exists('options', $options)) { + $options['options'] = ['options' => $options['options']]; + } + + parent::__construct($options); + } + + /** + * Returns the set adapter + * + * @return Barcode\AbstractAdapter + */ + public function getAdapter() + { + if (! ($this->options['adapter'] instanceof Barcode\AdapterInterface)) { + $this->setAdapter('Ean13'); + } + + return $this->options['adapter']; + } + + /** + * Sets a new barcode adapter + * + * @param string|Barcode\AbstractAdapter $adapter Barcode adapter to use + * @param array $options Options for this adapter + * @return Barcode + * @throws Exception\InvalidArgumentException + */ + public function setAdapter($adapter, $options = null) + { + if (is_string($adapter)) { + $adapter = ucfirst(strtolower($adapter)); + $adapter = 'Laminas\\Validator\\Barcode\\' . $adapter; + + if (! class_exists($adapter)) { + throw new Exception\InvalidArgumentException('Barcode adapter matching "' . $adapter . '" not found'); + } + + $adapter = new $adapter($options); + } + + if (! $adapter instanceof Barcode\AdapterInterface) { + throw new Exception\InvalidArgumentException( + sprintf( + "Adapter %s does not implement Laminas\\Validator\\Barcode\\AdapterInterface", + (is_object($adapter) ? get_class($adapter) : gettype($adapter)) + ) + ); + } + + $this->options['adapter'] = $adapter; + + return $this; + } + + /** + * Returns the checksum option + * + * @return string + */ + public function getChecksum() + { + return $this->getAdapter()->getChecksum(); + } + + /** + * Sets if checksum should be validated, if no value is given the actual setting is returned + * + * @param bool $checksum + * @return bool + */ + public function useChecksum($checksum = null) + { + return $this->getAdapter()->useChecksum($checksum); + } + + /** + * Defined by Laminas\Validator\ValidatorInterface + * + * Returns true if and only if $value contains a valid barcode + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + $adapter = $this->getAdapter(); + $this->options['length'] = $adapter->getLength(); + $result = $adapter->hasValidLength($value); + if (! $result) { + if (is_array($this->options['length'])) { + $temp = $this->options['length']; + $this->options['length'] = ""; + foreach ($temp as $length) { + $this->options['length'] .= "/"; + $this->options['length'] .= $length; + } + + $this->options['length'] = substr($this->options['length'], 1); + } + + $this->error(self::INVALID_LENGTH); + return false; + } + + $result = $adapter->hasValidCharacters($value); + if (! $result) { + $this->error(self::INVALID_CHARS); + return false; + } + + if ($this->useChecksum(null)) { + $result = $adapter->hasValidChecksum($value); + if (! $result) { + $this->error(self::FAILED); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/AbstractAdapter.php b/lib/laminas/laminas-validator/src/Barcode/AbstractAdapter.php new file mode 100644 index 000000000..38e2d9a3e --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/AbstractAdapter.php @@ -0,0 +1,309 @@ + null, // Allowed barcode lengths, integer, array, string + 'characters' => null, // Allowed barcode characters + 'checksum' => null, // Callback to checksum function + 'useChecksum' => true, // Is a checksum value included?, boolean + ]; + + /** + * Checks the length of a barcode + * + * @param string $value The barcode to check for proper length + * @return bool + */ + public function hasValidLength($value) + { + if (! is_string($value)) { + return false; + } + + $fixum = strlen($value); + $found = false; + $length = $this->getLength(); + if (is_array($length)) { + foreach ($length as $value) { + if ($fixum == $value) { + $found = true; + } + + if ($value == -1) { + $found = true; + } + } + } elseif ($fixum == $length) { + $found = true; + } elseif ($length == -1) { + $found = true; + } elseif ($length == 'even') { + $count = $fixum % 2; + $found = (0 == $count); + } elseif ($length == 'odd') { + $count = $fixum % 2; + $found = (1 == $count); + } + + return $found; + } + + /** + * Checks for allowed characters within the barcode + * + * @param string $value The barcode to check for allowed characters + * @return bool + */ + public function hasValidCharacters($value) + { + if (! is_string($value)) { + return false; + } + + $characters = $this->getCharacters(); + if ($characters == 128) { + for ($x = 0; $x < 128; ++$x) { + $value = str_replace(chr($x), '', $value); + } + } else { + $chars = str_split($characters); + foreach ($chars as $char) { + $value = str_replace($char, '', $value); + } + } + + if (strlen($value) > 0) { + return false; + } + + return true; + } + + /** + * Validates the checksum + * + * @param string $value The barcode to check the checksum for + * @return bool + */ + public function hasValidChecksum($value) + { + $checksum = $this->getChecksum(); + if (! empty($checksum)) { + if (method_exists($this, $checksum)) { + return $this->$checksum($value); + } + } + + return false; + } + + /** + * Returns the allowed barcode length + * + * @return int|array + */ + public function getLength() + { + return $this->options['length']; + } + + /** + * Returns the allowed characters + * + * @return int|string|array + */ + public function getCharacters() + { + return $this->options['characters']; + } + + /** + * Returns the checksum function name + * + */ + public function getChecksum() + { + return $this->options['checksum']; + } + + /** + * Sets the checksum validation method + * + * @param callable $checksum Checksum method to call + * @return AbstractAdapter + */ + protected function setChecksum($checksum) + { + $this->options['checksum'] = $checksum; + return $this; + } + + /** + * Sets the checksum validation, if no value is given, the actual setting is returned + * + * @param bool $check + * @return AbstractAdapter|bool + */ + public function useChecksum($check = null) + { + if ($check === null) { + return $this->options['useChecksum']; + } + + $this->options['useChecksum'] = (bool) $check; + return $this; + } + + /** + * Sets the length of this barcode + * + * @param int|array $length + * @return AbstractAdapter + */ + protected function setLength($length) + { + $this->options['length'] = $length; + return $this; + } + + /** + * Sets the allowed characters of this barcode + * + * @param int $characters + * @return AbstractAdapter + */ + protected function setCharacters($characters) + { + $this->options['characters'] = $characters; + return $this; + } + + /** + * Validates the checksum (Modulo 10) + * GTIN implementation factor 3 + * + * @param string $value The barcode to validate + * @return bool + */ + protected function gtin($value) + { + $barcode = substr($value, 0, -1); + $sum = 0; + $length = strlen($barcode) - 1; + + for ($i = 0; $i <= $length; $i++) { + if (($i % 2) === 0) { + $sum += $barcode[$length - $i] * 3; + } else { + $sum += $barcode[$length - $i]; + } + } + + $calc = $sum % 10; + $checksum = ($calc === 0) ? 0 : (10 - $calc); + if ($value[$length + 1] != $checksum) { + return false; + } + + return true; + } + + /** + * Validates the checksum (Modulo 10) + * IDENTCODE implementation factors 9 and 4 + * + * @param string $value The barcode to validate + * @return bool + */ + protected function identcode($value) + { + $barcode = substr($value, 0, -1); + $sum = 0; + $length = strlen($value) - 2; + + for ($i = 0; $i <= $length; $i++) { + if (($i % 2) === 0) { + $sum += $barcode[$length - $i] * 4; + } else { + $sum += $barcode[$length - $i] * 9; + } + } + + $calc = $sum % 10; + $checksum = ($calc === 0) ? 0 : (10 - $calc); + if ($value[$length + 1] != $checksum) { + return false; + } + + return true; + } + + /** + * Validates the checksum (Modulo 10) + * CODE25 implementation factor 3 + * + * @param string $value The barcode to validate + * @return bool + */ + protected function code25($value) + { + $barcode = substr($value, 0, -1); + $sum = 0; + $length = strlen($barcode) - 1; + + for ($i = 0; $i <= $length; $i++) { + if (($i % 2) === 0) { + $sum += $barcode[$i] * 3; + } else { + $sum += $barcode[$i]; + } + } + + $calc = $sum % 10; + $checksum = ($calc === 0) ? 0 : (10 - $calc); + if ($value[$length + 1] != $checksum) { + return false; + } + + return true; + } + + /** + * Validates the checksum () + * POSTNET implementation + * + * @param string $value The barcode to validate + * @return bool + */ + protected function postnet($value) + { + $checksum = substr($value, -1, 1); + $values = str_split(substr($value, 0, -1)); + + $check = 0; + foreach ($values as $row) { + $check += $row; + } + + $check %= 10; + $check = 10 - $check; + if ($check == $checksum) { + return true; + } + + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/AdapterInterface.php b/lib/laminas/laminas-validator/src/Barcode/AdapterInterface.php new file mode 100644 index 000000000..c9f7afe17 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/AdapterInterface.php @@ -0,0 +1,65 @@ +setLength(-1); + $this->setCharacters('0123456789-$:/.+ABCDTN*E'); + $this->useChecksum(false); + } + + /** + * Checks for allowed characters + * @see Laminas\Validator\Barcode.AbstractAdapter::checkChars() + */ + public function hasValidCharacters($value) + { + if (strpbrk($value, 'ABCD')) { + $first = $value[0]; + if (! strpbrk($first, 'ABCD')) { + // Missing start char + return false; + } + + $last = substr($value, -1, 1); + if (! strpbrk($last, 'ABCD')) { + // Missing stop char + return false; + } + + $value = substr($value, 1, -1); + } elseif (strpbrk($value, 'TN*E')) { + $first = $value[0]; + if (! strpbrk($first, 'TN*E')) { + // Missing start char + return false; + } + + $last = substr($value, -1, 1); + if (! strpbrk($last, 'TN*E')) { + // Missing stop char + return false; + } + + $value = substr($value, 1, -1); + } + + $chars = $this->getCharacters(); + $this->setCharacters('0123456789-$:/.+'); + $result = parent::hasValidCharacters($value); + $this->setCharacters($chars); + return $result; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code128.php b/lib/laminas/laminas-validator/src/Barcode/Code128.php new file mode 100644 index 000000000..cff83e07d --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code128.php @@ -0,0 +1,447 @@ +setLength(-1); + $this->setCharacters([ + 'A' => [ + 0 => ' ', 1 => '!', 2 => '"', 3 => '#', 4 => '$', 5 => '%', 6 => '&', 7 => "'", + 8 => '(', 9 => ')', 10 => '*', 11 => '+', 12 => ',', 13 => '-', 14 => '.', 15 => '/', + 16 => '0', 17 => '1', 18 => '2', 19 => '3', 20 => '4', 21 => '5', 22 => '6', 23 => '7', + 24 => '8', 25 => '9', 26 => ':', 27 => ';', 28 => '<', 29 => '=', 30 => '>', 31 => '?', + 32 => '@', 33 => 'A', 34 => 'B', 35 => 'C', 36 => 'D', 37 => 'E', 38 => 'F', 39 => 'G', + 40 => 'H', 41 => 'I', 42 => 'J', 43 => 'K', 44 => 'L', 45 => 'M', 46 => 'N', 47 => 'O', + 48 => 'P', 49 => 'Q', 50 => 'R', 51 => 'S', 52 => 'T', 53 => 'U', 54 => 'V', 55 => 'W', + 56 => 'X', 57 => 'Y', 58 => 'Z', 59 => '[', 60 => '\\', 61 => ']', 62 => '^', 63 => '_', + 64 => 0x00, 65 => 0x01, 66 => 0x02, 67 => 0x03, 68 => 0x04, 69 => 0x05, 70 => 0x06, 71 => 0x07, + 72 => 0x08, 73 => 0x09, 74 => 0x0A, 75 => 0x0B, 76 => 0x0C, 77 => 0x0D, 78 => 0x0E, 79 => 0x0F, + 80 => 0x10, 81 => 0x11, 82 => 0x12, 83 => 0x13, 84 => 0x14, 85 => 0x15, 86 => 0x16, 87 => 0x17, + 88 => 0x18, 89 => 0x19, 90 => 0x1A, 91 => 0x1B, 92 => 0x1C, 93 => 0x1D, 94 => 0x1E, 95 => 0x1F, + 96 => 'Ç', 97 => 'ü', 98 => 'é', 99 => 'â', 100 => 'ä', 101 => 'à', 102 => 'å', 103 => '‡', + 104 => 'ˆ', 105 => '‰', 106 => 'Š'], + 'B' => [ + 0 => ' ', 1 => '!', 2 => '"', 3 => '#', 4 => '$', 5 => '%', 6 => '&', 7 => "'", + 8 => '(', 9 => ')', 10 => '*', 11 => '+', 12 => ',', 13 => '-', 14 => '.', 15 => '/', + 16 => '0', 17 => '1', 18 => '2', 19 => '3', 20 => '4', 21 => '5', 22 => '6', 23 => '7', + 24 => '8', 25 => '9', 26 => ':', 27 => ';', 28 => '<', 29 => '=', 30 => '>', 31 => '?', + 32 => '@', 33 => 'A', 34 => 'B', 35 => 'C', 36 => 'D', 37 => 'E', 38 => 'F', 39 => 'G', + 40 => 'H', 41 => 'I', 42 => 'J', 43 => 'K', 44 => 'L', 45 => 'M', 46 => 'N', 47 => 'O', + 48 => 'P', 49 => 'Q', 50 => 'R', 51 => 'S', 52 => 'T', 53 => 'U', 54 => 'V', 55 => 'W', + 56 => 'X', 57 => 'Y', 58 => 'Z', 59 => '[', 60 => '\\', 61 => ']', 62 => '^', 63 => '_', + 64 => '`', 65 => 'a', 66 => 'b', 67 => 'c', 68 => 'd', 69 => 'e', 70 => 'f', 71 => 'g', + 72 => 'h', 73 => 'i', 74 => 'j', 75 => 'k', 76 => 'l', 77 => 'm', 78 => 'n', 79 => 'o', + 80 => 'p', 81 => 'q', 82 => 'r', 83 => 's', 84 => 't', 85 => 'u', 86 => 'v', 87 => 'w', + 88 => 'x', 89 => 'y', 90 => 'z', 91 => '{', 92 => '|', 93 => '}', 94 => '~', 95 => 0x7F, + 96 => 'Ç', 97 => 'ü', 98 => 'é', 99 => 'â', 100 => 'ä', 101 => 'à', 102 => 'å', 103 => '‡', + 104 => 'ˆ', 105 => '‰', 106 => 'Š'], + 'C' => [ + 0 => '00', 1 => '01', 2 => '02', 3 => '03', 4 => '04', 5 => '05', 6 => '06', 7 => '07', + 8 => '08', 9 => '09', 10 => '10', 11 => '11', 12 => '12', 13 => '13', 14 => '14', 15 => '15', + 16 => '16', 17 => '17', 18 => '18', 19 => '19', 20 => '20', 21 => '21', 22 => '22', 23 => '23', + 24 => '24', 25 => '25', 26 => '26', 27 => '27', 28 => '28', 29 => '29', 30 => '30', 31 => '31', + 32 => '32', 33 => '33', 34 => '34', 35 => '35', 36 => '36', 37 => '37', 38 => '38', 39 => '39', + 40 => '40', 41 => '41', 42 => '42', 43 => '43', 44 => '44', 45 => '45', 46 => '46', 47 => '47', + 48 => '48', 49 => '49', 50 => '50', 51 => '51', 52 => '52', 53 => '53', 54 => '54', 55 => '55', + 56 => '56', 57 => '57', 58 => '58', 59 => '59', 60 => '60', 61 => '61', 62 => '62', 63 => '63', + 64 => '64', 65 => '65', 66 => '66', 67 => '67', 68 => '68', 69 => '69', 70 => '70', 71 => '71', + 72 => '72', 73 => '73', 74 => '74', 75 => '75', 76 => '76', 77 => '77', 78 => '78', 79 => '79', + 80 => '80', 81 => '81', 82 => '82', 83 => '83', 84 => '84', 85 => '85', 86 => '86', 87 => '87', + 88 => '88', 89 => '89', 90 => '90', 91 => '91', 92 => '92', 93 => '93', 94 => '94', 95 => '95', + 96 => '96', 97 => '97', 98 => '98', 99 => '99', 100 => 'ä', 101 => 'à', 102 => 'å', 103 => '‡', + 104 => 'ˆ', 105 => '‰', 106 => 'Š'] + ]); + $this->setChecksum('code128'); + } + + public function setUtf8StringWrapper(StringWrapperInterface $utf8StringWrapper) + { + if (! $utf8StringWrapper->isSupported('UTF-8')) { + throw new Exception\InvalidArgumentException( + "The string wrapper needs to support UTF-8 character encoding" + ); + } + $this->utf8StringWrapper = $utf8StringWrapper; + } + + /** + * Get the string wrapper supporting UTF-8 character encoding + * + * @return StringWrapperInterface + */ + public function getUtf8StringWrapper() + { + if (! $this->utf8StringWrapper) { + $this->utf8StringWrapper = StringUtils::getWrapper('UTF-8'); + } + return $this->utf8StringWrapper; + } + + /** + * Checks for allowed characters within the barcode + * + * @param string $value The barcode to check for allowed characters + * @return bool + */ + public function hasValidCharacters($value) + { + if (! is_string($value)) { + return false; + } + + // get used string wrapper for UTF-8 character encoding + $strWrapper = $this->getUtf8StringWrapper(); + + // detect starting charset + $set = $this->getCodingSet($value); + $read = $set; + if ($set != '') { + $value = $strWrapper->substr($value, 1, null); + } + + // process barcode + while ($value != '') { + $char = $strWrapper->substr($value, 0, 1); + + switch ($char) { + // Function definition + case 'Ç': + case 'ü': + case 'å': + break; + + // Switch 1 char between A and B + case 'é': + if ($set == 'A') { + $read = 'B'; + } elseif ($set == 'B') { + $read = 'A'; + } + break; + + // Switch to C + case 'â': + $set = 'C'; + $read = 'C'; + break; + + // Switch to B + case 'ä': + $set = 'B'; + $read = 'B'; + break; + + // Switch to A + case 'à': + $set = 'A'; + $read = 'A'; + break; + + // Doubled start character + case '‡': + case 'ˆ': + case '‰': + return false; + + // Chars after the stop character + case 'Š': + break 2; + + default: + // Does the char exist within the charset to read? + if ($this->ord128($char, $read) == -1) { + return false; + } + + break; + } + + $value = $strWrapper->substr($value, 1, null); + $read = $set; + } + + if (($value != '') && ($strWrapper->strlen($value) != 1)) { + return false; + } + + return true; + } + + /** + * Validates the checksum () + * + * @param string $value The barcode to validate + * @return bool + */ + protected function code128($value) + { + $sum = 0; + $pos = 1; + $set = $this->getCodingSet($value); + $read = $set; + $usecheck = $this->useChecksum(null); + $strWrapper = $this->getUtf8StringWrapper(); + $char = $strWrapper->substr($value, 0, 1); + if ($char == '‡') { + $sum = 103; + } elseif ($char == 'ˆ') { + $sum = 104; + } elseif ($char == '‰') { + $sum = 105; + } elseif ($usecheck == true) { + // no start value, unable to detect a proper checksum + return false; + } + + $value = $strWrapper->substr($value, 1, null); + while ($strWrapper->strpos($value, 'Š') || ($value != '')) { + $char = $strWrapper->substr($value, 0, 1); + if ($read == 'C') { + $char = $strWrapper->substr($value, 0, 2); + } + + switch ($char) { + // Function definition + case 'Ç': + case 'ü': + case 'å': + $sum += ($pos * $this->ord128($char, $set)); + break; + + case 'é': + $sum += ($pos * $this->ord128($char, $set)); + if ($set == 'A') { + $read = 'B'; + } elseif ($set == 'B') { + $read = 'A'; + } + break; + + // Switch to C + case 'â': + $sum += ($pos * $this->ord128($char, $set)); + $set = 'C'; + $read = 'C'; + break; + + // Switch to B + case 'ä': + $sum += ($pos * $this->ord128($char, $set)); + $set = 'B'; + $read = 'B'; + break; + + // Switch to A + case 'à': + $sum += ($pos * $this->ord128($char, $set)); + $set = 'A'; + $read = 'A'; + break; + + case '‡': + case 'ˆ': + case '‰': + return false; + break; + + default: + // Does the char exist within the charset to read? + if ($this->ord128($char, $read) == -1) { + return false; + } + + $sum += ($pos * $this->ord128($char, $set)); + break; + } + + $value = $strWrapper->substr($value, 1); + ++$pos; + if (($strWrapper->strpos($value, 'Š') == 1) && ($strWrapper->strlen($value) == 2)) { + // break by stop and checksum char + break; + } + $read = $set; + } + + if (($strWrapper->strpos($value, 'Š') != 1) || ($strWrapper->strlen($value) != 2)) { + // return false if checksum is not readable and true if no startvalue is detected + return (! $usecheck); + } + + $mod = $sum % 103; + if ($strWrapper->substr($value, 0, 1) == $this->chr128($mod, $set)) { + return true; + } + + return false; + } + + /** + * Returns the coding set for a barcode + * + * @param string $value Barcode + * @return string + */ + protected function getCodingSet($value) + { + $value = $this->getUtf8StringWrapper()->substr($value, 0, 1); + switch ($value) { + case '‡': + return 'A'; + break; + case 'ˆ': + return 'B'; + break; + case '‰': + return 'C'; + break; + } + + return ''; + } + + /** + * Internal method to return the code128 integer from an ascii value + * + * Table A + * ASCII CODE128 + * 32 to 95 == 0 to 63 + * 0 to 31 == 64 to 95 + * 128 to 138 == 96 to 106 + * + * Table B + * ASCII CODE128 + * 32 to 138 == 0 to 106 + * + * Table C + * ASCII CODE128 + * "00" to "99" == 0 to 99 + * 132 to 138 == 100 to 106 + * + * @param string $value + * @param string $set + * @return int + */ + protected function ord128($value, $set) + { + $ord = ord($value); + if ($set == 'A') { + if ($ord < 32) { + return ($ord + 64); + } elseif ($ord < 96) { + return ($ord - 32); + } elseif ($ord > 138) { + return -1; + } else { + return ($ord - 32); + } + } elseif ($set == 'B') { + if ($ord < 32) { + return -1; + } elseif ($ord <= 138) { + return ($ord - 32); + } else { + return -1; + } + } elseif ($set == 'C') { + $val = (int) $value; + if (($val >= 0) && ($val <= 99)) { + return $val; + } elseif (($ord >= 132) && ($ord <= 138)) { + return ($ord - 32); + } else { + return -1; + } + } else { + if ($ord < 32) { + return ($ord + 64); + } elseif ($ord <= 138) { + return ($ord - 32); + } else { + return -1; + } + } + } + + /** + * Internal Method to return the ascii value from a code128 integer + * + * Table A + * ASCII CODE128 + * 32 to 95 == 0 to 63 + * 0 to 31 == 64 to 95 + * 128 to 138 == 96 to 106 + * + * Table B + * ASCII CODE128 + * 32 to 138 == 0 to 106 + * + * Table C + * ASCII CODE128 + * "00" to "99" == 0 to 99 + * 132 to 138 == 100 to 106 + * + * @param int $value + * @param string $set + * @return string + */ + protected function chr128($value, $set) + { + if ($set == 'A') { + if ($value < 64) { + return chr($value + 32); + } elseif ($value < 96) { + return chr($value - 64); + } elseif ($value > 106) { + return -1; + } else { + return chr($value + 32); + } + } elseif ($set == 'B') { + if ($value > 106) { + return -1; + } else { + return chr($value + 32); + } + } elseif ($set == 'C') { + if (($value >= 0) && ($value <= 9)) { + return "0" . (string) $value; + } elseif ($value <= 99) { + return (string) $value; + } elseif ($value <= 106) { + return chr($value + 32); + } else { + return -1; + } + } else { + if ($value <= 106) { + return ($value + 32); + } else { + return -1; + } + } + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code25.php b/lib/laminas/laminas-validator/src/Barcode/Code25.php new file mode 100644 index 000000000..f98f32238 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code25.php @@ -0,0 +1,23 @@ +setLength(-1); + $this->setCharacters('0123456789'); + $this->setChecksum('code25'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code25interleaved.php b/lib/laminas/laminas-validator/src/Barcode/Code25interleaved.php new file mode 100644 index 000000000..5675b225f --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code25interleaved.php @@ -0,0 +1,25 @@ +setLength('even'); + $this->setCharacters('0123456789'); + $this->setChecksum('code25'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code39.php b/lib/laminas/laminas-validator/src/Barcode/Code39.php new file mode 100644 index 000000000..dc583678b --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code39.php @@ -0,0 +1,59 @@ + 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, + '7' => 7, '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, + 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, + 'L' => 21, 'M' => 22, 'N' => 23, 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, + 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34, + 'Z' => 35, '-' => 36, '.' => 37, ' ' => 38, '$' => 39, '/' => 40, '+' => 41, + '%' => 42, + ]; + + /** + * Constructor for this barcode adapter + */ + public function __construct() + { + $this->setLength(-1); + $this->setCharacters('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -.$/+%'); + $this->setChecksum('code39'); + $this->useChecksum(false); + } + + /** + * Validates the checksum (Modulo 43) + * + * @param string $value The barcode to validate + * @return bool + */ + protected function code39($value) + { + $checksum = substr($value, -1, 1); + $value = str_split(substr($value, 0, -1)); + $count = 0; + foreach ($value as $char) { + $count += $this->check[$char]; + } + + $mod = $count % 43; + if ($mod == $this->check[$checksum]) { + return true; + } + + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code39ext.php b/lib/laminas/laminas-validator/src/Barcode/Code39ext.php new file mode 100644 index 000000000..142b85f81 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code39ext.php @@ -0,0 +1,22 @@ +setLength(-1); + $this->setCharacters(128); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code93.php b/lib/laminas/laminas-validator/src/Barcode/Code93.php new file mode 100644 index 000000000..4ce79cc1a --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code93.php @@ -0,0 +1,79 @@ + 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, + '7' => 7, '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, + 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, + 'L' => 21, 'M' => 22, 'N' => 23, 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, + 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34, + 'Z' => 35, '-' => 36, '.' => 37, ' ' => 38, '$' => 39, '/' => 40, '+' => 41, + '%' => 42, '!' => 43, '"' => 44, '§' => 45, '&' => 46, + ]; + + /** + * Constructor for this barcode adapter + */ + public function __construct() + { + $this->setLength(-1); + $this->setCharacters('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -.$/+%'); + $this->setChecksum('code93'); + $this->useChecksum(false); + } + + /** + * Validates the checksum (Modulo CK) + * + * @param string $value The barcode to validate + * @return bool + */ + protected function code93($value) + { + $checksum = substr($value, -2, 2); + $value = str_split(substr($value, 0, -2)); + $count = 0; + $length = count($value) % 20; + foreach ($value as $char) { + if ($length == 0) { + $length = 20; + } + + $count += $this->check[$char] * $length; + --$length; + } + + $check = array_search(($count % 47), $this->check); + $value[] = $check; + $count = 0; + $length = count($value) % 15; + foreach ($value as $char) { + if ($length == 0) { + $length = 15; + } + + $count += $this->check[$char] * $length; + --$length; + } + $check .= array_search(($count % 47), $this->check); + + if ($check == $checksum) { + return true; + } + + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code93ext.php b/lib/laminas/laminas-validator/src/Barcode/Code93ext.php new file mode 100644 index 000000000..f6c7aedd9 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code93ext.php @@ -0,0 +1,22 @@ +setLength(-1); + $this->setCharacters(128); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean12.php b/lib/laminas/laminas-validator/src/Barcode/Ean12.php new file mode 100644 index 000000000..a9251f2f3 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean12.php @@ -0,0 +1,22 @@ +setLength(12); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean13.php b/lib/laminas/laminas-validator/src/Barcode/Ean13.php new file mode 100644 index 000000000..51c7447ca --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean13.php @@ -0,0 +1,22 @@ +setLength(13); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean14.php b/lib/laminas/laminas-validator/src/Barcode/Ean14.php new file mode 100644 index 000000000..dc0c9e9a2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean14.php @@ -0,0 +1,22 @@ +setLength(14); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean18.php b/lib/laminas/laminas-validator/src/Barcode/Ean18.php new file mode 100644 index 000000000..516a47731 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean18.php @@ -0,0 +1,22 @@ +setLength(18); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean2.php b/lib/laminas/laminas-validator/src/Barcode/Ean2.php new file mode 100644 index 000000000..6222b5818 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean2.php @@ -0,0 +1,22 @@ +setLength(2); + $this->setCharacters('0123456789'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean5.php b/lib/laminas/laminas-validator/src/Barcode/Ean5.php new file mode 100644 index 000000000..b48153aed --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean5.php @@ -0,0 +1,24 @@ +setLength(5); + $this->setCharacters('0123456789'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean8.php b/lib/laminas/laminas-validator/src/Barcode/Ean8.php new file mode 100644 index 000000000..e7ba27f1b --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean8.php @@ -0,0 +1,39 @@ +setLength([7, 8]); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } + + /** + * Overrides parent checkLength + * + * @param string $value Value + * @return bool + */ + public function hasValidLength($value) + { + if (strlen($value) == 7) { + $this->useChecksum(false); + } else { + $this->useChecksum(true); + } + + return parent::hasValidLength($value); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Gtin12.php b/lib/laminas/laminas-validator/src/Barcode/Gtin12.php new file mode 100644 index 000000000..a65dbec3b --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Gtin12.php @@ -0,0 +1,22 @@ +setLength(12); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Gtin13.php b/lib/laminas/laminas-validator/src/Barcode/Gtin13.php new file mode 100644 index 000000000..315e20f3b --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Gtin13.php @@ -0,0 +1,22 @@ +setLength(13); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Gtin14.php b/lib/laminas/laminas-validator/src/Barcode/Gtin14.php new file mode 100644 index 000000000..bd0538c28 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Gtin14.php @@ -0,0 +1,22 @@ +setLength(14); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Identcode.php b/lib/laminas/laminas-validator/src/Barcode/Identcode.php new file mode 100644 index 000000000..8949ec93c --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Identcode.php @@ -0,0 +1,40 @@ +setLength(12); + $this->setCharacters('0123456789'); + $this->setChecksum('identcode'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Intelligentmail.php b/lib/laminas/laminas-validator/src/Barcode/Intelligentmail.php new file mode 100644 index 000000000..ea13b559b --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Intelligentmail.php @@ -0,0 +1,24 @@ +setLength([20, 25, 29, 31]); + $this->setCharacters('0123456789'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Issn.php b/lib/laminas/laminas-validator/src/Barcode/Issn.php new file mode 100644 index 000000000..8dbf602bd --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Issn.php @@ -0,0 +1,89 @@ +setLength([8, 13]); + $this->setCharacters('0123456789X'); + $this->setChecksum('gtin'); + } + + /** + * Allows X on length of 8 chars + * + * @param string $value The barcode to check for allowed characters + * @return bool + */ + public function hasValidCharacters($value) + { + if (strlen($value) != 8) { + if (strpos($value, 'X') !== false) { + return false; + } + } + + return parent::hasValidCharacters($value); + } + + /** + * Validates the checksum + * + * @param string $value The barcode to check the checksum for + * @return bool + */ + public function hasValidChecksum($value) + { + if (strlen($value) == 8) { + $this->setChecksum('issn'); + } else { + $this->setChecksum('gtin'); + } + + return parent::hasValidChecksum($value); + } + + /** + * Validates the checksum () + * ISSN implementation (reversed mod11) + * + * @param string $value The barcode to validate + * @return bool + */ + protected function issn($value) + { + $checksum = substr($value, -1, 1); + $values = str_split(substr($value, 0, -1)); + $check = 0; + $multi = 8; + foreach ($values as $token) { + if ($token == 'X') { + $token = 10; + } + + $check += ($token * $multi); + --$multi; + } + + $check %= 11; + $check = ($check === 0 ? 0 : (11 - $check)); + if ($check == $checksum) { + return true; + } elseif (($check == 10) && ($checksum == 'X')) { + return true; + } + + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Itf14.php b/lib/laminas/laminas-validator/src/Barcode/Itf14.php new file mode 100644 index 000000000..d7b196c34 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Itf14.php @@ -0,0 +1,22 @@ +setLength(14); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Leitcode.php b/lib/laminas/laminas-validator/src/Barcode/Leitcode.php new file mode 100644 index 000000000..4bd641687 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Leitcode.php @@ -0,0 +1,22 @@ +setLength(14); + $this->setCharacters('0123456789'); + $this->setChecksum('identcode'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Planet.php b/lib/laminas/laminas-validator/src/Barcode/Planet.php new file mode 100644 index 000000000..e99572aaa --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Planet.php @@ -0,0 +1,22 @@ +setLength([12, 14]); + $this->setCharacters('0123456789'); + $this->setChecksum('postnet'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Postnet.php b/lib/laminas/laminas-validator/src/Barcode/Postnet.php new file mode 100644 index 000000000..e6a4a50e2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Postnet.php @@ -0,0 +1,22 @@ +setLength([6, 7, 10, 12]); + $this->setCharacters('0123456789'); + $this->setChecksum('postnet'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Royalmail.php b/lib/laminas/laminas-validator/src/Barcode/Royalmail.php new file mode 100644 index 000000000..3cc5aa7ff --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Royalmail.php @@ -0,0 +1,92 @@ + 1, '1' => 1, '2' => 1, '3' => 1, '4' => 1, '5' => 1, + '6' => 2, '7' => 2, '8' => 2, '9' => 2, 'A' => 2, 'B' => 2, + 'C' => 3, 'D' => 3, 'E' => 3, 'F' => 3, 'G' => 3, 'H' => 3, + 'I' => 4, 'J' => 4, 'K' => 4, 'L' => 4, 'M' => 4, 'N' => 4, + 'O' => 5, 'P' => 5, 'Q' => 5, 'R' => 5, 'S' => 5, 'T' => 5, + 'U' => 0, 'V' => 0, 'W' => 0, 'X' => 0, 'Y' => 0, 'Z' => 0, + ]; + + protected $columns = [ + '0' => 1, '1' => 2, '2' => 3, '3' => 4, '4' => 5, '5' => 0, + '6' => 1, '7' => 2, '8' => 3, '9' => 4, 'A' => 5, 'B' => 0, + 'C' => 1, 'D' => 2, 'E' => 3, 'F' => 4, 'G' => 5, 'H' => 0, + 'I' => 1, 'J' => 2, 'K' => 3, 'L' => 4, 'M' => 5, 'N' => 0, + 'O' => 1, 'P' => 2, 'Q' => 3, 'R' => 4, 'S' => 5, 'T' => 0, + 'U' => 1, 'V' => 2, 'W' => 3, 'X' => 4, 'Y' => 5, 'Z' => 0, + ]; + + /** + * Constructor for this barcode adapter + */ + public function __construct() + { + $this->setLength(-1); + $this->setCharacters('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'); + $this->setChecksum('royalmail'); + } + + /** + * Validates the checksum () + * + * @param string $value The barcode to validate + * @return bool + */ + protected function royalmail($value) + { + $checksum = substr($value, -1, 1); + $values = str_split(substr($value, 0, -1)); + $rowvalue = 0; + $colvalue = 0; + foreach ($values as $row) { + $rowvalue += $this->rows[$row]; + $colvalue += $this->columns[$row]; + } + + $rowvalue %= 6; + $colvalue %= 6; + + $rowchkvalue = array_keys($this->rows, $rowvalue); + $colchkvalue = array_keys($this->columns, $colvalue); + $intersect = array_intersect($rowchkvalue, $colchkvalue); + $chkvalue = current($intersect); + if ($chkvalue == $checksum) { + return true; + } + + return false; + } + + /** + * Allows start and stop tag within checked chars + * + * @param string $value The barcode to check for allowed characters + * @return bool + */ + public function hasValidCharacters($value) + { + if ($value[0] == '(') { + $value = substr($value, 1); + + if ($value[strlen($value) - 1] == ')') { + $value = substr($value, 0, -1); + } else { + return false; + } + } + + return parent::hasValidCharacters($value); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Sscc.php b/lib/laminas/laminas-validator/src/Barcode/Sscc.php new file mode 100644 index 000000000..efd4e6522 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Sscc.php @@ -0,0 +1,22 @@ +setLength(18); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Upca.php b/lib/laminas/laminas-validator/src/Barcode/Upca.php new file mode 100644 index 000000000..559702429 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Upca.php @@ -0,0 +1,22 @@ +setLength(12); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Upce.php b/lib/laminas/laminas-validator/src/Barcode/Upce.php new file mode 100644 index 000000000..9a419cc35 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Upce.php @@ -0,0 +1,39 @@ +setLength([6, 7, 8]); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } + + /** + * Overrides parent checkLength + * + * @param string $value Value + * @return bool + */ + public function hasValidLength($value) + { + if (strlen($value) != 8) { + $this->useChecksum(false); + } else { + $this->useChecksum(true); + } + + return parent::hasValidLength($value); + } +} diff --git a/lib/laminas/laminas-validator/src/Between.php b/lib/laminas/laminas-validator/src/Between.php new file mode 100644 index 000000000..1127f6782 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Between.php @@ -0,0 +1,213 @@ + "The input is not between '%min%' and '%max%', inclusively", + self::NOT_BETWEEN_STRICT => "The input is not strictly between '%min%' and '%max%'", + self::VALUE_NOT_NUMERIC => "The min ('%min%') and max ('%max%') values are numeric, but the input is not", + self::VALUE_NOT_STRING => "The min ('%min%') and max ('%max%') values are non-numeric strings, " + . "but the input is not a string", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + ]; + + /** + * Options for the between validator + * + * @var array + */ + protected $options = [ + 'inclusive' => true, // Whether to do inclusive comparisons, allowing equivalence to min and/or max + 'min' => 0, + 'max' => PHP_INT_MAX, + ]; + + /** + * Sets validator options + * Accepts the following option keys: + * 'min' => scalar, minimum border + * 'max' => scalar, maximum border + * 'inclusive' => boolean, inclusive border values + * + * @param array|Traversable $options + * + * @throws Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + if (! is_array($options)) { + $options = func_get_args(); + $temp['min'] = array_shift($options); + if (! empty($options)) { + $temp['max'] = array_shift($options); + } + + if (! empty($options)) { + $temp['inclusive'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('min', $options) || ! array_key_exists('max', $options)) { + throw new Exception\InvalidArgumentException("Missing option: 'min' and 'max' have to be given"); + } + + if ((isset($options['min']) && is_numeric($options['min'])) + && (isset($options['max']) && is_numeric($options['max'])) + ) { + $this->numeric = true; + } elseif ((isset($options['min']) && is_string($options['min'])) + && (isset($options['max']) && is_string($options['max'])) + ) { + $this->numeric = false; + } else { + throw new Exception\InvalidArgumentException( + "Invalid options: 'min' and 'max' should be of the same scalar type" + ); + } + + parent::__construct($options); + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Sets the min option + * + * @param mixed $min + * @return Between Provides a fluent interface + */ + public function setMin($min) + { + $this->options['min'] = $min; + return $this; + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * Sets the max option + * + * @param mixed $max + * @return Between Provides a fluent interface + */ + public function setMax($max) + { + $this->options['max'] = $max; + return $this; + } + + /** + * Returns the inclusive option + * + * @return bool + */ + public function getInclusive() + { + return $this->options['inclusive']; + } + + /** + * Sets the inclusive option + * + * @param bool $inclusive + * @return Between Provides a fluent interface + */ + public function setInclusive($inclusive) + { + $this->options['inclusive'] = $inclusive; + return $this; + } + + /** + * Returns true if and only if $value is between min and max options, inclusively + * if inclusive option is true. + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if ($this->numeric && ! is_numeric($value)) { + $this->error(self::VALUE_NOT_NUMERIC); + return false; + } + if (! $this->numeric && ! is_string($value)) { + $this->error(self::VALUE_NOT_STRING); + return false; + } + + if ($this->getInclusive()) { + if ($this->getMin() > $value || $value > $this->getMax()) { + $this->error(self::NOT_BETWEEN); + return false; + } + } else { + if ($this->getMin() >= $value || $value >= $this->getMax()) { + $this->error(self::NOT_BETWEEN_STRICT); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Bitwise.php b/lib/laminas/laminas-validator/src/Bitwise.php new file mode 100644 index 000000000..157c95e02 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Bitwise.php @@ -0,0 +1,191 @@ + "The input has no common bit set with '%control%'", + self::NOT_AND_STRICT => "The input doesn't have the same bits set as '%control%'", + self::NOT_XOR => "The input has common bit set with '%control%'", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'control' => 'control', + ]; + + /** + * @var integer + */ + protected $operator; + + /** + * @var boolean + */ + protected $strict = false; + + /** + * Sets validator options + * Accepts the following option keys: + * 'control' => integer + * 'operator' => + * 'strict' => boolean + * + * @param array|Traversable $options + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } + + if (! is_array($options)) { + $options = func_get_args(); + + $temp['control'] = array_shift($options); + + if (! empty($options)) { + $temp['operator'] = array_shift($options); + } + + if (! empty($options)) { + $temp['strict'] = array_shift($options); + } + + $options = $temp; + } + + parent::__construct($options); + } + + /** + * Returns the control parameter. + * + * @return integer + */ + public function getControl() + { + return $this->control; + } + + /** + * Returns the operator parameter. + * + * @return string + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Returns the strict parameter. + * + * @return boolean + */ + public function getStrict() + { + return $this->strict; + } + + /** + * Returns true if and only if $value is between min and max options, inclusively + * if inclusive option is true. + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if (self::OP_AND === $this->operator) { + if ($this->strict) { + // All the bits set in value must be set in control + $this->error(self::NOT_AND_STRICT); + + return (bool) (($this->control & $value) == $value); + } else { + // At least one of the bits must be common between value and control + $this->error(self::NOT_AND); + + return (bool) ($this->control & $value); + } + } elseif (self::OP_XOR === $this->operator) { + $this->error(self::NOT_XOR); + + return (bool) (($this->control ^ $value) === ($this->control | $value)); + } + + return false; + } + + /** + * Sets the control parameter. + * + * @param integer $control + * @return Bitwise + */ + public function setControl($control) + { + $this->control = (int) $control; + + return $this; + } + + /** + * Sets the operator parameter. + * + * @param string $operator + * @return Bitwise + */ + public function setOperator($operator) + { + $this->operator = $operator; + + return $this; + } + + /** + * Sets the strict parameter. + * + * @param boolean $strict + * @return Bitwise + */ + public function setStrict($strict) + { + $this->strict = (bool) $strict; + + return $this; + } +} diff --git a/lib/laminas/laminas-validator/src/Callback.php b/lib/laminas/laminas-validator/src/Callback.php new file mode 100644 index 000000000..718f0b736 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Callback.php @@ -0,0 +1,149 @@ + "The input is not valid", + self::INVALID_CALLBACK => "An exception has been raised within the callback", + ]; + + /** + * Default options to set for the validator + * + * @var mixed + */ + protected $options = [ + 'callback' => null, // Callback in a call_user_func format, string || array + 'callbackOptions' => [], // Options for the callback + ]; + + /** + * Constructor + * + * @param array|callable $options + */ + public function __construct($options = null) + { + if (is_callable($options)) { + $options = ['callback' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the set callback + * + * @return mixed + */ + public function getCallback() + { + return $this->options['callback']; + } + + /** + * Sets the callback + * + * @param string|array|callable $callback + * @return Callback Provides a fluent interface + * @throws Exception\InvalidArgumentException + */ + public function setCallback($callback) + { + if (! is_callable($callback)) { + throw new Exception\InvalidArgumentException('Invalid callback given'); + } + + $this->options['callback'] = $callback; + return $this; + } + + /** + * Returns the set options for the callback + * + * @return mixed + */ + public function getCallbackOptions() + { + return $this->options['callbackOptions']; + } + + /** + * Sets options for the callback + * + * @param mixed $options + * @return Callback Provides a fluent interface + */ + public function setCallbackOptions($options) + { + $this->options['callbackOptions'] = (array) $options; + return $this; + } + + /** + * Returns true if and only if the set callback returns + * for the provided $value + * + * @param mixed $value + * @param mixed $context Additional context to provide to the callback + * @return bool + * @throws Exception\InvalidArgumentException + */ + public function isValid($value, $context = null) + { + $this->setValue($value); + + $options = $this->getCallbackOptions(); + $callback = $this->getCallback(); + if (empty($callback)) { + throw new Exception\InvalidArgumentException('No callback given'); + } + + $args = [$value]; + if (empty($options) && ! empty($context)) { + $args[] = $context; + } + if (! empty($options) && empty($context)) { + $args = array_merge($args, $options); + } + if (! empty($options) && ! empty($context)) { + $args[] = $context; + $args = array_merge($args, $options); + } + + try { + if (! call_user_func_array($callback, $args)) { + $this->error(self::INVALID_VALUE); + return false; + } + } catch (\Exception $e) { + $this->error(self::INVALID_CALLBACK); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/ConfigProvider.php b/lib/laminas/laminas-validator/src/ConfigProvider.php new file mode 100644 index 000000000..445303a87 --- /dev/null +++ b/lib/laminas/laminas-validator/src/ConfigProvider.php @@ -0,0 +1,44 @@ + $this->getDependencyConfig(), + ]; + } + + /** + * Return dependency mappings for this component. + * + * @return array + */ + public function getDependencyConfig() + { + return [ + 'aliases' => [ + 'ValidatorManager' => ValidatorPluginManager::class, + + // Legacy Zend Framework aliases + \Zend\Validator\ValidatorPluginManager::class => ValidatorPluginManager::class, + ], + 'factories' => [ + ValidatorPluginManager::class => ValidatorPluginManagerFactory::class, + ], + ]; + } +} diff --git a/lib/laminas/laminas-validator/src/CreditCard.php b/lib/laminas/laminas-validator/src/CreditCard.php new file mode 100644 index 000000000..21d71d8a1 --- /dev/null +++ b/lib/laminas/laminas-validator/src/CreditCard.php @@ -0,0 +1,332 @@ + "The input seems to contain an invalid checksum", + self::CONTENT => "The input must contain only digits", + self::INVALID => "Invalid type given. String expected", + self::LENGTH => "The input contains an invalid amount of digits", + self::PREFIX => "The input is not from an allowed institute", + self::SERVICE => "The input seems to be an invalid credit card number", + self::SERVICEFAILURE => "An exception has been raised while validating the input", + ]; + + /** + * List of CCV names + * + * @var array + */ + protected $cardName = [ + 0 => self::AMERICAN_EXPRESS, + 1 => self::DINERS_CLUB, + 2 => self::DINERS_CLUB_US, + 3 => self::DISCOVER, + 4 => self::JCB, + 5 => self::LASER, + 6 => self::MAESTRO, + 7 => self::MASTERCARD, + 8 => self::SOLO, + 9 => self::UNIONPAY, + 10 => self::VISA, + 11 => self::MIR, + ]; + + /** + * List of allowed CCV lengths + * + * @var array + */ + protected $cardLength = [ + self::AMERICAN_EXPRESS => [15], + self::DINERS_CLUB => [14], + self::DINERS_CLUB_US => [16], + self::DISCOVER => [16, 19], + self::JCB => [15, 16], + self::LASER => [16, 17, 18, 19], + self::MAESTRO => [12, 13, 14, 15, 16, 17, 18, 19], + self::MASTERCARD => [16], + self::SOLO => [16, 18, 19], + self::UNIONPAY => [16, 17, 18, 19], + self::VISA => [13, 16, 19], + self::MIR => [13, 16], + ]; + + /** + * List of accepted CCV provider tags + * + * @var array + */ + protected $cardType = [ + self::AMERICAN_EXPRESS => ['34', '37'], + self::DINERS_CLUB => ['300', '301', '302', '303', '304', '305', '36'], + self::DINERS_CLUB_US => ['54', '55'], + self::DISCOVER => ['6011', '622126', '622127', '622128', '622129', '62213', + '62214', '62215', '62216', '62217', '62218', '62219', + '6222', '6223', '6224', '6225', '6226', '6227', '6228', + '62290', '62291', '622920', '622921', '622922', '622923', + '622924', '622925', '644', '645', '646', '647', '648', + '649', '65'], + self::JCB => ['1800', '2131', '3528', '3529', '353', '354', '355', '356', '357', '358'], + self::LASER => ['6304', '6706', '6771', '6709'], + self::MAESTRO => ['5018', '5020', '5038', '6304', '6759', '6761', '6762', '6763', + '6764', '6765', '6766', '6772'], + self::MASTERCARD => ['2221', '2222', '2223', '2224', '2225', '2226', '2227', '2228', '2229', + '223', '224', '225', '226', '227', '228', '229', + '23', '24', '25', '26', '271', '2720', + '51', '52', '53', '54', '55'], + self::SOLO => ['6334', '6767'], + self::UNIONPAY => ['622126', '622127', '622128', '622129', '62213', '62214', + '62215', '62216', '62217', '62218', '62219', '6222', '6223', + '6224', '6225', '6226', '6227', '6228', '62290', '62291', + '622920', '622921', '622922', '622923', '622924', '622925'], + self::VISA => ['4'], + self::MIR => ['2200', '2201', '2202', '2203', '2204'], + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'service' => null, // Service callback for additional validation + 'type' => [], // CCIs which are accepted by validation + ]; + + /** + * Constructor + * + * @param string|array|Traversable $options OPTIONAL Type of CCI to allow + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp['type'] = array_shift($options); + if (! empty($options)) { + $temp['service'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('type', $options)) { + $options['type'] = self::ALL; + } + + $this->setType($options['type']); + unset($options['type']); + + if (array_key_exists('service', $options)) { + $this->setService($options['service']); + unset($options['service']); + } + + parent::__construct($options); + } + + /** + * Returns a list of accepted CCIs + * + * @return array + */ + public function getType() + { + return $this->options['type']; + } + + /** + * Sets CCIs which are accepted by validation + * + * @param string|array $type Type to allow for validation + * @return CreditCard Provides a fluid interface + */ + public function setType($type) + { + $this->options['type'] = []; + return $this->addType($type); + } + + /** + * Adds a CCI to be accepted by validation + * + * @param string|array $type Type to allow for validation + * @return CreditCard Provides a fluid interface + */ + public function addType($type) + { + if (is_string($type)) { + $type = [$type]; + } + + foreach ($type as $typ) { + if (($typ == self::ALL)) { + $this->options['type'] = array_keys($this->cardLength); + continue; + } + + if (in_array($typ, $this->options['type'])) { + continue; + } + + $constant = 'static::' . strtoupper($typ); + if (! defined($constant) || in_array(constant($constant), $this->options['type'])) { + continue; + } + $this->options['type'][] = constant($constant); + } + + return $this; + } + + /** + * Returns the actual set service + * + * @return callable + */ + public function getService() + { + return $this->options['service']; + } + + /** + * Sets a new callback for service validation + * + * @param callable $service + * @return CreditCard + * @throws Exception\InvalidArgumentException on invalid service callback + */ + public function setService($service) + { + if (! is_callable($service)) { + throw new Exception\InvalidArgumentException('Invalid callback given'); + } + + $this->options['service'] = $service; + return $this; + } + + /** + * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum) + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if (! is_string($value)) { + $this->error(self::INVALID, $value); + return false; + } + + if (! ctype_digit($value)) { + $this->error(self::CONTENT, $value); + return false; + } + + $length = strlen($value); + $types = $this->getType(); + $foundp = false; + $foundl = false; + foreach ($types as $type) { + foreach ($this->cardType[$type] as $prefix) { + if (0 === strpos($value, $prefix)) { + $foundp = true; + if (in_array($length, $this->cardLength[$type])) { + $foundl = true; + break 2; + } + } + } + } + + if ($foundp == false) { + $this->error(self::PREFIX, $value); + return false; + } + + if ($foundl == false) { + $this->error(self::LENGTH, $value); + return false; + } + + $sum = 0; + $weight = 2; + + for ($i = $length - 2; $i >= 0; $i--) { + $digit = $weight * $value[$i]; + $sum += floor($digit / 10) + $digit % 10; + $weight = $weight % 2 + 1; + } + + if ((10 - $sum % 10) % 10 != $value[$length - 1]) { + $this->error(self::CHECKSUM, $value); + return false; + } + + $service = $this->getService(); + if (! empty($service)) { + try { + $callback = new Callback($service); + $callback->setOptions($this->getType()); + if (! $callback->isValid($value)) { + $this->error(self::SERVICE, $value); + return false; + } + } catch (\Exception $e) { + $this->error(self::SERVICEFAILURE, $value); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Csrf.php b/lib/laminas/laminas-validator/src/Csrf.php new file mode 100644 index 000000000..8cc404eb0 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Csrf.php @@ -0,0 +1,377 @@ + "The form submitted did not originate from the expected site", + ]; + + /** + * Actual hash used. + * + * @var mixed + */ + protected $hash; + + /** + * Static cache of the session names to generated hashes + * @todo unused, left here to avoid BC breaks + * + * @var array + */ + protected static $hashCache; + + /** + * Name of CSRF element (used to create non-colliding hashes) + * + * @var string + */ + protected $name = 'csrf'; + + /** + * Salt for CSRF token + * @var string + */ + protected $salt = 'salt'; + + /** + * @var SessionContainer + */ + protected $session; + + /** + * TTL for CSRF token + * @var int|null + */ + protected $timeout = 300; + + /** + * Constructor + * + * @param array|Traversable $options + */ + public function __construct($options = []) + { + parent::__construct($options); + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if (! is_array($options)) { + $options = (array) $options; + } + + foreach ($options as $key => $value) { + switch (strtolower($key)) { + case 'name': + $this->setName($value); + break; + case 'salt': + $this->setSalt($value); + break; + case 'session': + $this->setSession($value); + break; + case 'timeout': + $this->setTimeout($value); + break; + default: + // ignore unknown options + break; + } + } + } + + /** + * Does the provided token match the one generated? + * + * @param string $value + * @param mixed $context + * @return bool + */ + public function isValid($value, $context = null) + { + if (! is_string($value)) { + return false; + } + + $this->setValue($value); + + $tokenId = $this->getTokenIdFromHash($value); + $hash = $this->getValidationToken($tokenId); + + $tokenFromValue = $this->getTokenFromHash($value); + $tokenFromHash = $this->getTokenFromHash($hash); + + if (! $tokenFromValue || ! $tokenFromHash || ($tokenFromValue !== $tokenFromHash)) { + $this->error(self::NOT_SAME); + return false; + } + + return true; + } + + /** + * Set CSRF name + * + * @param string $name + * @return Csrf + */ + public function setName($name) + { + $this->name = (string) $name; + return $this; + } + + /** + * Get CSRF name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set session container + * + * @param SessionContainer $session + * @return Csrf + */ + public function setSession(SessionContainer $session) + { + $this->session = $session; + if ($this->hash) { + $this->initCsrfToken(); + } + return $this; + } + + /** + * Get session container + * + * Instantiate session container if none currently exists + * + * @return SessionContainer + */ + public function getSession() + { + if (null === $this->session) { + // Using fully qualified name, to ensure polyfill class alias is used + $this->session = new SessionContainer($this->getSessionName()); + } + return $this->session; + } + + /** + * Salt for CSRF token + * + * @param string $salt + * @return Csrf + */ + public function setSalt($salt) + { + $this->salt = (string) $salt; + return $this; + } + + /** + * Retrieve salt for CSRF token + * + * @return string + */ + public function getSalt() + { + return $this->salt; + } + + /** + * Retrieve CSRF token + * + * If no CSRF token currently exists, or should be regenerated, + * generates one. + * + * @param bool $regenerate default false + * @return string + */ + public function getHash($regenerate = false) + { + if ((null === $this->hash) || $regenerate) { + $this->generateHash(); + } + return $this->hash; + } + + /** + * Get session namespace for CSRF token + * + * Generates a session namespace based on salt, element name, and class. + * + * @return string + */ + public function getSessionName() + { + return str_replace('\\', '_', __CLASS__) . '_' + . $this->getSalt() . '_' + . strtr($this->getName(), ['[' => '_', ']' => '']); + } + + /** + * Set timeout for CSRF session token + * + * @param int|null $ttl + * @return Csrf + */ + public function setTimeout($ttl) + { + $this->timeout = ($ttl !== null) ? (int) $ttl : null; + return $this; + } + + /** + * Get CSRF session token timeout + * + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Initialize CSRF token in session + * + * @return void + */ + protected function initCsrfToken() + { + $session = $this->getSession(); + $timeout = $this->getTimeout(); + if (null !== $timeout) { + $session->setExpirationSeconds($timeout); + } + + $hash = $this->getHash(); + $token = $this->getTokenFromHash($hash); + $tokenId = $this->getTokenIdFromHash($hash); + + if (! $session->tokenList) { + $session->tokenList = []; + } + $session->tokenList[$tokenId] = $token; + $session->hash = $hash; // @todo remove this, left for BC + } + + /** + * Generate CSRF token + * + * Generates CSRF token and stores both in {@link $hash} and element + * value. + * + * @return void + */ + protected function generateHash() + { + $token = md5($this->getSalt() . Rand::getBytes(32) . $this->getName()); + + $this->hash = $this->formatHash($token, $this->generateTokenId()); + + $this->setValue($this->hash); + $this->initCsrfToken(); + } + + /** + * @return string + */ + protected function generateTokenId() + { + return md5(Rand::getBytes(32)); + } + + /** + * Get validation token + * + * Retrieve token from session, if it exists. + * + * @param string $tokenId + * @return null|string + */ + protected function getValidationToken($tokenId = null) + { + $session = $this->getSession(); + + /** + * if no tokenId is passed we revert to the old behaviour + * @todo remove, here for BC + */ + if (! $tokenId && isset($session->hash)) { + return $session->hash; + } + + if ($tokenId && isset($session->tokenList[$tokenId])) { + return $this->formatHash($session->tokenList[$tokenId], $tokenId); + } + + return; + } + + /** + * @param $token + * @param $tokenId + * @return string + */ + protected function formatHash($token, $tokenId) + { + return sprintf('%s-%s', $token, $tokenId); + } + + /** + * @param $hash + * @return string + */ + protected function getTokenFromHash($hash) + { + $data = explode('-', $hash); + return $data[0] ?: null; + } + + /** + * @param $hash + * @return string + */ + protected function getTokenIdFromHash($hash) + { + $data = explode('-', $hash); + + if (! isset($data[1])) { + return; + } + + return $data[1]; + } +} diff --git a/lib/laminas/laminas-validator/src/Date.php b/lib/laminas/laminas-validator/src/Date.php new file mode 100644 index 000000000..a979b75c9 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Date.php @@ -0,0 +1,203 @@ + "Invalid type given. String, integer, array or DateTime expected", + self::INVALID_DATE => "The input does not appear to be a valid date", + self::FALSEFORMAT => "The input does not fit the date format '%format%'", + ]; + + /** + * @var array + */ + protected $messageVariables = [ + 'format' => 'format', + ]; + + /** + * @var string + */ + protected $format = self::FORMAT_DEFAULT; + + /** + * Sets validator options + * + * @param string|array|Traversable $options OPTIONAL + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp['format'] = array_shift($options); + $options = $temp; + } + + parent::__construct($options); + } + + /** + * Returns the format option + * + * @return string|null + */ + public function getFormat() + { + return $this->format; + } + + /** + * Sets the format option + * + * Format cannot be null. It will always default to 'Y-m-d', even + * if null is provided. + * + * @param string $format + * @return Date provides a fluent interface + * @todo validate the format + */ + public function setFormat($format = self::FORMAT_DEFAULT) + { + $this->format = (empty($format)) ? self::FORMAT_DEFAULT : $format; + return $this; + } + + /** + * Returns true if $value is a DateTime instance or can be converted into one. + * + * @param string|array|int|DateTime $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if (! $this->convertToDateTime($value)) { + $this->error(self::INVALID_DATE); + return false; + } + + return true; + } + + /** + * Attempts to convert an int, string, or array to a DateTime object + * + * @param string|int|array $param + * @param bool $addErrors + * @return bool|DateTime + */ + protected function convertToDateTime($param, $addErrors = true) + { + if ($param instanceof DateTime || $param instanceof DateTimeImmutable) { + return $param; + } + + $type = gettype($param); + if (! in_array($type, ['string', 'integer', 'double', 'array'])) { + if ($addErrors) { + $this->error(self::INVALID); + } + return false; + } + + $convertMethod = 'convert' . ucfirst($type); + return $this->{$convertMethod}($param, $addErrors); + } + + /** + * Attempts to convert an integer into a DateTime object + * + * @param integer $value + * @return bool|DateTime + */ + protected function convertInteger($value) + { + return date_create("@$value"); + } + + /** + * Attempts to convert an double into a DateTime object + * + * @param double $value + * @return bool|DateTime + */ + protected function convertDouble($value) + { + return DateTime::createFromFormat('U', $value); + } + + /** + * Attempts to convert a string into a DateTime object + * + * @param string $value + * @param bool $addErrors + * @return bool|DateTime + */ + protected function convertString($value, $addErrors = true) + { + $date = DateTime::createFromFormat($this->format, $value); + + // Invalid dates can show up as warnings (ie. "2007-02-99") + // and still return a DateTime object. + $errors = DateTime::getLastErrors(); + if ($errors['warning_count'] > 0) { + if ($addErrors) { + $this->error(self::FALSEFORMAT); + } + return false; + } + + return $date; + } + + /** + * Implodes the array into a string and proxies to {@link convertString()}. + * + * @param array $value + * @param bool $addErrors + * @return bool|DateTime + * @todo enhance the implosion + */ + protected function convertArray(array $value, $addErrors = true) + { + return $this->convertString(implode('-', $value), $addErrors); + } +} diff --git a/lib/laminas/laminas-validator/src/DateStep.php b/lib/laminas/laminas-validator/src/DateStep.php new file mode 100644 index 000000000..017948e5d --- /dev/null +++ b/lib/laminas/laminas-validator/src/DateStep.php @@ -0,0 +1,478 @@ + "Invalid type given. String, integer, array or DateTime expected", + self::INVALID_DATE => "The input does not appear to be a valid date", + self::FALSEFORMAT => "The input does not fit the date format '%format%'", + self::NOT_STEP => "The input is not a valid step", + ]; + + /** + * Optional base date value + * + * @var string|int|\DateTime + */ + protected $baseValue = '1970-01-01T00:00:00Z'; + + /** + * Date step interval (defaults to 1 day). + * Uses the DateInterval specification. + * + * @var DateInterval + */ + protected $step; + + /** + * Optional timezone to be used when the baseValue + * and validation values do not contain timezone info + * + * @var DateTimeZone + */ + protected $timezone; + + /** + * Set default options for this instance + * + * @param array $options + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp['baseValue'] = array_shift($options); + if (! empty($options)) { + $temp['step'] = array_shift($options); + } + if (! empty($options)) { + $temp['format'] = array_shift($options); + } + if (! empty($options)) { + $temp['timezone'] = array_shift($options); + } + + $options = $temp; + } + + if (! isset($options['step'])) { + $options['step'] = new DateInterval('P1D'); + } + if (! isset($options['timezone'])) { + $options['timezone'] = new DateTimeZone(date_default_timezone_get()); + } + + parent::__construct($options); + } + + /** + * Sets the base value from which the step should be computed + * + * @param string|int|\DateTime $baseValue + * @return DateStep + */ + public function setBaseValue($baseValue) + { + $this->baseValue = $baseValue; + return $this; + } + + /** + * Returns the base value from which the step should be computed + * + * @return string|int|\DateTime + */ + public function getBaseValue() + { + return $this->baseValue; + } + + /** + * Sets the step date interval + * + * @param DateInterval $step + * @return DateStep + */ + public function setStep(DateInterval $step) + { + $this->step = $step; + return $this; + } + + /** + * Returns the step date interval + * + * @return DateInterval + */ + public function getStep() + { + return $this->step; + } + + /** + * Returns the timezone option + * + * @return DateTimeZone + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * Sets the timezone option + * + * @param DateTimeZone $timezone + * @return DateStep + */ + public function setTimezone(DateTimeZone $timezone) + { + $this->timezone = $timezone; + return $this; + } + + /** + * Supports formats with ISO week (W) definitions + * + * @see Date::convertString() + */ + protected function convertString($value, $addErrors = true) + { + // Custom week format support + if (strpos($this->format, 'Y-\WW') === 0 + && preg_match('/^([0-9]{4})\-W([0-9]{2})/', $value, $matches) + ) { + $date = new DateTime(); + $date->setISODate($matches[1], $matches[2]); + } else { + $date = DateTime::createFromFormat($this->format, $value, new DateTimeZone('UTC')); + } + + // Invalid dates can show up as warnings (ie. "2007-02-99") + // and still return a DateTime object. + $errors = DateTime::getLastErrors(); + if ($errors['warning_count'] > 0) { + if ($addErrors) { + $this->error(self::FALSEFORMAT); + } + return false; + } + + return $date; + } + + /** + * Returns true if a date is within a valid step + * + * @param string|int|\DateTime $value + * @return bool + * @throws Exception\InvalidArgumentException + */ + public function isValid($value) + { + if (! parent::isValid($value)) { + return false; + } + + $valueDate = $this->convertToDateTime($value, false); // avoid duplicate errors + $baseDate = $this->convertToDateTime($this->baseValue, false); + $step = $this->getStep(); + + // Same date? + if ($valueDate == $baseDate) { + return true; + } + + // Optimization for simple intervals. + // Handle intervals of just one date or time unit. + $intervalParts = explode('|', $step->format('%y|%m|%d|%h|%i|%s')); + $partCounts = array_count_values($intervalParts); + + $unitKeys = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']; + $intervalParts = array_combine($unitKeys, $intervalParts); + + // Get absolute time difference to avoid special cases of missing/added time + $absoluteValueDate = new DateTime($valueDate->format('Y-m-d H:i:s'), new DateTimeZone('UTC')); + $absoluteBaseDate = new DateTime($baseDate->format('Y-m-d H:i:s'), new DateTimeZone('UTC')); + + $timeDiff = $absoluteValueDate->diff($absoluteBaseDate, 1); + $diffParts = array_combine($unitKeys, explode('|', $timeDiff->format('%y|%m|%d|%h|%i|%s'))); + + if (5 === $partCounts["0"]) { + // Find the unit with the non-zero interval + $intervalUnit = null; + $stepValue = null; + foreach ($intervalParts as $key => $value) { + if (0 != $value) { + $intervalUnit = $key; + $stepValue = (int) $value; + break; + } + } + + // Check date units + if (in_array($intervalUnit, ['years', 'months', 'days'])) { + switch ($intervalUnit) { + case 'years': + if (0 == $diffParts['months'] && 0 == $diffParts['days'] + && 0 == $diffParts['hours'] && 0 == $diffParts['minutes'] + && 0 == $diffParts['seconds'] + ) { + if (($diffParts['years'] % $stepValue) === 0) { + return true; + } + } + break; + case 'months': + if (0 == $diffParts['days'] && 0 == $diffParts['hours'] + && 0 == $diffParts['minutes'] && 0 == $diffParts['seconds'] + ) { + $months = ($diffParts['years'] * 12) + $diffParts['months']; + if (($months % $stepValue) === 0) { + return true; + } + } + break; + case 'days': + if (0 == $diffParts['hours'] && 0 == $diffParts['minutes'] + && 0 == $diffParts['seconds'] + ) { + $days = $timeDiff->format('%a'); // Total days + if (($days % $stepValue) === 0) { + return true; + } + } + break; + } + $this->error(self::NOT_STEP); + return false; + } + + // Check time units + if (in_array($intervalUnit, ['hours', 'minutes', 'seconds'])) { + // Simple test if $stepValue is 1. + if (1 == $stepValue) { + if ('hours' === $intervalUnit + && 0 == $diffParts['minutes'] && 0 == $diffParts['seconds'] + ) { + return true; + } elseif ('minutes' === $intervalUnit && 0 == $diffParts['seconds']) { + return true; + } elseif ('seconds' === $intervalUnit) { + return true; + } + + $this->error(self::NOT_STEP); + + return false; + } + + // Simple test for same day, when using default baseDate + if ($baseDate->format('Y-m-d') == $valueDate->format('Y-m-d') + && $baseDate->format('Y-m-d') == '1970-01-01' + ) { + switch ($intervalUnit) { + case 'hours': + if (0 == $diffParts['minutes'] && 0 == $diffParts['seconds']) { + if (($diffParts['hours'] % $stepValue) === 0) { + return true; + } + } + break; + case 'minutes': + if (0 == $diffParts['seconds']) { + $minutes = ($diffParts['hours'] * 60) + $diffParts['minutes']; + if (($minutes % $stepValue) === 0) { + return true; + } + } + break; + case 'seconds': + $seconds = ($diffParts['hours'] * 60 * 60) + + ($diffParts['minutes'] * 60) + + $diffParts['seconds']; + if (($seconds % $stepValue) === 0) { + return true; + } + break; + } + $this->error(self::NOT_STEP); + return false; + } + } + } + + return $this->fallbackIncrementalIterationLogic($baseDate, $valueDate, $intervalParts, $diffParts, $step); + } + + /** + * Fall back to slower (but accurate) method for complex intervals. + * Keep adding steps to the base date until a match is found + * or until the value is exceeded. + * + * This is really slow if the interval is small, especially if the + * default base date of 1/1/1970 is used. We can skip a chunk of + * iterations by starting at the lower bound of steps needed to reach + * the target + * + * @param DateTime $baseDate + * @param DateTime $valueDate + * @param int[] $intervalParts + * @param int[] $diffParts + * @param DateInterval $step + * + * @return bool + */ + private function fallbackIncrementalIterationLogic( + DateTime $baseDate, + DateTime $valueDate, + array $intervalParts, + array $diffParts, + DateInterval $step + ) { + list($minSteps, $requiredIterations) = $this->computeMinStepAndRequiredIterations($intervalParts, $diffParts); + $minimumInterval = $this->computeMinimumInterval($intervalParts, $minSteps); + $isIncrementalStepping = $baseDate < $valueDate; + $dateModificationOperation = $isIncrementalStepping ? 'add' : 'sub'; + + for ($offsetIterations = 0; $offsetIterations < $requiredIterations; $offsetIterations += 1) { + $baseDate->{$dateModificationOperation}($minimumInterval); + } + + while (($isIncrementalStepping && $baseDate < $valueDate) + || (! $isIncrementalStepping && $baseDate > $valueDate) + ) { + $baseDate->{$dateModificationOperation}($step); + + if ($baseDate == $valueDate) { + return true; + } + } + + $this->error(self::NOT_STEP); + + return false; + } + + /** + * Computes minimum interval to use for iterations while checking steps + * + * @param int[] $intervalParts + * @param int $minSteps + * + * @return DateInterval + */ + private function computeMinimumInterval(array $intervalParts, $minSteps) + { + return new DateInterval(sprintf( + 'P%dY%dM%dDT%dH%dM%dS', + $intervalParts['years'] * $minSteps, + $intervalParts['months'] * $minSteps, + $intervalParts['days'] * $minSteps, + $intervalParts['hours'] * $minSteps, + $intervalParts['minutes'] * $minSteps, + $intervalParts['seconds'] * $minSteps + )); + } + + /** + * @param int[] $intervalParts + * @param int[] $diffParts + * + * @return int[] (ordered tuple containing minimum steps and required step iterations + */ + private function computeMinStepAndRequiredIterations(array $intervalParts, array $diffParts) + { + $minSteps = $this->computeMinSteps($intervalParts, $diffParts); + + // If we use PHP_INT_MAX DateInterval::__construct falls over with a bad format error + // before we reach the max on 64 bit machines + $maxInteger = min(pow(2, 31), PHP_INT_MAX); + // check for integer overflow and split $minimum interval if needed + $maximumInterval = max($intervalParts); + $requiredStepIterations = 1; + + if (($minSteps * $maximumInterval) > $maxInteger) { + $requiredStepIterations = ceil(($minSteps * $maximumInterval) / $maxInteger); + $minSteps = floor($minSteps / $requiredStepIterations); + } + + return [$minSteps, $minSteps ? $requiredStepIterations : 0]; + } + + /** + * Multiply the step interval by the lower bound of steps to reach the target + * + * @param int[] $intervalParts + * @param int[] $diffParts + * + * @return int + */ + private function computeMinSteps(array $intervalParts, array $diffParts) + { + $intervalMaxSeconds = $this->computeIntervalMaxSeconds($intervalParts); + + return (0 == $intervalMaxSeconds) + ? 0 + : max(floor($this->computeDiffMinSeconds($diffParts) / $intervalMaxSeconds) - 1, 0); + } + + /** + * Get upper bound of the given interval in seconds + * Converts a given `$intervalParts` array into seconds + * + * @param int[] $intervalParts + * + * @return int + */ + private function computeIntervalMaxSeconds(array $intervalParts) + { + return ($intervalParts['years'] * 60 * 60 * 24 * 366) + + ($intervalParts['months'] * 60 * 60 * 24 * 31) + + ($intervalParts['days'] * 60 * 60 * 24) + + ($intervalParts['hours'] * 60 * 60) + + ($intervalParts['minutes'] * 60) + + $intervalParts['seconds']; + } + + /** + * Get lower bound of difference in secondss + * Converts a given `$diffParts` array into seconds + * + * @param int[] $diffParts + * + * @return int + */ + private function computeDiffMinSeconds(array $diffParts) + { + return ($diffParts['years'] * 60 * 60 * 24 * 365) + + ($diffParts['months'] * 60 * 60 * 24 * 28) + + ($diffParts['days'] * 60 * 60 * 24) + + ($diffParts['hours'] * 60 * 60) + + ($diffParts['minutes'] * 60) + + $diffParts['seconds']; + } +} diff --git a/lib/laminas/laminas-validator/src/Db/AbstractDb.php b/lib/laminas/laminas-validator/src/Db/AbstractDb.php new file mode 100644 index 000000000..baca40a02 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Db/AbstractDb.php @@ -0,0 +1,325 @@ + "No record matching the input was found", + self::ERROR_RECORD_FOUND => "A record matching the input was found", + ]; + + /** + * Select object to use. can be set, or will be auto-generated + * + * @var Select + */ + protected $select; + + /** + * @var string + */ + protected $schema = null; + + /** + * @var string + */ + protected $table = ''; + + /** + * @var string + */ + protected $field = ''; + + /** + * @var mixed + */ + protected $exclude = null; + + /** + * Provides basic configuration for use with Laminas\Validator\Db Validators + * Setting $exclude allows a single record to be excluded from matching. + * Exclude can either be a String containing a where clause, or an array with `field` and `value` keys + * to define the where clause added to the sql. + * A database adapter may optionally be supplied to avoid using the registered default adapter. + * + * The following option keys are supported: + * 'table' => The database table to validate against + * 'schema' => The schema keys + * 'field' => The field to check for a match + * 'exclude' => An optional where clause or field/value pair to exclude from the query + * 'adapter' => An optional database adapter to use + * + * @param array|Traversable|Select $options Options to use for this validator + * @throws \Laminas\Validator\Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + parent::__construct($options); + + if ($options instanceof Select) { + $this->setSelect($options); + return; + } + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (func_num_args() > 1) { + $options = func_get_args(); + $firstArgument = array_shift($options); + if (is_array($firstArgument)) { + $temp = ArrayUtils::iteratorToArray($firstArgument); + } else { + $temp['table'] = $firstArgument; + } + + $temp['field'] = array_shift($options); + + if (! empty($options)) { + $temp['exclude'] = array_shift($options); + } + + if (! empty($options)) { + $temp['adapter'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('table', $options) && ! array_key_exists('schema', $options)) { + throw new Exception\InvalidArgumentException('Table or Schema option missing!'); + } + + if (! array_key_exists('field', $options)) { + throw new Exception\InvalidArgumentException('Field option missing!'); + } + + if (array_key_exists('adapter', $options)) { + $this->setAdapter($options['adapter']); + } + + if (array_key_exists('exclude', $options)) { + $this->setExclude($options['exclude']); + } + + $this->setField($options['field']); + if (array_key_exists('table', $options)) { + $this->setTable($options['table']); + } + + if (array_key_exists('schema', $options)) { + $this->setSchema($options['schema']); + } + } + + /** + * Returns the set adapter + * + * @throws \Laminas\Validator\Exception\RuntimeException When no database adapter is defined + * @return DbAdapter + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Sets a new database adapter + * + * @param DbAdapter $adapter + * @return self Provides a fluent interface + */ + public function setAdapter(DbAdapter $adapter) + { + return $this->setDbAdapter($adapter); + } + + /** + * Returns the set exclude clause + * + * @return string|array + */ + public function getExclude() + { + return $this->exclude; + } + + /** + * Sets a new exclude clause + * + * @param string|array $exclude + * @return self Provides a fluent interface + */ + public function setExclude($exclude) + { + $this->exclude = $exclude; + $this->select = null; + return $this; + } + + /** + * Returns the set field + * + * @return string|array + */ + public function getField() + { + return $this->field; + } + + /** + * Sets a new field + * + * @param string $field + * @return AbstractDb + */ + public function setField($field) + { + $this->field = (string) $field; + $this->select = null; + return $this; + } + + /** + * Returns the set table + * + * @return string + */ + public function getTable() + { + return $this->table; + } + + /** + * Sets a new table + * + * @param string $table + * @return self Provides a fluent interface + */ + public function setTable($table) + { + $this->table = (string) $table; + $this->select = null; + return $this; + } + + /** + * Returns the set schema + * + * @return string + */ + public function getSchema() + { + return $this->schema; + } + + /** + * Sets a new schema + * + * @param string $schema + * @return self Provides a fluent interface + */ + public function setSchema($schema) + { + $this->schema = $schema; + $this->select = null; + return $this; + } + + /** + * Sets the select object to be used by the validator + * + * @param Select $select + * @return self Provides a fluent interface + */ + public function setSelect(Select $select) + { + $this->select = $select; + return $this; + } + + /** + * Gets the select object to be used by the validator. + * If no select object was supplied to the constructor, + * then it will auto-generate one from the given table, + * schema, field, and adapter options. + * + * @return Select The Select object which will be used + */ + public function getSelect() + { + if ($this->select instanceof Select) { + return $this->select; + } + + // Build select object + $select = new Select(); + $tableIdentifier = new TableIdentifier($this->table, $this->schema); + $select->from($tableIdentifier)->columns([$this->field]); + $select->where->equalTo($this->field, null); + + if ($this->exclude !== null) { + if (is_array($this->exclude)) { + $select->where->notEqualTo( + $this->exclude['field'], + $this->exclude['value'] + ); + } else { + $select->where($this->exclude); + } + } + + $this->select = $select; + + return $this->select; + } + + /** + * Run query and returns matches, or null if no matches are found. + * + * @param string $value + * @return array when matches are found. + */ + protected function query($value) + { + $sql = new Sql($this->getAdapter()); + $select = $this->getSelect(); + $statement = $sql->prepareStatementForSqlObject($select); + $parameters = $statement->getParameterContainer(); + $parameters['where1'] = $value; + $result = $statement->execute(); + + return $result->current(); + } +} diff --git a/lib/laminas/laminas-validator/src/Db/NoRecordExists.php b/lib/laminas/laminas-validator/src/Db/NoRecordExists.php new file mode 100644 index 000000000..16be50739 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Db/NoRecordExists.php @@ -0,0 +1,38 @@ +adapter) { + throw new Exception\RuntimeException('No database adapter present'); + } + + $valid = true; + $this->setValue($value); + + $result = $this->query($value); + if ($result) { + $valid = false; + $this->error(self::ERROR_RECORD_FOUND); + } + + return $valid; + } +} diff --git a/lib/laminas/laminas-validator/src/Db/RecordExists.php b/lib/laminas/laminas-validator/src/Db/RecordExists.php new file mode 100644 index 000000000..5bb1cc951 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Db/RecordExists.php @@ -0,0 +1,38 @@ +adapter) { + throw new Exception\RuntimeException('No database adapter present'); + } + + $valid = true; + $this->setValue($value); + + $result = $this->query($value); + if (! $result) { + $valid = false; + $this->error(self::ERROR_NO_RECORD_FOUND); + } + + return $valid; + } +} diff --git a/lib/laminas/laminas-validator/src/Digits.php b/lib/laminas/laminas-validator/src/Digits.php new file mode 100644 index 000000000..a3997919f --- /dev/null +++ b/lib/laminas/laminas-validator/src/Digits.php @@ -0,0 +1,68 @@ + "The input must contain only digits", + self::STRING_EMPTY => "The input is an empty string", + self::INVALID => "Invalid type given. String, integer or float expected", + ]; + + /** + * Returns true if and only if $value only contains digit characters + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value) && ! is_int($value) && ! is_float($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue((string) $value); + + if ('' === $this->getValue()) { + $this->error(self::STRING_EMPTY); + return false; + } + + if (null === static::$filter) { + static::$filter = new DigitsFilter(); + } + + if ($this->getValue() !== static::$filter->filter($this->getValue())) { + $this->error(self::NOT_DIGITS); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/EmailAddress.php b/lib/laminas/laminas-validator/src/EmailAddress.php new file mode 100644 index 000000000..cc40c97b0 --- /dev/null +++ b/lib/laminas/laminas-validator/src/EmailAddress.php @@ -0,0 +1,589 @@ + "Invalid type given. String expected", + self::INVALID_FORMAT => "The input is not a valid email address. Use the basic format local-part@hostname", + self::INVALID_HOSTNAME => "'%hostname%' is not a valid hostname for the email address", + self::INVALID_MX_RECORD => "'%hostname%' does not appear to have any valid MX or A records for the email address", + self::INVALID_SEGMENT => "'%hostname%' is not in a routable network segment. The email address should not be resolved from public network", + self::DOT_ATOM => "'%localPart%' can not be matched against dot-atom format", + self::QUOTED_STRING => "'%localPart%' can not be matched against quoted-string format", + self::INVALID_LOCAL_PART => "'%localPart%' is not a valid local part for the email address", + self::LENGTH_EXCEEDED => "The input exceeds the allowed length", + ]; + // @codingStandardsIgnoreEnd + + /** + * @var array + */ + protected $messageVariables = [ + 'hostname' => 'hostname', + 'localPart' => 'localPart' + ]; + + /** + * @var string + */ + protected $hostname; + + /** + * @var string + */ + protected $localPart; + + /** + * Returns the found mx record information + * + * @var array + */ + protected $mxRecord = []; + + /** + * Internal options array + */ + protected $options = [ + 'useMxCheck' => false, + 'useDeepMxCheck' => false, + 'useDomainCheck' => true, + 'allow' => Hostname::ALLOW_DNS, + 'strict' => true, + 'hostnameValidator' => null, + ]; + + /** + * Instantiates hostname validator for local use + * + * The following additional option keys are supported: + * 'hostnameValidator' => A hostname validator, see Laminas\Validator\Hostname + * 'allow' => Options for the hostname validator, see Laminas\Validator\Hostname::ALLOW_* + * 'strict' => Whether to adhere to strictest requirements in the spec + * 'useMxCheck' => If MX check should be enabled, boolean + * 'useDeepMxCheck' => If a deep MX check should be done, boolean + * + * @param array|\Traversable $options OPTIONAL + */ + public function __construct($options = []) + { + if (! is_array($options)) { + $options = func_get_args(); + $temp['allow'] = array_shift($options); + if (! empty($options)) { + $temp['useMxCheck'] = array_shift($options); + } + + if (! empty($options)) { + $temp['hostnameValidator'] = array_shift($options); + } + + $options = $temp; + } + + parent::__construct($options); + } + + /** + * Sets the validation failure message template for a particular key + * Adds the ability to set messages to the attached hostname validator + * + * @param string $messageString + * @param string $messageKey OPTIONAL + * @return AbstractValidator Provides a fluent interface + */ + public function setMessage($messageString, $messageKey = null) + { + if ($messageKey === null) { + $this->getHostnameValidator()->setMessage($messageString); + parent::setMessage($messageString); + return $this; + } + + if (! isset($this->messageTemplates[$messageKey])) { + $this->getHostnameValidator()->setMessage($messageString, $messageKey); + } else { + parent::setMessage($messageString, $messageKey); + } + + return $this; + } + + /** + * Returns the set hostname validator + * + * If was not previously set then lazy load a new one + * + * @return Hostname + */ + public function getHostnameValidator() + { + if (! isset($this->options['hostnameValidator'])) { + $this->options['hostnameValidator'] = new Hostname($this->getAllow()); + } + + return $this->options['hostnameValidator']; + } + + /** + * @param Hostname $hostnameValidator OPTIONAL + * @return EmailAddress Provides a fluent interface + */ + public function setHostnameValidator(Hostname $hostnameValidator = null) + { + $this->options['hostnameValidator'] = $hostnameValidator; + + return $this; + } + + /** + * Returns the allow option of the attached hostname validator + * + * @return int + */ + public function getAllow() + { + return $this->options['allow']; + } + + /** + * Sets the allow option of the hostname validator to use + * + * @param int $allow + * @return EmailAddress Provides a fluent interface + */ + public function setAllow($allow) + { + $this->options['allow'] = $allow; + if (isset($this->options['hostnameValidator'])) { + $this->options['hostnameValidator']->setAllow($allow); + } + + return $this; + } + + /** + * Whether MX checking via getmxrr is supported or not + * + * @return bool + */ + public function isMxSupported() + { + return function_exists('getmxrr'); + } + + /** + * Returns the set validateMx option + * + * @return bool + */ + public function getMxCheck() + { + return $this->options['useMxCheck']; + } + + /** + * Set whether we check for a valid MX record via DNS + * + * This only applies when DNS hostnames are validated + * + * @param bool $mx Set allowed to true to validate for MX records, and false to not validate them + * @return EmailAddress Fluid Interface + */ + public function useMxCheck($mx) + { + $this->options['useMxCheck'] = (bool) $mx; + return $this; + } + + /** + * Returns the set deepMxCheck option + * + * @return bool + */ + public function getDeepMxCheck() + { + return $this->options['useDeepMxCheck']; + } + + /** + * Use deep validation for MX records + * + * @param bool $deep Set deep to true to perform a deep validation process for MX records + * @return EmailAddress Fluid Interface + */ + public function useDeepMxCheck($deep) + { + $this->options['useDeepMxCheck'] = (bool) $deep; + return $this; + } + + /** + * Returns the set domainCheck option + * + * @return bool + */ + public function getDomainCheck() + { + return $this->options['useDomainCheck']; + } + + /** + * Sets if the domain should also be checked + * or only the local part of the email address + * + * @param bool $domain + * @return EmailAddress Fluid Interface + */ + public function useDomainCheck($domain = true) + { + $this->options['useDomainCheck'] = (bool) $domain; + return $this; + } + + /** + * Returns if the given host is reserved + * + * The following addresses are seen as reserved + * '0.0.0.0/8', '10.0.0.0/8', '127.0.0.0/8' + * '100.64.0.0/10' + * '172.16.0.0/12' + * '198.18.0.0/15' + * '169.254.0.0/16', '192.168.0.0/16' + * '192.0.2.0/24', '192.88.99.0/24', '198.51.100.0/24', '203.0.113.0/24' + * '224.0.0.0/4', '240.0.0.0/4' + * @see http://en.wikipedia.org/wiki/Reserved_IP_addresses + * + * As of RFC5753 (JAN 2010), the following blocks are no longer reserved: + * - 128.0.0.0/16 + * - 191.255.0.0/16 + * - 223.255.255.0/24 + * @see http://tools.ietf.org/html/rfc5735#page-6 + * + * As of RFC6598 (APR 2012), the following blocks are now reserved: + * - 100.64.0.0/10 + * @see http://tools.ietf.org/html/rfc6598#section-7 + * + * @param string $host + * @return bool Returns false when minimal one of the given addresses is not reserved + */ + protected function isReserved($host) + { + if (! preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $host)) { + $host = gethostbynamel($host); + } else { + $host = [$host]; + } + + if (empty($host)) { + return false; + } + + foreach ($host as $server) { + // @codingStandardsIgnoreStart + // Search for 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8 + if (!preg_match('/^(0|10|127)(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){3}$/', $server) && + // Search for 100.64.0.0/10 + !preg_match('/^100\.(6[0-4]|[7-9][0-9]|1[0-1][0-9]|12[0-7])(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) && + // Search for 172.16.0.0/12 + !preg_match('/^172\.(1[6-9]|2[0-9]|3[0-1])(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) && + // Search for 198.18.0.0/15 + !preg_match('/^198\.(1[8-9])(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) && + // Search for 169.254.0.0/16, 192.168.0.0/16 + !preg_match('/^(169\.254|192\.168)(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) && + // Search for 192.0.2.0/24, 192.88.99.0/24, 198.51.100.0/24, 203.0.113.0/24 + !preg_match('/^(192\.0\.2|192\.88\.99|198\.51\.100|203\.0\.113)\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))$/', $server) && + // Search for 224.0.0.0/4, 240.0.0.0/4 + !preg_match('/^(2(2[4-9]|[3-4][0-9]|5[0-5]))(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){3}$/', $server) + ) { + return false; + } + // @codingStandardsIgnoreEnd + } + + return true; + } + + /** + * Internal method to validate the local part of the email address + * + * @return bool + */ + protected function validateLocalPart() + { + // First try to match the local part on the common dot-atom format + $result = false; + + // Dot-atom characters are: 1*atext *("." 1*atext) + // atext: ALPHA / DIGIT / and "!", "#", "$", "%", "&", "'", "*", + // "+", "-", "/", "=", "?", "^", "_", "`", "{", "|", "}", "~" + $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d\x7e'; + if (preg_match('/^[' . $atext . ']+(\x2e+[' . $atext . ']+)*$/', $this->localPart)) { + $result = true; + } elseif ($this->validateInternationalizedLocalPart($this->localPart)) { + $result = true; + } else { + // Try quoted string format (RFC 5321 Chapter 4.1.2) + + // Quoted-string characters are: DQUOTE *(qtext/quoted-pair) DQUOTE + $qtext = '\x20-\x21\x23-\x5b\x5d-\x7e'; // %d32-33 / %d35-91 / %d93-126 + $quotedPair = '\x20-\x7e'; // %d92 %d32-126 + if (preg_match('/^"(['. $qtext .']|\x5c[' . $quotedPair . '])*"$/', $this->localPart)) { + $result = true; + } else { + $this->error(self::DOT_ATOM); + $this->error(self::QUOTED_STRING); + $this->error(self::INVALID_LOCAL_PART); + } + } + + return $result; + } + + /** + * @param string $localPart Address local part to validate. + * @return bool + */ + protected function validateInternationalizedLocalPart($localPart) + { + if (extension_loaded('intl') + && false === UConverter::transcode($localPart, 'UTF-8', 'UTF-8') + ) { + // invalid utf? + return false; + } + + $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d\x7e'; + // RFC 6532 extends atext to include non-ascii utf + // @see https://tools.ietf.org/html/rfc6532#section-3.1 + $uatext = $atext . '\x{80}-\x{FFFF}'; + return (bool) preg_match('/^[' . $uatext . ']+(\x2e+[' . $uatext . ']+)*$/u', $localPart); + } + + /** + * Returns the found MX Record information after validation including weight for further processing + * + * @return array + */ + public function getMXRecord() + { + return $this->mxRecord; + } + + /** + * Internal method to validate the servers MX records + * + * @return bool + */ + protected function validateMXRecords() + { + $mxHosts = []; + $weight = []; + $result = getmxrr($this->hostname, $mxHosts, $weight); + if (! empty($mxHosts) && ! empty($weight)) { + $this->mxRecord = array_combine($mxHosts, $weight) ?: []; + } else { + $this->mxRecord = []; + } + + arsort($this->mxRecord); + + // Fallback to IPv4 hosts if no MX record found (RFC 2821 SS 5). + if (! $result) { + $result = gethostbynamel($this->hostname); + if (is_array($result)) { + $this->mxRecord = array_flip($result); + } + } + + if (! $result) { + $this->error(self::INVALID_MX_RECORD); + return $result; + } + + if (! $this->options['useDeepMxCheck']) { + return $result; + } + + $validAddress = false; + $reserved = true; + foreach ($this->mxRecord as $hostname => $weight) { + $res = $this->isReserved($hostname); + if (! $res) { + $reserved = false; + } + + if (! $res + && (checkdnsrr($hostname, "A") + || checkdnsrr($hostname, "AAAA") + || checkdnsrr($hostname, "A6")) + ) { + $validAddress = true; + break; + } + } + + if (! $validAddress) { + $result = false; + $error = ($reserved) ? self::INVALID_SEGMENT : self::INVALID_MX_RECORD; + $this->error($error); + } + + return $result; + } + + /** + * Internal method to validate the hostname part of the email address + * + * @return bool + */ + protected function validateHostnamePart() + { + $hostname = $this->getHostnameValidator()->setTranslator($this->getTranslator()) + ->isValid($this->hostname); + if (! $hostname) { + $this->error(self::INVALID_HOSTNAME); + // Get messages and errors from hostnameValidator + foreach ($this->getHostnameValidator()->getMessages() as $code => $message) { + $this->abstractOptions['messages'][$code] = $message; + } + } elseif ($this->options['useMxCheck']) { + // MX check on hostname + $hostname = $this->validateMXRecords(); + } + + return $hostname; + } + + /** + * Splits the given value in hostname and local part of the email address + * + * @param string $value Email address to be split + * @return bool Returns false when the email can not be split + */ + protected function splitEmailParts($value) + { + $value = is_string($value) ? $value : ''; + + // Split email address up and disallow '..' + if (strpos($value, '..') !== false + || ! preg_match('/^(.+)@([^@]+)$/', $value, $matches) + ) { + return false; + } + + $this->localPart = $matches[1]; + $this->hostname = $this->idnToAscii($matches[2]); + + return true; + } + + /** + * Defined by Laminas\Validator\ValidatorInterface + * + * Returns true if and only if $value is a valid email address + * according to RFC2822 + * + * @link http://www.ietf.org/rfc/rfc2822.txt RFC2822 + * @link http://www.columbia.edu/kermit/ascii.html US-ASCII characters + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $length = true; + $this->setValue($value); + + // Split email address up and disallow '..' + if (! $this->splitEmailParts($this->getValue())) { + $this->error(self::INVALID_FORMAT); + return false; + } + + if ($this->getOption('strict') && (strlen($this->localPart) > 64) || (strlen($this->hostname) > 255)) { + $length = false; + $this->error(self::LENGTH_EXCEEDED); + } + + // Match hostname part + $hostname = false; + if ($this->options['useDomainCheck']) { + $hostname = $this->validateHostnamePart(); + } + + $local = $this->validateLocalPart(); + + // If both parts valid, return true + return ($local && $length) && (! $this->options['useDomainCheck'] || $hostname); + } + + /** + * Safely convert UTF-8 encoded domain name to ASCII + * @param string $email the UTF-8 encoded email + * @return string + */ + protected function idnToAscii($email) + { + if (extension_loaded('intl')) { + if (defined('INTL_IDNA_VARIANT_UTS46')) { + return (idn_to_ascii($email, 0, INTL_IDNA_VARIANT_UTS46) ?: $email); + } + return (idn_to_ascii($email) ?: $email); + } + return $email; + } + + /** + * Safely convert ASCII encoded domain name to UTF-8 + * @param string $email the ASCII encoded email + * @return string + */ + protected function idnToUtf8($email) + { + if (strlen($email) == 0) { + return $email; + } + + if (extension_loaded('intl')) { + // The documentation does not clarify what kind of failure + // can happen in idn_to_utf8. One can assume if the source + // is not IDN encoded, it would fail, but it usually returns + // the source string in those cases. + // But not when the source string is long enough. + // Thus we default to source string ourselves. + if (defined('INTL_IDNA_VARIANT_UTS46')) { + return idn_to_utf8($email, 0, INTL_IDNA_VARIANT_UTS46) ?: $email; + } + return idn_to_utf8($email) ?: $email; + } + return $email; + } +} diff --git a/lib/laminas/laminas-validator/src/Exception/BadMethodCallException.php b/lib/laminas/laminas-validator/src/Exception/BadMethodCallException.php new file mode 100644 index 000000000..460d78821 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Exception/BadMethodCallException.php @@ -0,0 +1,13 @@ + "Invalid type given", + ]; + + /** + * @var array + */ + protected $messageVariables = []; + + /** + * @var string + */ + protected $valueDelimiter = ','; + + /** + * @var ValidatorInterface + */ + protected $validator; + + /** + * @var bool + */ + protected $breakOnFirstFailure = false; + + /** + * Sets the delimiter string that the values will be split upon + * + * @param string $delimiter + * @return Explode + */ + public function setValueDelimiter($delimiter) + { + $this->valueDelimiter = $delimiter; + return $this; + } + + /** + * Returns the delimiter string that the values will be split upon + * + * @return string + */ + public function getValueDelimiter() + { + return $this->valueDelimiter; + } + + /** + * Set validator plugin manager + * + * @param ValidatorPluginManager $pluginManager + */ + public function setValidatorPluginManager(ValidatorPluginManager $pluginManager) + { + $this->pluginManager = $pluginManager; + } + + /** + * Get validator plugin manager + * + * @return ValidatorPluginManager + */ + public function getValidatorPluginManager() + { + if (! $this->pluginManager) { + $this->setValidatorPluginManager(new ValidatorPluginManager(new ServiceManager)); + } + + return $this->pluginManager; + } + + /** + * Sets the Validator for validating each value + * + * @param ValidatorInterface|array $validator + * @throws Exception\RuntimeException + * @return Explode + */ + public function setValidator($validator) + { + if (is_array($validator)) { + if (! isset($validator['name'])) { + throw new Exception\RuntimeException( + 'Invalid validator specification provided; does not include "name" key' + ); + } + $name = $validator['name']; + $options = isset($validator['options']) ? $validator['options'] : []; + $validator = $this->getValidatorPluginManager()->get($name, $options); + } + + if (! $validator instanceof ValidatorInterface) { + throw new Exception\RuntimeException( + 'Invalid validator given' + ); + } + + $this->validator = $validator; + return $this; + } + + /** + * Gets the Validator for validating each value + * + * @return ValidatorInterface + */ + public function getValidator() + { + return $this->validator; + } + + /** + * Set break on first failure setting + * + * @param bool $break + * @return Explode + */ + public function setBreakOnFirstFailure($break) + { + $this->breakOnFirstFailure = (bool) $break; + return $this; + } + + /** + * Get break on first failure setting + * + * @return bool + */ + public function isBreakOnFirstFailure() + { + return $this->breakOnFirstFailure; + } + + /** + * Defined by Laminas\Validator\ValidatorInterface + * + * Returns true if all values validate true + * + * @param mixed $value + * @param mixed $context Extra "context" to provide the composed validator + * @return bool + * @throws Exception\RuntimeException + */ + public function isValid($value, $context = null) + { + $this->setValue($value); + + if ($value instanceof Traversable) { + $value = ArrayUtils::iteratorToArray($value); + } + + if (is_array($value)) { + $values = $value; + } elseif (is_string($value)) { + $delimiter = $this->getValueDelimiter(); + // Skip explode if delimiter is null, + // used when value is expected to be either an + // array when multiple values and a string for + // single values (ie. MultiCheckbox form behavior) + $values = (null !== $delimiter) + ? explode($this->valueDelimiter, $value) + : [$value]; + } else { + $values = [$value]; + } + + $validator = $this->getValidator(); + + if (! $validator) { + throw new Exception\RuntimeException(sprintf( + '%s expects a validator to be set; none given', + __METHOD__ + )); + } + + foreach ($values as $value) { + if (! $validator->isValid($value, $context)) { + $this->abstractOptions['messages'][] = $validator->getMessages(); + + if ($this->isBreakOnFirstFailure()) { + return false; + } + } + } + + return ! $this->abstractOptions['messages']; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Count.php b/lib/laminas/laminas-validator/src/File/Count.php new file mode 100644 index 000000000..05434e76e --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Count.php @@ -0,0 +1,253 @@ + "Too many files, maximum '%max%' are allowed but '%count%' are given", + self::TOO_FEW => "Too few files, minimum '%min%' are expected but '%count%' are given", + ]; + + /** + * @var array Error message template variables + */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + 'count' => 'count' + ]; + + /** + * Actual filecount + * + * @var int + */ + protected $count; + + /** + * Internal file array + * @var array + */ + protected $files; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'min' => null, // Minimum file count, if null there is no minimum file count + 'max' => null, // Maximum file count, if null there is no maximum file count + ]; + + /** + * Sets validator options + * + * Min limits the file count, when used with max=null it is the maximum file count + * It also accepts an array with the keys 'min' and 'max' + * + * If $options is an integer, it will be used as maximum file count + * As Array is accepts the following keys: + * 'min': Minimum filecount + * 'max': Maximum filecount + * + * @param int|array|\Traversable $options Options for the adapter + */ + public function __construct($options = null) + { + if (1 < func_num_args()) { + $args = func_get_args(); + $options = [ + 'min' => array_shift($args), + 'max' => array_shift($args), + ]; + } + + if (is_string($options) || is_numeric($options)) { + $options = ['max' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the minimum file count + * + * @return int + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Sets the minimum file count + * + * @param int|array $min The minimum file count + * @return Count Provides a fluent interface + * @throws Exception\InvalidArgumentException When min is greater than max + */ + public function setMin($min) + { + if (is_array($min) && isset($min['min'])) { + $min = $min['min']; + } + + if (! is_numeric($min)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $min = (int) $min; + if (($this->getMax() !== null) && ($min > $this->getMax())) { + throw new Exception\InvalidArgumentException( + "The minimum must be less than or equal to the maximum file count, but {$min} > {$this->getMax()}" + ); + } + + $this->options['min'] = $min; + return $this; + } + + /** + * Returns the maximum file count + * + * @return int + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * Sets the maximum file count + * + * @param int|array $max The maximum file count + * @return Count Provides a fluent interface + * @throws Exception\InvalidArgumentException When max is smaller than min + */ + public function setMax($max) + { + if (is_array($max) && isset($max['max'])) { + $max = $max['max']; + } + + if (! is_numeric($max)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $max = (int) $max; + if (($this->getMin() !== null) && ($max < $this->getMin())) { + throw new Exception\InvalidArgumentException( + "The maximum must be greater than or equal to the minimum file count, but {$max} < {$this->getMin()}" + ); + } + + $this->options['max'] = $max; + return $this; + } + + /** + * Adds a file for validation + * + * @param string|array $file + * @return Count + */ + public function addFile($file) + { + if (is_string($file)) { + $file = [$file]; + } + + if (is_array($file)) { + foreach ($file as $name) { + if (! isset($this->files[$name]) && ! empty($name)) { + $this->files[$name] = $name; + } + } + } + + return $this; + } + + /** + * Returns true if and only if the file count of all checked files is at least min and + * not bigger than max (when max is not null). Attention: When checking with set min you + * must give all files with the first call, otherwise you will get a false. + * + * @param string|array $value Filenames to check for count + * @param array $file File data from \Laminas\File\Transfer\Transfer + * @return bool + */ + public function isValid($value, $file = null) + { + if (($file !== null) && ! array_key_exists('destination', $file)) { + $file['destination'] = dirname($value); + } + + if (($file !== null) && array_key_exists('tmp_name', $file)) { + $value = $file['destination'] . DIRECTORY_SEPARATOR . $file['name']; + } + + if (($file === null) || ! empty($file['tmp_name'])) { + $this->addFile($value); + } + + $this->count = count($this->files); + if (($this->getMax() !== null) && ($this->count > $this->getMax())) { + return $this->throwError($file, self::TOO_MANY); + } + + if (($this->getMin() !== null) && ($this->count < $this->getMin())) { + return $this->throwError($file, self::TOO_FEW); + } + + return true; + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function throwError($file, $errorType) + { + if ($file !== null) { + if (is_array($file)) { + if (array_key_exists('name', $file)) { + $this->value = $file['name']; + } + } elseif (is_string($file)) { + $this->value = $file; + } + } + + $this->error($errorType); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Crc32.php b/lib/laminas/laminas-validator/src/File/Crc32.php new file mode 100644 index 000000000..eb66e6019 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Crc32.php @@ -0,0 +1,115 @@ + "File does not match the given crc32 hashes", + self::NOT_DETECTED => "A crc32 hash could not be evaluated for the given file", + self::NOT_FOUND => "File is not readable or does not exist", + ]; + + /** + * Options for this validator + * + * @var string + */ + protected $options = [ + 'algorithm' => 'crc32', + 'hash' => null, + ]; + + /** + * Returns all set crc32 hashes + * + * @return array + */ + public function getCrc32() + { + return $this->getHash(); + } + + /** + * Sets the crc32 hash for one or multiple files + * + * @param string|array $options + * @return self Provides a fluent interface + */ + public function setCrc32($options) + { + $this->setHash($options); + return $this; + } + + /** + * Adds the crc32 hash for one or multiple files + * + * @param string|array $options + * @return self Provides a fluent interface + */ + public function addCrc32($options) + { + $this->addHash($options); + return $this; + } + + /** + * Returns true if and only if the given file confirms the set hash + * + * @param string|array $value Filename to check for hash + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $hashes = array_unique(array_keys($this->getHash())); + $filehash = hash_file('crc32', $fileInfo['file']); + if ($filehash === false) { + $this->error(self::NOT_DETECTED); + return false; + } + + foreach ($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + + $this->error(self::DOES_NOT_MATCH); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/ExcludeExtension.php b/lib/laminas/laminas-validator/src/File/ExcludeExtension.php new file mode 100644 index 000000000..056b91741 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/ExcludeExtension.php @@ -0,0 +1,74 @@ + "File has an incorrect extension", + self::NOT_FOUND => "File is not readable or does not exist", + ]; + + /** + * Returns true if and only if the file extension of $value is not included in the + * set extension list + * + * @param string|array $value Real file to check for extension + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $extension = substr($fileInfo['filename'], strrpos($fileInfo['filename'], '.') + 1); + $extensions = $this->getExtension(); + + if ($this->getCase() && (! in_array($extension, $extensions))) { + return true; + } elseif (! $this->getCase()) { + foreach ($extensions as $ext) { + if (strtolower($ext) == strtolower($extension)) { + $this->error(self::FALSE_EXTENSION); + return false; + } + } + + return true; + } + + $this->error(self::FALSE_EXTENSION); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/ExcludeMimeType.php b/lib/laminas/laminas-validator/src/File/ExcludeMimeType.php new file mode 100644 index 000000000..974a2e121 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/ExcludeMimeType.php @@ -0,0 +1,98 @@ + "File has an incorrect mimetype of '%type%'", + self::NOT_DETECTED => "The mimetype could not be detected from the file", + self::NOT_READABLE => "File is not readable or does not exist", + ]; + + /** + * Returns true if the mimetype of the file does not matche the given ones. Also parts + * of mimetypes can be checked. If you give for example "image" all image + * mime types will not be accepted like "image/gif", "image/jpeg" and so on. + * + * @param string|array $value Real file to check for mimetype + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file, true); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_READABLE); + return false; + } + + $mimefile = $this->getMagicFile(); + if (class_exists('finfo', false)) { + if (! $this->isMagicFileDisabled() && (! empty($mimefile) && empty($this->finfo))) { + $this->finfo = finfo_open(FILEINFO_MIME_TYPE, $mimefile); + } + + if (empty($this->finfo)) { + $this->finfo = finfo_open(FILEINFO_MIME_TYPE); + } + + $this->type = null; + if (! empty($this->finfo)) { + $this->type = finfo_file($this->finfo, $fileInfo['file']); + } + } + + if (empty($this->type) && $this->getHeaderCheck()) { + $this->type = $fileInfo['filetype']; + } + + if (empty($this->type)) { + $this->error(self::NOT_DETECTED); + return false; + } + + $mimetype = $this->getMimeType(true); + if (in_array($this->type, $mimetype)) { + $this->error(self::FALSE_TYPE); + return false; + } + + $types = explode('/', $this->type); + $types = array_merge($types, explode('-', $this->type)); + $types = array_merge($types, explode(';', $this->type)); + foreach ($mimetype as $mime) { + if (in_array($mime, $types)) { + $this->error(self::FALSE_TYPE); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Exists.php b/lib/laminas/laminas-validator/src/File/Exists.php new file mode 100644 index 000000000..0e4b0ead2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Exists.php @@ -0,0 +1,182 @@ + "File does not exist", + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'directory' => null, // internal list of directories + ]; + + /** + * @var array Error message template variables + */ + protected $messageVariables = [ + 'directory' => ['options' => 'directory'], + ]; + + /** + * Sets validator options + * + * @param string|array|\Traversable $options + */ + public function __construct($options = null) + { + if (is_string($options)) { + $options = explode(',', $options); + } + + if (is_array($options) && ! array_key_exists('directory', $options)) { + $options = ['directory' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the set file directories which are checked + * + * @param bool $asArray Returns the values as array; when false, a concatenated string is returned + * @return string|null + */ + public function getDirectory($asArray = false) + { + $asArray = (bool) $asArray; + $directory = $this->options['directory']; + if ($asArray && isset($directory)) { + $directory = explode(',', (string) $directory); + } + + return $directory; + } + + /** + * Sets the file directory which will be checked + * + * @param string|array $directory The directories to validate + * @return Extension Provides a fluent interface + */ + public function setDirectory($directory) + { + $this->options['directory'] = null; + $this->addDirectory($directory); + return $this; + } + + /** + * Adds the file directory which will be checked + * + * @param string|array $directory The directory to add for validation + * @return Extension Provides a fluent interface + * @throws Exception\InvalidArgumentException + */ + public function addDirectory($directory) + { + $directories = $this->getDirectory(true); + if (! isset($directories)) { + $directories = []; + } + + if (is_string($directory)) { + $directory = explode(',', $directory); + } elseif (! is_array($directory)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + foreach ($directory as $content) { + if (empty($content) || ! is_string($content)) { + continue; + } + + $directories[] = trim($content); + } + $directories = array_unique($directories); + + // Sanity check to ensure no empty values + foreach ($directories as $key => $dir) { + if (empty($dir)) { + unset($directories[$key]); + } + } + + $this->options['directory'] = (! empty($directory)) + ? implode(',', $directories) : null; + + return $this; + } + + /** + * Returns true if and only if the file already exists in the set directories + * + * @param string|array $value Real file to check for existence + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file, false, true); + + $this->setValue($fileInfo['filename']); + + $check = false; + $directories = $this->getDirectory(true); + if (! isset($directories)) { + $check = true; + if (! file_exists($fileInfo['file'])) { + $this->error(self::DOES_NOT_EXIST); + return false; + } + } else { + foreach ($directories as $directory) { + if (! isset($directory) || '' === $directory) { + continue; + } + + $check = true; + if (! file_exists($directory . DIRECTORY_SEPARATOR . $fileInfo['basename'])) { + $this->error(self::DOES_NOT_EXIST); + return false; + } + } + } + + if (! $check) { + $this->error(self::DOES_NOT_EXIST); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Extension.php b/lib/laminas/laminas-validator/src/File/Extension.php new file mode 100644 index 000000000..5c8c43e8f --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Extension.php @@ -0,0 +1,208 @@ + "File has an incorrect extension", + self::NOT_FOUND => "File is not readable or does not exist", + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'case' => false, // Validate case sensitive + 'extension' => '', // List of extensions + ]; + + /** + * @var array Error message template variables + */ + protected $messageVariables = [ + 'extension' => ['options' => 'extension'], + ]; + + /** + * Sets validator options + * + * @param string|array|Traversable $options + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + $case = null; + if (1 < func_num_args()) { + $case = func_get_arg(1); + } + + if (is_array($options)) { + if (isset($options['case'])) { + $case = $options['case']; + unset($options['case']); + } + + if (! array_key_exists('extension', $options)) { + $options = ['extension' => $options]; + } + } else { + $options = ['extension' => $options]; + } + + if ($case !== null) { + $options['case'] = $case; + } + + parent::__construct($options); + } + + /** + * Returns the case option + * + * @return bool + */ + public function getCase() + { + return $this->options['case']; + } + + /** + * Sets the case to use + * + * @param bool $case + * @return self Provides a fluent interface + */ + public function setCase($case) + { + $this->options['case'] = (bool) $case; + return $this; + } + + /** + * Returns the set file extension + * + * @return array + */ + public function getExtension() + { + $extension = explode(',', $this->options['extension']); + + return $extension; + } + + /** + * Sets the file extensions + * + * @param string|array $extension The extensions to validate + * @return self Provides a fluent interface + */ + public function setExtension($extension) + { + $this->options['extension'] = null; + $this->addExtension($extension); + return $this; + } + + /** + * Adds the file extensions + * + * @param string|array $extension The extensions to add for validation + * @return self Provides a fluent interface + */ + public function addExtension($extension) + { + $extensions = $this->getExtension(); + if (is_string($extension)) { + $extension = explode(',', $extension); + } + + foreach ($extension as $content) { + if (empty($content) || ! is_string($content)) { + continue; + } + + $extensions[] = trim($content); + } + + $extensions = array_unique($extensions); + + // Sanity check to ensure no empty values + foreach ($extensions as $key => $ext) { + if (empty($ext)) { + unset($extensions[$key]); + } + } + + $this->options['extension'] = implode(',', $extensions); + return $this; + } + + /** + * Returns true if and only if the file extension of $value is included in the + * set extension list + * + * @param string|array $value Real file to check for extension + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $extension = substr($fileInfo['filename'], strrpos($fileInfo['filename'], '.') + 1); + $extensions = $this->getExtension(); + + if ($this->getCase() && (in_array($extension, $extensions))) { + return true; + } elseif (! $this->getCase()) { + foreach ($extensions as $ext) { + if (strtolower($ext) == strtolower($extension)) { + return true; + } + } + } + + $this->error(self::FALSE_EXTENSION); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/FileInformationTrait.php b/lib/laminas/laminas-validator/src/File/FileInformationTrait.php new file mode 100644 index 000000000..12c827e53 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/FileInformationTrait.php @@ -0,0 +1,167 @@ +getLegacyFileInfo($file, $hasType, $hasBasename); + } + + if (is_array($value)) { + return $this->getSapiFileInfo($value, $hasType, $hasBasename); + } + + if ($value instanceof UploadedFileInterface) { + return $this->getPsr7FileInfo($value, $hasType, $hasBasename); + } + + return $this->getFileBasedFileInfo($value, $hasType, $hasBasename); + } + + /** + * Generate file information array with legacy Laminas_File_Transfer API + * + * @param array $file File data + * @param bool $hasType Return with filetype + * @param bool $hasBasename Basename is calculated from location path + * @return array + */ + private function getLegacyFileInfo( + array $file, + $hasType = false, + $hasBasename = false + ) { + $fileInfo = []; + + $fileInfo['filename'] = $file['name']; + $fileInfo['file'] = $file['tmp_name']; + + if ($hasBasename) { + $fileInfo['basename'] = basename($fileInfo['file']); + } + + if ($hasType) { + $fileInfo['filetype'] = $file['type']; + } + + return $fileInfo; + } + + /** + * Generate file information array with SAPI + * + * @param array $file File data from SAPI + * @param bool $hasType Return with filetype + * @param bool $hasBasename Filename is calculated from location path + * @return array + */ + private function getSapiFileInfo( + array $file, + $hasType = false, + $hasBasename = false + ) { + if (! isset($file['tmp_name']) || ! isset($file['name'])) { + throw new Exception\InvalidArgumentException( + 'Value array must be in $_FILES format' + ); + } + + $fileInfo = []; + + $fileInfo['file'] = $file['tmp_name']; + $fileInfo['filename'] = $file['name']; + + if ($hasBasename) { + $fileInfo['basename'] = basename($fileInfo['file']); + } + + if ($hasType) { + $fileInfo['filetype'] = $file['type']; + } + + return $fileInfo; + } + + /** + * Generate file information array with PSR-7 UploadedFileInterface + * + * @param UploadedFileInterface $file + * @param bool $hasType Return with filetype + * @param bool $hasBasename Filename is calculated from location path + * @return array + */ + private function getPsr7FileInfo( + UploadedFileInterface $file, + $hasType = false, + $hasBasename = false + ) { + $fileInfo = []; + + $fileInfo['file'] = $file->getStream()->getMetadata('uri'); + $fileInfo['filename'] = $file->getClientFilename(); + + if ($hasBasename) { + $fileInfo['basename'] = basename($fileInfo['file']); + } + + if ($hasType) { + $fileInfo['filetype'] = $file->getClientMediaType(); + } + + return $fileInfo; + } + + /** + * Generate file information array with base method + * + * @param string $file File path + * @param bool $hasType Return with filetype + * @param bool $hasBasename Filename is calculated from location path + * @return array + */ + private function getFileBasedFileInfo( + $file, + $hasType = false, + $hasBasename = false + ) { + $fileInfo = []; + + $fileInfo['file'] = $file; + $fileInfo['filename'] = basename($fileInfo['file']); + + if ($hasBasename) { + $fileInfo['basename'] = basename($fileInfo['file']); + } + + if ($hasType) { + $fileInfo['filetype'] = null; + } + + return $fileInfo; + } +} diff --git a/lib/laminas/laminas-validator/src/File/FilesSize.php b/lib/laminas/laminas-validator/src/File/FilesSize.php new file mode 100644 index 000000000..a2fcd7b6d --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/FilesSize.php @@ -0,0 +1,182 @@ + "All files in sum should have a maximum size of '%max%' but '%size%' were detected", + self::TOO_SMALL => "All files in sum should have a minimum size of '%min%' but '%size%' were detected", + self::NOT_READABLE => "One or more files can not be read", + ]; + + /** + * Internal file array + * + * @var array + */ + protected $files; + + /** + * Sets validator options + * + * Min limits the used disk space for all files, when used with max=null it is the maximum file size + * It also accepts an array with the keys 'min' and 'max' + * + * @param int|array|Traversable $options Options for this validator + * @throws \Laminas\Validator\Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + $this->files = []; + $this->setSize(0); + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (is_scalar($options)) { + $options = ['max' => $options]; + } elseif (! is_array($options)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + if (1 < func_num_args()) { + $argv = func_get_args(); + array_shift($argv); + $options['max'] = array_shift($argv); + if (! empty($argv)) { + $options['useByteString'] = array_shift($argv); + } + } + + parent::__construct($options); + } + + /** + * Returns true if and only if the disk usage of all files is at least min and + * not bigger than max (when max is not null). + * + * @param string|array $value Real file to check for size + * @param array $file File data from \Laminas\File\Transfer\Transfer + * @return bool + */ + public function isValid($value, $file = null) + { + if (is_string($value)) { + $value = [$value]; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $value = [$value]; + } + + $min = $this->getMin(true); + $max = $this->getMax(true); + $size = $this->getSize(); + foreach ($value as $files) { + if (is_array($files)) { + if (! isset($files['tmp_name']) || ! isset($files['name'])) { + throw new Exception\InvalidArgumentException( + 'Value array must be in $_FILES format' + ); + } + $file = $files; + $files = $files['tmp_name']; + } + + // Is file readable ? + if (empty($files) || false === is_readable($files)) { + $this->throwError($file, self::NOT_READABLE); + continue; + } + + if (! isset($this->files[$files])) { + $this->files[$files] = $files; + } else { + // file already counted... do not count twice + continue; + } + + // limited to 2GB files + ErrorHandler::start(); + $size += filesize($files); + ErrorHandler::stop(); + $this->size = $size; + if (($max !== null) && ($max < $size)) { + if ($this->getByteString()) { + $this->options['max'] = $this->toByteString($max); + $this->size = $this->toByteString($size); + $this->throwError($file, self::TOO_BIG); + $this->options['max'] = $max; + $this->size = $size; + } else { + $this->throwError($file, self::TOO_BIG); + } + } + } + + // Check that aggregate files are >= minimum size + if (($min !== null) && ($size < $min)) { + if ($this->getByteString()) { + $this->options['min'] = $this->toByteString($min); + $this->size = $this->toByteString($size); + $this->throwError($file, self::TOO_SMALL); + $this->options['min'] = $min; + $this->size = $size; + } else { + $this->throwError($file, self::TOO_SMALL); + } + } + + if ($this->getMessages()) { + return false; + } + + return true; + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function throwError($file, $errorType) + { + if ($file !== null) { + if (is_array($file)) { + if (array_key_exists('name', $file)) { + $this->value = $file['name']; + } + } elseif (is_string($file)) { + $this->value = $file; + } + } + + $this->error($errorType); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Hash.php b/lib/laminas/laminas-validator/src/File/Hash.php new file mode 100644 index 000000000..791168826 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Hash.php @@ -0,0 +1,167 @@ + "File does not match the given hashes", + self::NOT_DETECTED => "A hash could not be evaluated for the given file", + self::NOT_FOUND => "File is not readable or does not exist" + ]; + + /** + * Options for this validator + * + * @var string + */ + protected $options = [ + 'algorithm' => 'crc32', + 'hash' => null, + ]; + + /** + * Sets validator options + * + * @param string|array $options + */ + public function __construct($options = null) + { + if (is_scalar($options) || + (is_array($options) && ! array_key_exists('hash', $options))) { + $options = ['hash' => $options]; + } + + if (1 < func_num_args()) { + $options['algorithm'] = func_get_arg(1); + } + + parent::__construct($options); + } + + /** + * Returns the set hash values as array, the hash as key and the algorithm the value + * + * @return array + */ + public function getHash() + { + return $this->options['hash']; + } + + /** + * Sets the hash for one or multiple files + * + * @param string|array $options + * @return self Provides a fluent interface + */ + public function setHash($options) + { + $this->options['hash'] = null; + $this->addHash($options); + + return $this; + } + + /** + * Adds the hash for one or multiple files + * + * @param string|array $options + * @throws Exception\InvalidArgumentException + * @return self Provides a fluent interface + */ + public function addHash($options) + { + if (is_string($options)) { + $options = [$options]; + } elseif (! is_array($options)) { + throw new Exception\InvalidArgumentException("False parameter given"); + } + + $known = hash_algos(); + if (! isset($options['algorithm'])) { + $algorithm = $this->options['algorithm']; + } else { + $algorithm = $options['algorithm']; + unset($options['algorithm']); + } + + if (! in_array($algorithm, $known)) { + throw new Exception\InvalidArgumentException("Unknown algorithm '{$algorithm}'"); + } + + foreach ($options as $value) { + if (! is_string($value)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Hash must be a string, %s received', + is_object($value) ? get_class($value) : gettype($value) + )); + } + $this->options['hash'][$value] = $algorithm; + } + + return $this; + } + + /** + * Returns true if and only if the given file confirms the set hash + * + * @param string|array $value File to check for hash + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $algos = array_unique(array_values($this->getHash())); + foreach ($algos as $algorithm) { + $filehash = hash_file($algorithm, $fileInfo['file']); + + if ($filehash === false) { + $this->error(self::NOT_DETECTED); + return false; + } + + if (isset($this->getHash()[$filehash]) && $this->getHash()[$filehash] === $algorithm) { + return true; + } + } + + $this->error(self::DOES_NOT_MATCH); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/ImageSize.php b/lib/laminas/laminas-validator/src/File/ImageSize.php new file mode 100644 index 000000000..4a864d47c --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/ImageSize.php @@ -0,0 +1,380 @@ + "Maximum allowed width for image should be '%maxwidth%' but '%width%' detected", + self::WIDTH_TOO_SMALL => "Minimum expected width for image should be '%minwidth%' but '%width%' detected", + self::HEIGHT_TOO_BIG => "Maximum allowed height for image should be '%maxheight%' but '%height%' detected", + self::HEIGHT_TOO_SMALL => "Minimum expected height for image should be '%minheight%' but '%height%' detected", + self::NOT_DETECTED => "The size of image could not be detected", + self::NOT_READABLE => "File is not readable or does not exist", + ]; + + /** + * @var array Error message template variables + */ + protected $messageVariables = [ + 'minwidth' => ['options' => 'minWidth'], + 'maxwidth' => ['options' => 'maxWidth'], + 'minheight' => ['options' => 'minHeight'], + 'maxheight' => ['options' => 'maxHeight'], + 'width' => 'width', + 'height' => 'height' + ]; + + /** + * Detected width + * + * @var int + */ + protected $width; + + /** + * Detected height + * + * @var int + */ + protected $height; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'minWidth' => null, // Minimum image width + 'maxWidth' => null, // Maximum image width + 'minHeight' => null, // Minimum image height + 'maxHeight' => null, // Maximum image height + ]; + + /** + * Sets validator options + * + * Accepts the following option keys: + * - minheight + * - minwidth + * - maxheight + * - maxwidth + * + * @param array|\Traversable $options + */ + public function __construct($options = null) + { + if (1 < func_num_args()) { + if (! is_array($options)) { + $options = ['minWidth' => $options]; + } + + $argv = func_get_args(); + array_shift($argv); + $options['minHeight'] = array_shift($argv); + if (! empty($argv)) { + $options['maxWidth'] = array_shift($argv); + if (! empty($argv)) { + $options['maxHeight'] = array_shift($argv); + } + } + } + + parent::__construct($options); + } + + /** + * Returns the minimum allowed width + * + * @return int + */ + public function getMinWidth() + { + return $this->options['minWidth']; + } + + /** + * Sets the minimum allowed width + * + * @param int $minWidth + * @throws Exception\InvalidArgumentException When minwidth is greater than maxwidth + * @return self Provides a fluid interface + */ + public function setMinWidth($minWidth) + { + if (($this->getMaxWidth() !== null) && ($minWidth > $this->getMaxWidth())) { + throw new Exception\InvalidArgumentException( + "The minimum image width must be less than or equal to the " + . " maximum image width, but {$minWidth} > {$this->getMaxWidth()}" + ); + } + + $this->options['minWidth'] = (int) $minWidth; + return $this; + } + + /** + * Returns the maximum allowed width + * + * @return int + */ + public function getMaxWidth() + { + return $this->options['maxWidth']; + } + + /** + * Sets the maximum allowed width + * + * @param int $maxWidth + * @throws Exception\InvalidArgumentException When maxwidth is less than minwidth + * @return self Provides a fluid interface + */ + public function setMaxWidth($maxWidth) + { + if (($this->getMinWidth() !== null) && ($maxWidth < $this->getMinWidth())) { + throw new Exception\InvalidArgumentException( + "The maximum image width must be greater than or equal to the " + . "minimum image width, but {$maxWidth} < {$this->getMinWidth()}" + ); + } + + $this->options['maxWidth'] = (int) $maxWidth; + return $this; + } + + /** + * Returns the minimum allowed height + * + * @return int + */ + public function getMinHeight() + { + return $this->options['minHeight']; + } + + /** + * Sets the minimum allowed height + * + * @param int $minHeight + * @throws Exception\InvalidArgumentException When minheight is greater than maxheight + * @return self Provides a fluid interface + */ + public function setMinHeight($minHeight) + { + if (($this->getMaxHeight() !== null) && ($minHeight > $this->getMaxHeight())) { + throw new Exception\InvalidArgumentException( + "The minimum image height must be less than or equal to the " + . " maximum image height, but {$minHeight} > {$this->getMaxHeight()}" + ); + } + + $this->options['minHeight'] = (int) $minHeight; + return $this; + } + + /** + * Returns the maximum allowed height + * + * @return int + */ + public function getMaxHeight() + { + return $this->options['maxHeight']; + } + + /** + * Sets the maximum allowed height + * + * @param int $maxHeight + * @throws Exception\InvalidArgumentException When maxheight is less than minheight + * @return self Provides a fluid interface + */ + public function setMaxHeight($maxHeight) + { + if (($this->getMinHeight() !== null) && ($maxHeight < $this->getMinHeight())) { + throw new Exception\InvalidArgumentException( + "The maximum image height must be greater than or equal to the " + . "minimum image height, but {$maxHeight} < {$this->getMinHeight()}" + ); + } + + $this->options['maxHeight'] = (int) $maxHeight; + return $this; + } + + /** + * Returns the set minimum image sizes + * + * @return array + */ + public function getImageMin() + { + return ['minWidth' => $this->getMinWidth(), 'minHeight' => $this->getMinHeight()]; + } + + /** + * Returns the set maximum image sizes + * + * @return array + */ + public function getImageMax() + { + return ['maxWidth' => $this->getMaxWidth(), 'maxHeight' => $this->getMaxHeight()]; + } + + /** + * Returns the set image width sizes + * + * @return array + */ + public function getImageWidth() + { + return ['minWidth' => $this->getMinWidth(), 'maxWidth' => $this->getMaxWidth()]; + } + + /** + * Returns the set image height sizes + * + * @return array + */ + public function getImageHeight() + { + return ['minHeight' => $this->getMinHeight(), 'maxHeight' => $this->getMaxHeight()]; + } + + /** + * Sets the minimum image size + * + * @param array $options The minimum image dimensions + * @return self Provides a fluent interface + */ + public function setImageMin($options) + { + $this->setOptions($options); + return $this; + } + + /** + * Sets the maximum image size + * + * @param array|\Traversable $options The maximum image dimensions + * @return self Provides a fluent interface + */ + public function setImageMax($options) + { + $this->setOptions($options); + return $this; + } + + /** + * Sets the minimum and maximum image width + * + * @param array $options The image width dimensions + * @return self Provides a fluent interface + */ + public function setImageWidth($options) + { + $this->setImageMin($options); + $this->setImageMax($options); + + return $this; + } + + /** + * Sets the minimum and maximum image height + * + * @param array $options The image height dimensions + * @return self Provides a fluent interface + */ + public function setImageHeight($options) + { + $this->setImageMin($options); + $this->setImageMax($options); + + return $this; + } + + /** + * Returns true if and only if the image size of $value is at least min and + * not bigger than max + * + * @param string|array $value Real file to check for image size + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_READABLE); + return false; + } + + ErrorHandler::start(); + $size = getimagesize($fileInfo['file']); + ErrorHandler::stop(); + + if (empty($size) || ($size[0] === 0) || ($size[1] === 0)) { + $this->error(self::NOT_DETECTED); + return false; + } + + $this->width = $size[0]; + $this->height = $size[1]; + if ($this->width < $this->getMinWidth()) { + $this->error(self::WIDTH_TOO_SMALL); + } + + if (($this->getMaxWidth() !== null) && ($this->getMaxWidth() < $this->width)) { + $this->error(self::WIDTH_TOO_BIG); + } + + if ($this->height < $this->getMinHeight()) { + $this->error(self::HEIGHT_TOO_SMALL); + } + + if (($this->getMaxHeight() !== null) && ($this->getMaxHeight() < $this->height)) { + $this->error(self::HEIGHT_TOO_BIG); + } + + if ($this->getMessages()) { + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/File/IsCompressed.php b/lib/laminas/laminas-validator/src/File/IsCompressed.php new file mode 100644 index 000000000..bfa8e2a6e --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/IsCompressed.php @@ -0,0 +1,92 @@ + "File is not compressed, '%type%' detected", + self::NOT_DETECTED => "The mimetype could not be detected from the file", + self::NOT_READABLE => "File is not readable or does not exist", + ]; + + /** + * Sets validator options + * + * @param string|array|Traversable $options + */ + public function __construct($options = []) + { + // http://hul.harvard.edu/ois/systems/wax/wax-public-help/mimetypes.htm + $default = [ + 'application/arj', + 'application/gnutar', + 'application/lha', + 'application/lzx', + 'application/vnd.ms-cab-compressed', + 'application/x-ace-compressed', + 'application/x-arc', + 'application/x-archive', + 'application/x-arj', + 'application/x-bzip', + 'application/x-bzip2', + 'application/x-cab-compressed', + 'application/x-compress', + 'application/x-compressed', + 'application/x-cpio', + 'application/x-debian-package', + 'application/x-eet', + 'application/x-gzip', + 'application/x-java-pack200', + 'application/x-lha', + 'application/x-lharc', + 'application/x-lzh', + 'application/x-lzma', + 'application/x-lzx', + 'application/x-rar', + 'application/x-sit', + 'application/x-stuffit', + 'application/x-tar', + 'application/zip', + 'application/x-zip', + 'application/zoo', + 'multipart/x-gzip', + ]; + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if ($options === null) { + $options = []; + } + + parent::__construct($options); + + if (! $this->getMimeType()) { + $this->setMimeType($default); + } + } +} diff --git a/lib/laminas/laminas-validator/src/File/IsImage.php b/lib/laminas/laminas-validator/src/File/IsImage.php new file mode 100644 index 000000000..28fa344d3 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/IsImage.php @@ -0,0 +1,116 @@ + "File is no image, '%type%' detected", + self::NOT_DETECTED => "The mimetype could not be detected from the file", + self::NOT_READABLE => "File is not readable or does not exist", + ]; + + /** + * Sets validator options + * + * @param array|Traversable|string $options + */ + public function __construct($options = []) + { + // http://www.iana.org/assignments/media-types/media-types.xhtml#image + $default = [ + 'application/cdf', + 'application/dicom', + 'application/fractals', + 'application/postscript', + 'application/vnd.hp-hpgl', + 'application/vnd.oasis.opendocument.graphics', + 'application/x-cdf', + 'application/x-cmu-raster', + 'application/x-ima', + 'application/x-inventor', + 'application/x-koan', + 'application/x-portable-anymap', + 'application/x-world-x-3dmf', + 'image/bmp', + 'image/c', + 'image/cgm', + 'image/fif', + 'image/gif', + 'image/jpeg', + 'image/jpm', + 'image/jpx', + 'image/jp2', + 'image/naplps', + 'image/pjpeg', + 'image/png', + 'image/svg', + 'image/svg+xml', + 'image/tiff', + 'image/vnd.adobe.photoshop', + 'image/vnd.djvu', + 'image/vnd.fpx', + 'image/vnd.net-fpx', + 'image/webp', + 'image/x-cmu-raster', + 'image/x-cmx', + 'image/x-coreldraw', + 'image/x-cpi', + 'image/x-emf', + 'image/x-ico', + 'image/x-icon', + 'image/x-jg', + 'image/x-ms-bmp', + 'image/x-niff', + 'image/x-pict', + 'image/x-pcx', + 'image/x-png', + 'image/x-portable-anymap', + 'image/x-portable-bitmap', + 'image/x-portable-greymap', + 'image/x-portable-pixmap', + 'image/x-quicktime', + 'image/x-rgb', + 'image/x-tiff', + 'image/x-unknown', + 'image/x-windows-bmp', + 'image/x-xpmi', + ]; + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if ($options === null) { + $options = []; + } + + parent::__construct($options); + + if (! $this->getMimeType()) { + $this->setMimeType($default); + } + } +} diff --git a/lib/laminas/laminas-validator/src/File/Md5.php b/lib/laminas/laminas-validator/src/File/Md5.php new file mode 100644 index 000000000..dbba38070 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Md5.php @@ -0,0 +1,115 @@ + "File does not match the given md5 hashes", + self::NOT_DETECTED => "An md5 hash could not be evaluated for the given file", + self::NOT_FOUND => "File is not readable or does not exist", + ]; + + /** + * Options for this validator + * + * @var string + */ + protected $options = [ + 'algorithm' => 'md5', + 'hash' => null, + ]; + + /** + * Returns all set md5 hashes + * + * @return array + */ + public function getMd5() + { + return $this->getHash(); + } + + /** + * Sets the md5 hash for one or multiple files + * + * @param string|array $options + * @return Hash Provides a fluent interface + */ + public function setMd5($options) + { + $this->setHash($options); + return $this; + } + + /** + * Adds the md5 hash for one or multiple files + * + * @param string|array $options + * @return Hash Provides a fluent interface + */ + public function addMd5($options) + { + $this->addHash($options); + return $this; + } + + /** + * Returns true if and only if the given file confirms the set hash + * + * @param string|array $value Filename to check for hash + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $hashes = array_unique(array_keys($this->getHash())); + $filehash = hash_file('md5', $fileInfo['file']); + if ($filehash === false) { + $this->error(self::NOT_DETECTED); + return false; + } + + foreach ($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + + $this->error(self::DOES_NOT_MATCH); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/MimeType.php b/lib/laminas/laminas-validator/src/File/MimeType.php new file mode 100644 index 000000000..8edcf7094 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/MimeType.php @@ -0,0 +1,403 @@ + "File has an incorrect mimetype of '%type%'", + self::NOT_DETECTED => "The mimetype could not be detected from the file", + self::NOT_READABLE => "File is not readable or does not exist", + ]; + + /** + * @var array + */ + protected $messageVariables = [ + 'type' => 'type' + ]; + + /** + * @var string + */ + protected $type; + + /** + * Finfo object to use + * + * @var resource + */ + protected $finfo; + + /** + * If no environment variable 'MAGIC' is set, try and autodiscover it based on common locations + * @var array + */ + protected $magicFiles = [ + '/usr/share/misc/magic', + '/usr/share/misc/magic.mime', + '/usr/share/misc/magic.mgc', + '/usr/share/mime/magic', + '/usr/share/mime/magic.mime', + '/usr/share/mime/magic.mgc', + '/usr/share/file/magic', + '/usr/share/file/magic.mime', + '/usr/share/file/magic.mgc', + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'enableHeaderCheck' => false, // Allow header check + 'disableMagicFile' => false, // Disable usage of magicfile + 'magicFile' => null, // Magicfile to use + 'mimeType' => null, // Mimetype to allow + ]; + + /** + * Sets validator options + * + * Mimetype to accept + * - NULL means default PHP usage by using the environment variable 'magic' + * - FALSE means disabling searching for mimetype, should be used for PHP 5.3 + * - A string is the mimetype file to use + * + * @param string|array|Traversable $options + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (is_string($options)) { + $this->setMimeType($options); + $options = []; + } elseif (is_array($options)) { + if (isset($options['magicFile'])) { + $this->setMagicFile($options['magicFile']); + unset($options['magicFile']); + } + + if (isset($options['enableHeaderCheck'])) { + $this->enableHeaderCheck($options['enableHeaderCheck']); + unset($options['enableHeaderCheck']); + } + + if (array_key_exists('mimeType', $options)) { + $this->setMimeType($options['mimeType']); + unset($options['mimeType']); + } + + // Handle cases where mimetypes are interspersed with options, or + // options are simply an array of mime types + foreach (array_keys($options) as $key) { + if (! is_int($key)) { + continue; + } + $this->addMimeType($options[$key]); + unset($options[$key]); + } + } + + parent::__construct($options); + } + + /** + * Returns the actual set magicfile + * + * @return string + */ + public function getMagicFile() + { + if (null === $this->options['magicFile']) { + $magic = getenv('magic'); + if (! empty($magic)) { + $this->setMagicFile($magic); + if ($this->options['magicFile'] === null) { + $this->options['magicFile'] = false; + } + return $this->options['magicFile']; + } + + foreach ($this->magicFiles as $file) { + try { + $this->setMagicFile($file); + } catch (Exception\ExceptionInterface $e) { + // suppressing errors which are thrown due to open_basedir restrictions + continue; + } + + if ($this->options['magicFile'] !== null) { + return $this->options['magicFile']; + } + } + + if ($this->options['magicFile'] === null) { + $this->options['magicFile'] = false; + } + } + + return $this->options['magicFile']; + } + + /** + * Sets the magicfile to use + * if null, the MAGIC constant from php is used + * if the MAGIC file is erroneous, no file will be set + * if false, the default MAGIC file from PHP will be used + * + * @param string $file + * @throws Exception\RuntimeException When finfo can not read the magicfile + * @throws Exception\InvalidArgumentException + * @throws Exception\InvalidMagicMimeFileException + * @return self Provides fluid interface + */ + public function setMagicFile($file) + { + if ($file === false) { + $this->options['magicFile'] = false; + } elseif (empty($file)) { + $this->options['magicFile'] = null; + } elseif (! (class_exists('finfo', false))) { + $this->options['magicFile'] = null; + throw new Exception\RuntimeException('Magicfile can not be set; there is no finfo extension installed'); + } elseif (! is_file($file) || ! is_readable($file)) { + throw new Exception\InvalidArgumentException(sprintf( + 'The given magicfile ("%s") could not be read', + $file + )); + } else { + ErrorHandler::start(E_NOTICE | E_WARNING); + $this->finfo = finfo_open(FILEINFO_MIME_TYPE, $file); + $error = ErrorHandler::stop(); + if (empty($this->finfo)) { + $this->finfo = null; + throw new Exception\InvalidMagicMimeFileException(sprintf( + 'The given magicfile ("%s") could not be used by ext/finfo', + $file + ), 0, $error); + } + $this->options['magicFile'] = $file; + } + + return $this; + } + + /** + * Disables usage of MagicFile + * + * @param $disable boolean False disables usage of magic file + * @return self Provides fluid interface + */ + public function disableMagicFile($disable) + { + $this->options['disableMagicFile'] = (bool) $disable; + return $this; + } + + /** + * Is usage of MagicFile disabled? + * + * @return bool + */ + public function isMagicFileDisabled() + { + return $this->options['disableMagicFile']; + } + + /** + * Returns the Header Check option + * + * @return bool + */ + public function getHeaderCheck() + { + return $this->options['enableHeaderCheck']; + } + + /** + * Defines if the http header should be used + * Note that this is unsafe and therefor the default value is false + * + * @param bool $headerCheck + * @return self Provides fluid interface + */ + public function enableHeaderCheck($headerCheck = true) + { + $this->options['enableHeaderCheck'] = (bool) $headerCheck; + return $this; + } + + /** + * Returns the set mimetypes + * + * @param bool $asArray Returns the values as array, when false a concatenated string is returned + * @return string|array + */ + public function getMimeType($asArray = false) + { + $asArray = (bool) $asArray; + $mimetype = (string) $this->options['mimeType']; + if ($asArray) { + $mimetype = explode(',', $mimetype); + } + + return $mimetype; + } + + /** + * Sets the mimetypes + * + * @param string|array $mimetype The mimetypes to validate + * @return self Provides a fluent interface + */ + public function setMimeType($mimetype) + { + $this->options['mimeType'] = null; + $this->addMimeType($mimetype); + return $this; + } + + /** + * Adds the mimetypes + * + * @param string|array $mimetype The mimetypes to add for validation + * @throws Exception\InvalidArgumentException + * @return self Provides a fluent interface + */ + public function addMimeType($mimetype) + { + $mimetypes = $this->getMimeType(true); + + if (is_string($mimetype)) { + $mimetype = explode(',', $mimetype); + } elseif (! is_array($mimetype)) { + throw new Exception\InvalidArgumentException("Invalid options to validator provided"); + } + + if (isset($mimetype['magicFile'])) { + unset($mimetype['magicFile']); + } + + foreach ($mimetype as $content) { + if (empty($content) || ! is_string($content)) { + continue; + } + $mimetypes[] = trim($content); + } + $mimetypes = array_unique($mimetypes); + + // Sanity check to ensure no empty values + foreach ($mimetypes as $key => $mt) { + if (empty($mt)) { + unset($mimetypes[$key]); + } + } + + $this->options['mimeType'] = implode(',', $mimetypes); + + return $this; + } + + /** + * Defined by Laminas\Validator\ValidatorInterface + * + * Returns true if the mimetype of the file matches the given ones. Also parts + * of mimetypes can be checked. If you give for example "image" all image + * mime types will be accepted like "image/gif", "image/jpeg" and so on. + * + * @param string|array $value Real file to check for mimetype + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file, true); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(static::NOT_READABLE); + return false; + } + + $mimefile = $this->getMagicFile(); + if (class_exists('finfo', false)) { + if (! $this->isMagicFileDisabled() && (! empty($mimefile) && empty($this->finfo))) { + ErrorHandler::start(E_NOTICE | E_WARNING); + $this->finfo = finfo_open(FILEINFO_MIME_TYPE, $mimefile); + ErrorHandler::stop(); + } + + if (empty($this->finfo)) { + ErrorHandler::start(E_NOTICE | E_WARNING); + $this->finfo = finfo_open(FILEINFO_MIME_TYPE); + ErrorHandler::stop(); + } + + $this->type = null; + if (! empty($this->finfo)) { + $this->type = finfo_file($this->finfo, $fileInfo['file']); + unset($this->finfo); + } + } + + if (empty($this->type) && $this->getHeaderCheck()) { + $this->type = $fileInfo['filetype']; + } + + if (empty($this->type)) { + $this->error(static::NOT_DETECTED); + return false; + } + + $mimetype = $this->getMimeType(true); + if (in_array($this->type, $mimetype)) { + return true; + } + + $types = explode('/', $this->type); + $types = array_merge($types, explode('-', $this->type)); + $types = array_merge($types, explode(';', $this->type)); + foreach ($mimetype as $mime) { + if (in_array($mime, $types)) { + return true; + } + } + + $this->error(static::FALSE_TYPE); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/NotExists.php b/lib/laminas/laminas-validator/src/File/NotExists.php new file mode 100644 index 000000000..0c34ba2c0 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/NotExists.php @@ -0,0 +1,75 @@ + "File exists", + ]; + + /** + * Returns true if and only if the file does not exist in the set destinations + * + * @param string|array $value Real file to check for existence + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file, false, true); + + $this->setValue($fileInfo['filename']); + + $check = false; + $directories = $this->getDirectory(true); + if (! isset($directories)) { + $check = true; + if (file_exists($fileInfo['file'])) { + $this->error(self::DOES_EXIST); + return false; + } + } else { + foreach ($directories as $directory) { + if (! isset($directory) || '' === $directory) { + continue; + } + + $check = true; + if (file_exists($directory . DIRECTORY_SEPARATOR . $fileInfo['basename'])) { + $this->error(self::DOES_EXIST); + return false; + } + } + } + + if (! $check) { + $this->error(self::DOES_EXIST); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Sha1.php b/lib/laminas/laminas-validator/src/File/Sha1.php new file mode 100644 index 000000000..f48030d1c --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Sha1.php @@ -0,0 +1,115 @@ + "File does not match the given sha1 hashes", + self::NOT_DETECTED => "A sha1 hash could not be evaluated for the given file", + self::NOT_FOUND => "File is not readable or does not exist", + ]; + + /** + * Options for this validator + * + * @var string + */ + protected $options = [ + 'algorithm' => 'sha1', + 'hash' => null, + ]; + + /** + * Returns all set sha1 hashes + * + * @return array + */ + public function getSha1() + { + return $this->getHash(); + } + + /** + * Sets the sha1 hash for one or multiple files + * + * @param string|array $options + * @return Hash Provides a fluent interface + */ + public function setSha1($options) + { + $this->setHash($options); + return $this; + } + + /** + * Adds the sha1 hash for one or multiple files + * + * @param string|array $options + * @return Hash Provides a fluent interface + */ + public function addSha1($options) + { + $this->addHash($options); + return $this; + } + + /** + * Returns true if and only if the given file confirms the set hash + * + * @param string $value|array Filename to check for hash + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $hashes = array_unique(array_keys($this->getHash())); + $filehash = hash_file('sha1', $fileInfo['file']); + if ($filehash === false) { + $this->error(self::NOT_DETECTED); + return false; + } + + foreach ($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + + $this->error(self::DOES_NOT_MATCH); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Size.php b/lib/laminas/laminas-validator/src/File/Size.php new file mode 100644 index 000000000..99f37d78b --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Size.php @@ -0,0 +1,356 @@ + "Maximum allowed size for file is '%max%' but '%size%' detected", + self::TOO_SMALL => "Minimum expected size for file is '%min%' but '%size%' detected", + self::NOT_FOUND => "File is not readable or does not exist", + ]; + + /** + * @var array Error message template variables + */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + 'size' => 'size', + ]; + + /** + * Detected size + * + * @var int + */ + protected $size; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'min' => null, // Minimum file size, if null there is no minimum + 'max' => null, // Maximum file size, if null there is no maximum + 'useByteString' => true, // Use byte string? + ]; + + /** + * Sets validator options + * + * If $options is an integer, it will be used as maximum file size + * As Array is accepts the following keys: + * 'min': Minimum file size + * 'max': Maximum file size + * 'useByteString': Use bytestring or real size for messages + * + * @param int|array|\Traversable $options Options for the adapter + */ + public function __construct($options = null) + { + if (is_string($options) || is_numeric($options)) { + $options = ['max' => $options]; + } + + if (1 < func_num_args()) { + $argv = func_get_args(); + array_shift($argv); + $options['max'] = array_shift($argv); + if (! empty($argv)) { + $options['useByteString'] = array_shift($argv); + } + } + + parent::__construct($options); + } + + /** + * Should messages return bytes as integer or as string in SI notation + * + * @param bool $byteString Use bytestring ? + * @return int + */ + public function useByteString($byteString = true) + { + $this->options['useByteString'] = (bool) $byteString; + return $this; + } + + /** + * Will bytestring be used? + * + * @return bool + */ + public function getByteString() + { + return $this->options['useByteString']; + } + + /** + * Returns the minimum file size + * + * @param bool $raw Whether or not to force return of the raw value (defaults off) + * @return int|string + */ + public function getMin($raw = false) + { + $min = $this->options['min']; + if (! $raw && $this->getByteString()) { + $min = $this->toByteString($min); + } + + return $min; + } + + /** + * Sets the minimum file size + * + * File size can be an integer or a byte string + * This includes 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' + * For example: 2000, 2MB, 0.2GB + * + * @param int|string $min The minimum file size + * @throws Exception\InvalidArgumentException When min is greater than max + * @return self Provides a fluent interface + */ + public function setMin($min) + { + if (! is_string($min) && ! is_numeric($min)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $min = (int) $this->fromByteString($min); + $max = $this->getMax(true); + if (($max !== null) && ($min > $max)) { + throw new Exception\InvalidArgumentException( + "The minimum must be less than or equal to the maximum file size, but $min > $max" + ); + } + + $this->options['min'] = $min; + return $this; + } + + /** + * Returns the maximum file size + * + * @param bool $raw Whether or not to force return of the raw value (defaults off) + * @return int|string + */ + public function getMax($raw = false) + { + $max = $this->options['max']; + if (! $raw && $this->getByteString()) { + $max = $this->toByteString($max); + } + + return $max; + } + + /** + * Sets the maximum file size + * + * File size can be an integer or a byte string + * This includes 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' + * For example: 2000, 2MB, 0.2GB + * + * @param int|string $max The maximum file size + * @throws Exception\InvalidArgumentException When max is smaller than min + * @return self Provides a fluent interface + */ + public function setMax($max) + { + if (! is_string($max) && ! is_numeric($max)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $max = (int) $this->fromByteString($max); + $min = $this->getMin(true); + if (($min !== null) && ($max < $min)) { + throw new Exception\InvalidArgumentException( + "The maximum must be greater than or equal to the minimum file size, but $max < $min" + ); + } + + $this->options['max'] = $max; + return $this; + } + + /** + * Retrieve current detected file size + * + * @return int + */ + protected function getSize() + { + return $this->size; + } + + /** + * Set current size + * + * @param int $size + * @return self + */ + protected function setSize($size) + { + $this->size = $size; + return $this; + } + + /** + * Returns true if and only if the file size of $value is at least min and + * not bigger than max (when max is not null). + * + * @param string|array $value File to check for size + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + // limited to 4GB files + ErrorHandler::start(); + $size = sprintf("%u", filesize($fileInfo['file'])); + ErrorHandler::stop(); + $this->size = $size; + + // Check to see if it's smaller than min size + $min = $this->getMin(true); + $max = $this->getMax(true); + if (($min !== null) && ($size < $min)) { + if ($this->getByteString()) { + $this->options['min'] = $this->toByteString($min); + $this->size = $this->toByteString($size); + $this->error(self::TOO_SMALL); + $this->options['min'] = $min; + $this->size = $size; + } else { + $this->error(self::TOO_SMALL); + } + } + + // Check to see if it's larger than max size + if (($max !== null) && ($max < $size)) { + if ($this->getByteString()) { + $this->options['max'] = $this->toByteString($max); + $this->size = $this->toByteString($size); + $this->error(self::TOO_BIG); + $this->options['max'] = $max; + $this->size = $size; + } else { + $this->error(self::TOO_BIG); + } + } + + if ($this->getMessages()) { + return false; + } + + return true; + } + + /** + * Returns the formatted size + * + * @param int $size + * @return string + */ + protected function toByteString($size) + { + $sizes = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + for ($i = 0; $size >= 1024 && $i < 9; $i++) { + $size /= 1024; + } + + return round($size, 2) . $sizes[$i]; + } + + /** + * Returns the unformatted size + * + * @param string $size + * @return int + */ + protected function fromByteString($size) + { + if (is_numeric($size)) { + return (int) $size; + } + + $type = trim(substr($size, -2, 1)); + + $value = substr($size, 0, -1); + if (! is_numeric($value)) { + $value = trim(substr($value, 0, -1)); + } + + switch (strtoupper($type)) { + case 'Y': + $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024); + break; + case 'Z': + $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024); + break; + case 'E': + $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024); + break; + case 'P': + $value *= (1024 * 1024 * 1024 * 1024 * 1024); + break; + case 'T': + $value *= (1024 * 1024 * 1024 * 1024); + break; + case 'G': + $value *= (1024 * 1024 * 1024); + break; + case 'M': + $value *= (1024 * 1024); + break; + case 'K': + $value *= 1024; + break; + default: + break; + } + + return $value; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Upload.php b/lib/laminas/laminas-validator/src/File/Upload.php new file mode 100644 index 000000000..af8e994dc --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Upload.php @@ -0,0 +1,269 @@ + "File '%value%' exceeds upload_max_filesize directive in php.ini", + self::FORM_SIZE => "File '%value%' exceeds the MAX_FILE_SIZE directive that was " + . 'specified in the HTML form', + self::PARTIAL => "File '%value%' was only partially uploaded", + self::NO_FILE => "File '%value%' was not uploaded", + self::NO_TMP_DIR => "Missing a temporary folder to store '%value%'", + self::CANT_WRITE => "Failed to write file '%value%' to disk", + self::EXTENSION => "A PHP extension stopped uploading the file '%value%'", + self::ATTACK => "File '%value%' was illegally uploaded. This could be a possible attack", + self::FILE_NOT_FOUND => "File '%value%' was not found", + self::UNKNOWN => "Unknown error while uploading file '%value%'" + ]; + + protected $options = [ + 'files' => [], + ]; + + /** + * Sets validator options + * + * The array $files must be given in syntax of Laminas\File\Transfer\Transfer to be checked + * If no files are given the $_FILES array will be used automatically. + * NOTE: This validator will only work with HTTP POST uploads! + * + * @param array|\Traversable $options Array of files in syntax of \Laminas\File\Transfer\Transfer + */ + public function __construct($options = []) + { + if (is_array($options) && ! array_key_exists('files', $options)) { + $options = ['files' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the array of set files + * + * @param string $file (Optional) The file to return in detail + * @return array + * @throws Exception\InvalidArgumentException If file is not found + */ + public function getFiles($file = null) + { + if ($file !== null) { + $return = []; + foreach ($this->options['files'] as $name => $content) { + if ($name === $file) { + $return[$file] = $this->options['files'][$name]; + } + + if ($content instanceof UploadedFileInterface) { + if ($content->getClientFilename() === $file) { + $return[$name] = $this->options['files'][$name]; + } + } elseif ($content['name'] === $file) { + $return[$name] = $this->options['files'][$name]; + } + } + + if (! $return) { + throw new Exception\InvalidArgumentException("The file '$file' was not found"); + } + + return $return; + } + + return $this->options['files']; + } + + /** + * Sets the files to be checked + * + * @param array $files The files to check in syntax of \Laminas\File\Transfer\Transfer + * @return Upload Provides a fluent interface + */ + public function setFiles($files = []) + { + if (null === $files + || ((is_array($files) || $files instanceof Countable) + && count($files) === 0) + ) { + $this->options['files'] = $_FILES; + } else { + $this->options['files'] = $files; + } + + if ($this->options['files'] === null) { + $this->options['files'] = []; + } + + foreach ($this->options['files'] as $file => $content) { + if (! $content instanceof UploadedFileInterface + && ! isset($content['error']) + ) { + unset($this->options['files'][$file]); + } + } + + return $this; + } + + /** + * Returns true if and only if the file was uploaded without errors + * + * @param string $value Single file to check for upload errors, when giving null the $_FILES array + * from initialization will be used + * @param mixed $file + * @return bool + */ + public function isValid($value, $file = null) + { + $files = []; + $this->setValue($value); + if (array_key_exists($value, $this->getFiles())) { + $files = array_merge($files, $this->getFiles($value)); + } else { + foreach ($this->getFiles() as $file => $content) { + if ($content instanceof UploadedFileInterface) { + if ($content->getClientFilename() === $value) { + $files = array_merge($files, $this->getFiles($file)); + } + + // PSR cannot search by tmp_name because it does not have + // a public interface to get it, only user defined name + // from form field. + continue; + } + + + if (isset($content['name']) && ($content['name'] === $value)) { + $files = array_merge($files, $this->getFiles($file)); + } + + if (isset($content['tmp_name']) && ($content['tmp_name'] === $value)) { + $files = array_merge($files, $this->getFiles($file)); + } + } + } + + if (empty($files)) { + return $this->throwError($file, self::FILE_NOT_FOUND); + } + + foreach ($files as $file => $content) { + $this->value = $file; + $error = $content instanceof UploadedFileInterface + ? $content->getError() + : $content['error']; + + switch ($error) { + case 0: + if ($content instanceof UploadedFileInterface) { + // done! + break; + } + + // For standard SAPI environments, check that the upload + // was valid + if (! is_uploaded_file($content['tmp_name'])) { + $this->throwError($content, self::ATTACK); + } + break; + + case 1: + $this->throwError($content, self::INI_SIZE); + break; + + case 2: + $this->throwError($content, self::FORM_SIZE); + break; + + case 3: + $this->throwError($content, self::PARTIAL); + break; + + case 4: + $this->throwError($content, self::NO_FILE); + break; + + case 6: + $this->throwError($content, self::NO_TMP_DIR); + break; + + case 7: + $this->throwError($content, self::CANT_WRITE); + break; + + case 8: + $this->throwError($content, self::EXTENSION); + break; + + default: + $this->throwError($content, self::UNKNOWN); + break; + } + } + + if ($this->getMessages()) { + return false; + } + + return true; + } + + /** + * Throws an error of the given type + * + * @param array|string|UploadedFileInterface $file + * @param string $errorType + * @return false + */ + protected function throwError($file, $errorType) + { + if ($file !== null) { + if (is_array($file)) { + if (array_key_exists('name', $file)) { + $this->value = $file['name']; + } + } elseif (is_string($file)) { + $this->value = $file; + } elseif ($file instanceof UploadedFileInterface) { + $this->value = $file->getClientFilename(); + } + } + + $this->error($errorType); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/UploadFile.php b/lib/laminas/laminas-validator/src/File/UploadFile.php new file mode 100644 index 000000000..58f16368f --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/UploadFile.php @@ -0,0 +1,168 @@ + 'The uploaded file exceeds the upload_max_filesize directive in php.ini', + self::FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was ' + . 'specified in the HTML form', + self::PARTIAL => 'The uploaded file was only partially uploaded', + self::NO_FILE => 'No file was uploaded', + self::NO_TMP_DIR => 'Missing a temporary folder', + self::CANT_WRITE => 'Failed to write file to disk', + self::EXTENSION => 'A PHP extension stopped the file upload', + self::ATTACK => 'File was illegally uploaded. This could be a possible attack', + self::FILE_NOT_FOUND => 'File was not found', + self::UNKNOWN => 'Unknown error while uploading file', + ]; + + /** + * Returns true if and only if the file was uploaded without errors + * + * @param string|array|UploadedFileInterface $value File to check for upload errors + * @return bool + * @throws Exception\InvalidArgumentException + */ + public function isValid($value) + { + if (is_array($value)) { + if (! isset($value['tmp_name']) || ! isset($value['name']) || ! isset($value['error'])) { + throw new Exception\InvalidArgumentException( + 'Value array must be in $_FILES format' + ); + } + + return $this->validateUploadedFile( + $value['error'], + $value['name'], + $value['tmp_name'] + ); + } + + if ($value instanceof UploadedFileInterface) { + return $this->validatePsr7UploadedFile($value); + } + + if (is_string($value)) { + return $this->validateUploadedFile(0, basename($value), $value); + } + + $this->error(self::UNKNOWN); + return false; + } + + /** + * @param int $error UPLOAD_ERR_* constant value + * @return bool + */ + private function validateFileFromErrorCode($error) + { + switch ($error) { + case UPLOAD_ERR_OK: + return true; + + case UPLOAD_ERR_INI_SIZE: + $this->error(self::INI_SIZE); + return false; + + case UPLOAD_ERR_FORM_SIZE: + $this->error(self::FORM_SIZE); + return false; + + case UPLOAD_ERR_PARTIAL: + $this->error(self::PARTIAL); + return false; + + case UPLOAD_ERR_NO_FILE: + $this->error(self::NO_FILE); + return false; + + case UPLOAD_ERR_NO_TMP_DIR: + $this->error(self::NO_TMP_DIR); + return false; + + case UPLOAD_ERR_CANT_WRITE: + $this->error(self::CANT_WRITE); + return false; + + case UPLOAD_ERR_EXTENSION: + $this->error(self::EXTENSION); + return false; + + default: + $this->error(self::UNKNOWN); + return false; + } + } + + /** + * @param int $error UPLOAD_ERR_* constant + * @param string $filename + * @param string $uploadedFile Name of uploaded file (gen tmp_name) + * @return bool + */ + private function validateUploadedFile($error, $filename, $uploadedFile) + { + $this->setValue($filename); + + // Normal errors can be validated normally + if ($error !== UPLOAD_ERR_OK) { + return $this->validateFileFromErrorCode($error); + } + + // Did we get no name? Is the file missing? + if (empty($uploadedFile) || false === is_file($uploadedFile)) { + $this->error(self::FILE_NOT_FOUND); + return false; + } + + // Do we have an invalid upload? + if (! is_uploaded_file($uploadedFile)) { + $this->error(self::ATTACK); + return false; + } + + return true; + } + + /** + * @return bool + */ + private function validatePsr7UploadedFile(UploadedFileInterface $uploadedFile) + { + $this->setValue($uploadedFile); + return $this->validateFileFromErrorCode($uploadedFile->getError()); + } +} diff --git a/lib/laminas/laminas-validator/src/File/WordCount.php b/lib/laminas/laminas-validator/src/File/WordCount.php new file mode 100644 index 000000000..71f6001cf --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/WordCount.php @@ -0,0 +1,204 @@ + "Too many words, maximum '%max%' are allowed but '%count%' were counted", + self::TOO_LESS => "Too few words, minimum '%min%' are expected but '%count%' were counted", + self::NOT_FOUND => "File is not readable or does not exist", + ]; + + /** + * @var array Error message template variables + */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + 'count' => 'count' + ]; + + /** + * Word count + * + * @var int + */ + protected $count; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'min' => null, // Minimum word count, if null there is no minimum word count + 'max' => null, // Maximum word count, if null there is no maximum word count + ]; + + /** + * Sets validator options + * + * Min limits the word count, when used with max=null it is the maximum word count + * It also accepts an array with the keys 'min' and 'max' + * + * If $options is an integer, it will be used as maximum word count + * As Array is accepts the following keys: + * 'min': Minimum word count + * 'max': Maximum word count + * + * @param int|array|\Traversable $options Options for the adapter + */ + public function __construct($options = null) + { + if (1 < func_num_args()) { + $args = func_get_args(); + $options = [ + 'min' => array_shift($args), + 'max' => array_shift($args), + ]; + } + + if (is_string($options) || is_numeric($options)) { + $options = ['max' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the minimum word count + * + * @return int + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Sets the minimum word count + * + * @param int|array $min The minimum word count + * @throws Exception\InvalidArgumentException When min is greater than max + * @return self Provides a fluent interface + */ + public function setMin($min) + { + if (is_array($min) and isset($min['min'])) { + $min = $min['min']; + } + + if (! is_numeric($min)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $min = (int) $min; + if (($this->getMax() !== null) && ($min > $this->getMax())) { + throw new Exception\InvalidArgumentException( + "The minimum must be less than or equal to the maximum word count, but $min > {$this->getMax()}" + ); + } + + $this->options['min'] = $min; + return $this; + } + + /** + * Returns the maximum word count + * + * @return int + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * Sets the maximum file count + * + * @param int|array $max The maximum word count + * @throws Exception\InvalidArgumentException When max is smaller than min + * @return self Provides a fluent interface + */ + public function setMax($max) + { + if (is_array($max) and isset($max['max'])) { + $max = $max['max']; + } + + if (! is_numeric($max)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $max = (int) $max; + if (($this->getMin() !== null) && ($max < $this->getMin())) { + throw new Exception\InvalidArgumentException( + "The maximum must be greater than or equal to the minimum word count, but $max < {$this->getMin()}" + ); + } + + $this->options['max'] = $max; + return $this; + } + + /** + * Returns true if and only if the counted words are at least min and + * not bigger than max (when max is not null). + * + * @param string|array $value Filename to check for word count + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $content = file_get_contents($fileInfo['file']); + $this->count = str_word_count($content); + if (($this->getMax() !== null) && ($this->count > $this->getMax())) { + $this->error(self::TOO_MUCH); + return false; + } + + if (($this->getMin() !== null) && ($this->count < $this->getMin())) { + $this->error(self::TOO_LESS); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/GpsPoint.php b/lib/laminas/laminas-validator/src/GpsPoint.php new file mode 100644 index 000000000..72849c3f0 --- /dev/null +++ b/lib/laminas/laminas-validator/src/GpsPoint.php @@ -0,0 +1,130 @@ + '%value% is out of Bounds.', + 'gpsPointConvertError' => '%value% can not converted into a Decimal Degree Value.', + 'gpsPointIncompleteCoordinate' => '%value% did not provided a complete Coordinate', + ]; + + /** + * Returns true if and only if $value meets the validation requirements + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @param mixed $value + * @return bool + * @throws Exception\RuntimeException If validation of $value is impossible + */ + public function isValid($value) + { + if (strpos($value, ',') === false) { + $this->error(GpsPoint::INCOMPLETE_COORDINATE, $value); + return false; + } + + list($lat, $long) = explode(',', $value); + + if ($this->isValidCoordinate($lat, 90.0000) && $this->isValidCoordinate($long, 180.000)) { + return true; + } + + return false; + } + + /** + * @param string $value + * @param $maxBoundary + * @return bool + */ + private function isValidCoordinate($value, $maxBoundary) + { + $this->value = $value; + + $value = $this->removeWhiteSpace($value); + if ($this->isDMSValue($value)) { + $value = $this->convertValue($value); + } else { + $value = $this->removeDegreeSign($value); + } + + if ($value === false || $value === null) { + $this->error(self::CONVERT_ERROR); + return false; + } + + $doubleLatitude = (double)$value; + + if ($doubleLatitude <= $maxBoundary && $doubleLatitude >= $maxBoundary * -1) { + return true; + } + + $this->error(self::OUT_OF_BOUNDS); + return false; + } + + /** + * Determines if the give value is a Degrees Minutes Second Definition + * + * @param $value + * @return bool + */ + private function isDMSValue($value) + { + return preg_match('/([°\'"]+[NESW])/', $value) > 0; + } + + + /** + * @param string $value + * @return bool|string + */ + private function convertValue($value) + { + $matches = []; + $result = preg_match_all('/(\d{1,3})°(\d{1,2})\'(\d{1,2}[\.\d]{0,6})"[NESW]/i', $value, $matches); + + if ($result === false || $result === 0) { + return false; + } + + return $matches[1][0] + $matches[2][0] / 60 + ((double)$matches[3][0]) / 3600; + } + + /** + * @param string $value + * @return string + */ + private function removeWhiteSpace($value) + { + return preg_replace('/\s/', '', $value); + } + + /** + * @param string $value + * @return string + */ + private function removeDegreeSign($value) + { + return str_replace('°', '', $value); + } +} diff --git a/lib/laminas/laminas-validator/src/GreaterThan.php b/lib/laminas/laminas-validator/src/GreaterThan.php new file mode 100644 index 000000000..8f890dc6d --- /dev/null +++ b/lib/laminas/laminas-validator/src/GreaterThan.php @@ -0,0 +1,157 @@ + "The input is not greater than '%min%'", + self::NOT_GREATER_INCLUSIVE => "The input is not greater than or equal to '%min%'" + ]; + + /** + * @var array + */ + protected $messageVariables = [ + 'min' => 'min' + ]; + + /** + * Minimum value + * + * @var mixed + */ + protected $min; + + /** + * Whether to do inclusive comparisons, allowing equivalence to max + * + * If false, then strict comparisons are done, and the value may equal + * the min option + * + * @var bool + */ + protected $inclusive; + + /** + * Sets validator options + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + if (! is_array($options)) { + $options = func_get_args(); + $temp['min'] = array_shift($options); + + if (! empty($options)) { + $temp['inclusive'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('min', $options)) { + throw new Exception\InvalidArgumentException("Missing option 'min'"); + } + + if (! array_key_exists('inclusive', $options)) { + $options['inclusive'] = false; + } + + $this->setMin($options['min']) + ->setInclusive($options['inclusive']); + + parent::__construct($options); + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->min; + } + + /** + * Sets the min option + * + * @param mixed $min + * @return GreaterThan Provides a fluent interface + */ + public function setMin($min) + { + $this->min = $min; + return $this; + } + + /** + * Returns the inclusive option + * + * @return bool + */ + public function getInclusive() + { + return $this->inclusive; + } + + /** + * Sets the inclusive option + * + * @param bool $inclusive + * @return GreaterThan Provides a fluent interface + */ + public function setInclusive($inclusive) + { + $this->inclusive = $inclusive; + return $this; + } + + /** + * Returns true if and only if $value is greater than min option + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if ($this->inclusive) { + if ($this->min > $value) { + $this->error(self::NOT_GREATER_INCLUSIVE); + return false; + } + } else { + if ($this->min >= $value) { + $this->error(self::NOT_GREATER); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Hex.php b/lib/laminas/laminas-validator/src/Hex.php new file mode 100644 index 000000000..fb12b2740 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hex.php @@ -0,0 +1,47 @@ + "Invalid type given. String expected", + self::NOT_HEX => "The input contains non-hexadecimal characters", + ]; + + /** + * Returns true if and only if $value contains only hexadecimal digit characters + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value) && ! is_int($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + if (! ctype_xdigit((string) $value)) { + $this->error(self::NOT_HEX); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Hostname.php b/lib/laminas/laminas-validator/src/Hostname.php new file mode 100644 index 000000000..f7209b889 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname.php @@ -0,0 +1,2293 @@ + "The input appears to be a DNS hostname but the given punycode notation cannot be decoded", + self::INVALID => "Invalid type given. String expected", + self::INVALID_DASH => "The input appears to be a DNS hostname but contains a dash in an invalid position", + self::INVALID_HOSTNAME => "The input does not match the expected structure for a DNS hostname", + self::INVALID_HOSTNAME_SCHEMA => "The input appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'", + self::INVALID_LOCAL_NAME => "The input does not appear to be a valid local network name", + self::INVALID_URI => "The input does not appear to be a valid URI hostname", + self::IP_ADDRESS_NOT_ALLOWED => "The input appears to be an IP address, but IP addresses are not allowed", + self::LOCAL_NAME_NOT_ALLOWED => "The input appears to be a local network name but local network names are not allowed", + self::UNDECIPHERABLE_TLD => "The input appears to be a DNS hostname but cannot extract TLD part", + self::UNKNOWN_TLD => "The input appears to be a DNS hostname but cannot match TLD against known list", + ]; + // @codingStandardsIgnoreEnd + + /** + * @var array + */ + protected $messageVariables = [ + 'tld' => 'tld', + ]; + + const ALLOW_DNS = 1; // Allows Internet domain names (e.g., example.com) + const ALLOW_IP = 2; // Allows IP addresses + const ALLOW_LOCAL = 4; // Allows local network names (e.g., localhost, www.localdomain) + const ALLOW_URI = 8; // Allows URI hostnames + const ALLOW_ALL = 15; // Allows all types of hostnames + + /** + * Array of valid top-level-domains + * IanaVersion 2019081200 + * + * @see ftp://data.iana.org/TLD/tlds-alpha-by-domain.txt List of all TLDs by domain + * @see http://www.iana.org/domains/root/db/ Official list of supported TLDs + * @var array + */ + protected $validTlds = [ + 'aaa', + 'aarp', + 'abarth', + 'abb', + 'abbott', + 'abbvie', + 'abc', + 'able', + 'abogado', + 'abudhabi', + 'ac', + 'academy', + 'accenture', + 'accountant', + 'accountants', + 'aco', + 'actor', + 'ad', + 'adac', + 'ads', + 'adult', + 'ae', + 'aeg', + 'aero', + 'aetna', + 'af', + 'afamilycompany', + 'afl', + 'africa', + 'ag', + 'agakhan', + 'agency', + 'ai', + 'aig', + 'aigo', + 'airbus', + 'airforce', + 'airtel', + 'akdn', + 'al', + 'alfaromeo', + 'alibaba', + 'alipay', + 'allfinanz', + 'allstate', + 'ally', + 'alsace', + 'alstom', + 'am', + 'americanexpress', + 'americanfamily', + 'amex', + 'amfam', + 'amica', + 'amsterdam', + 'analytics', + 'android', + 'anquan', + 'anz', + 'ao', + 'aol', + 'apartments', + 'app', + 'apple', + 'aq', + 'aquarelle', + 'ar', + 'arab', + 'aramco', + 'archi', + 'army', + 'arpa', + 'art', + 'arte', + 'as', + 'asda', + 'asia', + 'associates', + 'at', + 'athleta', + 'attorney', + 'au', + 'auction', + 'audi', + 'audible', + 'audio', + 'auspost', + 'author', + 'auto', + 'autos', + 'avianca', + 'aw', + 'aws', + 'ax', + 'axa', + 'az', + 'azure', + 'ba', + 'baby', + 'baidu', + 'banamex', + 'bananarepublic', + 'band', + 'bank', + 'bar', + 'barcelona', + 'barclaycard', + 'barclays', + 'barefoot', + 'bargains', + 'baseball', + 'basketball', + 'bauhaus', + 'bayern', + 'bb', + 'bbc', + 'bbt', + 'bbva', + 'bcg', + 'bcn', + 'bd', + 'be', + 'beats', + 'beauty', + 'beer', + 'bentley', + 'berlin', + 'best', + 'bestbuy', + 'bet', + 'bf', + 'bg', + 'bh', + 'bharti', + 'bi', + 'bible', + 'bid', + 'bike', + 'bing', + 'bingo', + 'bio', + 'biz', + 'bj', + 'black', + 'blackfriday', + 'blockbuster', + 'blog', + 'bloomberg', + 'blue', + 'bm', + 'bms', + 'bmw', + 'bn', + 'bnpparibas', + 'bo', + 'boats', + 'boehringer', + 'bofa', + 'bom', + 'bond', + 'boo', + 'book', + 'booking', + 'bosch', + 'bostik', + 'boston', + 'bot', + 'boutique', + 'box', + 'br', + 'bradesco', + 'bridgestone', + 'broadway', + 'broker', + 'brother', + 'brussels', + 'bs', + 'bt', + 'budapest', + 'bugatti', + 'build', + 'builders', + 'business', + 'buy', + 'buzz', + 'bv', + 'bw', + 'by', + 'bz', + 'bzh', + 'ca', + 'cab', + 'cafe', + 'cal', + 'call', + 'calvinklein', + 'cam', + 'camera', + 'camp', + 'cancerresearch', + 'canon', + 'capetown', + 'capital', + 'capitalone', + 'car', + 'caravan', + 'cards', + 'care', + 'career', + 'careers', + 'cars', + 'cartier', + 'casa', + 'case', + 'caseih', + 'cash', + 'casino', + 'cat', + 'catering', + 'catholic', + 'cba', + 'cbn', + 'cbre', + 'cbs', + 'cc', + 'cd', + 'ceb', + 'center', + 'ceo', + 'cern', + 'cf', + 'cfa', + 'cfd', + 'cg', + 'ch', + 'chanel', + 'channel', + 'charity', + 'chase', + 'chat', + 'cheap', + 'chintai', + 'christmas', + 'chrome', + 'chrysler', + 'church', + 'ci', + 'cipriani', + 'circle', + 'cisco', + 'citadel', + 'citi', + 'citic', + 'city', + 'cityeats', + 'ck', + 'cl', + 'claims', + 'cleaning', + 'click', + 'clinic', + 'clinique', + 'clothing', + 'cloud', + 'club', + 'clubmed', + 'cm', + 'cn', + 'co', + 'coach', + 'codes', + 'coffee', + 'college', + 'cologne', + 'com', + 'comcast', + 'commbank', + 'community', + 'company', + 'compare', + 'computer', + 'comsec', + 'condos', + 'construction', + 'consulting', + 'contact', + 'contractors', + 'cooking', + 'cookingchannel', + 'cool', + 'coop', + 'corsica', + 'country', + 'coupon', + 'coupons', + 'courses', + 'cr', + 'credit', + 'creditcard', + 'creditunion', + 'cricket', + 'crown', + 'crs', + 'cruise', + 'cruises', + 'csc', + 'cu', + 'cuisinella', + 'cv', + 'cw', + 'cx', + 'cy', + 'cymru', + 'cyou', + 'cz', + 'dabur', + 'dad', + 'dance', + 'data', + 'date', + 'dating', + 'datsun', + 'day', + 'dclk', + 'dds', + 'de', + 'deal', + 'dealer', + 'deals', + 'degree', + 'delivery', + 'dell', + 'deloitte', + 'delta', + 'democrat', + 'dental', + 'dentist', + 'desi', + 'design', + 'dev', + 'dhl', + 'diamonds', + 'diet', + 'digital', + 'direct', + 'directory', + 'discount', + 'discover', + 'dish', + 'diy', + 'dj', + 'dk', + 'dm', + 'dnp', + 'do', + 'docs', + 'doctor', + 'dodge', + 'dog', + 'domains', + 'dot', + 'download', + 'drive', + 'dtv', + 'dubai', + 'duck', + 'dunlop', + 'duns', + 'dupont', + 'durban', + 'dvag', + 'dvr', + 'dz', + 'earth', + 'eat', + 'ec', + 'eco', + 'edeka', + 'edu', + 'education', + 'ee', + 'eg', + 'email', + 'emerck', + 'energy', + 'engineer', + 'engineering', + 'enterprises', + 'epson', + 'equipment', + 'er', + 'ericsson', + 'erni', + 'es', + 'esq', + 'estate', + 'esurance', + 'et', + 'etisalat', + 'eu', + 'eurovision', + 'eus', + 'events', + 'everbank', + 'exchange', + 'expert', + 'exposed', + 'express', + 'extraspace', + 'fage', + 'fail', + 'fairwinds', + 'faith', + 'family', + 'fan', + 'fans', + 'farm', + 'farmers', + 'fashion', + 'fast', + 'fedex', + 'feedback', + 'ferrari', + 'ferrero', + 'fi', + 'fiat', + 'fidelity', + 'fido', + 'film', + 'final', + 'finance', + 'financial', + 'fire', + 'firestone', + 'firmdale', + 'fish', + 'fishing', + 'fit', + 'fitness', + 'fj', + 'fk', + 'flickr', + 'flights', + 'flir', + 'florist', + 'flowers', + 'fly', + 'fm', + 'fo', + 'foo', + 'food', + 'foodnetwork', + 'football', + 'ford', + 'forex', + 'forsale', + 'forum', + 'foundation', + 'fox', + 'fr', + 'free', + 'fresenius', + 'frl', + 'frogans', + 'frontdoor', + 'frontier', + 'ftr', + 'fujitsu', + 'fujixerox', + 'fun', + 'fund', + 'furniture', + 'futbol', + 'fyi', + 'ga', + 'gal', + 'gallery', + 'gallo', + 'gallup', + 'game', + 'games', + 'gap', + 'garden', + 'gay', + 'gb', + 'gbiz', + 'gd', + 'gdn', + 'ge', + 'gea', + 'gent', + 'genting', + 'george', + 'gf', + 'gg', + 'ggee', + 'gh', + 'gi', + 'gift', + 'gifts', + 'gives', + 'giving', + 'gl', + 'glade', + 'glass', + 'gle', + 'global', + 'globo', + 'gm', + 'gmail', + 'gmbh', + 'gmo', + 'gmx', + 'gn', + 'godaddy', + 'gold', + 'goldpoint', + 'golf', + 'goo', + 'goodyear', + 'goog', + 'google', + 'gop', + 'got', + 'gov', + 'gp', + 'gq', + 'gr', + 'grainger', + 'graphics', + 'gratis', + 'green', + 'gripe', + 'grocery', + 'group', + 'gs', + 'gt', + 'gu', + 'guardian', + 'gucci', + 'guge', + 'guide', + 'guitars', + 'guru', + 'gw', + 'gy', + 'hair', + 'hamburg', + 'hangout', + 'haus', + 'hbo', + 'hdfc', + 'hdfcbank', + 'health', + 'healthcare', + 'help', + 'helsinki', + 'here', + 'hermes', + 'hgtv', + 'hiphop', + 'hisamitsu', + 'hitachi', + 'hiv', + 'hk', + 'hkt', + 'hm', + 'hn', + 'hockey', + 'holdings', + 'holiday', + 'homedepot', + 'homegoods', + 'homes', + 'homesense', + 'honda', + 'horse', + 'hospital', + 'host', + 'hosting', + 'hot', + 'hoteles', + 'hotels', + 'hotmail', + 'house', + 'how', + 'hr', + 'hsbc', + 'ht', + 'hu', + 'hughes', + 'hyatt', + 'hyundai', + 'ibm', + 'icbc', + 'ice', + 'icu', + 'id', + 'ie', + 'ieee', + 'ifm', + 'ikano', + 'il', + 'im', + 'imamat', + 'imdb', + 'immo', + 'immobilien', + 'in', + 'inc', + 'industries', + 'infiniti', + 'info', + 'ing', + 'ink', + 'institute', + 'insurance', + 'insure', + 'int', + 'intel', + 'international', + 'intuit', + 'investments', + 'io', + 'ipiranga', + 'iq', + 'ir', + 'irish', + 'is', + 'ismaili', + 'ist', + 'istanbul', + 'it', + 'itau', + 'itv', + 'iveco', + 'jaguar', + 'java', + 'jcb', + 'jcp', + 'je', + 'jeep', + 'jetzt', + 'jewelry', + 'jio', + 'jll', + 'jm', + 'jmp', + 'jnj', + 'jo', + 'jobs', + 'joburg', + 'jot', + 'joy', + 'jp', + 'jpmorgan', + 'jprs', + 'juegos', + 'juniper', + 'kaufen', + 'kddi', + 'ke', + 'kerryhotels', + 'kerrylogistics', + 'kerryproperties', + 'kfh', + 'kg', + 'kh', + 'ki', + 'kia', + 'kim', + 'kinder', + 'kindle', + 'kitchen', + 'kiwi', + 'km', + 'kn', + 'koeln', + 'komatsu', + 'kosher', + 'kp', + 'kpmg', + 'kpn', + 'kr', + 'krd', + 'kred', + 'kuokgroup', + 'kw', + 'ky', + 'kyoto', + 'kz', + 'la', + 'lacaixa', + 'ladbrokes', + 'lamborghini', + 'lamer', + 'lancaster', + 'lancia', + 'lancome', + 'land', + 'landrover', + 'lanxess', + 'lasalle', + 'lat', + 'latino', + 'latrobe', + 'law', + 'lawyer', + 'lb', + 'lc', + 'lds', + 'lease', + 'leclerc', + 'lefrak', + 'legal', + 'lego', + 'lexus', + 'lgbt', + 'li', + 'liaison', + 'lidl', + 'life', + 'lifeinsurance', + 'lifestyle', + 'lighting', + 'like', + 'lilly', + 'limited', + 'limo', + 'lincoln', + 'linde', + 'link', + 'lipsy', + 'live', + 'living', + 'lixil', + 'lk', + 'llc', + 'loan', + 'loans', + 'locker', + 'locus', + 'loft', + 'lol', + 'london', + 'lotte', + 'lotto', + 'love', + 'lpl', + 'lplfinancial', + 'lr', + 'ls', + 'lt', + 'ltd', + 'ltda', + 'lu', + 'lundbeck', + 'lupin', + 'luxe', + 'luxury', + 'lv', + 'ly', + 'ma', + 'macys', + 'madrid', + 'maif', + 'maison', + 'makeup', + 'man', + 'management', + 'mango', + 'map', + 'market', + 'marketing', + 'markets', + 'marriott', + 'marshalls', + 'maserati', + 'mattel', + 'mba', + 'mc', + 'mckinsey', + 'md', + 'me', + 'med', + 'media', + 'meet', + 'melbourne', + 'meme', + 'memorial', + 'men', + 'menu', + 'merckmsd', + 'metlife', + 'mg', + 'mh', + 'miami', + 'microsoft', + 'mil', + 'mini', + 'mint', + 'mit', + 'mitsubishi', + 'mk', + 'ml', + 'mlb', + 'mls', + 'mm', + 'mma', + 'mn', + 'mo', + 'mobi', + 'mobile', + 'mobily', + 'moda', + 'moe', + 'moi', + 'mom', + 'monash', + 'money', + 'monster', + 'mopar', + 'mormon', + 'mortgage', + 'moscow', + 'moto', + 'motorcycles', + 'mov', + 'movie', + 'movistar', + 'mp', + 'mq', + 'mr', + 'ms', + 'msd', + 'mt', + 'mtn', + 'mtr', + 'mu', + 'museum', + 'mutual', + 'mv', + 'mw', + 'mx', + 'my', + 'mz', + 'na', + 'nab', + 'nadex', + 'nagoya', + 'name', + 'nationwide', + 'natura', + 'navy', + 'nba', + 'nc', + 'ne', + 'nec', + 'net', + 'netbank', + 'netflix', + 'network', + 'neustar', + 'new', + 'newholland', + 'news', + 'next', + 'nextdirect', + 'nexus', + 'nf', + 'nfl', + 'ng', + 'ngo', + 'nhk', + 'ni', + 'nico', + 'nike', + 'nikon', + 'ninja', + 'nissan', + 'nissay', + 'nl', + 'no', + 'nokia', + 'northwesternmutual', + 'norton', + 'now', + 'nowruz', + 'nowtv', + 'np', + 'nr', + 'nra', + 'nrw', + 'ntt', + 'nu', + 'nyc', + 'nz', + 'obi', + 'observer', + 'off', + 'office', + 'okinawa', + 'olayan', + 'olayangroup', + 'oldnavy', + 'ollo', + 'om', + 'omega', + 'one', + 'ong', + 'onl', + 'online', + 'onyourside', + 'ooo', + 'open', + 'oracle', + 'orange', + 'org', + 'organic', + 'origins', + 'osaka', + 'otsuka', + 'ott', + 'ovh', + 'pa', + 'page', + 'panasonic', + 'paris', + 'pars', + 'partners', + 'parts', + 'party', + 'passagens', + 'pay', + 'pccw', + 'pe', + 'pet', + 'pf', + 'pfizer', + 'pg', + 'ph', + 'pharmacy', + 'phd', + 'philips', + 'phone', + 'photo', + 'photography', + 'photos', + 'physio', + 'piaget', + 'pics', + 'pictet', + 'pictures', + 'pid', + 'pin', + 'ping', + 'pink', + 'pioneer', + 'pizza', + 'pk', + 'pl', + 'place', + 'play', + 'playstation', + 'plumbing', + 'plus', + 'pm', + 'pn', + 'pnc', + 'pohl', + 'poker', + 'politie', + 'porn', + 'post', + 'pr', + 'pramerica', + 'praxi', + 'press', + 'prime', + 'pro', + 'prod', + 'productions', + 'prof', + 'progressive', + 'promo', + 'properties', + 'property', + 'protection', + 'pru', + 'prudential', + 'ps', + 'pt', + 'pub', + 'pw', + 'pwc', + 'py', + 'qa', + 'qpon', + 'quebec', + 'quest', + 'qvc', + 'racing', + 'radio', + 'raid', + 're', + 'read', + 'realestate', + 'realtor', + 'realty', + 'recipes', + 'red', + 'redstone', + 'redumbrella', + 'rehab', + 'reise', + 'reisen', + 'reit', + 'reliance', + 'ren', + 'rent', + 'rentals', + 'repair', + 'report', + 'republican', + 'rest', + 'restaurant', + 'review', + 'reviews', + 'rexroth', + 'rich', + 'richardli', + 'ricoh', + 'rightathome', + 'ril', + 'rio', + 'rip', + 'rmit', + 'ro', + 'rocher', + 'rocks', + 'rodeo', + 'rogers', + 'room', + 'rs', + 'rsvp', + 'ru', + 'rugby', + 'ruhr', + 'run', + 'rw', + 'rwe', + 'ryukyu', + 'sa', + 'saarland', + 'safe', + 'safety', + 'sakura', + 'sale', + 'salon', + 'samsclub', + 'samsung', + 'sandvik', + 'sandvikcoromant', + 'sanofi', + 'sap', + 'sarl', + 'sas', + 'save', + 'saxo', + 'sb', + 'sbi', + 'sbs', + 'sc', + 'sca', + 'scb', + 'schaeffler', + 'schmidt', + 'scholarships', + 'school', + 'schule', + 'schwarz', + 'science', + 'scjohnson', + 'scor', + 'scot', + 'sd', + 'se', + 'search', + 'seat', + 'secure', + 'security', + 'seek', + 'select', + 'sener', + 'services', + 'ses', + 'seven', + 'sew', + 'sex', + 'sexy', + 'sfr', + 'sg', + 'sh', + 'shangrila', + 'sharp', + 'shaw', + 'shell', + 'shia', + 'shiksha', + 'shoes', + 'shop', + 'shopping', + 'shouji', + 'show', + 'showtime', + 'shriram', + 'si', + 'silk', + 'sina', + 'singles', + 'site', + 'sj', + 'sk', + 'ski', + 'skin', + 'sky', + 'skype', + 'sl', + 'sling', + 'sm', + 'smart', + 'smile', + 'sn', + 'sncf', + 'so', + 'soccer', + 'social', + 'softbank', + 'software', + 'sohu', + 'solar', + 'solutions', + 'song', + 'sony', + 'soy', + 'space', + 'sport', + 'spot', + 'spreadbetting', + 'sr', + 'srl', + 'srt', + 'ss', + 'st', + 'stada', + 'staples', + 'star', + 'statebank', + 'statefarm', + 'stc', + 'stcgroup', + 'stockholm', + 'storage', + 'store', + 'stream', + 'studio', + 'study', + 'style', + 'su', + 'sucks', + 'supplies', + 'supply', + 'support', + 'surf', + 'surgery', + 'suzuki', + 'sv', + 'swatch', + 'swiftcover', + 'swiss', + 'sx', + 'sy', + 'sydney', + 'symantec', + 'systems', + 'sz', + 'tab', + 'taipei', + 'talk', + 'taobao', + 'target', + 'tatamotors', + 'tatar', + 'tattoo', + 'tax', + 'taxi', + 'tc', + 'tci', + 'td', + 'tdk', + 'team', + 'tech', + 'technology', + 'tel', + 'telefonica', + 'temasek', + 'tennis', + 'teva', + 'tf', + 'tg', + 'th', + 'thd', + 'theater', + 'theatre', + 'tiaa', + 'tickets', + 'tienda', + 'tiffany', + 'tips', + 'tires', + 'tirol', + 'tj', + 'tjmaxx', + 'tjx', + 'tk', + 'tkmaxx', + 'tl', + 'tm', + 'tmall', + 'tn', + 'to', + 'today', + 'tokyo', + 'tools', + 'top', + 'toray', + 'toshiba', + 'total', + 'tours', + 'town', + 'toyota', + 'toys', + 'tr', + 'trade', + 'trading', + 'training', + 'travel', + 'travelchannel', + 'travelers', + 'travelersinsurance', + 'trust', + 'trv', + 'tt', + 'tube', + 'tui', + 'tunes', + 'tushu', + 'tv', + 'tvs', + 'tw', + 'tz', + 'ua', + 'ubank', + 'ubs', + 'uconnect', + 'ug', + 'uk', + 'unicom', + 'university', + 'uno', + 'uol', + 'ups', + 'us', + 'uy', + 'uz', + 'va', + 'vacations', + 'vana', + 'vanguard', + 'vc', + 've', + 'vegas', + 'ventures', + 'verisign', + 'versicherung', + 'vet', + 'vg', + 'vi', + 'viajes', + 'video', + 'vig', + 'viking', + 'villas', + 'vin', + 'vip', + 'virgin', + 'visa', + 'vision', + 'vistaprint', + 'viva', + 'vivo', + 'vlaanderen', + 'vn', + 'vodka', + 'volkswagen', + 'volvo', + 'vote', + 'voting', + 'voto', + 'voyage', + 'vu', + 'vuelos', + 'wales', + 'walmart', + 'walter', + 'wang', + 'wanggou', + 'warman', + 'watch', + 'watches', + 'weather', + 'weatherchannel', + 'webcam', + 'weber', + 'website', + 'wed', + 'wedding', + 'weibo', + 'weir', + 'wf', + 'whoswho', + 'wien', + 'wiki', + 'williamhill', + 'win', + 'windows', + 'wine', + 'winners', + 'wme', + 'wolterskluwer', + 'woodside', + 'work', + 'works', + 'world', + 'wow', + 'ws', + 'wtc', + 'wtf', + 'xbox', + 'xerox', + 'xfinity', + 'xihuan', + 'xin', + 'कॉम', + 'セール', + '佛山', + 'ಭಾರತ', + '慈善', + '集团', + '在线', + '한국', + 'ଭାରତ', + '大众汽车', + '点看', + 'คอม', + 'ভাৰত', + 'ভারত', + '八卦', + 'موقع', + 'বাংলা', + '公益', + '公司', + '香格里拉', + '网站', + '移动', + '我爱你', + 'москва', + 'қаз', + 'католик', + 'онлайн', + 'сайт', + '联通', + 'срб', + 'бг', + 'бел', + 'קום', + '时尚', + '微博', + '淡马锡', + 'ファッション', + 'орг', + 'नेट', + 'ストア', + '삼성', + 'சிங்கப்பூர்', + '商标', + '商店', + '商城', + 'дети', + 'мкд', + 'ею', + 'ポイント', + '新闻', + '工行', + '家電', + 'كوم', + '中文网', + '中信', + '中国', + '中國', + '娱乐', + '谷歌', + 'భారత్', + 'ලංකා', + '電訊盈科', + '购物', + 'クラウド', + 'ભારત', + '通販', + 'भारतम्', + 'भारत', + 'भारोत', + '网店', + 'संगठन', + '餐厅', + '网络', + 'ком', + 'укр', + '香港', + '诺基亚', + '食品', + '飞利浦', + '台湾', + '台灣', + '手表', + '手机', + 'мон', + 'الجزائر', + 'عمان', + 'ارامكو', + 'ایران', + 'العليان', + 'اتصالات', + 'امارات', + 'بازار', + 'موريتانيا', + 'پاکستان', + 'الاردن', + 'موبايلي', + 'بارت', + 'بھارت', + 'المغرب', + 'ابوظبي', + 'السعودية', + 'ڀارت', + 'كاثوليك', + 'سودان', + 'همراه', + 'عراق', + 'مليسيا', + '澳門', + '닷컴', + '政府', + 'شبكة', + 'بيتك', + 'عرب', + 'გე', + '机构', + '组织机构', + '健康', + 'ไทย', + 'سورية', + '招聘', + 'рус', + 'рф', + '珠宝', + 'تونس', + '大拿', + 'みんな', + 'グーグル', + 'ελ', + '世界', + '書籍', + 'ഭാരതം', + 'ਭਾਰਤ', + '网址', + '닷넷', + 'コム', + '天主教', + '游戏', + 'vermögensberater', + 'vermögensberatung', + '企业', + '信息', + '嘉里大酒店', + '嘉里', + 'مصر', + 'قطر', + '广东', + 'இலங்கை', + 'இந்தியா', + 'հայ', + '新加坡', + 'فلسطين', + '政务', + 'xxx', + 'xyz', + 'yachts', + 'yahoo', + 'yamaxun', + 'yandex', + 'ye', + 'yodobashi', + 'yoga', + 'yokohama', + 'you', + 'youtube', + 'yt', + 'yun', + 'za', + 'zappos', + 'zara', + 'zero', + 'zip', + 'zm', + 'zone', + 'zuerich', + 'zw', + ]; + + // @codingStandardsIgnoreStart + /** + * Array for valid Idns + * @see http://www.iana.org/domains/idn-tables/ Official list of supported IDN Chars + * (.AC) Ascension Island http://www.nic.ac/pdf/AC-IDN-Policy.pdf + * (.AR) Argentina http://www.nic.ar/faqidn.html + * (.AS) American Samoa http://www.nic.as/idn/chars.cfm + * (.AT) Austria http://www.nic.at/en/service/technical_information/idn/charset_converter/ + * (.BIZ) International http://www.iana.org/domains/idn-tables/ + * (.BR) Brazil http://registro.br/faq/faq6.html + * (.BV) Bouvett Island http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html + * (.CAT) Catalan http://www.iana.org/domains/idn-tables/tables/cat_ca_1.0.html + * (.CH) Switzerland https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1 + * (.CL) Chile http://www.iana.org/domains/idn-tables/tables/cl_latn_1.0.html + * (.COM) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html + * (.DE) Germany https://www.denic.de/en/know-how/idn-domains/idn-character-list/ + * (.DK) Danmark http://www.dk-hostmaster.dk/index.php?id=151 + * (.EE) Estonia https://www.iana.org/domains/idn-tables/tables/pl_et-pl_1.0.html + * (.ES) Spain https://www.nic.es/media/2008-05/1210147705287.pdf + * (.FI) Finland http://www.ficora.fi/en/index/palvelut/fiverkkotunnukset/aakkostenkaytto.html + * (.GR) Greece https://grweb.ics.forth.gr/CharacterTable1_en.jsp + * (.HR) Croatia https://www.dns.hr/en/portal/files/Odluka-1,2alfanum-dijak.pdf + * (.HU) Hungary http://www.domain.hu/domain/English/szabalyzat/szabalyzat.html + * (.IL) Israel http://www.isoc.org.il/domains/il-domain-rules.html + * (.INFO) International http://www.nic.info/info/idn + * (.IO) British Indian Ocean Territory http://www.nic.io/IO-IDN-Policy.pdf + * (.IR) Iran http://www.nic.ir/Allowable_Characters_dot-iran + * (.IS) Iceland https://www.isnic.is/en/domain/rules#2 + * (.KR) Korea http://www.iana.org/domains/idn-tables/tables/kr_ko-kr_1.0.html + * (.LI) Liechtenstein https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1 + * (.LT) Lithuania http://www.domreg.lt/static/doc/public/idn_symbols-en.pdf + * (.MD) Moldova http://www.register.md/ + * (.MUSEUM) International http://www.iana.org/domains/idn-tables/tables/museum_latn_1.0.html + * (.NET) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html + * (.NO) Norway http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html + * (.NU) Niue http://www.worldnames.net/ + * (.ORG) International http://www.pir.org/index.php?db=content/FAQs&tbl=FAQs_Registrant&id=2 + * (.PE) Peru https://www.nic.pe/nuevas_politicas_faq_2.php + * (.PL) Poland http://www.dns.pl/IDN/allowed_character_sets.pdf + * (.PR) Puerto Rico http://www.nic.pr/idn_rules.asp + * (.PT) Portugal https://online.dns.pt/dns_2008/do?com=DS;8216320233;111;+PAGE(4000058)+K-CAT-CODIGO(C.125)+RCNT(100); + * (.RU) Russia http://www.iana.org/domains/idn-tables/tables/ru_ru-ru_1.0.html + * (.SA) Saudi Arabia http://www.iana.org/domains/idn-tables/tables/sa_ar_1.0.html + * (.SE) Sweden http://www.iis.se/english/IDN_campaignsite.shtml?lang=en + * (.SH) Saint Helena http://www.nic.sh/SH-IDN-Policy.pdf + * (.SJ) Svalbard and Jan Mayen http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html + * (.TH) Thailand http://www.iana.org/domains/idn-tables/tables/th_th-th_1.0.html + * (.TM) Turkmenistan http://www.nic.tm/TM-IDN-Policy.pdf + * (.TR) Turkey https://www.nic.tr/index.php + * (.UA) Ukraine http://www.iana.org/domains/idn-tables/tables/ua_cyrl_1.2.html + * (.VE) Venice http://www.iana.org/domains/idn-tables/tables/ve_es_1.0.html + * (.VN) Vietnam http://www.vnnic.vn/english/5-6-300-2-2-04-20071115.htm#1.%20Introduction + * + * @var array + */ + protected $validIdns = [ + 'AC' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēėęěĝġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśŝşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'], + 'AR' => [1 => '/^[\x{002d}0-9a-zà-ãç-êìíñ-õü]{1,63}$/iu'], + 'AS' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĸĺļľłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźż]{1,63}$/iu'], + 'AT' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿœšž]{1,63}$/iu'], + 'BIZ' => 'Hostname/Biz.php', + 'BR' => [1 => '/^[\x{002d}0-9a-zà-ãçéíó-õúü]{1,63}$/iu'], + 'BV' => [1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'], + 'CAT' => [1 => '/^[\x{002d}0-9a-z·àç-éíïòóúü]{1,63}$/iu'], + 'CH' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'], + 'CL' => [1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu'], + 'CN' => 'Hostname/Cn.php', + 'COM' => 'Hostname/Com.php', + 'DE' => [1 => '/^[\x{002d}0-9a-záàăâåäãąāæćĉčċçďđéèĕêěëėęēğĝġģĥħíìĭîïĩįīıĵķĺľļłńňñņŋóòŏôöőõøōœĸŕřŗśŝšşßťţŧúùŭûůüűũųūŵýŷÿźžżðþ]{1,63}$/iu'], + 'DK' => [1 => '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu'], + 'EE' => [1 => '/^[\x{002d}0-9a-zäõöüšž]{1,63}$/iu'], + 'ES' => [1 => '/^[\x{002d}0-9a-zàáçèéíïñòóúü·]{1,63}$/iu'], + 'EU' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿ]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-zāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĺļľŀłńņňʼnŋōŏőœŕŗřśŝšťŧũūŭůűųŵŷźżž]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-zșț]{1,63}$/iu', + 4 => '/^[\x{002d}0-9a-zΐάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ]{1,63}$/iu', + 5 => '/^[\x{002d}0-9a-zабвгдежзийклмнопрстуфхцчшщъыьэюя]{1,63}$/iu', + 6 => '/^[\x{002d}0-9a-zἀ-ἇἐ-ἕἠ-ἧἰ-ἷὀ-ὅὐ-ὗὠ-ὧὰ-ὼώᾀ-ᾇᾐ-ᾗᾠ-ᾧᾰ-ᾴᾶᾷῂῃῄῆῇῐ-ῒΐῖῗῠ-ῧῲῳῴῶῷ]{1,63}$/iu'], + 'FI' => [1 => '/^[\x{002d}0-9a-zäåö]{1,63}$/iu'], + 'GR' => [1 => '/^[\x{002d}0-9a-zΆΈΉΊΌΎ-ΡΣ-ώἀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼῂῃῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲῳῴῶ-ῼ]{1,63}$/iu'], + 'HK' => 'Hostname/Cn.php', + 'HR' => [1 => '/^[\x{002d}0-9a-zžćčđš]{1,63}$/iu'], + 'HU' => [1 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu'], + 'IL' => [1 => '/^[\x{002d}0-9\x{05D0}-\x{05EA}]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-z]{1,63}$/i'], + 'INFO' => [1 => '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-záæéíðóöúýþ]{1,63}$/iu', + 4 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu', + 5 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu', + 6 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu', + 7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu', + 8 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu'], + 'IO' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'], + 'IS' => [1 => '/^[\x{002d}0-9a-záéýúíóþæöð]{1,63}$/iu'], + 'IT' => [1 => '/^[\x{002d}0-9a-zàâäèéêëìîïòôöùûüæœçÿß-]{1,63}$/iu'], + 'JP' => 'Hostname/Jp.php', + 'KR' => [1 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu'], + 'LI' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'], + 'LT' => [1 => '/^[\x{002d}0-9ąčęėįšųūž]{1,63}$/iu'], + 'MD' => [1 => '/^[\x{002d}0-9ăâîşţ]{1,63}$/iu'], + 'MUSEUM' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćċčďđēėęěğġģħīįıķĺļľłńņňŋōőœŕŗřśşšţťŧūůűųŵŷźżžǎǐǒǔ\x{01E5}\x{01E7}\x{01E9}\x{01EF}ə\x{0292}ẁẃẅỳ]{1,63}$/iu'], + 'NET' => 'Hostname/Com.php', + 'NO' => [1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'], + 'NU' => 'Hostname/Com.php', + 'ORG' => [1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu', + 4 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu', + 5 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu', + 6 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu', + 7 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu'], + 'PE' => [1 => '/^[\x{002d}0-9a-zñáéíóúü]{1,63}$/iu'], + 'PL' => [1 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu', + 2 => '/^[\x{002d}а-ик-ш\x{0450}ѓѕјљњќџ]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu', + 4 => '/^[\x{002d}0-9а-яё\x{04C2}]{1,63}$/iu', + 5 => '/^[\x{002d}0-9a-zàáâèéêìíîòóôùúûċġħż]{1,63}$/iu', + 6 => '/^[\x{002d}0-9a-zàäåæéêòóôöøü]{1,63}$/iu', + 7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu', + 8 => '/^[\x{002d}0-9a-zàáâãçéêíòóôõúü]{1,63}$/iu', + 9 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu', + 10 => '/^[\x{002d}0-9a-záäéíóôúýčďĺľňŕšťž]{1,63}$/iu', + 11 => '/^[\x{002d}0-9a-zçë]{1,63}$/iu', + 12 => '/^[\x{002d}0-9а-ик-шђјљњћџ]{1,63}$/iu', + 13 => '/^[\x{002d}0-9a-zćčđšž]{1,63}$/iu', + 14 => '/^[\x{002d}0-9a-zâçöûüğış]{1,63}$/iu', + 15 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu', + 16 => '/^[\x{002d}0-9a-zäõöüšž]{1,63}$/iu', + 17 => '/^[\x{002d}0-9a-zĉĝĥĵŝŭ]{1,63}$/iu', + 18 => '/^[\x{002d}0-9a-zâäéëîô]{1,63}$/iu', + 19 => '/^[\x{002d}0-9a-zàáâäåæçèéêëìíîïðñòôöøùúûüýćčłńřśš]{1,63}$/iu', + 20 => '/^[\x{002d}0-9a-zäåæõöøüšž]{1,63}$/iu', + 21 => '/^[\x{002d}0-9a-zàáçèéìíòóùú]{1,63}$/iu', + 22 => '/^[\x{002d}0-9a-zàáéíóöúüőű]{1,63}$/iu', + 23 => '/^[\x{002d}0-9ΐά-ώ]{1,63}$/iu', + 24 => '/^[\x{002d}0-9a-zàáâåæçèéêëðóôöøüþœ]{1,63}$/iu', + 25 => '/^[\x{002d}0-9a-záäéíóöúüýčďěňřšťůž]{1,63}$/iu', + 26 => '/^[\x{002d}0-9a-z·àçèéíïòóúü]{1,63}$/iu', + 27 => '/^[\x{002d}0-9а-ъьюя\x{0450}\x{045D}]{1,63}$/iu', + 28 => '/^[\x{002d}0-9а-яёіў]{1,63}$/iu', + 29 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu', + 30 => '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu', + 31 => '/^[\x{002d}0-9a-zàâæçèéêëîïñôùûüÿœ]{1,63}$/iu', + 32 => '/^[\x{002d}0-9а-щъыьэюяёєіїґ]{1,63}$/iu', + 33 => '/^[\x{002d}0-9א-ת]{1,63}$/iu'], + 'PR' => [1 => '/^[\x{002d}0-9a-záéíóúñäëïüöâêîôûàèùæçœãõ]{1,63}$/iu'], + 'PT' => [1 => '/^[\x{002d}0-9a-záàâãçéêíóôõú]{1,63}$/iu'], + 'RS' => [1 => '/^[\x{002d}0-9a-zßáâäçéëíîóôöúüýăąćčďđęěĺľłńňőŕřśşšţťůűźżž]{1,63}$/iu'], + 'RU' => [1 => '/^[\x{002d}0-9а-яё]{1,63}$/iu'], + 'SA' => [1 => '/^[\x{002d}.0-9\x{0621}-\x{063A}\x{0641}-\x{064A}\x{0660}-\x{0669}]{1,63}$/iu'], + 'SE' => [1 => '/^[\x{002d}0-9a-zäåéöü]{1,63}$/iu'], + 'SH' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'], + 'SI' => [ + 1 => '/^[\x{002d}0-9a-zà-öø-ÿ]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-zāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĺļľŀłńņňʼnŋōŏőœŕŗřśŝšťŧũūŭůűųŵŷźżž]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-zșț]{1,63}$/iu'], + 'SJ' => [1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'], + 'TH' => [1 => '/^[\x{002d}0-9a-z\x{0E01}-\x{0E3A}\x{0E40}-\x{0E4D}\x{0E50}-\x{0E59}]{1,63}$/iu'], + 'TM' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēėęěĝġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśŝşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'], + 'TW' => 'Hostname/Cn.php', + 'TR' => [1 => '/^[\x{002d}0-9a-zğıüşöç]{1,63}$/iu'], + 'UA' => [1 => '/^[\x{002d}0-9a-zабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљњћќѝўџґӂʼ]{1,63}$/iu'], + 'VE' => [1 => '/^[\x{002d}0-9a-záéíóúüñ]{1,63}$/iu'], + 'VN' => [1 => '/^[ÀÁÂÃÈÉÊÌÍÒÓÔÕÙÚÝàáâãèéêìíòóôõùúýĂăĐđĨĩŨũƠơƯư\x{1EA0}-\x{1EF9}]{1,63}$/iu'], + 'мон' => [1 => '/^[\x{002d}0-9\x{0430}-\x{044F}]{1,63}$/iu'], + 'срб' => [1 => '/^[\x{002d}0-9а-ик-шђјљњћџ]{1,63}$/iu'], + 'сайт' => [1 => '/^[\x{002d}0-9а-яёіїѝйўґг]{1,63}$/iu'], + 'онлайн' => [1 => '/^[\x{002d}0-9а-яёіїѝйўґг]{1,63}$/iu'], + '中国' => 'Hostname/Cn.php', + '中國' => 'Hostname/Cn.php', + 'ලංකා' => [1 => '/^[\x{0d80}-\x{0dff}]{1,63}$/iu'], + '香港' => 'Hostname/Cn.php', + '台湾' => 'Hostname/Cn.php', + '台灣' => 'Hostname/Cn.php', + 'امارات' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'الاردن' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'السعودية' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'ไทย' => [1 => '/^[\x{002d}0-9a-z\x{0E01}-\x{0E3A}\x{0E40}-\x{0E4D}\x{0E50}-\x{0E59}]{1,63}$/iu'], + 'рф' => [1 => '/^[\x{002d}0-9а-яё]{1,63}$/iu'], + 'تونس' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'مصر' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'இலங்கை' => [1 => '/^[\x{0b80}-\x{0bff}]{1,63}$/iu'], + 'فلسطين' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'شبكة' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + ]; + // @codingStandardsIgnoreEnd + + protected $idnLength = [ + 'BIZ' => [5 => 17, 11 => 15, 12 => 20], + 'CN' => [1 => 20], + 'COM' => [3 => 17, 5 => 20], + 'HK' => [1 => 15], + 'INFO' => [4 => 17], + 'KR' => [1 => 17], + 'NET' => [3 => 17, 5 => 20], + 'ORG' => [6 => 17], + 'TW' => [1 => 20], + 'امارات' => [1 => 30], + 'الاردن' => [1 => 30], + 'السعودية' => [1 => 30], + 'تونس' => [1 => 30], + 'مصر' => [1 => 30], + 'فلسطين' => [1 => 30], + 'شبكة' => [1 => 30], + '中国' => [1 => 20], + '中國' => [1 => 20], + '香港' => [1 => 20], + '台湾' => [1 => 20], + '台灣' => [1 => 20], + ]; + + protected $tld; + + /** + * Options for the hostname validator + * + * @var array + */ + protected $options = [ + 'allow' => self::ALLOW_DNS, // Allow these hostnames + 'useIdnCheck' => true, // Check IDN domains + 'useTldCheck' => true, // Check TLD elements + 'ipValidator' => null, // IP validator to use + ]; + + /** + * Sets validator options. + * + * @param int $allow OPTIONAL Set what types of hostname to allow (default ALLOW_DNS) + * @param bool $useIdnCheck OPTIONAL Set whether IDN domains are validated (default true) + * @param bool $useTldCheck Set whether the TLD element of a hostname is validated (default true) + * @param Ip $ipValidator OPTIONAL + * @see http://www.iana.org/cctld/specifications-policies-cctlds-01apr02.htm Technical Specifications for ccTLDs + */ + public function __construct($options = []) + { + if (! is_array($options)) { + $options = func_get_args(); + $temp['allow'] = array_shift($options); + if (! empty($options)) { + $temp['useIdnCheck'] = array_shift($options); + } + + if (! empty($options)) { + $temp['useTldCheck'] = array_shift($options); + } + + if (! empty($options)) { + $temp['ipValidator'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('ipValidator', $options)) { + $options['ipValidator'] = null; + } + + parent::__construct($options); + } + + /** + * Returns the set ip validator + * + * @return Ip + */ + public function getIpValidator() + { + return $this->options['ipValidator']; + } + + /** + * + * @param Ip $ipValidator OPTIONAL + * @return Hostname; + */ + public function setIpValidator(Ip $ipValidator = null) + { + if ($ipValidator === null) { + $ipValidator = new Ip(); + } + + $this->options['ipValidator'] = $ipValidator; + return $this; + } + + /** + * Returns the allow option + * + * @return int + */ + public function getAllow() + { + return $this->options['allow']; + } + + /** + * Sets the allow option + * + * @param int $allow + * @return Hostname Provides a fluent interface + */ + public function setAllow($allow) + { + $this->options['allow'] = $allow; + return $this; + } + + /** + * Returns the set idn option + * + * @return bool + */ + public function getIdnCheck() + { + return $this->options['useIdnCheck']; + } + + /** + * Set whether IDN domains are validated + * + * This only applies when DNS hostnames are validated + * + * @param bool $useIdnCheck Set to true to validate IDN domains + * @return Hostname + */ + public function useIdnCheck($useIdnCheck) + { + $this->options['useIdnCheck'] = (bool) $useIdnCheck; + return $this; + } + + /** + * Returns the set tld option + * + * @return bool + */ + public function getTldCheck() + { + return $this->options['useTldCheck']; + } + + /** + * Set whether the TLD element of a hostname is validated + * + * This only applies when DNS hostnames are validated + * + * @param bool $useTldCheck Set to true to validate TLD elements + * @return Hostname + */ + public function useTldCheck($useTldCheck) + { + $this->options['useTldCheck'] = (bool) $useTldCheck; + return $this; + } + + /** + * Defined by Interface + * + * Returns true if and only if the $value is a valid hostname with respect to the current allow option + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + // Check input against IP address schema + if (((preg_match('/^[0-9.]*$/', $value) && strpos($value, '.') !== false) + || (preg_match('/^[0-9a-f:.]*$/i', $value) && strpos($value, ':') !== false)) + && $this->getIpValidator()->setTranslator($this->getTranslator())->isValid($value) + ) { + if (! ($this->getAllow() & self::ALLOW_IP)) { + $this->error(self::IP_ADDRESS_NOT_ALLOWED); + return false; + } + + return true; + } + + // Local hostnames are allowed to be partial (ending '.') + if ($this->getAllow() & self::ALLOW_LOCAL) { + if (substr($value, -1) === '.') { + $value = substr($value, 0, -1); + if (substr($value, -1) === '.') { + // Empty hostnames (ending '..') are not allowed + $this->error(self::INVALID_LOCAL_NAME); + return false; + } + } + } + + $domainParts = explode('.', $value); + + // Prevent partial IP V4 addresses (ending '.') + if (count($domainParts) == 4 && preg_match('/^[0-9.a-e:.]*$/i', $value) + && $this->getIpValidator()->setTranslator($this->getTranslator())->isValid($value) + ) { + $this->error(self::INVALID_LOCAL_NAME); + } + + $utf8StrWrapper = StringUtils::getWrapper('UTF-8'); + + // Check input against DNS hostname schema + if (count($domainParts) > 1 + && $utf8StrWrapper->strlen($value) >= 4 + && $utf8StrWrapper->strlen($value) <= 254 + ) { + $status = false; + + do { + // First check TLD + $matches = []; + if (preg_match('/([^.]{2,63})$/u', end($domainParts), $matches) + || (array_key_exists(end($domainParts), $this->validIdns)) + ) { + reset($domainParts); + + // Hostname characters are: *(label dot)(label dot label); max 254 chars + // label: id-prefix [*ldh{61} id-prefix]; max 63 chars + // id-prefix: alpha / digit + // ldh: alpha / digit / dash + + $this->tld = $matches[1]; + // Decode Punycode TLD to IDN + if (strpos($this->tld, 'xn--') === 0) { + $this->tld = $this->decodePunycode(substr($this->tld, 4)); + if ($this->tld === false) { + return false; + } + } else { + $this->tld = strtoupper($this->tld); + } + + // Match TLD against known list + $removedTld = false; + if ($this->getTldCheck()) { + if (! in_array(strtolower($this->tld), $this->validTlds) + && ! in_array($this->tld, $this->validTlds)) { + $this->error(self::UNKNOWN_TLD); + $status = false; + break; + } + // We have already validated that the TLD is fine. We don't want it to go through the below + // checks as new UTF-8 TLDs will incorrectly fail if there is no IDN regex for it. + array_pop($domainParts); + $removedTld = true; + } + + /** + * Match against IDN hostnames + * Note: Keep label regex short to avoid issues with long patterns when matching IDN hostnames + * + * @see Hostname\Interface + */ + $regexChars = [0 => '/^[a-z0-9\x2d]{1,63}$/i']; + if ($this->getIdnCheck() && isset($this->validIdns[$this->tld])) { + if (is_string($this->validIdns[$this->tld])) { + $regexChars += include __DIR__ . '/' . $this->validIdns[$this->tld]; + } else { + $regexChars += $this->validIdns[$this->tld]; + } + } + + // Check each hostname part + $check = 0; + $lastDomainPart = end($domainParts); + if (! $removedTld) { + $lastDomainPart = prev($domainParts); + } + foreach ($domainParts as $domainPart) { + // Decode Punycode domain names to IDN + if (strpos($domainPart, 'xn--') === 0) { + $domainPart = $this->decodePunycode(substr($domainPart, 4)); + if ($domainPart === false) { + return false; + } + } + + // Skip following checks if domain part is empty, as it definitely is not a valid hostname then + if ($domainPart === '') { + $this->error(self::INVALID_HOSTNAME); + $status = false; + break 2; + } + + // Check dash (-) does not start, end or appear in 3rd and 4th positions + if ($utf8StrWrapper->strpos($domainPart, '-') === 0 + || ($utf8StrWrapper->strlen($domainPart) > 2 + && $utf8StrWrapper->strpos($domainPart, '-', 2) == 2 + && $utf8StrWrapper->strpos($domainPart, '-', 3) == 3 + ) + || ( + $utf8StrWrapper->strpos($domainPart, '-') === ( + $utf8StrWrapper->strlen($domainPart) - 1 + ) + ) + ) { + $this->error(self::INVALID_DASH); + $status = false; + break 2; + } + + // Check each domain part + $checked = false; + $isSubDomain = $domainPart != $lastDomainPart; + $partRegexChars = $isSubDomain ? ['/^[a-z0-9_\x2d]{1,63}$/i'] + $regexChars : $regexChars; + foreach ($partRegexChars as $regexKey => $regexChar) { + $status = preg_match($regexChar, $domainPart); + if ($status > 0) { + $length = 63; + if (array_key_exists($this->tld, $this->idnLength) + && array_key_exists($regexKey, $this->idnLength[$this->tld]) + ) { + $length = $this->idnLength[$this->tld]; + } + + if ($utf8StrWrapper->strlen($domainPart) > $length) { + $this->error(self::INVALID_HOSTNAME); + $status = false; + } else { + $checked = true; + break; + } + } + } + + if ($checked) { + ++$check; + } + } + + // If one of the labels doesn't match, the hostname is invalid + if ($check !== count($domainParts)) { + $this->error(self::INVALID_HOSTNAME_SCHEMA); + $status = false; + } + } else { + // Hostname not long enough + $this->error(self::UNDECIPHERABLE_TLD); + $status = false; + } + } while (false); + + // If the input passes as an Internet domain name, and domain names are allowed, then the hostname + // passes validation + if ($status && ($this->getAllow() & self::ALLOW_DNS)) { + return true; + } + } elseif ($this->getAllow() & self::ALLOW_DNS) { + $this->error(self::INVALID_HOSTNAME); + } + + // Check for URI Syntax (RFC3986) + if ($this->getAllow() & self::ALLOW_URI) { + if (preg_match("/^([a-zA-Z0-9-._~!$&\'()*+,;=]|%[[:xdigit:]]{2}){1,254}$/i", $value)) { + return true; + } + + $this->error(self::INVALID_URI); + } + + // Check input against local network name schema; last chance to pass validation + $regexLocal = '/^(([a-zA-Z0-9\x2d]{1,63}\x2e)*[a-zA-Z0-9\x2d]{1,63}[\x2e]{0,1}){1,254}$/'; + $status = preg_match($regexLocal, $value); + + // If the input passes as a local network name, and local network names are allowed, then the + // hostname passes validation + $allowLocal = $this->getAllow() & self::ALLOW_LOCAL; + if ($status && $allowLocal) { + return true; + } + + // If the input does not pass as a local network name, add a message + if (! $status) { + $this->error(self::INVALID_LOCAL_NAME); + } + + // If local network names are not allowed, add a message + if ($status && ! $allowLocal) { + $this->error(self::LOCAL_NAME_NOT_ALLOWED); + } + + return false; + } + + /** + * Decodes a punycode encoded string to it's original utf8 string + * Returns false in case of a decoding failure. + * + * @param string $encoded Punycode encoded string to decode + * @return string|false + */ + protected function decodePunycode($encoded) + { + if (! preg_match('/^[a-z0-9-]+$/i', $encoded)) { + // no punycode encoded string + $this->error(self::CANNOT_DECODE_PUNYCODE); + return false; + } + + $decoded = []; + $separator = strrpos($encoded, '-'); + if ($separator > 0) { + for ($x = 0; $x < $separator; ++$x) { + // prepare decoding matrix + $decoded[] = ord($encoded[$x]); + } + } + + $lengthd = count($decoded); + $lengthe = strlen($encoded); + + // decoding + $init = true; + $base = 72; + $index = 0; + $char = 0x80; + + for ($indexe = ($separator) ? ($separator + 1) : 0; $indexe < $lengthe; ++$lengthd) { + for ($oldIndex = $index, $pos = 1, $key = 36; 1; $key += 36) { + $hex = ord($encoded[$indexe++]); + $digit = ($hex - 48 < 10) ? $hex - 22 + : (($hex - 65 < 26) ? $hex - 65 + : (($hex - 97 < 26) ? $hex - 97 + : 36)); + + $index += $digit * $pos; + $tag = ($key <= $base) ? 1 : (($key >= $base + 26) ? 26 : ($key - $base)); + if ($digit < $tag) { + break; + } + + $pos = (int) ($pos * (36 - $tag)); + } + + $delta = intval($init ? (($index - $oldIndex) / 700) : (($index - $oldIndex) / 2)); + $delta += intval($delta / ($lengthd + 1)); + for ($key = 0; $delta > 910 / 2; $key += 36) { + $delta = intval($delta / 35); + } + + $base = intval($key + 36 * $delta / ($delta + 38)); + $init = false; + $char += (int) ($index / ($lengthd + 1)); + $index %= ($lengthd + 1); + if ($lengthd > 0) { + for ($i = $lengthd; $i > $index; $i--) { + $decoded[$i] = $decoded[($i - 1)]; + } + } + + $decoded[$index++] = $char; + } + + // convert decoded ucs4 to utf8 string + foreach ($decoded as $key => $value) { + if ($value < 128) { + $decoded[$key] = chr($value); + } elseif ($value < (1 << 11)) { + $decoded[$key] = chr(192 + ($value >> 6)); + $decoded[$key] .= chr(128 + ($value & 63)); + } elseif ($value < (1 << 16)) { + $decoded[$key] = chr(224 + ($value >> 12)); + $decoded[$key] .= chr(128 + (($value >> 6) & 63)); + $decoded[$key] .= chr(128 + ($value & 63)); + } elseif ($value < (1 << 21)) { + $decoded[$key] = chr(240 + ($value >> 18)); + $decoded[$key] .= chr(128 + (($value >> 12) & 63)); + $decoded[$key] .= chr(128 + (($value >> 6) & 63)); + $decoded[$key] .= chr(128 + ($value & 63)); + } else { + $this->error(self::CANNOT_DECODE_PUNYCODE); + return false; + } + } + + return implode($decoded); + } +} diff --git a/lib/laminas/laminas-validator/src/Hostname/Biz.php b/lib/laminas/laminas-validator/src/Hostname/Biz.php new file mode 100644 index 000000000..2dfaaf7b5 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname/Biz.php @@ -0,0 +1,2902 @@ + '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu', + 4 => '/^[\x{002d}0-9a-záæéíðóöúýþ]{1,63}$/iu', + 5 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu', + 6 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu', + 7 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu', + 8 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu', + 9 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu', + 10 => '/^[\x{002d}0-9a-záàâãçéêíóôõú]{1,63}$/iu', + 11 => '/^[\x{002d}0-9a-z\x{3005}-\x{3007}\x{3041}-\x{3093}\x{309D}\x{309E}\x{30A1}-\x{30F6}\x{30FC}' . + '\x{30FD}\x{30FE}\x{4E00}\x{4E01}\x{4E03}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' . + '\x{4E0B}\x{4E0D}\x{4E0E}\x{4E10}\x{4E11}\x{4E14}\x{4E15}\x{4E16}\x{4E17}' . + '\x{4E18}\x{4E19}\x{4E1E}\x{4E21}\x{4E26}\x{4E2A}\x{4E2D}\x{4E31}\x{4E32}' . + '\x{4E36}\x{4E38}\x{4E39}\x{4E3B}\x{4E3C}\x{4E3F}\x{4E42}\x{4E43}\x{4E45}' . + '\x{4E4B}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E55}\x{4E56}\x{4E57}\x{4E58}\x{4E59}' . + '\x{4E5D}\x{4E5E}\x{4E5F}\x{4E62}\x{4E71}\x{4E73}\x{4E7E}\x{4E80}\x{4E82}' . + '\x{4E85}\x{4E86}\x{4E88}\x{4E89}\x{4E8A}\x{4E8B}\x{4E8C}\x{4E8E}\x{4E91}' . + '\x{4E92}\x{4E94}\x{4E95}\x{4E98}\x{4E99}\x{4E9B}\x{4E9C}\x{4E9E}\x{4E9F}' . + '\x{4EA0}\x{4EA1}\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA8}\x{4EAB}\x{4EAC}' . + '\x{4EAD}\x{4EAE}\x{4EB0}\x{4EB3}\x{4EB6}\x{4EBA}\x{4EC0}\x{4EC1}\x{4EC2}' . + '\x{4EC4}\x{4EC6}\x{4EC7}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED4}' . + '\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE3}' . + '\x{4EE4}\x{4EE5}\x{4EED}\x{4EEE}\x{4EF0}\x{4EF2}\x{4EF6}\x{4EF7}\x{4EFB}' . + '\x{4F01}\x{4F09}\x{4F0A}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}\x{4F11}\x{4F1A}' . + '\x{4F1C}\x{4F1D}\x{4F2F}\x{4F30}\x{4F34}\x{4F36}\x{4F38}\x{4F3A}\x{4F3C}' . + '\x{4F3D}\x{4F43}\x{4F46}\x{4F47}\x{4F4D}\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}' . + '\x{4F53}\x{4F55}\x{4F57}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}' . + '\x{4F69}\x{4F6F}\x{4F70}\x{4F73}\x{4F75}\x{4F76}\x{4F7B}\x{4F7C}\x{4F7F}' . + '\x{4F83}\x{4F86}\x{4F88}\x{4F8B}\x{4F8D}\x{4F8F}\x{4F91}\x{4F96}\x{4F98}' . + '\x{4F9B}\x{4F9D}\x{4FA0}\x{4FA1}\x{4FAB}\x{4FAD}\x{4FAE}\x{4FAF}\x{4FB5}' . + '\x{4FB6}\x{4FBF}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FCA}\x{4FCE}\x{4FD0}\x{4FD1}' . + '\x{4FD4}\x{4FD7}\x{4FD8}\x{4FDA}\x{4FDB}\x{4FDD}\x{4FDF}\x{4FE1}\x{4FE3}' . + '\x{4FE4}\x{4FE5}\x{4FEE}\x{4FEF}\x{4FF3}\x{4FF5}\x{4FF6}\x{4FF8}\x{4FFA}' . + '\x{4FFE}\x{5005}\x{5006}\x{5009}\x{500B}\x{500D}\x{500F}\x{5011}\x{5012}' . + '\x{5014}\x{5016}\x{5019}\x{501A}\x{501F}\x{5021}\x{5023}\x{5024}\x{5025}' . + '\x{5026}\x{5028}\x{5029}\x{502A}\x{502B}\x{502C}\x{502D}\x{5036}\x{5039}' . + '\x{5043}\x{5047}\x{5048}\x{5049}\x{504F}\x{5050}\x{5055}\x{5056}\x{505A}' . + '\x{505C}\x{5065}\x{506C}\x{5072}\x{5074}\x{5075}\x{5076}\x{5078}\x{507D}' . + '\x{5080}\x{5085}\x{508D}\x{5091}\x{5098}\x{5099}\x{509A}\x{50AC}\x{50AD}' . + '\x{50B2}\x{50B3}\x{50B4}\x{50B5}\x{50B7}\x{50BE}\x{50C2}\x{50C5}\x{50C9}' . + '\x{50CA}\x{50CD}\x{50CF}\x{50D1}\x{50D5}\x{50D6}\x{50DA}\x{50DE}\x{50E3}' . + '\x{50E5}\x{50E7}\x{50ED}\x{50EE}\x{50F5}\x{50F9}\x{50FB}\x{5100}\x{5101}' . + '\x{5102}\x{5104}\x{5109}\x{5112}\x{5114}\x{5115}\x{5116}\x{5118}\x{511A}' . + '\x{511F}\x{5121}\x{512A}\x{5132}\x{5137}\x{513A}\x{513B}\x{513C}\x{513F}' . + '\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' . + '\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5152}\x{5154}\x{515A}\x{515C}' . + '\x{5162}\x{5165}\x{5168}\x{5169}\x{516A}\x{516B}\x{516C}\x{516D}\x{516E}' . + '\x{5171}\x{5175}\x{5176}\x{5177}\x{5178}\x{517C}\x{5180}\x{5182}\x{5185}' . + '\x{5186}\x{5189}\x{518A}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}' . + '\x{5193}\x{5195}\x{5196}\x{5197}\x{5199}\x{51A0}\x{51A2}\x{51A4}\x{51A5}' . + '\x{51A6}\x{51A8}\x{51A9}\x{51AA}\x{51AB}\x{51AC}\x{51B0}\x{51B1}\x{51B2}' . + '\x{51B3}\x{51B4}\x{51B5}\x{51B6}\x{51B7}\x{51BD}\x{51C4}\x{51C5}\x{51C6}' . + '\x{51C9}\x{51CB}\x{51CC}\x{51CD}\x{51D6}\x{51DB}\x{51DC}\x{51DD}\x{51E0}' . + '\x{51E1}\x{51E6}\x{51E7}\x{51E9}\x{51EA}\x{51ED}\x{51F0}\x{51F1}\x{51F5}' . + '\x{51F6}\x{51F8}\x{51F9}\x{51FA}\x{51FD}\x{51FE}\x{5200}\x{5203}\x{5204}' . + '\x{5206}\x{5207}\x{5208}\x{520A}\x{520B}\x{520E}\x{5211}\x{5214}\x{5217}' . + '\x{521D}\x{5224}\x{5225}\x{5227}\x{5229}\x{522A}\x{522E}\x{5230}\x{5233}' . + '\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{5243}\x{5244}\x{5247}' . + '\x{524A}\x{524B}\x{524C}\x{524D}\x{524F}\x{5254}\x{5256}\x{525B}\x{525E}' . + '\x{5263}\x{5264}\x{5265}\x{5269}\x{526A}\x{526F}\x{5270}\x{5271}\x{5272}' . + '\x{5273}\x{5274}\x{5275}\x{527D}\x{527F}\x{5283}\x{5287}\x{5288}\x{5289}' . + '\x{528D}\x{5291}\x{5292}\x{5294}\x{529B}\x{529F}\x{52A0}\x{52A3}\x{52A9}' . + '\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52B1}\x{52B4}\x{52B5}\x{52B9}\x{52BC}' . + '\x{52BE}\x{52C1}\x{52C3}\x{52C5}\x{52C7}\x{52C9}\x{52CD}\x{52D2}\x{52D5}' . + '\x{52D7}\x{52D8}\x{52D9}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}' . + '\x{52E4}\x{52E6}\x{52E7}\x{52F2}\x{52F3}\x{52F5}\x{52F8}\x{52F9}\x{52FA}' . + '\x{52FE}\x{52FF}\x{5301}\x{5302}\x{5305}\x{5306}\x{5308}\x{530D}\x{530F}' . + '\x{5310}\x{5315}\x{5316}\x{5317}\x{5319}\x{531A}\x{531D}\x{5320}\x{5321}' . + '\x{5323}\x{532A}\x{532F}\x{5331}\x{5333}\x{5338}\x{5339}\x{533A}\x{533B}' . + '\x{533F}\x{5340}\x{5341}\x{5343}\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}' . + '\x{534A}\x{534D}\x{5351}\x{5352}\x{5353}\x{5354}\x{5357}\x{5358}\x{535A}' . + '\x{535C}\x{535E}\x{5360}\x{5366}\x{5369}\x{536E}\x{536F}\x{5370}\x{5371}' . + '\x{5373}\x{5374}\x{5375}\x{5377}\x{5378}\x{537B}\x{537F}\x{5382}\x{5384}' . + '\x{5396}\x{5398}\x{539A}\x{539F}\x{53A0}\x{53A5}\x{53A6}\x{53A8}\x{53A9}' . + '\x{53AD}\x{53AE}\x{53B0}\x{53B3}\x{53B6}\x{53BB}\x{53C2}\x{53C3}\x{53C8}' . + '\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}\x{53D4}\x{53D6}\x{53D7}' . + '\x{53D9}\x{53DB}\x{53DF}\x{53E1}\x{53E2}\x{53E3}\x{53E4}\x{53E5}\x{53E8}' . + '\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}\x{53EF}\x{53F0}\x{53F1}' . + '\x{53F2}\x{53F3}\x{53F6}\x{53F7}\x{53F8}\x{53FA}\x{5401}\x{5403}\x{5404}' . + '\x{5408}\x{5409}\x{540A}\x{540B}\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}' . + '\x{5411}\x{541B}\x{541D}\x{541F}\x{5420}\x{5426}\x{5429}\x{542B}\x{542C}' . + '\x{542D}\x{542E}\x{5436}\x{5438}\x{5439}\x{543B}\x{543C}\x{543D}\x{543E}' . + '\x{5440}\x{5442}\x{5446}\x{5448}\x{5449}\x{544A}\x{544E}\x{5451}\x{545F}' . + '\x{5468}\x{546A}\x{5470}\x{5471}\x{5473}\x{5475}\x{5476}\x{5477}\x{547B}' . + '\x{547C}\x{547D}\x{5480}\x{5484}\x{5486}\x{548B}\x{548C}\x{548E}\x{548F}' . + '\x{5490}\x{5492}\x{54A2}\x{54A4}\x{54A5}\x{54A8}\x{54AB}\x{54AC}\x{54AF}' . + '\x{54B2}\x{54B3}\x{54B8}\x{54BC}\x{54BD}\x{54BE}\x{54C0}\x{54C1}\x{54C2}' . + '\x{54C4}\x{54C7}\x{54C8}\x{54C9}\x{54D8}\x{54E1}\x{54E2}\x{54E5}\x{54E6}' . + '\x{54E8}\x{54E9}\x{54ED}\x{54EE}\x{54F2}\x{54FA}\x{54FD}\x{5504}\x{5506}' . + '\x{5507}\x{550F}\x{5510}\x{5514}\x{5516}\x{552E}\x{552F}\x{5531}\x{5533}' . + '\x{5538}\x{5539}\x{553E}\x{5540}\x{5544}\x{5545}\x{5546}\x{554C}\x{554F}' . + '\x{5553}\x{5556}\x{5557}\x{555C}\x{555D}\x{5563}\x{557B}\x{557C}\x{557E}' . + '\x{5580}\x{5583}\x{5584}\x{5587}\x{5589}\x{558A}\x{558B}\x{5598}\x{5599}' . + '\x{559A}\x{559C}\x{559D}\x{559E}\x{559F}\x{55A7}\x{55A8}\x{55A9}\x{55AA}' . + '\x{55AB}\x{55AC}\x{55AE}\x{55B0}\x{55B6}\x{55C4}\x{55C5}\x{55C7}\x{55D4}' . + '\x{55DA}\x{55DC}\x{55DF}\x{55E3}\x{55E4}\x{55F7}\x{55F9}\x{55FD}\x{55FE}' . + '\x{5606}\x{5609}\x{5614}\x{5616}\x{5617}\x{5618}\x{561B}\x{5629}\x{562F}' . + '\x{5631}\x{5632}\x{5634}\x{5636}\x{5638}\x{5642}\x{564C}\x{564E}\x{5650}' . + '\x{565B}\x{5664}\x{5668}\x{566A}\x{566B}\x{566C}\x{5674}\x{5678}\x{567A}' . + '\x{5680}\x{5686}\x{5687}\x{568A}\x{568F}\x{5694}\x{56A0}\x{56A2}\x{56A5}' . + '\x{56AE}\x{56B4}\x{56B6}\x{56BC}\x{56C0}\x{56C1}\x{56C2}\x{56C3}\x{56C8}' . + '\x{56CE}\x{56D1}\x{56D3}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DE}\x{56E0}' . + '\x{56E3}\x{56EE}\x{56F0}\x{56F2}\x{56F3}\x{56F9}\x{56FA}\x{56FD}\x{56FF}' . + '\x{5700}\x{5703}\x{5704}\x{5708}\x{5709}\x{570B}\x{570D}\x{570F}\x{5712}' . + '\x{5713}\x{5716}\x{5718}\x{571C}\x{571F}\x{5726}\x{5727}\x{5728}\x{572D}' . + '\x{5730}\x{5737}\x{5738}\x{573B}\x{5740}\x{5742}\x{5747}\x{574A}\x{574E}' . + '\x{574F}\x{5750}\x{5751}\x{5761}\x{5764}\x{5766}\x{5769}\x{576A}\x{577F}' . + '\x{5782}\x{5788}\x{5789}\x{578B}\x{5793}\x{57A0}\x{57A2}\x{57A3}\x{57A4}' . + '\x{57AA}\x{57B0}\x{57B3}\x{57C0}\x{57C3}\x{57C6}\x{57CB}\x{57CE}\x{57D2}' . + '\x{57D3}\x{57D4}\x{57D6}\x{57DC}\x{57DF}\x{57E0}\x{57E3}\x{57F4}\x{57F7}' . + '\x{57F9}\x{57FA}\x{57FC}\x{5800}\x{5802}\x{5805}\x{5806}\x{580A}\x{580B}' . + '\x{5815}\x{5819}\x{581D}\x{5821}\x{5824}\x{582A}\x{582F}\x{5830}\x{5831}' . + '\x{5834}\x{5835}\x{583A}\x{583D}\x{5840}\x{5841}\x{584A}\x{584B}\x{5851}' . + '\x{5852}\x{5854}\x{5857}\x{5858}\x{5859}\x{585A}\x{585E}\x{5862}\x{5869}' . + '\x{586B}\x{5870}\x{5872}\x{5875}\x{5879}\x{587E}\x{5883}\x{5885}\x{5893}' . + '\x{5897}\x{589C}\x{589F}\x{58A8}\x{58AB}\x{58AE}\x{58B3}\x{58B8}\x{58B9}' . + '\x{58BA}\x{58BB}\x{58BE}\x{58C1}\x{58C5}\x{58C7}\x{58CA}\x{58CC}\x{58D1}' . + '\x{58D3}\x{58D5}\x{58D7}\x{58D8}\x{58D9}\x{58DC}\x{58DE}\x{58DF}\x{58E4}' . + '\x{58E5}\x{58EB}\x{58EC}\x{58EE}\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F7}' . + '\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{5902}\x{5909}\x{590A}\x{590F}' . + '\x{5910}\x{5915}\x{5916}\x{5918}\x{5919}\x{591A}\x{591B}\x{591C}\x{5922}' . + '\x{5925}\x{5927}\x{5929}\x{592A}\x{592B}\x{592C}\x{592D}\x{592E}\x{5931}' . + '\x{5932}\x{5937}\x{5938}\x{593E}\x{5944}\x{5947}\x{5948}\x{5949}\x{594E}' . + '\x{594F}\x{5950}\x{5951}\x{5954}\x{5955}\x{5957}\x{5958}\x{595A}\x{5960}' . + '\x{5962}\x{5965}\x{5967}\x{5968}\x{5969}\x{596A}\x{596C}\x{596E}\x{5973}' . + '\x{5974}\x{5978}\x{597D}\x{5981}\x{5982}\x{5983}\x{5984}\x{598A}\x{598D}' . + '\x{5993}\x{5996}\x{5999}\x{599B}\x{599D}\x{59A3}\x{59A5}\x{59A8}\x{59AC}' . + '\x{59B2}\x{59B9}\x{59BB}\x{59BE}\x{59C6}\x{59C9}\x{59CB}\x{59D0}\x{59D1}' . + '\x{59D3}\x{59D4}\x{59D9}\x{59DA}\x{59DC}\x{59E5}\x{59E6}\x{59E8}\x{59EA}' . + '\x{59EB}\x{59F6}\x{59FB}\x{59FF}\x{5A01}\x{5A03}\x{5A09}\x{5A11}\x{5A18}' . + '\x{5A1A}\x{5A1C}\x{5A1F}\x{5A20}\x{5A25}\x{5A29}\x{5A2F}\x{5A35}\x{5A36}' . + '\x{5A3C}\x{5A40}\x{5A41}\x{5A46}\x{5A49}\x{5A5A}\x{5A62}\x{5A66}\x{5A6A}' . + '\x{5A6C}\x{5A7F}\x{5A92}\x{5A9A}\x{5A9B}\x{5ABC}\x{5ABD}\x{5ABE}\x{5AC1}' . + '\x{5AC2}\x{5AC9}\x{5ACB}\x{5ACC}\x{5AD0}\x{5AD6}\x{5AD7}\x{5AE1}\x{5AE3}' . + '\x{5AE6}\x{5AE9}\x{5AFA}\x{5AFB}\x{5B09}\x{5B0B}\x{5B0C}\x{5B16}\x{5B22}' . + '\x{5B2A}\x{5B2C}\x{5B30}\x{5B32}\x{5B36}\x{5B3E}\x{5B40}\x{5B43}\x{5B45}' . + '\x{5B50}\x{5B51}\x{5B54}\x{5B55}\x{5B57}\x{5B58}\x{5B5A}\x{5B5B}\x{5B5C}' . + '\x{5B5D}\x{5B5F}\x{5B63}\x{5B64}\x{5B65}\x{5B66}\x{5B69}\x{5B6B}\x{5B70}' . + '\x{5B71}\x{5B73}\x{5B75}\x{5B78}\x{5B7A}\x{5B80}\x{5B83}\x{5B85}\x{5B87}' . + '\x{5B88}\x{5B89}\x{5B8B}\x{5B8C}\x{5B8D}\x{5B8F}\x{5B95}\x{5B97}\x{5B98}' . + '\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9F}\x{5BA2}\x{5BA3}\x{5BA4}' . + '\x{5BA5}\x{5BA6}\x{5BAE}\x{5BB0}\x{5BB3}\x{5BB4}\x{5BB5}\x{5BB6}\x{5BB8}' . + '\x{5BB9}\x{5BBF}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BC9}' . + '\x{5BCC}\x{5BD0}\x{5BD2}\x{5BD3}\x{5BD4}\x{5BDB}\x{5BDD}\x{5BDE}\x{5BDF}' . + '\x{5BE1}\x{5BE2}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}\x{5BE8}\x{5BE9}\x{5BEB}' . + '\x{5BEE}\x{5BF0}\x{5BF3}\x{5BF5}\x{5BF6}\x{5BF8}\x{5BFA}\x{5BFE}\x{5BFF}' . + '\x{5C01}\x{5C02}\x{5C04}\x{5C05}\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}' . + '\x{5C0B}\x{5C0D}\x{5C0E}\x{5C0F}\x{5C11}\x{5C13}\x{5C16}\x{5C1A}\x{5C20}' . + '\x{5C22}\x{5C24}\x{5C28}\x{5C2D}\x{5C31}\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}' . + '\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}\x{5C41}\x{5C45}\x{5C46}\x{5C48}' . + '\x{5C4A}\x{5C4B}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C53}\x{5C55}' . + '\x{5C5E}\x{5C60}\x{5C61}\x{5C64}\x{5C65}\x{5C6C}\x{5C6E}\x{5C6F}\x{5C71}' . + '\x{5C76}\x{5C79}\x{5C8C}\x{5C90}\x{5C91}\x{5C94}\x{5CA1}\x{5CA8}\x{5CA9}' . + '\x{5CAB}\x{5CAC}\x{5CB1}\x{5CB3}\x{5CB6}\x{5CB7}\x{5CB8}\x{5CBB}\x{5CBC}' . + '\x{5CBE}\x{5CC5}\x{5CC7}\x{5CD9}\x{5CE0}\x{5CE1}\x{5CE8}\x{5CE9}\x{5CEA}' . + '\x{5CED}\x{5CEF}\x{5CF0}\x{5CF6}\x{5CFA}\x{5CFB}\x{5CFD}\x{5D07}\x{5D0B}' . + '\x{5D0E}\x{5D11}\x{5D14}\x{5D15}\x{5D16}\x{5D17}\x{5D18}\x{5D19}\x{5D1A}' . + '\x{5D1B}\x{5D1F}\x{5D22}\x{5D29}\x{5D4B}\x{5D4C}\x{5D4E}\x{5D50}\x{5D52}' . + '\x{5D5C}\x{5D69}\x{5D6C}\x{5D6F}\x{5D73}\x{5D76}\x{5D82}\x{5D84}\x{5D87}' . + '\x{5D8B}\x{5D8C}\x{5D90}\x{5D9D}\x{5DA2}\x{5DAC}\x{5DAE}\x{5DB7}\x{5DBA}' . + '\x{5DBC}\x{5DBD}\x{5DC9}\x{5DCC}\x{5DCD}\x{5DD2}\x{5DD3}\x{5DD6}\x{5DDB}' . + '\x{5DDD}\x{5DDE}\x{5DE1}\x{5DE3}\x{5DE5}\x{5DE6}\x{5DE7}\x{5DE8}\x{5DEB}' . + '\x{5DEE}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DFB}\x{5DFD}' . + '\x{5DFE}\x{5E02}\x{5E03}\x{5E06}\x{5E0B}\x{5E0C}\x{5E11}\x{5E16}\x{5E19}' . + '\x{5E1A}\x{5E1B}\x{5E1D}\x{5E25}\x{5E2B}\x{5E2D}\x{5E2F}\x{5E30}\x{5E33}' . + '\x{5E36}\x{5E37}\x{5E38}\x{5E3D}\x{5E40}\x{5E43}\x{5E44}\x{5E45}\x{5E47}' . + '\x{5E4C}\x{5E4E}\x{5E54}\x{5E55}\x{5E57}\x{5E5F}\x{5E61}\x{5E62}\x{5E63}' . + '\x{5E64}\x{5E72}\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E78}\x{5E79}\x{5E7A}' . + '\x{5E7B}\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E81}\x{5E83}\x{5E84}\x{5E87}' . + '\x{5E8A}\x{5E8F}\x{5E95}\x{5E96}\x{5E97}\x{5E9A}\x{5E9C}\x{5EA0}\x{5EA6}' . + '\x{5EA7}\x{5EAB}\x{5EAD}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EC1}\x{5EC2}' . + '\x{5EC3}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECF}\x{5ED0}\x{5ED3}\x{5ED6}\x{5EDA}' . + '\x{5EDB}\x{5EDD}\x{5EDF}\x{5EE0}\x{5EE1}\x{5EE2}\x{5EE3}\x{5EE8}\x{5EE9}' . + '\x{5EEC}\x{5EF0}\x{5EF1}\x{5EF3}\x{5EF4}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}' . + '\x{5EFB}\x{5EFC}\x{5EFE}\x{5EFF}\x{5F01}\x{5F03}\x{5F04}\x{5F09}\x{5F0A}' . + '\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F10}\x{5F11}\x{5F13}\x{5F14}\x{5F15}' . + '\x{5F16}\x{5F17}\x{5F18}\x{5F1B}\x{5F1F}\x{5F25}\x{5F26}\x{5F27}\x{5F29}' . + '\x{5F2D}\x{5F2F}\x{5F31}\x{5F35}\x{5F37}\x{5F38}\x{5F3C}\x{5F3E}\x{5F41}' . + '\x{5F48}\x{5F4A}\x{5F4C}\x{5F4E}\x{5F51}\x{5F53}\x{5F56}\x{5F57}\x{5F59}' . + '\x{5F5C}\x{5F5D}\x{5F61}\x{5F62}\x{5F66}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}' . + '\x{5F6D}\x{5F70}\x{5F71}\x{5F73}\x{5F77}\x{5F79}\x{5F7C}\x{5F7F}\x{5F80}' . + '\x{5F81}\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F87}\x{5F88}\x{5F8A}\x{5F8B}' . + '\x{5F8C}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F97}\x{5F98}\x{5F99}\x{5F9E}' . + '\x{5FA0}\x{5FA1}\x{5FA8}\x{5FA9}\x{5FAA}\x{5FAD}\x{5FAE}\x{5FB3}\x{5FB4}' . + '\x{5FB9}\x{5FBC}\x{5FBD}\x{5FC3}\x{5FC5}\x{5FCC}\x{5FCD}\x{5FD6}\x{5FD7}' . + '\x{5FD8}\x{5FD9}\x{5FDC}\x{5FDD}\x{5FE0}\x{5FE4}\x{5FEB}\x{5FF0}\x{5FF1}' . + '\x{5FF5}\x{5FF8}\x{5FFB}\x{5FFD}\x{5FFF}\x{600E}\x{600F}\x{6010}\x{6012}' . + '\x{6015}\x{6016}\x{6019}\x{601B}\x{601C}\x{601D}\x{6020}\x{6021}\x{6025}' . + '\x{6026}\x{6027}\x{6028}\x{6029}\x{602A}\x{602B}\x{602F}\x{6031}\x{603A}' . + '\x{6041}\x{6042}\x{6043}\x{6046}\x{604A}\x{604B}\x{604D}\x{6050}\x{6052}' . + '\x{6055}\x{6059}\x{605A}\x{605F}\x{6060}\x{6062}\x{6063}\x{6064}\x{6065}' . + '\x{6068}\x{6069}\x{606A}\x{606B}\x{606C}\x{606D}\x{606F}\x{6070}\x{6075}' . + '\x{6077}\x{6081}\x{6083}\x{6084}\x{6089}\x{608B}\x{608C}\x{608D}\x{6092}' . + '\x{6094}\x{6096}\x{6097}\x{609A}\x{609B}\x{609F}\x{60A0}\x{60A3}\x{60A6}' . + '\x{60A7}\x{60A9}\x{60AA}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B8}' . + '\x{60BC}\x{60BD}\x{60C5}\x{60C6}\x{60C7}\x{60D1}\x{60D3}\x{60D8}\x{60DA}' . + '\x{60DC}\x{60DF}\x{60E0}\x{60E1}\x{60E3}\x{60E7}\x{60E8}\x{60F0}\x{60F1}' . + '\x{60F3}\x{60F4}\x{60F6}\x{60F7}\x{60F9}\x{60FA}\x{60FB}\x{6100}\x{6101}' . + '\x{6103}\x{6106}\x{6108}\x{6109}\x{610D}\x{610E}\x{610F}\x{6115}\x{611A}' . + '\x{611B}\x{611F}\x{6121}\x{6127}\x{6128}\x{612C}\x{6134}\x{613C}\x{613D}' . + '\x{613E}\x{613F}\x{6142}\x{6144}\x{6147}\x{6148}\x{614A}\x{614B}\x{614C}' . + '\x{614D}\x{614E}\x{6153}\x{6155}\x{6158}\x{6159}\x{615A}\x{615D}\x{615F}' . + '\x{6162}\x{6163}\x{6165}\x{6167}\x{6168}\x{616B}\x{616E}\x{616F}\x{6170}' . + '\x{6171}\x{6173}\x{6174}\x{6175}\x{6176}\x{6177}\x{617E}\x{6182}\x{6187}' . + '\x{618A}\x{618E}\x{6190}\x{6191}\x{6194}\x{6196}\x{6199}\x{619A}\x{61A4}' . + '\x{61A7}\x{61A9}\x{61AB}\x{61AC}\x{61AE}\x{61B2}\x{61B6}\x{61BA}\x{61BE}' . + '\x{61C3}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' . + '\x{61D0}\x{61E3}\x{61E6}\x{61F2}\x{61F4}\x{61F6}\x{61F7}\x{61F8}\x{61FA}' . + '\x{61FC}\x{61FD}\x{61FE}\x{61FF}\x{6200}\x{6208}\x{6209}\x{620A}\x{620C}' . + '\x{620D}\x{620E}\x{6210}\x{6211}\x{6212}\x{6214}\x{6216}\x{621A}\x{621B}' . + '\x{621D}\x{621E}\x{621F}\x{6221}\x{6226}\x{622A}\x{622E}\x{622F}\x{6230}' . + '\x{6232}\x{6233}\x{6234}\x{6238}\x{623B}\x{623F}\x{6240}\x{6241}\x{6247}' . + '\x{6248}\x{6249}\x{624B}\x{624D}\x{624E}\x{6253}\x{6255}\x{6258}\x{625B}' . + '\x{625E}\x{6260}\x{6263}\x{6268}\x{626E}\x{6271}\x{6276}\x{6279}\x{627C}' . + '\x{627E}\x{627F}\x{6280}\x{6282}\x{6283}\x{6284}\x{6289}\x{628A}\x{6291}' . + '\x{6292}\x{6293}\x{6294}\x{6295}\x{6296}\x{6297}\x{6298}\x{629B}\x{629C}' . + '\x{629E}\x{62AB}\x{62AC}\x{62B1}\x{62B5}\x{62B9}\x{62BB}\x{62BC}\x{62BD}' . + '\x{62C2}\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CC}\x{62CD}' . + '\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D7}\x{62D8}\x{62D9}' . + '\x{62DB}\x{62DC}\x{62DD}\x{62E0}\x{62E1}\x{62EC}\x{62ED}\x{62EE}\x{62EF}' . + '\x{62F1}\x{62F3}\x{62F5}\x{62F6}\x{62F7}\x{62FE}\x{62FF}\x{6301}\x{6302}' . + '\x{6307}\x{6308}\x{6309}\x{630C}\x{6311}\x{6319}\x{631F}\x{6327}\x{6328}' . + '\x{632B}\x{632F}\x{633A}\x{633D}\x{633E}\x{633F}\x{6349}\x{634C}\x{634D}' . + '\x{634F}\x{6350}\x{6355}\x{6357}\x{635C}\x{6367}\x{6368}\x{6369}\x{636B}' . + '\x{636E}\x{6372}\x{6376}\x{6377}\x{637A}\x{637B}\x{6380}\x{6383}\x{6388}' . + '\x{6389}\x{638C}\x{638E}\x{638F}\x{6392}\x{6396}\x{6398}\x{639B}\x{639F}' . + '\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A5}\x{63A7}\x{63A8}\x{63A9}\x{63AA}' . + '\x{63AB}\x{63AC}\x{63B2}\x{63B4}\x{63B5}\x{63BB}\x{63BE}\x{63C0}\x{63C3}' . + '\x{63C4}\x{63C6}\x{63C9}\x{63CF}\x{63D0}\x{63D2}\x{63D6}\x{63DA}\x{63DB}' . + '\x{63E1}\x{63E3}\x{63E9}\x{63EE}\x{63F4}\x{63F6}\x{63FA}\x{6406}\x{640D}' . + '\x{640F}\x{6413}\x{6416}\x{6417}\x{641C}\x{6426}\x{6428}\x{642C}\x{642D}' . + '\x{6434}\x{6436}\x{643A}\x{643E}\x{6442}\x{644E}\x{6458}\x{6467}\x{6469}' . + '\x{646F}\x{6476}\x{6478}\x{647A}\x{6483}\x{6488}\x{6492}\x{6493}\x{6495}' . + '\x{649A}\x{649E}\x{64A4}\x{64A5}\x{64A9}\x{64AB}\x{64AD}\x{64AE}\x{64B0}' . + '\x{64B2}\x{64B9}\x{64BB}\x{64BC}\x{64C1}\x{64C2}\x{64C5}\x{64C7}\x{64CD}' . + '\x{64D2}\x{64D4}\x{64D8}\x{64DA}\x{64E0}\x{64E1}\x{64E2}\x{64E3}\x{64E6}' . + '\x{64E7}\x{64EC}\x{64EF}\x{64F1}\x{64F2}\x{64F4}\x{64F6}\x{64FA}\x{64FD}' . + '\x{64FE}\x{6500}\x{6505}\x{6518}\x{651C}\x{651D}\x{6523}\x{6524}\x{652A}' . + '\x{652B}\x{652C}\x{652F}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}' . + '\x{653B}\x{653E}\x{653F}\x{6545}\x{6548}\x{654D}\x{654F}\x{6551}\x{6555}' . + '\x{6556}\x{6557}\x{6558}\x{6559}\x{655D}\x{655E}\x{6562}\x{6563}\x{6566}' . + '\x{656C}\x{6570}\x{6572}\x{6574}\x{6575}\x{6577}\x{6578}\x{6582}\x{6583}' . + '\x{6587}\x{6588}\x{6589}\x{658C}\x{658E}\x{6590}\x{6591}\x{6597}\x{6599}' . + '\x{659B}\x{659C}\x{659F}\x{65A1}\x{65A4}\x{65A5}\x{65A7}\x{65AB}\x{65AC}' . + '\x{65AD}\x{65AF}\x{65B0}\x{65B7}\x{65B9}\x{65BC}\x{65BD}\x{65C1}\x{65C3}' . + '\x{65C4}\x{65C5}\x{65C6}\x{65CB}\x{65CC}\x{65CF}\x{65D2}\x{65D7}\x{65D9}' . + '\x{65DB}\x{65E0}\x{65E1}\x{65E2}\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}' . + '\x{65EC}\x{65ED}\x{65F1}\x{65FA}\x{65FB}\x{6602}\x{6603}\x{6606}\x{6607}' . + '\x{660A}\x{660C}\x{660E}\x{660F}\x{6613}\x{6614}\x{661C}\x{661F}\x{6620}' . + '\x{6625}\x{6627}\x{6628}\x{662D}\x{662F}\x{6634}\x{6635}\x{6636}\x{663C}' . + '\x{663F}\x{6641}\x{6642}\x{6643}\x{6644}\x{6649}\x{664B}\x{664F}\x{6652}' . + '\x{665D}\x{665E}\x{665F}\x{6662}\x{6664}\x{6666}\x{6667}\x{6668}\x{6669}' . + '\x{666E}\x{666F}\x{6670}\x{6674}\x{6676}\x{667A}\x{6681}\x{6683}\x{6684}' . + '\x{6687}\x{6688}\x{6689}\x{668E}\x{6691}\x{6696}\x{6697}\x{6698}\x{669D}' . + '\x{66A2}\x{66A6}\x{66AB}\x{66AE}\x{66B4}\x{66B8}\x{66B9}\x{66BC}\x{66BE}' . + '\x{66C1}\x{66C4}\x{66C7}\x{66C9}\x{66D6}\x{66D9}\x{66DA}\x{66DC}\x{66DD}' . + '\x{66E0}\x{66E6}\x{66E9}\x{66F0}\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F7}' . + '\x{66F8}\x{66F9}\x{66FC}\x{66FD}\x{66FE}\x{66FF}\x{6700}\x{6703}\x{6708}' . + '\x{6709}\x{670B}\x{670D}\x{670F}\x{6714}\x{6715}\x{6716}\x{6717}\x{671B}' . + '\x{671D}\x{671E}\x{671F}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}' . + '\x{672D}\x{672E}\x{6731}\x{6734}\x{6736}\x{6737}\x{6738}\x{673A}\x{673D}' . + '\x{673F}\x{6741}\x{6746}\x{6749}\x{674E}\x{674F}\x{6750}\x{6751}\x{6753}' . + '\x{6756}\x{6759}\x{675C}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' . + '\x{6764}\x{6765}\x{676A}\x{676D}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}' . + '\x{6775}\x{6777}\x{677C}\x{677E}\x{677F}\x{6785}\x{6787}\x{6789}\x{678B}' . + '\x{678C}\x{6790}\x{6795}\x{6797}\x{679A}\x{679C}\x{679D}\x{67A0}\x{67A1}' . + '\x{67A2}\x{67A6}\x{67A9}\x{67AF}\x{67B3}\x{67B4}\x{67B6}\x{67B7}\x{67B8}' . + '\x{67B9}\x{67C1}\x{67C4}\x{67C6}\x{67CA}\x{67CE}\x{67CF}\x{67D0}\x{67D1}' . + '\x{67D3}\x{67D4}\x{67D8}\x{67DA}\x{67DD}\x{67DE}\x{67E2}\x{67E4}\x{67E7}' . + '\x{67E9}\x{67EC}\x{67EE}\x{67EF}\x{67F1}\x{67F3}\x{67F4}\x{67F5}\x{67FB}' . + '\x{67FE}\x{67FF}\x{6802}\x{6803}\x{6804}\x{6813}\x{6816}\x{6817}\x{681E}' . + '\x{6821}\x{6822}\x{6829}\x{682A}\x{682B}\x{6832}\x{6834}\x{6838}\x{6839}' . + '\x{683C}\x{683D}\x{6840}\x{6841}\x{6842}\x{6843}\x{6846}\x{6848}\x{684D}' . + '\x{684E}\x{6850}\x{6851}\x{6853}\x{6854}\x{6859}\x{685C}\x{685D}\x{685F}' . + '\x{6863}\x{6867}\x{6874}\x{6876}\x{6877}\x{687E}\x{687F}\x{6881}\x{6883}' . + '\x{6885}\x{688D}\x{688F}\x{6893}\x{6894}\x{6897}\x{689B}\x{689D}\x{689F}' . + '\x{68A0}\x{68A2}\x{68A6}\x{68A7}\x{68A8}\x{68AD}\x{68AF}\x{68B0}\x{68B1}' . + '\x{68B3}\x{68B5}\x{68B6}\x{68B9}\x{68BA}\x{68BC}\x{68C4}\x{68C6}\x{68C9}' . + '\x{68CA}\x{68CB}\x{68CD}\x{68D2}\x{68D4}\x{68D5}\x{68D7}\x{68D8}\x{68DA}' . + '\x{68DF}\x{68E0}\x{68E1}\x{68E3}\x{68E7}\x{68EE}\x{68EF}\x{68F2}\x{68F9}' . + '\x{68FA}\x{6900}\x{6901}\x{6904}\x{6905}\x{6908}\x{690B}\x{690C}\x{690D}' . + '\x{690E}\x{690F}\x{6912}\x{6919}\x{691A}\x{691B}\x{691C}\x{6921}\x{6922}' . + '\x{6923}\x{6925}\x{6926}\x{6928}\x{692A}\x{6930}\x{6934}\x{6936}\x{6939}' . + '\x{693D}\x{693F}\x{694A}\x{6953}\x{6954}\x{6955}\x{6959}\x{695A}\x{695C}' . + '\x{695D}\x{695E}\x{6960}\x{6961}\x{6962}\x{696A}\x{696B}\x{696D}\x{696E}' . + '\x{696F}\x{6973}\x{6974}\x{6975}\x{6977}\x{6978}\x{6979}\x{697C}\x{697D}' . + '\x{697E}\x{6981}\x{6982}\x{698A}\x{698E}\x{6991}\x{6994}\x{6995}\x{699B}' . + '\x{699C}\x{69A0}\x{69A7}\x{69AE}\x{69B1}\x{69B2}\x{69B4}\x{69BB}\x{69BE}' . + '\x{69BF}\x{69C1}\x{69C3}\x{69C7}\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}' . + '\x{69D0}\x{69D3}\x{69D8}\x{69D9}\x{69DD}\x{69DE}\x{69E7}\x{69E8}\x{69EB}' . + '\x{69ED}\x{69F2}\x{69F9}\x{69FB}\x{69FD}\x{69FF}\x{6A02}\x{6A05}\x{6A0A}' . + '\x{6A0B}\x{6A0C}\x{6A12}\x{6A13}\x{6A14}\x{6A17}\x{6A19}\x{6A1B}\x{6A1E}' . + '\x{6A1F}\x{6A21}\x{6A22}\x{6A23}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2E}\x{6A35}' . + '\x{6A36}\x{6A38}\x{6A39}\x{6A3A}\x{6A3D}\x{6A44}\x{6A47}\x{6A48}\x{6A4B}' . + '\x{6A58}\x{6A59}\x{6A5F}\x{6A61}\x{6A62}\x{6A66}\x{6A72}\x{6A78}\x{6A7F}' . + '\x{6A80}\x{6A84}\x{6A8D}\x{6A8E}\x{6A90}\x{6A97}\x{6A9C}\x{6AA0}\x{6AA2}' . + '\x{6AA3}\x{6AAA}\x{6AAC}\x{6AAE}\x{6AB3}\x{6AB8}\x{6ABB}\x{6AC1}\x{6AC2}' . + '\x{6AC3}\x{6AD1}\x{6AD3}\x{6ADA}\x{6ADB}\x{6ADE}\x{6ADF}\x{6AE8}\x{6AEA}' . + '\x{6AFA}\x{6AFB}\x{6B04}\x{6B05}\x{6B0A}\x{6B12}\x{6B16}\x{6B1D}\x{6B1F}' . + '\x{6B20}\x{6B21}\x{6B23}\x{6B27}\x{6B32}\x{6B37}\x{6B38}\x{6B39}\x{6B3A}' . + '\x{6B3D}\x{6B3E}\x{6B43}\x{6B47}\x{6B49}\x{6B4C}\x{6B4E}\x{6B50}\x{6B53}' . + '\x{6B54}\x{6B59}\x{6B5B}\x{6B5F}\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B66}' . + '\x{6B69}\x{6B6A}\x{6B6F}\x{6B73}\x{6B74}\x{6B78}\x{6B79}\x{6B7B}\x{6B7F}' . + '\x{6B80}\x{6B83}\x{6B84}\x{6B86}\x{6B89}\x{6B8A}\x{6B8B}\x{6B8D}\x{6B95}' . + '\x{6B96}\x{6B98}\x{6B9E}\x{6BA4}\x{6BAA}\x{6BAB}\x{6BAF}\x{6BB1}\x{6BB2}' . + '\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB7}\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBF}\x{6BC0}' . + '\x{6BC5}\x{6BC6}\x{6BCB}\x{6BCD}\x{6BCE}\x{6BD2}\x{6BD3}\x{6BD4}\x{6BD8}' . + '\x{6BDB}\x{6BDF}\x{6BEB}\x{6BEC}\x{6BEF}\x{6BF3}\x{6C08}\x{6C0F}\x{6C11}' . + '\x{6C13}\x{6C14}\x{6C17}\x{6C1B}\x{6C23}\x{6C24}\x{6C34}\x{6C37}\x{6C38}' . + '\x{6C3E}\x{6C40}\x{6C41}\x{6C42}\x{6C4E}\x{6C50}\x{6C55}\x{6C57}\x{6C5A}' . + '\x{6C5D}\x{6C5E}\x{6C5F}\x{6C60}\x{6C62}\x{6C68}\x{6C6A}\x{6C70}\x{6C72}' . + '\x{6C73}\x{6C7A}\x{6C7D}\x{6C7E}\x{6C81}\x{6C82}\x{6C83}\x{6C88}\x{6C8C}' . + '\x{6C8D}\x{6C90}\x{6C92}\x{6C93}\x{6C96}\x{6C99}\x{6C9A}\x{6C9B}\x{6CA1}' . + '\x{6CA2}\x{6CAB}\x{6CAE}\x{6CB1}\x{6CB3}\x{6CB8}\x{6CB9}\x{6CBA}\x{6CBB}' . + '\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC1}\x{6CC4}\x{6CC5}\x{6CC9}\x{6CCA}' . + '\x{6CCC}\x{6CD3}\x{6CD5}\x{6CD7}\x{6CD9}\x{6CDB}\x{6CDD}\x{6CE1}\x{6CE2}' . + '\x{6CE3}\x{6CE5}\x{6CE8}\x{6CEA}\x{6CEF}\x{6CF0}\x{6CF1}\x{6CF3}\x{6D0B}' . + '\x{6D0C}\x{6D12}\x{6D17}\x{6D19}\x{6D1B}\x{6D1E}\x{6D1F}\x{6D25}\x{6D29}' . + '\x{6D2A}\x{6D2B}\x{6D32}\x{6D33}\x{6D35}\x{6D36}\x{6D38}\x{6D3B}\x{6D3D}' . + '\x{6D3E}\x{6D41}\x{6D44}\x{6D45}\x{6D59}\x{6D5A}\x{6D5C}\x{6D63}\x{6D64}' . + '\x{6D66}\x{6D69}\x{6D6A}\x{6D6C}\x{6D6E}\x{6D74}\x{6D77}\x{6D78}\x{6D79}' . + '\x{6D85}\x{6D88}\x{6D8C}\x{6D8E}\x{6D93}\x{6D95}\x{6D99}\x{6D9B}\x{6D9C}' . + '\x{6DAF}\x{6DB2}\x{6DB5}\x{6DB8}\x{6DBC}\x{6DC0}\x{6DC5}\x{6DC6}\x{6DC7}' . + '\x{6DCB}\x{6DCC}\x{6DD1}\x{6DD2}\x{6DD5}\x{6DD8}\x{6DD9}\x{6DDE}\x{6DE1}' . + '\x{6DE4}\x{6DE6}\x{6DE8}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DEE}\x{6DF1}\x{6DF3}' . + '\x{6DF5}\x{6DF7}\x{6DF9}\x{6DFA}\x{6DFB}\x{6E05}\x{6E07}\x{6E08}\x{6E09}' . + '\x{6E0A}\x{6E0B}\x{6E13}\x{6E15}\x{6E19}\x{6E1A}\x{6E1B}\x{6E1D}\x{6E1F}' . + '\x{6E20}\x{6E21}\x{6E23}\x{6E24}\x{6E25}\x{6E26}\x{6E29}\x{6E2B}\x{6E2C}' . + '\x{6E2D}\x{6E2E}\x{6E2F}\x{6E38}\x{6E3A}\x{6E3E}\x{6E43}\x{6E4A}\x{6E4D}' . + '\x{6E4E}\x{6E56}\x{6E58}\x{6E5B}\x{6E5F}\x{6E67}\x{6E6B}\x{6E6E}\x{6E6F}' . + '\x{6E72}\x{6E76}\x{6E7E}\x{6E7F}\x{6E80}\x{6E82}\x{6E8C}\x{6E8F}\x{6E90}' . + '\x{6E96}\x{6E98}\x{6E9C}\x{6E9D}\x{6E9F}\x{6EA2}\x{6EA5}\x{6EAA}\x{6EAF}' . + '\x{6EB2}\x{6EB6}\x{6EB7}\x{6EBA}\x{6EBD}\x{6EC2}\x{6EC4}\x{6EC5}\x{6EC9}' . + '\x{6ECB}\x{6ECC}\x{6ED1}\x{6ED3}\x{6ED4}\x{6ED5}\x{6EDD}\x{6EDE}\x{6EEC}' . + '\x{6EEF}\x{6EF2}\x{6EF4}\x{6EF7}\x{6EF8}\x{6EFE}\x{6EFF}\x{6F01}\x{6F02}' . + '\x{6F06}\x{6F09}\x{6F0F}\x{6F11}\x{6F13}\x{6F14}\x{6F15}\x{6F20}\x{6F22}' . + '\x{6F23}\x{6F2B}\x{6F2C}\x{6F31}\x{6F32}\x{6F38}\x{6F3E}\x{6F3F}\x{6F41}' . + '\x{6F45}\x{6F54}\x{6F58}\x{6F5B}\x{6F5C}\x{6F5F}\x{6F64}\x{6F66}\x{6F6D}' . + '\x{6F6E}\x{6F6F}\x{6F70}\x{6F74}\x{6F78}\x{6F7A}\x{6F7C}\x{6F80}\x{6F81}' . + '\x{6F82}\x{6F84}\x{6F86}\x{6F8E}\x{6F91}\x{6F97}\x{6FA1}\x{6FA3}\x{6FA4}' . + '\x{6FAA}\x{6FB1}\x{6FB3}\x{6FB9}\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC6}' . + '\x{6FD4}\x{6FD5}\x{6FD8}\x{6FDB}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE4}\x{6FEB}' . + '\x{6FEC}\x{6FEE}\x{6FEF}\x{6FF1}\x{6FF3}\x{6FF6}\x{6FFA}\x{6FFE}\x{7001}' . + '\x{7009}\x{700B}\x{700F}\x{7011}\x{7015}\x{7018}\x{701A}\x{701B}\x{701D}' . + '\x{701E}\x{701F}\x{7026}\x{7027}\x{702C}\x{7030}\x{7032}\x{703E}\x{704C}' . + '\x{7051}\x{7058}\x{7063}\x{706B}\x{706F}\x{7070}\x{7078}\x{707C}\x{707D}' . + '\x{7089}\x{708A}\x{708E}\x{7092}\x{7099}\x{70AC}\x{70AD}\x{70AE}\x{70AF}' . + '\x{70B3}\x{70B8}\x{70B9}\x{70BA}\x{70C8}\x{70CB}\x{70CF}\x{70D9}\x{70DD}' . + '\x{70DF}\x{70F1}\x{70F9}\x{70FD}\x{7109}\x{7114}\x{7119}\x{711A}\x{711C}' . + '\x{7121}\x{7126}\x{7136}\x{713C}\x{7149}\x{714C}\x{714E}\x{7155}\x{7156}' . + '\x{7159}\x{7162}\x{7164}\x{7165}\x{7166}\x{7167}\x{7169}\x{716C}\x{716E}' . + '\x{717D}\x{7184}\x{7188}\x{718A}\x{718F}\x{7194}\x{7195}\x{7199}\x{719F}' . + '\x{71A8}\x{71AC}\x{71B1}\x{71B9}\x{71BE}\x{71C3}\x{71C8}\x{71C9}\x{71CE}' . + '\x{71D0}\x{71D2}\x{71D4}\x{71D5}\x{71D7}\x{71DF}\x{71E0}\x{71E5}\x{71E6}' . + '\x{71E7}\x{71EC}\x{71ED}\x{71EE}\x{71F5}\x{71F9}\x{71FB}\x{71FC}\x{71FF}' . + '\x{7206}\x{720D}\x{7210}\x{721B}\x{7228}\x{722A}\x{722C}\x{722D}\x{7230}' . + '\x{7232}\x{7235}\x{7236}\x{723A}\x{723B}\x{723C}\x{723D}\x{723E}\x{723F}' . + '\x{7240}\x{7246}\x{7247}\x{7248}\x{724B}\x{724C}\x{7252}\x{7258}\x{7259}' . + '\x{725B}\x{725D}\x{725F}\x{7261}\x{7262}\x{7267}\x{7269}\x{7272}\x{7274}' . + '\x{7279}\x{727D}\x{727E}\x{7280}\x{7281}\x{7282}\x{7287}\x{7292}\x{7296}' . + '\x{72A0}\x{72A2}\x{72A7}\x{72AC}\x{72AF}\x{72B2}\x{72B6}\x{72B9}\x{72C2}' . + '\x{72C3}\x{72C4}\x{72C6}\x{72CE}\x{72D0}\x{72D2}\x{72D7}\x{72D9}\x{72DB}' . + '\x{72E0}\x{72E1}\x{72E2}\x{72E9}\x{72EC}\x{72ED}\x{72F7}\x{72F8}\x{72F9}' . + '\x{72FC}\x{72FD}\x{730A}\x{7316}\x{7317}\x{731B}\x{731C}\x{731D}\x{731F}' . + '\x{7325}\x{7329}\x{732A}\x{732B}\x{732E}\x{732F}\x{7334}\x{7336}\x{7337}' . + '\x{733E}\x{733F}\x{7344}\x{7345}\x{734E}\x{734F}\x{7357}\x{7363}\x{7368}' . + '\x{736A}\x{7370}\x{7372}\x{7375}\x{7378}\x{737A}\x{737B}\x{7384}\x{7387}' . + '\x{7389}\x{738B}\x{7396}\x{73A9}\x{73B2}\x{73B3}\x{73BB}\x{73C0}\x{73C2}' . + '\x{73C8}\x{73CA}\x{73CD}\x{73CE}\x{73DE}\x{73E0}\x{73E5}\x{73EA}\x{73ED}' . + '\x{73EE}\x{73F1}\x{73F8}\x{73FE}\x{7403}\x{7405}\x{7406}\x{7409}\x{7422}' . + '\x{7425}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{743A}\x{743F}\x{7441}' . + '\x{7455}\x{7459}\x{745A}\x{745B}\x{745C}\x{745E}\x{745F}\x{7460}\x{7463}' . + '\x{7464}\x{7469}\x{746A}\x{746F}\x{7470}\x{7473}\x{7476}\x{747E}\x{7483}' . + '\x{748B}\x{749E}\x{74A2}\x{74A7}\x{74B0}\x{74BD}\x{74CA}\x{74CF}\x{74D4}' . + '\x{74DC}\x{74E0}\x{74E2}\x{74E3}\x{74E6}\x{74E7}\x{74E9}\x{74EE}\x{74F0}' . + '\x{74F1}\x{74F2}\x{74F6}\x{74F7}\x{74F8}\x{7503}\x{7504}\x{7505}\x{750C}' . + '\x{750D}\x{750E}\x{7511}\x{7513}\x{7515}\x{7518}\x{751A}\x{751C}\x{751E}' . + '\x{751F}\x{7523}\x{7525}\x{7526}\x{7528}\x{752B}\x{752C}\x{7530}\x{7531}' . + '\x{7532}\x{7533}\x{7537}\x{7538}\x{753A}\x{753B}\x{753C}\x{7544}\x{7546}' . + '\x{7549}\x{754A}\x{754B}\x{754C}\x{754D}\x{754F}\x{7551}\x{7554}\x{7559}' . + '\x{755A}\x{755B}\x{755C}\x{755D}\x{7560}\x{7562}\x{7564}\x{7565}\x{7566}' . + '\x{7567}\x{7569}\x{756A}\x{756B}\x{756D}\x{7570}\x{7573}\x{7574}\x{7576}' . + '\x{7577}\x{7578}\x{757F}\x{7582}\x{7586}\x{7587}\x{7589}\x{758A}\x{758B}' . + '\x{758E}\x{758F}\x{7591}\x{7594}\x{759A}\x{759D}\x{75A3}\x{75A5}\x{75AB}' . + '\x{75B1}\x{75B2}\x{75B3}\x{75B5}\x{75B8}\x{75B9}\x{75BC}\x{75BD}\x{75BE}' . + '\x{75C2}\x{75C3}\x{75C5}\x{75C7}\x{75CA}\x{75CD}\x{75D2}\x{75D4}\x{75D5}' . + '\x{75D8}\x{75D9}\x{75DB}\x{75DE}\x{75E2}\x{75E3}\x{75E9}\x{75F0}\x{75F2}' . + '\x{75F3}\x{75F4}\x{75FA}\x{75FC}\x{75FE}\x{75FF}\x{7601}\x{7609}\x{760B}' . + '\x{760D}\x{761F}\x{7620}\x{7621}\x{7622}\x{7624}\x{7627}\x{7630}\x{7634}' . + '\x{763B}\x{7642}\x{7646}\x{7647}\x{7648}\x{764C}\x{7652}\x{7656}\x{7658}' . + '\x{765C}\x{7661}\x{7662}\x{7667}\x{7668}\x{7669}\x{766A}\x{766C}\x{7670}' . + '\x{7672}\x{7676}\x{7678}\x{767A}\x{767B}\x{767C}\x{767D}\x{767E}\x{7680}' . + '\x{7683}\x{7684}\x{7686}\x{7687}\x{7688}\x{768B}\x{768E}\x{7690}\x{7693}' . + '\x{7696}\x{7699}\x{769A}\x{76AE}\x{76B0}\x{76B4}\x{76B7}\x{76B8}\x{76B9}' . + '\x{76BA}\x{76BF}\x{76C2}\x{76C3}\x{76C6}\x{76C8}\x{76CA}\x{76CD}\x{76D2}' . + '\x{76D6}\x{76D7}\x{76DB}\x{76DC}\x{76DE}\x{76DF}\x{76E1}\x{76E3}\x{76E4}' . + '\x{76E5}\x{76E7}\x{76EA}\x{76EE}\x{76F2}\x{76F4}\x{76F8}\x{76FB}\x{76FE}' . + '\x{7701}\x{7704}\x{7707}\x{7708}\x{7709}\x{770B}\x{770C}\x{771B}\x{771E}' . + '\x{771F}\x{7720}\x{7724}\x{7725}\x{7726}\x{7729}\x{7737}\x{7738}\x{773A}' . + '\x{773C}\x{7740}\x{7747}\x{775A}\x{775B}\x{7761}\x{7763}\x{7765}\x{7766}' . + '\x{7768}\x{776B}\x{7779}\x{777E}\x{777F}\x{778B}\x{778E}\x{7791}\x{779E}' . + '\x{77A0}\x{77A5}\x{77AC}\x{77AD}\x{77B0}\x{77B3}\x{77B6}\x{77B9}\x{77BB}' . + '\x{77BC}\x{77BD}\x{77BF}\x{77C7}\x{77CD}\x{77D7}\x{77DA}\x{77DB}\x{77DC}' . + '\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E9}\x{77ED}\x{77EE}\x{77EF}\x{77F3}' . + '\x{77FC}\x{7802}\x{780C}\x{7812}\x{7814}\x{7815}\x{7820}\x{7825}\x{7826}' . + '\x{7827}\x{7832}\x{7834}\x{783A}\x{783F}\x{7845}\x{785D}\x{786B}\x{786C}' . + '\x{786F}\x{7872}\x{7874}\x{787C}\x{7881}\x{7886}\x{7887}\x{788C}\x{788D}' . + '\x{788E}\x{7891}\x{7893}\x{7895}\x{7897}\x{789A}\x{78A3}\x{78A7}\x{78A9}' . + '\x{78AA}\x{78AF}\x{78B5}\x{78BA}\x{78BC}\x{78BE}\x{78C1}\x{78C5}\x{78C6}' . + '\x{78CA}\x{78CB}\x{78D0}\x{78D1}\x{78D4}\x{78DA}\x{78E7}\x{78E8}\x{78EC}' . + '\x{78EF}\x{78F4}\x{78FD}\x{7901}\x{7907}\x{790E}\x{7911}\x{7912}\x{7919}' . + '\x{7926}\x{792A}\x{792B}\x{792C}\x{793A}\x{793C}\x{793E}\x{7940}\x{7941}' . + '\x{7947}\x{7948}\x{7949}\x{7950}\x{7953}\x{7955}\x{7956}\x{7957}\x{795A}' . + '\x{795D}\x{795E}\x{795F}\x{7960}\x{7962}\x{7965}\x{7968}\x{796D}\x{7977}' . + '\x{797A}\x{797F}\x{7980}\x{7981}\x{7984}\x{7985}\x{798A}\x{798D}\x{798E}' . + '\x{798F}\x{799D}\x{79A6}\x{79A7}\x{79AA}\x{79AE}\x{79B0}\x{79B3}\x{79B9}' . + '\x{79BA}\x{79BD}\x{79BE}\x{79BF}\x{79C0}\x{79C1}\x{79C9}\x{79CB}\x{79D1}' . + '\x{79D2}\x{79D5}\x{79D8}\x{79DF}\x{79E1}\x{79E3}\x{79E4}\x{79E6}\x{79E7}' . + '\x{79E9}\x{79EC}\x{79F0}\x{79FB}\x{7A00}\x{7A08}\x{7A0B}\x{7A0D}\x{7A0E}' . + '\x{7A14}\x{7A17}\x{7A18}\x{7A19}\x{7A1A}\x{7A1C}\x{7A1F}\x{7A20}\x{7A2E}' . + '\x{7A31}\x{7A32}\x{7A37}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' . + '\x{7A42}\x{7A43}\x{7A46}\x{7A49}\x{7A4D}\x{7A4E}\x{7A4F}\x{7A50}\x{7A57}' . + '\x{7A61}\x{7A62}\x{7A63}\x{7A69}\x{7A6B}\x{7A70}\x{7A74}\x{7A76}\x{7A79}' . + '\x{7A7A}\x{7A7D}\x{7A7F}\x{7A81}\x{7A83}\x{7A84}\x{7A88}\x{7A92}\x{7A93}' . + '\x{7A95}\x{7A96}\x{7A97}\x{7A98}\x{7A9F}\x{7AA9}\x{7AAA}\x{7AAE}\x{7AAF}' . + '\x{7AB0}\x{7AB6}\x{7ABA}\x{7ABF}\x{7AC3}\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}' . + '\x{7ACA}\x{7ACB}\x{7ACD}\x{7ACF}\x{7AD2}\x{7AD3}\x{7AD5}\x{7AD9}\x{7ADA}' . + '\x{7ADC}\x{7ADD}\x{7ADF}\x{7AE0}\x{7AE1}\x{7AE2}\x{7AE3}\x{7AE5}\x{7AE6}' . + '\x{7AEA}\x{7AED}\x{7AEF}\x{7AF0}\x{7AF6}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFF}' . + '\x{7B02}\x{7B04}\x{7B06}\x{7B08}\x{7B0A}\x{7B0B}\x{7B0F}\x{7B11}\x{7B18}' . + '\x{7B19}\x{7B1B}\x{7B1E}\x{7B20}\x{7B25}\x{7B26}\x{7B28}\x{7B2C}\x{7B33}' . + '\x{7B35}\x{7B36}\x{7B39}\x{7B45}\x{7B46}\x{7B48}\x{7B49}\x{7B4B}\x{7B4C}' . + '\x{7B4D}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B56}\x{7B5D}\x{7B65}' . + '\x{7B67}\x{7B6C}\x{7B6E}\x{7B70}\x{7B71}\x{7B74}\x{7B75}\x{7B7A}\x{7B86}' . + '\x{7B87}\x{7B8B}\x{7B8D}\x{7B8F}\x{7B92}\x{7B94}\x{7B95}\x{7B97}\x{7B98}' . + '\x{7B99}\x{7B9A}\x{7B9C}\x{7B9D}\x{7B9F}\x{7BA1}\x{7BAA}\x{7BAD}\x{7BB1}' . + '\x{7BB4}\x{7BB8}\x{7BC0}\x{7BC1}\x{7BC4}\x{7BC6}\x{7BC7}\x{7BC9}\x{7BCB}' . + '\x{7BCC}\x{7BCF}\x{7BDD}\x{7BE0}\x{7BE4}\x{7BE5}\x{7BE6}\x{7BE9}\x{7BED}' . + '\x{7BF3}\x{7BF6}\x{7BF7}\x{7C00}\x{7C07}\x{7C0D}\x{7C11}\x{7C12}\x{7C13}' . + '\x{7C14}\x{7C17}\x{7C1F}\x{7C21}\x{7C23}\x{7C27}\x{7C2A}\x{7C2B}\x{7C37}' . + '\x{7C38}\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C43}\x{7C4C}\x{7C4D}\x{7C4F}' . + '\x{7C50}\x{7C54}\x{7C56}\x{7C58}\x{7C5F}\x{7C60}\x{7C64}\x{7C65}\x{7C6C}' . + '\x{7C73}\x{7C75}\x{7C7E}\x{7C81}\x{7C82}\x{7C83}\x{7C89}\x{7C8B}\x{7C8D}' . + '\x{7C90}\x{7C92}\x{7C95}\x{7C97}\x{7C98}\x{7C9B}\x{7C9F}\x{7CA1}\x{7CA2}' . + '\x{7CA4}\x{7CA5}\x{7CA7}\x{7CA8}\x{7CAB}\x{7CAD}\x{7CAE}\x{7CB1}\x{7CB2}' . + '\x{7CB3}\x{7CB9}\x{7CBD}\x{7CBE}\x{7CC0}\x{7CC2}\x{7CC5}\x{7CCA}\x{7CCE}' . + '\x{7CD2}\x{7CD6}\x{7CD8}\x{7CDC}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE7}' . + '\x{7CEF}\x{7CF2}\x{7CF4}\x{7CF6}\x{7CF8}\x{7CFA}\x{7CFB}\x{7CFE}\x{7D00}' . + '\x{7D02}\x{7D04}\x{7D05}\x{7D06}\x{7D0A}\x{7D0B}\x{7D0D}\x{7D10}\x{7D14}' . + '\x{7D15}\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D20}\x{7D21}' . + '\x{7D22}\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D32}\x{7D33}\x{7D35}' . + '\x{7D39}\x{7D3A}\x{7D3F}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}\x{7D4B}' . + '\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D56}\x{7D5B}\x{7D5E}\x{7D61}\x{7D62}' . + '\x{7D63}\x{7D66}\x{7D68}\x{7D6E}\x{7D71}\x{7D72}\x{7D73}\x{7D75}\x{7D76}' . + '\x{7D79}\x{7D7D}\x{7D89}\x{7D8F}\x{7D93}\x{7D99}\x{7D9A}\x{7D9B}\x{7D9C}' . + '\x{7D9F}\x{7DA2}\x{7DA3}\x{7DAB}\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}' . + '\x{7DB1}\x{7DB2}\x{7DB4}\x{7DB5}\x{7DB8}\x{7DBA}\x{7DBB}\x{7DBD}\x{7DBE}' . + '\x{7DBF}\x{7DC7}\x{7DCA}\x{7DCB}\x{7DCF}\x{7DD1}\x{7DD2}\x{7DD5}\x{7DD8}' . + '\x{7DDA}\x{7DDC}\x{7DDD}\x{7DDE}\x{7DE0}\x{7DE1}\x{7DE4}\x{7DE8}\x{7DE9}' . + '\x{7DEC}\x{7DEF}\x{7DF2}\x{7DF4}\x{7DFB}\x{7E01}\x{7E04}\x{7E05}\x{7E09}' . + '\x{7E0A}\x{7E0B}\x{7E12}\x{7E1B}\x{7E1E}\x{7E1F}\x{7E21}\x{7E22}\x{7E23}' . + '\x{7E26}\x{7E2B}\x{7E2E}\x{7E31}\x{7E32}\x{7E35}\x{7E37}\x{7E39}\x{7E3A}' . + '\x{7E3B}\x{7E3D}\x{7E3E}\x{7E41}\x{7E43}\x{7E46}\x{7E4A}\x{7E4B}\x{7E4D}' . + '\x{7E54}\x{7E55}\x{7E56}\x{7E59}\x{7E5A}\x{7E5D}\x{7E5E}\x{7E66}\x{7E67}' . + '\x{7E69}\x{7E6A}\x{7E6D}\x{7E70}\x{7E79}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7F}' . + '\x{7E82}\x{7E83}\x{7E88}\x{7E89}\x{7E8C}\x{7E8E}\x{7E8F}\x{7E90}\x{7E92}' . + '\x{7E93}\x{7E94}\x{7E96}\x{7E9B}\x{7E9C}\x{7F36}\x{7F38}\x{7F3A}\x{7F45}' . + '\x{7F4C}\x{7F4D}\x{7F4E}\x{7F50}\x{7F51}\x{7F54}\x{7F55}\x{7F58}\x{7F5F}' . + '\x{7F60}\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6E}\x{7F70}\x{7F72}' . + '\x{7F75}\x{7F77}\x{7F78}\x{7F79}\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}' . + '\x{7F88}\x{7F8A}\x{7F8C}\x{7F8E}\x{7F94}\x{7F9A}\x{7F9D}\x{7F9E}\x{7FA3}' . + '\x{7FA4}\x{7FA8}\x{7FA9}\x{7FAE}\x{7FAF}\x{7FB2}\x{7FB6}\x{7FB8}\x{7FB9}' . + '\x{7FBD}\x{7FC1}\x{7FC5}\x{7FC6}\x{7FCA}\x{7FCC}\x{7FD2}\x{7FD4}\x{7FD5}' . + '\x{7FE0}\x{7FE1}\x{7FE6}\x{7FE9}\x{7FEB}\x{7FF0}\x{7FF3}\x{7FF9}\x{7FFB}' . + '\x{7FFC}\x{8000}\x{8001}\x{8003}\x{8004}\x{8005}\x{8006}\x{800B}\x{800C}' . + '\x{8010}\x{8012}\x{8015}\x{8017}\x{8018}\x{8019}\x{801C}\x{8021}\x{8028}' . + '\x{8033}\x{8036}\x{803B}\x{803D}\x{803F}\x{8046}\x{804A}\x{8052}\x{8056}' . + '\x{8058}\x{805A}\x{805E}\x{805F}\x{8061}\x{8062}\x{8068}\x{806F}\x{8070}' . + '\x{8072}\x{8073}\x{8074}\x{8076}\x{8077}\x{8079}\x{807D}\x{807E}\x{807F}' . + '\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808B}\x{808C}\x{8093}\x{8096}' . + '\x{8098}\x{809A}\x{809B}\x{809D}\x{80A1}\x{80A2}\x{80A5}\x{80A9}\x{80AA}' . + '\x{80AC}\x{80AD}\x{80AF}\x{80B1}\x{80B2}\x{80B4}\x{80BA}\x{80C3}\x{80C4}' . + '\x{80C6}\x{80CC}\x{80CE}\x{80D6}\x{80D9}\x{80DA}\x{80DB}\x{80DD}\x{80DE}' . + '\x{80E1}\x{80E4}\x{80E5}\x{80EF}\x{80F1}\x{80F4}\x{80F8}\x{80FC}\x{80FD}' . + '\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{811A}\x{811B}' . + '\x{8123}\x{8129}\x{812F}\x{8131}\x{8133}\x{8139}\x{813E}\x{8146}\x{814B}' . + '\x{814E}\x{8150}\x{8151}\x{8153}\x{8154}\x{8155}\x{815F}\x{8165}\x{8166}' . + '\x{816B}\x{816E}\x{8170}\x{8171}\x{8174}\x{8178}\x{8179}\x{817A}\x{817F}' . + '\x{8180}\x{8182}\x{8183}\x{8188}\x{818A}\x{818F}\x{8193}\x{8195}\x{819A}' . + '\x{819C}\x{819D}\x{81A0}\x{81A3}\x{81A4}\x{81A8}\x{81A9}\x{81B0}\x{81B3}' . + '\x{81B5}\x{81B8}\x{81BA}\x{81BD}\x{81BE}\x{81BF}\x{81C0}\x{81C2}\x{81C6}' . + '\x{81C8}\x{81C9}\x{81CD}\x{81D1}\x{81D3}\x{81D8}\x{81D9}\x{81DA}\x{81DF}' . + '\x{81E0}\x{81E3}\x{81E5}\x{81E7}\x{81E8}\x{81EA}\x{81ED}\x{81F3}\x{81F4}' . + '\x{81FA}\x{81FB}\x{81FC}\x{81FE}\x{8201}\x{8202}\x{8205}\x{8207}\x{8208}' . + '\x{8209}\x{820A}\x{820C}\x{820D}\x{820E}\x{8210}\x{8212}\x{8216}\x{8217}' . + '\x{8218}\x{821B}\x{821C}\x{821E}\x{821F}\x{8229}\x{822A}\x{822B}\x{822C}' . + '\x{822E}\x{8233}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{8240}\x{8247}' . + '\x{8258}\x{8259}\x{825A}\x{825D}\x{825F}\x{8262}\x{8264}\x{8266}\x{8268}' . + '\x{826A}\x{826B}\x{826E}\x{826F}\x{8271}\x{8272}\x{8276}\x{8277}\x{8278}' . + '\x{827E}\x{828B}\x{828D}\x{8292}\x{8299}\x{829D}\x{829F}\x{82A5}\x{82A6}' . + '\x{82AB}\x{82AC}\x{82AD}\x{82AF}\x{82B1}\x{82B3}\x{82B8}\x{82B9}\x{82BB}' . + '\x{82BD}\x{82C5}\x{82D1}\x{82D2}\x{82D3}\x{82D4}\x{82D7}\x{82D9}\x{82DB}' . + '\x{82DC}\x{82DE}\x{82DF}\x{82E1}\x{82E3}\x{82E5}\x{82E6}\x{82E7}\x{82EB}' . + '\x{82F1}\x{82F3}\x{82F4}\x{82F9}\x{82FA}\x{82FB}\x{8302}\x{8303}\x{8304}' . + '\x{8305}\x{8306}\x{8309}\x{830E}\x{8316}\x{8317}\x{8318}\x{831C}\x{8323}' . + '\x{8328}\x{832B}\x{832F}\x{8331}\x{8332}\x{8334}\x{8335}\x{8336}\x{8338}' . + '\x{8339}\x{8340}\x{8345}\x{8349}\x{834A}\x{834F}\x{8350}\x{8352}\x{8358}' . + '\x{8373}\x{8375}\x{8377}\x{837B}\x{837C}\x{8385}\x{8387}\x{8389}\x{838A}' . + '\x{838E}\x{8393}\x{8396}\x{839A}\x{839E}\x{839F}\x{83A0}\x{83A2}\x{83A8}' . + '\x{83AA}\x{83AB}\x{83B1}\x{83B5}\x{83BD}\x{83C1}\x{83C5}\x{83CA}\x{83CC}' . + '\x{83CE}\x{83D3}\x{83D6}\x{83D8}\x{83DC}\x{83DF}\x{83E0}\x{83E9}\x{83EB}' . + '\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F4}\x{83F7}\x{83FB}\x{83FD}\x{8403}' . + '\x{8404}\x{8407}\x{840B}\x{840C}\x{840D}\x{840E}\x{8413}\x{8420}\x{8422}' . + '\x{8429}\x{842A}\x{842C}\x{8431}\x{8435}\x{8438}\x{843C}\x{843D}\x{8446}' . + '\x{8449}\x{844E}\x{8457}\x{845B}\x{8461}\x{8462}\x{8463}\x{8466}\x{8469}' . + '\x{846B}\x{846C}\x{846D}\x{846E}\x{846F}\x{8471}\x{8475}\x{8477}\x{8479}' . + '\x{847A}\x{8482}\x{8484}\x{848B}\x{8490}\x{8494}\x{8499}\x{849C}\x{849F}' . + '\x{84A1}\x{84AD}\x{84B2}\x{84B8}\x{84B9}\x{84BB}\x{84BC}\x{84BF}\x{84C1}' . + '\x{84C4}\x{84C6}\x{84C9}\x{84CA}\x{84CB}\x{84CD}\x{84D0}\x{84D1}\x{84D6}' . + '\x{84D9}\x{84DA}\x{84EC}\x{84EE}\x{84F4}\x{84FC}\x{84FF}\x{8500}\x{8506}' . + '\x{8511}\x{8513}\x{8514}\x{8515}\x{8517}\x{8518}\x{851A}\x{851F}\x{8521}' . + '\x{8526}\x{852C}\x{852D}\x{8535}\x{853D}\x{8540}\x{8541}\x{8543}\x{8548}' . + '\x{8549}\x{854A}\x{854B}\x{854E}\x{8555}\x{8557}\x{8558}\x{855A}\x{8563}' . + '\x{8568}\x{8569}\x{856A}\x{856D}\x{8577}\x{857E}\x{8580}\x{8584}\x{8587}' . + '\x{8588}\x{858A}\x{8590}\x{8591}\x{8594}\x{8597}\x{8599}\x{859B}\x{859C}' . + '\x{85A4}\x{85A6}\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AE}\x{85AF}' . + '\x{85B9}\x{85BA}\x{85C1}\x{85C9}\x{85CD}\x{85CF}\x{85D0}\x{85D5}\x{85DC}' . + '\x{85DD}\x{85E4}\x{85E5}\x{85E9}\x{85EA}\x{85F7}\x{85F9}\x{85FA}\x{85FB}' . + '\x{85FE}\x{8602}\x{8606}\x{8607}\x{860A}\x{860B}\x{8613}\x{8616}\x{8617}' . + '\x{861A}\x{8622}\x{862D}\x{862F}\x{8630}\x{863F}\x{864D}\x{864E}\x{8650}' . + '\x{8654}\x{8655}\x{865A}\x{865C}\x{865E}\x{865F}\x{8667}\x{866B}\x{8671}' . + '\x{8679}\x{867B}\x{868A}\x{868B}\x{868C}\x{8693}\x{8695}\x{86A3}\x{86A4}' . + '\x{86A9}\x{86AA}\x{86AB}\x{86AF}\x{86B0}\x{86B6}\x{86C4}\x{86C6}\x{86C7}' . + '\x{86C9}\x{86CB}\x{86CD}\x{86CE}\x{86D4}\x{86D9}\x{86DB}\x{86DE}\x{86DF}' . + '\x{86E4}\x{86E9}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F8}\x{86F9}\x{86FB}' . + '\x{86FE}\x{8700}\x{8702}\x{8703}\x{8706}\x{8708}\x{8709}\x{870A}\x{870D}' . + '\x{8711}\x{8712}\x{8718}\x{871A}\x{871C}\x{8725}\x{8729}\x{8734}\x{8737}' . + '\x{873B}\x{873F}\x{8749}\x{874B}\x{874C}\x{874E}\x{8753}\x{8755}\x{8757}' . + '\x{8759}\x{875F}\x{8760}\x{8763}\x{8766}\x{8768}\x{876A}\x{876E}\x{8774}' . + '\x{8776}\x{8778}\x{877F}\x{8782}\x{878D}\x{879F}\x{87A2}\x{87AB}\x{87AF}' . + '\x{87B3}\x{87BA}\x{87BB}\x{87BD}\x{87C0}\x{87C4}\x{87C6}\x{87C7}\x{87CB}' . + '\x{87D0}\x{87D2}\x{87E0}\x{87EF}\x{87F2}\x{87F6}\x{87F7}\x{87F9}\x{87FB}' . + '\x{87FE}\x{8805}\x{880D}\x{880E}\x{880F}\x{8811}\x{8815}\x{8816}\x{8821}' . + '\x{8822}\x{8823}\x{8827}\x{8831}\x{8836}\x{8839}\x{883B}\x{8840}\x{8842}' . + '\x{8844}\x{8846}\x{884C}\x{884D}\x{8852}\x{8853}\x{8857}\x{8859}\x{885B}' . + '\x{885D}\x{885E}\x{8861}\x{8862}\x{8863}\x{8868}\x{886B}\x{8870}\x{8872}' . + '\x{8875}\x{8877}\x{887D}\x{887E}\x{887F}\x{8881}\x{8882}\x{8888}\x{888B}' . + '\x{888D}\x{8892}\x{8896}\x{8897}\x{8899}\x{889E}\x{88A2}\x{88A4}\x{88AB}' . + '\x{88AE}\x{88B0}\x{88B1}\x{88B4}\x{88B5}\x{88B7}\x{88BF}\x{88C1}\x{88C2}' . + '\x{88C3}\x{88C4}\x{88C5}\x{88CF}\x{88D4}\x{88D5}\x{88D8}\x{88D9}\x{88DC}' . + '\x{88DD}\x{88DF}\x{88E1}\x{88E8}\x{88F2}\x{88F3}\x{88F4}\x{88F8}\x{88F9}' . + '\x{88FC}\x{88FD}\x{88FE}\x{8902}\x{8904}\x{8907}\x{890A}\x{890C}\x{8910}' . + '\x{8912}\x{8913}\x{891D}\x{891E}\x{8925}\x{892A}\x{892B}\x{8936}\x{8938}' . + '\x{893B}\x{8941}\x{8943}\x{8944}\x{894C}\x{894D}\x{8956}\x{895E}\x{895F}' . + '\x{8960}\x{8964}\x{8966}\x{896A}\x{896D}\x{896F}\x{8972}\x{8974}\x{8977}' . + '\x{897E}\x{897F}\x{8981}\x{8983}\x{8986}\x{8987}\x{8988}\x{898A}\x{898B}' . + '\x{898F}\x{8993}\x{8996}\x{8997}\x{8998}\x{899A}\x{89A1}\x{89A6}\x{89A7}' . + '\x{89A9}\x{89AA}\x{89AC}\x{89AF}\x{89B2}\x{89B3}\x{89BA}\x{89BD}\x{89BF}' . + '\x{89C0}\x{89D2}\x{89DA}\x{89DC}\x{89DD}\x{89E3}\x{89E6}\x{89E7}\x{89F4}' . + '\x{89F8}\x{8A00}\x{8A02}\x{8A03}\x{8A08}\x{8A0A}\x{8A0C}\x{8A0E}\x{8A10}' . + '\x{8A13}\x{8A16}\x{8A17}\x{8A18}\x{8A1B}\x{8A1D}\x{8A1F}\x{8A23}\x{8A25}' . + '\x{8A2A}\x{8A2D}\x{8A31}\x{8A33}\x{8A34}\x{8A36}\x{8A3A}\x{8A3B}\x{8A3C}' . + '\x{8A41}\x{8A46}\x{8A48}\x{8A50}\x{8A51}\x{8A52}\x{8A54}\x{8A55}\x{8A5B}' . + '\x{8A5E}\x{8A60}\x{8A62}\x{8A63}\x{8A66}\x{8A69}\x{8A6B}\x{8A6C}\x{8A6D}' . + '\x{8A6E}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A7C}\x{8A82}\x{8A84}\x{8A85}' . + '\x{8A87}\x{8A89}\x{8A8C}\x{8A8D}\x{8A91}\x{8A93}\x{8A95}\x{8A98}\x{8A9A}' . + '\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA8}\x{8AAC}' . + '\x{8AAD}\x{8AB0}\x{8AB2}\x{8AB9}\x{8ABC}\x{8ABF}\x{8AC2}\x{8AC4}\x{8AC7}' . + '\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACF}\x{8AD2}\x{8AD6}\x{8ADA}\x{8ADB}\x{8ADC}' . + '\x{8ADE}\x{8AE0}\x{8AE1}\x{8AE2}\x{8AE4}\x{8AE6}\x{8AE7}\x{8AEB}\x{8AED}' . + '\x{8AEE}\x{8AF1}\x{8AF3}\x{8AF7}\x{8AF8}\x{8AFA}\x{8AFE}\x{8B00}\x{8B01}' . + '\x{8B02}\x{8B04}\x{8B07}\x{8B0C}\x{8B0E}\x{8B10}\x{8B14}\x{8B16}\x{8B17}' . + '\x{8B19}\x{8B1A}\x{8B1B}\x{8B1D}\x{8B20}\x{8B21}\x{8B26}\x{8B28}\x{8B2B}' . + '\x{8B2C}\x{8B33}\x{8B39}\x{8B3E}\x{8B41}\x{8B49}\x{8B4C}\x{8B4E}\x{8B4F}' . + '\x{8B56}\x{8B58}\x{8B5A}\x{8B5B}\x{8B5C}\x{8B5F}\x{8B66}\x{8B6B}\x{8B6C}' . + '\x{8B6F}\x{8B70}\x{8B71}\x{8B72}\x{8B74}\x{8B77}\x{8B7D}\x{8B80}\x{8B83}' . + '\x{8B8A}\x{8B8C}\x{8B8E}\x{8B90}\x{8B92}\x{8B93}\x{8B96}\x{8B99}\x{8B9A}' . + '\x{8C37}\x{8C3A}\x{8C3F}\x{8C41}\x{8C46}\x{8C48}\x{8C4A}\x{8C4C}\x{8C4E}' . + '\x{8C50}\x{8C55}\x{8C5A}\x{8C61}\x{8C62}\x{8C6A}\x{8C6B}\x{8C6C}\x{8C78}' . + '\x{8C79}\x{8C7A}\x{8C7C}\x{8C82}\x{8C85}\x{8C89}\x{8C8A}\x{8C8C}\x{8C8D}' . + '\x{8C8E}\x{8C94}\x{8C98}\x{8C9D}\x{8C9E}\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA7}' . + '\x{8CA8}\x{8CA9}\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}' . + '\x{8CB2}\x{8CB3}\x{8CB4}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CBB}\x{8CBC}\x{8CBD}' . + '\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}\x{8CC7}\x{8CC8}\x{8CCA}' . + '\x{8CCD}\x{8CCE}\x{8CD1}\x{8CD3}\x{8CDA}\x{8CDB}\x{8CDC}\x{8CDE}\x{8CE0}' . + '\x{8CE2}\x{8CE3}\x{8CE4}\x{8CE6}\x{8CEA}\x{8CED}\x{8CFA}\x{8CFB}\x{8CFC}' . + '\x{8CFD}\x{8D04}\x{8D05}\x{8D07}\x{8D08}\x{8D0A}\x{8D0B}\x{8D0D}\x{8D0F}' . + '\x{8D10}\x{8D13}\x{8D14}\x{8D16}\x{8D64}\x{8D66}\x{8D67}\x{8D6B}\x{8D6D}' . + '\x{8D70}\x{8D71}\x{8D73}\x{8D74}\x{8D77}\x{8D81}\x{8D85}\x{8D8A}\x{8D99}' . + '\x{8DA3}\x{8DA8}\x{8DB3}\x{8DBA}\x{8DBE}\x{8DC2}\x{8DCB}\x{8DCC}\x{8DCF}' . + '\x{8DD6}\x{8DDA}\x{8DDB}\x{8DDD}\x{8DDF}\x{8DE1}\x{8DE3}\x{8DE8}\x{8DEA}' . + '\x{8DEB}\x{8DEF}\x{8DF3}\x{8DF5}\x{8DFC}\x{8DFF}\x{8E08}\x{8E09}\x{8E0A}' . + '\x{8E0F}\x{8E10}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E2A}\x{8E30}\x{8E34}\x{8E35}' . + '\x{8E42}\x{8E44}\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4C}\x{8E50}\x{8E55}' . + '\x{8E59}\x{8E5F}\x{8E60}\x{8E63}\x{8E64}\x{8E72}\x{8E74}\x{8E76}\x{8E7C}' . + '\x{8E81}\x{8E84}\x{8E85}\x{8E87}\x{8E8A}\x{8E8B}\x{8E8D}\x{8E91}\x{8E93}' . + '\x{8E94}\x{8E99}\x{8EA1}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAF}\x{8EB0}\x{8EB1}' . + '\x{8EBE}\x{8EC5}\x{8EC6}\x{8EC8}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ED2}' . + '\x{8EDB}\x{8EDF}\x{8EE2}\x{8EE3}\x{8EEB}\x{8EF8}\x{8EFB}\x{8EFC}\x{8EFD}' . + '\x{8EFE}\x{8F03}\x{8F05}\x{8F09}\x{8F0A}\x{8F0C}\x{8F12}\x{8F13}\x{8F14}' . + '\x{8F15}\x{8F19}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1F}\x{8F26}\x{8F29}\x{8F2A}' . + '\x{8F2F}\x{8F33}\x{8F38}\x{8F39}\x{8F3B}\x{8F3E}\x{8F3F}\x{8F42}\x{8F44}' . + '\x{8F45}\x{8F46}\x{8F49}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F57}\x{8F5C}\x{8F5F}' . + '\x{8F61}\x{8F62}\x{8F63}\x{8F64}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA3}' . + '\x{8FA7}\x{8FA8}\x{8FAD}\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB7}' . + '\x{8FBA}\x{8FBB}\x{8FBC}\x{8FBF}\x{8FC2}\x{8FC4}\x{8FC5}\x{8FCE}\x{8FD1}' . + '\x{8FD4}\x{8FDA}\x{8FE2}\x{8FE5}\x{8FE6}\x{8FE9}\x{8FEA}\x{8FEB}\x{8FED}' . + '\x{8FEF}\x{8FF0}\x{8FF4}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}\x{8FFD}\x{9000}' . + '\x{9001}\x{9003}\x{9005}\x{9006}\x{900B}\x{900D}\x{900E}\x{900F}\x{9010}' . + '\x{9011}\x{9013}\x{9014}\x{9015}\x{9016}\x{9017}\x{9019}\x{901A}\x{901D}' . + '\x{901E}\x{901F}\x{9020}\x{9021}\x{9022}\x{9023}\x{9027}\x{902E}\x{9031}' . + '\x{9032}\x{9035}\x{9036}\x{9038}\x{9039}\x{903C}\x{903E}\x{9041}\x{9042}' . + '\x{9045}\x{9047}\x{9049}\x{904A}\x{904B}\x{904D}\x{904E}\x{904F}\x{9050}' . + '\x{9051}\x{9052}\x{9053}\x{9054}\x{9055}\x{9056}\x{9058}\x{9059}\x{905C}' . + '\x{905E}\x{9060}\x{9061}\x{9063}\x{9065}\x{9068}\x{9069}\x{906D}\x{906E}' . + '\x{906F}\x{9072}\x{9075}\x{9076}\x{9077}\x{9078}\x{907A}\x{907C}\x{907D}' . + '\x{907F}\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9087}\x{9089}\x{908A}' . + '\x{908F}\x{9091}\x{90A3}\x{90A6}\x{90A8}\x{90AA}\x{90AF}\x{90B1}\x{90B5}' . + '\x{90B8}\x{90C1}\x{90CA}\x{90CE}\x{90DB}\x{90E1}\x{90E2}\x{90E4}\x{90E8}' . + '\x{90ED}\x{90F5}\x{90F7}\x{90FD}\x{9102}\x{9112}\x{9119}\x{912D}\x{9130}' . + '\x{9132}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}\x{914E}\x{9152}\x{9154}' . + '\x{9156}\x{9158}\x{9162}\x{9163}\x{9165}\x{9169}\x{916A}\x{916C}\x{9172}' . + '\x{9173}\x{9175}\x{9177}\x{9178}\x{9182}\x{9187}\x{9189}\x{918B}\x{918D}' . + '\x{9190}\x{9192}\x{9197}\x{919C}\x{91A2}\x{91A4}\x{91AA}\x{91AB}\x{91AF}' . + '\x{91B4}\x{91B5}\x{91B8}\x{91BA}\x{91C0}\x{91C1}\x{91C6}\x{91C7}\x{91C8}' . + '\x{91C9}\x{91CB}\x{91CC}\x{91CD}\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D6}' . + '\x{91D8}\x{91DB}\x{91DC}\x{91DD}\x{91DF}\x{91E1}\x{91E3}\x{91E6}\x{91E7}' . + '\x{91F5}\x{91F6}\x{91FC}\x{91FF}\x{920D}\x{920E}\x{9211}\x{9214}\x{9215}' . + '\x{921E}\x{9229}\x{922C}\x{9234}\x{9237}\x{923F}\x{9244}\x{9245}\x{9248}' . + '\x{9249}\x{924B}\x{9250}\x{9257}\x{925A}\x{925B}\x{925E}\x{9262}\x{9264}' . + '\x{9266}\x{9271}\x{927E}\x{9280}\x{9283}\x{9285}\x{9291}\x{9293}\x{9295}' . + '\x{9296}\x{9298}\x{929A}\x{929B}\x{929C}\x{92AD}\x{92B7}\x{92B9}\x{92CF}' . + '\x{92D2}\x{92E4}\x{92E9}\x{92EA}\x{92ED}\x{92F2}\x{92F3}\x{92F8}\x{92FA}' . + '\x{92FC}\x{9306}\x{930F}\x{9310}\x{9318}\x{9319}\x{931A}\x{9320}\x{9322}' . + '\x{9323}\x{9326}\x{9328}\x{932B}\x{932C}\x{932E}\x{932F}\x{9332}\x{9335}' . + '\x{933A}\x{933B}\x{9344}\x{934B}\x{934D}\x{9354}\x{9356}\x{935B}\x{935C}' . + '\x{9360}\x{936C}\x{936E}\x{9375}\x{937C}\x{937E}\x{938C}\x{9394}\x{9396}' . + '\x{9397}\x{939A}\x{93A7}\x{93AC}\x{93AD}\x{93AE}\x{93B0}\x{93B9}\x{93C3}' . + '\x{93C8}\x{93D0}\x{93D1}\x{93D6}\x{93D7}\x{93D8}\x{93DD}\x{93E1}\x{93E4}' . + '\x{93E5}\x{93E8}\x{9403}\x{9407}\x{9410}\x{9413}\x{9414}\x{9418}\x{9419}' . + '\x{941A}\x{9421}\x{942B}\x{9435}\x{9436}\x{9438}\x{943A}\x{9441}\x{9444}' . + '\x{9451}\x{9452}\x{9453}\x{945A}\x{945B}\x{945E}\x{9460}\x{9462}\x{946A}' . + '\x{9470}\x{9475}\x{9477}\x{947C}\x{947D}\x{947E}\x{947F}\x{9481}\x{9577}' . + '\x{9580}\x{9582}\x{9583}\x{9587}\x{9589}\x{958A}\x{958B}\x{958F}\x{9591}' . + '\x{9593}\x{9594}\x{9596}\x{9598}\x{9599}\x{95A0}\x{95A2}\x{95A3}\x{95A4}' . + '\x{95A5}\x{95A7}\x{95A8}\x{95AD}\x{95B2}\x{95B9}\x{95BB}\x{95BC}\x{95BE}' . + '\x{95C3}\x{95C7}\x{95CA}\x{95CC}\x{95CD}\x{95D4}\x{95D5}\x{95D6}\x{95D8}' . + '\x{95DC}\x{95E1}\x{95E2}\x{95E5}\x{961C}\x{9621}\x{9628}\x{962A}\x{962E}' . + '\x{962F}\x{9632}\x{963B}\x{963F}\x{9640}\x{9642}\x{9644}\x{964B}\x{964C}' . + '\x{964D}\x{964F}\x{9650}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9662}' . + '\x{9663}\x{9664}\x{9665}\x{9666}\x{966A}\x{966C}\x{9670}\x{9672}\x{9673}' . + '\x{9675}\x{9676}\x{9677}\x{9678}\x{967A}\x{967D}\x{9685}\x{9686}\x{9688}' . + '\x{968A}\x{968B}\x{968D}\x{968E}\x{968F}\x{9694}\x{9695}\x{9697}\x{9698}' . + '\x{9699}\x{969B}\x{969C}\x{96A0}\x{96A3}\x{96A7}\x{96A8}\x{96AA}\x{96B0}' . + '\x{96B1}\x{96B2}\x{96B4}\x{96B6}\x{96B7}\x{96B8}\x{96B9}\x{96BB}\x{96BC}' . + '\x{96C0}\x{96C1}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C9}\x{96CB}\x{96CC}' . + '\x{96CD}\x{96CE}\x{96D1}\x{96D5}\x{96D6}\x{96D9}\x{96DB}\x{96DC}\x{96E2}' . + '\x{96E3}\x{96E8}\x{96EA}\x{96EB}\x{96F0}\x{96F2}\x{96F6}\x{96F7}\x{96F9}' . + '\x{96FB}\x{9700}\x{9704}\x{9706}\x{9707}\x{9708}\x{970A}\x{970D}\x{970E}' . + '\x{970F}\x{9711}\x{9713}\x{9716}\x{9719}\x{971C}\x{971E}\x{9724}\x{9727}' . + '\x{972A}\x{9730}\x{9732}\x{9738}\x{9739}\x{973D}\x{973E}\x{9742}\x{9744}' . + '\x{9746}\x{9748}\x{9749}\x{9752}\x{9756}\x{9759}\x{975C}\x{975E}\x{9760}' . + '\x{9761}\x{9762}\x{9764}\x{9766}\x{9768}\x{9769}\x{976B}\x{976D}\x{9771}' . + '\x{9774}\x{9779}\x{977A}\x{977C}\x{9781}\x{9784}\x{9785}\x{9786}\x{978B}' . + '\x{978D}\x{978F}\x{9790}\x{9798}\x{979C}\x{97A0}\x{97A3}\x{97A6}\x{97A8}' . + '\x{97AB}\x{97AD}\x{97B3}\x{97B4}\x{97C3}\x{97C6}\x{97C8}\x{97CB}\x{97D3}' . + '\x{97DC}\x{97ED}\x{97EE}\x{97F2}\x{97F3}\x{97F5}\x{97F6}\x{97FB}\x{97FF}' . + '\x{9801}\x{9802}\x{9803}\x{9805}\x{9806}\x{9808}\x{980C}\x{980F}\x{9810}' . + '\x{9811}\x{9812}\x{9813}\x{9817}\x{9818}\x{981A}\x{9821}\x{9824}\x{982C}' . + '\x{982D}\x{9834}\x{9837}\x{9838}\x{983B}\x{983C}\x{983D}\x{9846}\x{984B}' . + '\x{984C}\x{984D}\x{984E}\x{984F}\x{9854}\x{9855}\x{9858}\x{985B}\x{985E}' . + '\x{9867}\x{986B}\x{986F}\x{9870}\x{9871}\x{9873}\x{9874}\x{98A8}\x{98AA}' . + '\x{98AF}\x{98B1}\x{98B6}\x{98C3}\x{98C4}\x{98C6}\x{98DB}\x{98DC}\x{98DF}' . + '\x{98E2}\x{98E9}\x{98EB}\x{98ED}\x{98EE}\x{98EF}\x{98F2}\x{98F4}\x{98FC}' . + '\x{98FD}\x{98FE}\x{9903}\x{9905}\x{9909}\x{990A}\x{990C}\x{9910}\x{9912}' . + '\x{9913}\x{9914}\x{9918}\x{991D}\x{991E}\x{9920}\x{9921}\x{9924}\x{9928}' . + '\x{992C}\x{992E}\x{993D}\x{993E}\x{9942}\x{9945}\x{9949}\x{994B}\x{994C}' . + '\x{9950}\x{9951}\x{9952}\x{9955}\x{9957}\x{9996}\x{9997}\x{9998}\x{9999}' . + '\x{99A5}\x{99A8}\x{99AC}\x{99AD}\x{99AE}\x{99B3}\x{99B4}\x{99BC}\x{99C1}' . + '\x{99C4}\x{99C5}\x{99C6}\x{99C8}\x{99D0}\x{99D1}\x{99D2}\x{99D5}\x{99D8}' . + '\x{99DB}\x{99DD}\x{99DF}\x{99E2}\x{99ED}\x{99EE}\x{99F1}\x{99F2}\x{99F8}' . + '\x{99FB}\x{99FF}\x{9A01}\x{9A05}\x{9A0E}\x{9A0F}\x{9A12}\x{9A13}\x{9A19}' . + '\x{9A28}\x{9A2B}\x{9A30}\x{9A37}\x{9A3E}\x{9A40}\x{9A42}\x{9A43}\x{9A45}' . + '\x{9A4D}\x{9A55}\x{9A57}\x{9A5A}\x{9A5B}\x{9A5F}\x{9A62}\x{9A64}\x{9A65}' . + '\x{9A69}\x{9A6A}\x{9A6B}\x{9AA8}\x{9AAD}\x{9AB0}\x{9AB8}\x{9ABC}\x{9AC0}' . + '\x{9AC4}\x{9ACF}\x{9AD1}\x{9AD3}\x{9AD4}\x{9AD8}\x{9ADE}\x{9ADF}\x{9AE2}' . + '\x{9AE3}\x{9AE6}\x{9AEA}\x{9AEB}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF4}' . + '\x{9AF7}\x{9AFB}\x{9B06}\x{9B18}\x{9B1A}\x{9B1F}\x{9B22}\x{9B23}\x{9B25}' . + '\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2E}\x{9B2F}\x{9B31}\x{9B32}\x{9B3B}' . + '\x{9B3C}\x{9B41}\x{9B42}\x{9B43}\x{9B44}\x{9B45}\x{9B4D}\x{9B4E}\x{9B4F}' . + '\x{9B51}\x{9B54}\x{9B58}\x{9B5A}\x{9B6F}\x{9B74}\x{9B83}\x{9B8E}\x{9B91}' . + '\x{9B92}\x{9B93}\x{9B96}\x{9B97}\x{9B9F}\x{9BA0}\x{9BA8}\x{9BAA}\x{9BAB}' . + '\x{9BAD}\x{9BAE}\x{9BB4}\x{9BB9}\x{9BC0}\x{9BC6}\x{9BC9}\x{9BCA}\x{9BCF}' . + '\x{9BD1}\x{9BD2}\x{9BD4}\x{9BD6}\x{9BDB}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}' . + '\x{9BE8}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF5}\x{9C04}\x{9C06}\x{9C08}\x{9C09}' . + '\x{9C0A}\x{9C0C}\x{9C0D}\x{9C10}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C1B}' . + '\x{9C21}\x{9C24}\x{9C25}\x{9C2D}\x{9C2E}\x{9C2F}\x{9C30}\x{9C32}\x{9C39}' . + '\x{9C3A}\x{9C3B}\x{9C3E}\x{9C46}\x{9C47}\x{9C48}\x{9C52}\x{9C57}\x{9C5A}' . + '\x{9C60}\x{9C67}\x{9C76}\x{9C78}\x{9CE5}\x{9CE7}\x{9CE9}\x{9CEB}\x{9CEC}' . + '\x{9CF0}\x{9CF3}\x{9CF4}\x{9CF6}\x{9D03}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' . + '\x{9D0E}\x{9D12}\x{9D15}\x{9D1B}\x{9D1F}\x{9D23}\x{9D26}\x{9D28}\x{9D2A}' . + '\x{9D2B}\x{9D2C}\x{9D3B}\x{9D3E}\x{9D3F}\x{9D41}\x{9D44}\x{9D46}\x{9D48}' . + '\x{9D50}\x{9D51}\x{9D59}\x{9D5C}\x{9D5D}\x{9D5E}\x{9D60}\x{9D61}\x{9D64}' . + '\x{9D6C}\x{9D6F}\x{9D72}\x{9D7A}\x{9D87}\x{9D89}\x{9D8F}\x{9D9A}\x{9DA4}' . + '\x{9DA9}\x{9DAB}\x{9DAF}\x{9DB2}\x{9DB4}\x{9DB8}\x{9DBA}\x{9DBB}\x{9DC1}' . + '\x{9DC2}\x{9DC4}\x{9DC6}\x{9DCF}\x{9DD3}\x{9DD9}\x{9DE6}\x{9DED}\x{9DEF}' . + '\x{9DF2}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFD}\x{9E1A}\x{9E1B}\x{9E1E}\x{9E75}' . + '\x{9E78}\x{9E79}\x{9E7D}\x{9E7F}\x{9E81}\x{9E88}\x{9E8B}\x{9E8C}\x{9E91}' . + '\x{9E92}\x{9E93}\x{9E95}\x{9E97}\x{9E9D}\x{9E9F}\x{9EA5}\x{9EA6}\x{9EA9}' . + '\x{9EAA}\x{9EAD}\x{9EB8}\x{9EB9}\x{9EBA}\x{9EBB}\x{9EBC}\x{9EBE}\x{9EBF}' . + '\x{9EC4}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED2}\x{9ED4}\x{9ED8}' . + '\x{9ED9}\x{9EDB}\x{9EDC}\x{9EDD}\x{9EDE}\x{9EE0}\x{9EE5}\x{9EE8}\x{9EEF}' . + '\x{9EF4}\x{9EF6}\x{9EF7}\x{9EF9}\x{9EFB}\x{9EFC}\x{9EFD}\x{9F07}\x{9F08}' . + '\x{9F0E}\x{9F13}\x{9F15}\x{9F20}\x{9F21}\x{9F2C}\x{9F3B}\x{9F3E}\x{9F4A}' . + '\x{9F4B}\x{9F4E}\x{9F4F}\x{9F52}\x{9F54}\x{9F5F}\x{9F60}\x{9F61}\x{9F62}' . + '\x{9F63}\x{9F66}\x{9F67}\x{9F6A}\x{9F6C}\x{9F72}\x{9F76}\x{9F77}\x{9F8D}' . + '\x{9F95}\x{9F9C}\x{9F9D}\x{9FA0}]{1,15}$/iu', + 12 => '/^[\x{002d}0-9a-z\x{3447}\x{3473}\x{359E}\x{360E}\x{361A}\x{3918}\x{396E}\x{39CF}\x{39D0}' . + '\x{39DF}\x{3A73}\x{3B4E}\x{3C6E}\x{3CE0}\x{4056}\x{415F}\x{4337}\x{43AC}' . + '\x{43B1}\x{43DD}\x{44D6}\x{464C}\x{4661}\x{4723}\x{4729}\x{477C}\x{478D}' . + '\x{4947}\x{497A}\x{497D}\x{4982}\x{4983}\x{4985}\x{4986}\x{499B}\x{499F}' . + '\x{49B6}\x{49B7}\x{4C77}\x{4C9F}\x{4CA0}\x{4CA1}\x{4CA2}\x{4CA3}\x{4D13}' . + '\x{4D14}\x{4D15}\x{4D16}\x{4D17}\x{4D18}\x{4D19}\x{4DAE}\x{4E00}\x{4E01}' . + '\x{4E02}\x{4E03}\x{4E04}\x{4E05}\x{4E06}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' . + '\x{4E0B}\x{4E0C}\x{4E0D}\x{4E0E}\x{4E0F}\x{4E10}\x{4E11}\x{4E13}\x{4E14}' . + '\x{4E15}\x{4E16}\x{4E17}\x{4E18}\x{4E19}\x{4E1A}\x{4E1B}\x{4E1C}\x{4E1D}' . + '\x{4E1E}\x{4E1F}\x{4E20}\x{4E21}\x{4E22}\x{4E23}\x{4E24}\x{4E25}\x{4E26}' . + '\x{4E27}\x{4E28}\x{4E2A}\x{4E2B}\x{4E2C}\x{4E2D}\x{4E2E}\x{4E2F}\x{4E30}' . + '\x{4E31}\x{4E32}\x{4E33}\x{4E34}\x{4E35}\x{4E36}\x{4E37}\x{4E38}\x{4E39}' . + '\x{4E3A}\x{4E3B}\x{4E3C}\x{4E3D}\x{4E3E}\x{4E3F}\x{4E40}\x{4E41}\x{4E42}' . + '\x{4E43}\x{4E44}\x{4E45}\x{4E46}\x{4E47}\x{4E48}\x{4E49}\x{4E4A}\x{4E4B}' . + '\x{4E4C}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E50}\x{4E51}\x{4E52}\x{4E53}\x{4E54}' . + '\x{4E56}\x{4E57}\x{4E58}\x{4E59}\x{4E5A}\x{4E5B}\x{4E5C}\x{4E5D}\x{4E5E}' . + '\x{4E5F}\x{4E60}\x{4E61}\x{4E62}\x{4E63}\x{4E64}\x{4E65}\x{4E66}\x{4E67}' . + '\x{4E69}\x{4E6A}\x{4E6B}\x{4E6C}\x{4E6D}\x{4E6E}\x{4E6F}\x{4E70}\x{4E71}' . + '\x{4E72}\x{4E73}\x{4E74}\x{4E75}\x{4E76}\x{4E77}\x{4E78}\x{4E7A}\x{4E7B}' . + '\x{4E7C}\x{4E7D}\x{4E7E}\x{4E7F}\x{4E80}\x{4E81}\x{4E82}\x{4E83}\x{4E84}' . + '\x{4E85}\x{4E86}\x{4E87}\x{4E88}\x{4E89}\x{4E8B}\x{4E8C}\x{4E8D}\x{4E8E}' . + '\x{4E8F}\x{4E90}\x{4E91}\x{4E92}\x{4E93}\x{4E94}\x{4E95}\x{4E97}\x{4E98}' . + '\x{4E99}\x{4E9A}\x{4E9B}\x{4E9C}\x{4E9D}\x{4E9E}\x{4E9F}\x{4EA0}\x{4EA1}' . + '\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA7}\x{4EA8}\x{4EA9}\x{4EAA}\x{4EAB}' . + '\x{4EAC}\x{4EAD}\x{4EAE}\x{4EAF}\x{4EB0}\x{4EB1}\x{4EB2}\x{4EB3}\x{4EB4}' . + '\x{4EB5}\x{4EB6}\x{4EB7}\x{4EB8}\x{4EB9}\x{4EBA}\x{4EBB}\x{4EBD}\x{4EBE}' . + '\x{4EBF}\x{4EC0}\x{4EC1}\x{4EC2}\x{4EC3}\x{4EC4}\x{4EC5}\x{4EC6}\x{4EC7}' . + '\x{4EC8}\x{4EC9}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED0}\x{4ED1}' . + '\x{4ED2}\x{4ED3}\x{4ED4}\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDA}' . + '\x{4EDB}\x{4EDC}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE0}\x{4EE1}\x{4EE2}\x{4EE3}' . + '\x{4EE4}\x{4EE5}\x{4EE6}\x{4EE8}\x{4EE9}\x{4EEA}\x{4EEB}\x{4EEC}\x{4EEF}' . + '\x{4EF0}\x{4EF1}\x{4EF2}\x{4EF3}\x{4EF4}\x{4EF5}\x{4EF6}\x{4EF7}\x{4EFB}' . + '\x{4EFD}\x{4EFF}\x{4F00}\x{4F01}\x{4F02}\x{4F03}\x{4F04}\x{4F05}\x{4F06}' . + '\x{4F08}\x{4F09}\x{4F0A}\x{4F0B}\x{4F0C}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}' . + '\x{4F11}\x{4F12}\x{4F13}\x{4F14}\x{4F15}\x{4F17}\x{4F18}\x{4F19}\x{4F1A}' . + '\x{4F1B}\x{4F1C}\x{4F1D}\x{4F1E}\x{4F1F}\x{4F20}\x{4F21}\x{4F22}\x{4F23}' . + '\x{4F24}\x{4F25}\x{4F26}\x{4F27}\x{4F29}\x{4F2A}\x{4F2B}\x{4F2C}\x{4F2D}' . + '\x{4F2E}\x{4F2F}\x{4F30}\x{4F32}\x{4F33}\x{4F34}\x{4F36}\x{4F38}\x{4F39}' . + '\x{4F3A}\x{4F3B}\x{4F3C}\x{4F3D}\x{4F3E}\x{4F3F}\x{4F41}\x{4F42}\x{4F43}' . + '\x{4F45}\x{4F46}\x{4F47}\x{4F48}\x{4F49}\x{4F4A}\x{4F4B}\x{4F4C}\x{4F4D}' . + '\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}\x{4F52}\x{4F53}\x{4F54}\x{4F55}\x{4F56}' . + '\x{4F57}\x{4F58}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}\x{4F5F}' . + '\x{4F60}\x{4F61}\x{4F62}\x{4F63}\x{4F64}\x{4F65}\x{4F66}\x{4F67}\x{4F68}' . + '\x{4F69}\x{4F6A}\x{4F6B}\x{4F6C}\x{4F6D}\x{4F6E}\x{4F6F}\x{4F70}\x{4F72}' . + '\x{4F73}\x{4F74}\x{4F75}\x{4F76}\x{4F77}\x{4F78}\x{4F79}\x{4F7A}\x{4F7B}' . + '\x{4F7C}\x{4F7D}\x{4F7E}\x{4F7F}\x{4F80}\x{4F81}\x{4F82}\x{4F83}\x{4F84}' . + '\x{4F85}\x{4F86}\x{4F87}\x{4F88}\x{4F89}\x{4F8A}\x{4F8B}\x{4F8D}\x{4F8F}' . + '\x{4F90}\x{4F91}\x{4F92}\x{4F93}\x{4F94}\x{4F95}\x{4F96}\x{4F97}\x{4F98}' . + '\x{4F99}\x{4F9A}\x{4F9B}\x{4F9C}\x{4F9D}\x{4F9E}\x{4F9F}\x{4FA0}\x{4FA1}' . + '\x{4FA3}\x{4FA4}\x{4FA5}\x{4FA6}\x{4FA7}\x{4FA8}\x{4FA9}\x{4FAA}\x{4FAB}' . + '\x{4FAC}\x{4FAE}\x{4FAF}\x{4FB0}\x{4FB1}\x{4FB2}\x{4FB3}\x{4FB4}\x{4FB5}' . + '\x{4FB6}\x{4FB7}\x{4FB8}\x{4FB9}\x{4FBA}\x{4FBB}\x{4FBC}\x{4FBE}\x{4FBF}' . + '\x{4FC0}\x{4FC1}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FC5}\x{4FC7}\x{4FC9}\x{4FCA}' . + '\x{4FCB}\x{4FCD}\x{4FCE}\x{4FCF}\x{4FD0}\x{4FD1}\x{4FD2}\x{4FD3}\x{4FD4}' . + '\x{4FD5}\x{4FD6}\x{4FD7}\x{4FD8}\x{4FD9}\x{4FDA}\x{4FDB}\x{4FDC}\x{4FDD}' . + '\x{4FDE}\x{4FDF}\x{4FE0}\x{4FE1}\x{4FE3}\x{4FE4}\x{4FE5}\x{4FE6}\x{4FE7}' . + '\x{4FE8}\x{4FE9}\x{4FEA}\x{4FEB}\x{4FEC}\x{4FED}\x{4FEE}\x{4FEF}\x{4FF0}' . + '\x{4FF1}\x{4FF2}\x{4FF3}\x{4FF4}\x{4FF5}\x{4FF6}\x{4FF7}\x{4FF8}\x{4FF9}' . + '\x{4FFA}\x{4FFB}\x{4FFE}\x{4FFF}\x{5000}\x{5001}\x{5002}\x{5003}\x{5004}' . + '\x{5005}\x{5006}\x{5007}\x{5008}\x{5009}\x{500A}\x{500B}\x{500C}\x{500D}' . + '\x{500E}\x{500F}\x{5011}\x{5012}\x{5013}\x{5014}\x{5015}\x{5016}\x{5017}' . + '\x{5018}\x{5019}\x{501A}\x{501B}\x{501C}\x{501D}\x{501E}\x{501F}\x{5020}' . + '\x{5021}\x{5022}\x{5023}\x{5024}\x{5025}\x{5026}\x{5027}\x{5028}\x{5029}' . + '\x{502A}\x{502B}\x{502C}\x{502D}\x{502E}\x{502F}\x{5030}\x{5031}\x{5032}' . + '\x{5033}\x{5035}\x{5036}\x{5037}\x{5039}\x{503A}\x{503B}\x{503C}\x{503E}' . + '\x{503F}\x{5040}\x{5041}\x{5043}\x{5044}\x{5045}\x{5046}\x{5047}\x{5048}' . + '\x{5049}\x{504A}\x{504B}\x{504C}\x{504D}\x{504E}\x{504F}\x{5051}\x{5053}' . + '\x{5054}\x{5055}\x{5056}\x{5057}\x{5059}\x{505A}\x{505B}\x{505C}\x{505D}' . + '\x{505E}\x{505F}\x{5060}\x{5061}\x{5062}\x{5063}\x{5064}\x{5065}\x{5066}' . + '\x{5067}\x{5068}\x{5069}\x{506A}\x{506B}\x{506C}\x{506D}\x{506E}\x{506F}' . + '\x{5070}\x{5071}\x{5072}\x{5073}\x{5074}\x{5075}\x{5076}\x{5077}\x{5078}' . + '\x{5079}\x{507A}\x{507B}\x{507D}\x{507E}\x{507F}\x{5080}\x{5082}\x{5083}' . + '\x{5084}\x{5085}\x{5086}\x{5087}\x{5088}\x{5089}\x{508A}\x{508B}\x{508C}' . + '\x{508D}\x{508E}\x{508F}\x{5090}\x{5091}\x{5092}\x{5094}\x{5095}\x{5096}' . + '\x{5098}\x{5099}\x{509A}\x{509B}\x{509C}\x{509D}\x{509E}\x{50A2}\x{50A3}' . + '\x{50A4}\x{50A5}\x{50A6}\x{50A7}\x{50A8}\x{50A9}\x{50AA}\x{50AB}\x{50AC}' . + '\x{50AD}\x{50AE}\x{50AF}\x{50B0}\x{50B1}\x{50B2}\x{50B3}\x{50B4}\x{50B5}' . + '\x{50B6}\x{50B7}\x{50B8}\x{50BA}\x{50BB}\x{50BC}\x{50BD}\x{50BE}\x{50BF}' . + '\x{50C0}\x{50C1}\x{50C2}\x{50C4}\x{50C5}\x{50C6}\x{50C7}\x{50C8}\x{50C9}' . + '\x{50CA}\x{50CB}\x{50CC}\x{50CD}\x{50CE}\x{50CF}\x{50D0}\x{50D1}\x{50D2}' . + '\x{50D3}\x{50D4}\x{50D5}\x{50D6}\x{50D7}\x{50D9}\x{50DA}\x{50DB}\x{50DC}' . + '\x{50DD}\x{50DE}\x{50E0}\x{50E3}\x{50E4}\x{50E5}\x{50E6}\x{50E7}\x{50E8}' . + '\x{50E9}\x{50EA}\x{50EC}\x{50ED}\x{50EE}\x{50EF}\x{50F0}\x{50F1}\x{50F2}' . + '\x{50F3}\x{50F5}\x{50F6}\x{50F8}\x{50F9}\x{50FA}\x{50FB}\x{50FC}\x{50FD}' . + '\x{50FE}\x{50FF}\x{5100}\x{5101}\x{5102}\x{5103}\x{5104}\x{5105}\x{5106}' . + '\x{5107}\x{5108}\x{5109}\x{510A}\x{510B}\x{510C}\x{510D}\x{510E}\x{510F}' . + '\x{5110}\x{5111}\x{5112}\x{5113}\x{5114}\x{5115}\x{5116}\x{5117}\x{5118}' . + '\x{5119}\x{511A}\x{511C}\x{511D}\x{511E}\x{511F}\x{5120}\x{5121}\x{5122}' . + '\x{5123}\x{5124}\x{5125}\x{5126}\x{5127}\x{5129}\x{512A}\x{512C}\x{512D}' . + '\x{512E}\x{512F}\x{5130}\x{5131}\x{5132}\x{5133}\x{5134}\x{5135}\x{5136}' . + '\x{5137}\x{5138}\x{5139}\x{513A}\x{513B}\x{513C}\x{513D}\x{513E}\x{513F}' . + '\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' . + '\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5151}\x{5152}\x{5154}\x{5155}' . + '\x{5156}\x{5157}\x{5159}\x{515A}\x{515B}\x{515C}\x{515D}\x{515E}\x{515F}' . + '\x{5161}\x{5162}\x{5163}\x{5165}\x{5166}\x{5167}\x{5168}\x{5169}\x{516A}' . + '\x{516B}\x{516C}\x{516D}\x{516E}\x{516F}\x{5170}\x{5171}\x{5173}\x{5174}' . + '\x{5175}\x{5176}\x{5177}\x{5178}\x{5179}\x{517A}\x{517B}\x{517C}\x{517D}' . + '\x{517F}\x{5180}\x{5181}\x{5182}\x{5185}\x{5186}\x{5187}\x{5188}\x{5189}' . + '\x{518A}\x{518B}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}\x{5193}' . + '\x{5194}\x{5195}\x{5196}\x{5197}\x{5198}\x{5199}\x{519A}\x{519B}\x{519C}' . + '\x{519D}\x{519E}\x{519F}\x{51A0}\x{51A2}\x{51A4}\x{51A5}\x{51A6}\x{51A7}' . + '\x{51A8}\x{51AA}\x{51AB}\x{51AC}\x{51AE}\x{51AF}\x{51B0}\x{51B1}\x{51B2}' . + '\x{51B3}\x{51B5}\x{51B6}\x{51B7}\x{51B9}\x{51BB}\x{51BC}\x{51BD}\x{51BE}' . + '\x{51BF}\x{51C0}\x{51C1}\x{51C3}\x{51C4}\x{51C5}\x{51C6}\x{51C7}\x{51C8}' . + '\x{51C9}\x{51CA}\x{51CB}\x{51CC}\x{51CD}\x{51CE}\x{51CF}\x{51D0}\x{51D1}' . + '\x{51D4}\x{51D5}\x{51D6}\x{51D7}\x{51D8}\x{51D9}\x{51DA}\x{51DB}\x{51DC}' . + '\x{51DD}\x{51DE}\x{51E0}\x{51E1}\x{51E2}\x{51E3}\x{51E4}\x{51E5}\x{51E7}' . + '\x{51E8}\x{51E9}\x{51EA}\x{51EB}\x{51ED}\x{51EF}\x{51F0}\x{51F1}\x{51F3}' . + '\x{51F4}\x{51F5}\x{51F6}\x{51F7}\x{51F8}\x{51F9}\x{51FA}\x{51FB}\x{51FC}' . + '\x{51FD}\x{51FE}\x{51FF}\x{5200}\x{5201}\x{5202}\x{5203}\x{5204}\x{5205}' . + '\x{5206}\x{5207}\x{5208}\x{5209}\x{520A}\x{520B}\x{520C}\x{520D}\x{520E}' . + '\x{520F}\x{5210}\x{5211}\x{5212}\x{5213}\x{5214}\x{5215}\x{5216}\x{5217}' . + '\x{5218}\x{5219}\x{521A}\x{521B}\x{521C}\x{521D}\x{521E}\x{521F}\x{5220}' . + '\x{5221}\x{5222}\x{5223}\x{5224}\x{5225}\x{5226}\x{5228}\x{5229}\x{522A}' . + '\x{522B}\x{522C}\x{522D}\x{522E}\x{522F}\x{5230}\x{5231}\x{5232}\x{5233}' . + '\x{5234}\x{5235}\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{523C}' . + '\x{523D}\x{523E}\x{523F}\x{5240}\x{5241}\x{5242}\x{5243}\x{5244}\x{5245}' . + '\x{5246}\x{5247}\x{5248}\x{5249}\x{524A}\x{524B}\x{524C}\x{524D}\x{524E}' . + '\x{5250}\x{5251}\x{5252}\x{5254}\x{5255}\x{5256}\x{5257}\x{5258}\x{5259}' . + '\x{525A}\x{525B}\x{525C}\x{525D}\x{525E}\x{525F}\x{5260}\x{5261}\x{5262}' . + '\x{5263}\x{5264}\x{5265}\x{5267}\x{5268}\x{5269}\x{526A}\x{526B}\x{526C}' . + '\x{526D}\x{526E}\x{526F}\x{5270}\x{5272}\x{5273}\x{5274}\x{5275}\x{5276}' . + '\x{5277}\x{5278}\x{527A}\x{527B}\x{527C}\x{527D}\x{527E}\x{527F}\x{5280}' . + '\x{5281}\x{5282}\x{5283}\x{5284}\x{5286}\x{5287}\x{5288}\x{5289}\x{528A}' . + '\x{528B}\x{528C}\x{528D}\x{528F}\x{5290}\x{5291}\x{5292}\x{5293}\x{5294}' . + '\x{5295}\x{5296}\x{5297}\x{5298}\x{5299}\x{529A}\x{529B}\x{529C}\x{529D}' . + '\x{529E}\x{529F}\x{52A0}\x{52A1}\x{52A2}\x{52A3}\x{52A5}\x{52A6}\x{52A7}' . + '\x{52A8}\x{52A9}\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52AE}\x{52AF}\x{52B0}' . + '\x{52B1}\x{52B2}\x{52B3}\x{52B4}\x{52B5}\x{52B6}\x{52B7}\x{52B8}\x{52B9}' . + '\x{52BA}\x{52BB}\x{52BC}\x{52BD}\x{52BE}\x{52BF}\x{52C0}\x{52C1}\x{52C2}' . + '\x{52C3}\x{52C6}\x{52C7}\x{52C9}\x{52CA}\x{52CB}\x{52CD}\x{52CF}\x{52D0}' . + '\x{52D2}\x{52D3}\x{52D5}\x{52D6}\x{52D7}\x{52D8}\x{52D9}\x{52DA}\x{52DB}' . + '\x{52DC}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}\x{52E4}\x{52E6}' . + '\x{52E7}\x{52E8}\x{52E9}\x{52EA}\x{52EB}\x{52EC}\x{52ED}\x{52EF}\x{52F0}' . + '\x{52F1}\x{52F2}\x{52F3}\x{52F4}\x{52F5}\x{52F6}\x{52F7}\x{52F8}\x{52F9}' . + '\x{52FA}\x{52FB}\x{52FC}\x{52FD}\x{52FE}\x{52FF}\x{5300}\x{5301}\x{5302}' . + '\x{5305}\x{5306}\x{5307}\x{5308}\x{5309}\x{530A}\x{530B}\x{530C}\x{530D}' . + '\x{530E}\x{530F}\x{5310}\x{5311}\x{5312}\x{5313}\x{5314}\x{5315}\x{5316}' . + '\x{5317}\x{5319}\x{531A}\x{531C}\x{531D}\x{531F}\x{5320}\x{5321}\x{5322}' . + '\x{5323}\x{5324}\x{5325}\x{5326}\x{5328}\x{532A}\x{532B}\x{532C}\x{532D}' . + '\x{532E}\x{532F}\x{5330}\x{5331}\x{5333}\x{5334}\x{5337}\x{5339}\x{533A}' . + '\x{533B}\x{533C}\x{533D}\x{533E}\x{533F}\x{5340}\x{5341}\x{5343}\x{5344}' . + '\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}\x{534A}\x{534B}\x{534C}\x{534D}' . + '\x{534E}\x{534F}\x{5350}\x{5351}\x{5352}\x{5353}\x{5354}\x{5355}\x{5356}' . + '\x{5357}\x{5358}\x{5359}\x{535A}\x{535C}\x{535E}\x{535F}\x{5360}\x{5361}' . + '\x{5362}\x{5363}\x{5364}\x{5365}\x{5366}\x{5367}\x{5369}\x{536B}\x{536C}' . + '\x{536E}\x{536F}\x{5370}\x{5371}\x{5372}\x{5373}\x{5374}\x{5375}\x{5376}' . + '\x{5377}\x{5378}\x{5379}\x{537A}\x{537B}\x{537C}\x{537D}\x{537E}\x{537F}' . + '\x{5381}\x{5382}\x{5383}\x{5384}\x{5385}\x{5386}\x{5387}\x{5388}\x{5389}' . + '\x{538A}\x{538B}\x{538C}\x{538D}\x{538E}\x{538F}\x{5390}\x{5391}\x{5392}' . + '\x{5393}\x{5394}\x{5395}\x{5396}\x{5397}\x{5398}\x{5399}\x{539A}\x{539B}' . + '\x{539C}\x{539D}\x{539E}\x{539F}\x{53A0}\x{53A2}\x{53A3}\x{53A4}\x{53A5}' . + '\x{53A6}\x{53A7}\x{53A8}\x{53A9}\x{53AC}\x{53AD}\x{53AE}\x{53B0}\x{53B1}' . + '\x{53B2}\x{53B3}\x{53B4}\x{53B5}\x{53B6}\x{53B7}\x{53B8}\x{53B9}\x{53BB}' . + '\x{53BC}\x{53BD}\x{53BE}\x{53BF}\x{53C0}\x{53C1}\x{53C2}\x{53C3}\x{53C4}' . + '\x{53C6}\x{53C7}\x{53C8}\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}' . + '\x{53D0}\x{53D1}\x{53D2}\x{53D3}\x{53D4}\x{53D5}\x{53D6}\x{53D7}\x{53D8}' . + '\x{53D9}\x{53DB}\x{53DC}\x{53DF}\x{53E0}\x{53E1}\x{53E2}\x{53E3}\x{53E4}' . + '\x{53E5}\x{53E6}\x{53E8}\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}' . + '\x{53EF}\x{53F0}\x{53F1}\x{53F2}\x{53F3}\x{53F4}\x{53F5}\x{53F6}\x{53F7}' . + '\x{53F8}\x{53F9}\x{53FA}\x{53FB}\x{53FC}\x{53FD}\x{53FE}\x{5401}\x{5402}' . + '\x{5403}\x{5404}\x{5405}\x{5406}\x{5407}\x{5408}\x{5409}\x{540A}\x{540B}' . + '\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}\x{5411}\x{5412}\x{5413}\x{5414}' . + '\x{5415}\x{5416}\x{5417}\x{5418}\x{5419}\x{541B}\x{541C}\x{541D}\x{541E}' . + '\x{541F}\x{5420}\x{5421}\x{5423}\x{5424}\x{5425}\x{5426}\x{5427}\x{5428}' . + '\x{5429}\x{542A}\x{542B}\x{542C}\x{542D}\x{542E}\x{542F}\x{5430}\x{5431}' . + '\x{5432}\x{5433}\x{5434}\x{5435}\x{5436}\x{5437}\x{5438}\x{5439}\x{543A}' . + '\x{543B}\x{543C}\x{543D}\x{543E}\x{543F}\x{5440}\x{5441}\x{5442}\x{5443}' . + '\x{5444}\x{5445}\x{5446}\x{5447}\x{5448}\x{5449}\x{544A}\x{544B}\x{544D}' . + '\x{544E}\x{544F}\x{5450}\x{5451}\x{5452}\x{5453}\x{5454}\x{5455}\x{5456}' . + '\x{5457}\x{5458}\x{5459}\x{545A}\x{545B}\x{545C}\x{545E}\x{545F}\x{5460}' . + '\x{5461}\x{5462}\x{5463}\x{5464}\x{5465}\x{5466}\x{5467}\x{5468}\x{546A}' . + '\x{546B}\x{546C}\x{546D}\x{546E}\x{546F}\x{5470}\x{5471}\x{5472}\x{5473}' . + '\x{5474}\x{5475}\x{5476}\x{5477}\x{5478}\x{5479}\x{547A}\x{547B}\x{547C}' . + '\x{547D}\x{547E}\x{547F}\x{5480}\x{5481}\x{5482}\x{5483}\x{5484}\x{5485}' . + '\x{5486}\x{5487}\x{5488}\x{5489}\x{548B}\x{548C}\x{548D}\x{548E}\x{548F}' . + '\x{5490}\x{5491}\x{5492}\x{5493}\x{5494}\x{5495}\x{5496}\x{5497}\x{5498}' . + '\x{5499}\x{549A}\x{549B}\x{549C}\x{549D}\x{549E}\x{549F}\x{54A0}\x{54A1}' . + '\x{54A2}\x{54A3}\x{54A4}\x{54A5}\x{54A6}\x{54A7}\x{54A8}\x{54A9}\x{54AA}' . + '\x{54AB}\x{54AC}\x{54AD}\x{54AE}\x{54AF}\x{54B0}\x{54B1}\x{54B2}\x{54B3}' . + '\x{54B4}\x{54B6}\x{54B7}\x{54B8}\x{54B9}\x{54BA}\x{54BB}\x{54BC}\x{54BD}' . + '\x{54BE}\x{54BF}\x{54C0}\x{54C1}\x{54C2}\x{54C3}\x{54C4}\x{54C5}\x{54C6}' . + '\x{54C7}\x{54C8}\x{54C9}\x{54CA}\x{54CB}\x{54CC}\x{54CD}\x{54CE}\x{54CF}' . + '\x{54D0}\x{54D1}\x{54D2}\x{54D3}\x{54D4}\x{54D5}\x{54D6}\x{54D7}\x{54D8}' . + '\x{54D9}\x{54DA}\x{54DB}\x{54DC}\x{54DD}\x{54DE}\x{54DF}\x{54E0}\x{54E1}' . + '\x{54E2}\x{54E3}\x{54E4}\x{54E5}\x{54E6}\x{54E7}\x{54E8}\x{54E9}\x{54EA}' . + '\x{54EB}\x{54EC}\x{54ED}\x{54EE}\x{54EF}\x{54F0}\x{54F1}\x{54F2}\x{54F3}' . + '\x{54F4}\x{54F5}\x{54F7}\x{54F8}\x{54F9}\x{54FA}\x{54FB}\x{54FC}\x{54FD}' . + '\x{54FE}\x{54FF}\x{5500}\x{5501}\x{5502}\x{5503}\x{5504}\x{5505}\x{5506}' . + '\x{5507}\x{5508}\x{5509}\x{550A}\x{550B}\x{550C}\x{550D}\x{550E}\x{550F}' . + '\x{5510}\x{5511}\x{5512}\x{5513}\x{5514}\x{5516}\x{5517}\x{551A}\x{551B}' . + '\x{551C}\x{551D}\x{551E}\x{551F}\x{5520}\x{5521}\x{5522}\x{5523}\x{5524}' . + '\x{5525}\x{5526}\x{5527}\x{5528}\x{5529}\x{552A}\x{552B}\x{552C}\x{552D}' . + '\x{552E}\x{552F}\x{5530}\x{5531}\x{5532}\x{5533}\x{5534}\x{5535}\x{5536}' . + '\x{5537}\x{5538}\x{5539}\x{553A}\x{553B}\x{553C}\x{553D}\x{553E}\x{553F}' . + '\x{5540}\x{5541}\x{5542}\x{5543}\x{5544}\x{5545}\x{5546}\x{5548}\x{5549}' . + '\x{554A}\x{554B}\x{554C}\x{554D}\x{554E}\x{554F}\x{5550}\x{5551}\x{5552}' . + '\x{5553}\x{5554}\x{5555}\x{5556}\x{5557}\x{5558}\x{5559}\x{555A}\x{555B}' . + '\x{555C}\x{555D}\x{555E}\x{555F}\x{5561}\x{5562}\x{5563}\x{5564}\x{5565}' . + '\x{5566}\x{5567}\x{5568}\x{5569}\x{556A}\x{556B}\x{556C}\x{556D}\x{556E}' . + '\x{556F}\x{5570}\x{5571}\x{5572}\x{5573}\x{5574}\x{5575}\x{5576}\x{5577}' . + '\x{5578}\x{5579}\x{557B}\x{557C}\x{557D}\x{557E}\x{557F}\x{5580}\x{5581}' . + '\x{5582}\x{5583}\x{5584}\x{5585}\x{5586}\x{5587}\x{5588}\x{5589}\x{558A}' . + '\x{558B}\x{558C}\x{558D}\x{558E}\x{558F}\x{5590}\x{5591}\x{5592}\x{5593}' . + '\x{5594}\x{5595}\x{5596}\x{5597}\x{5598}\x{5599}\x{559A}\x{559B}\x{559C}' . + '\x{559D}\x{559E}\x{559F}\x{55A0}\x{55A1}\x{55A2}\x{55A3}\x{55A4}\x{55A5}' . + '\x{55A6}\x{55A7}\x{55A8}\x{55A9}\x{55AA}\x{55AB}\x{55AC}\x{55AD}\x{55AE}' . + '\x{55AF}\x{55B0}\x{55B1}\x{55B2}\x{55B3}\x{55B4}\x{55B5}\x{55B6}\x{55B7}' . + '\x{55B8}\x{55B9}\x{55BA}\x{55BB}\x{55BC}\x{55BD}\x{55BE}\x{55BF}\x{55C0}' . + '\x{55C1}\x{55C2}\x{55C3}\x{55C4}\x{55C5}\x{55C6}\x{55C7}\x{55C8}\x{55C9}' . + '\x{55CA}\x{55CB}\x{55CC}\x{55CD}\x{55CE}\x{55CF}\x{55D0}\x{55D1}\x{55D2}' . + '\x{55D3}\x{55D4}\x{55D5}\x{55D6}\x{55D7}\x{55D8}\x{55D9}\x{55DA}\x{55DB}' . + '\x{55DC}\x{55DD}\x{55DE}\x{55DF}\x{55E1}\x{55E2}\x{55E3}\x{55E4}\x{55E5}' . + '\x{55E6}\x{55E7}\x{55E8}\x{55E9}\x{55EA}\x{55EB}\x{55EC}\x{55ED}\x{55EE}' . + '\x{55EF}\x{55F0}\x{55F1}\x{55F2}\x{55F3}\x{55F4}\x{55F5}\x{55F6}\x{55F7}' . + '\x{55F9}\x{55FA}\x{55FB}\x{55FC}\x{55FD}\x{55FE}\x{55FF}\x{5600}\x{5601}' . + '\x{5602}\x{5603}\x{5604}\x{5606}\x{5607}\x{5608}\x{5609}\x{560C}\x{560D}' . + '\x{560E}\x{560F}\x{5610}\x{5611}\x{5612}\x{5613}\x{5614}\x{5615}\x{5616}' . + '\x{5617}\x{5618}\x{5619}\x{561A}\x{561B}\x{561C}\x{561D}\x{561E}\x{561F}' . + '\x{5621}\x{5622}\x{5623}\x{5624}\x{5625}\x{5626}\x{5627}\x{5628}\x{5629}' . + '\x{562A}\x{562C}\x{562D}\x{562E}\x{562F}\x{5630}\x{5631}\x{5632}\x{5633}' . + '\x{5634}\x{5635}\x{5636}\x{5638}\x{5639}\x{563A}\x{563B}\x{563D}\x{563E}' . + '\x{563F}\x{5640}\x{5641}\x{5642}\x{5643}\x{5645}\x{5646}\x{5647}\x{5648}' . + '\x{5649}\x{564A}\x{564C}\x{564D}\x{564E}\x{564F}\x{5650}\x{5652}\x{5653}' . + '\x{5654}\x{5655}\x{5657}\x{5658}\x{5659}\x{565A}\x{565B}\x{565C}\x{565D}' . + '\x{565E}\x{5660}\x{5662}\x{5663}\x{5664}\x{5665}\x{5666}\x{5667}\x{5668}' . + '\x{5669}\x{566A}\x{566B}\x{566C}\x{566D}\x{566E}\x{566F}\x{5670}\x{5671}' . + '\x{5672}\x{5673}\x{5674}\x{5676}\x{5677}\x{5678}\x{5679}\x{567A}\x{567B}' . + '\x{567C}\x{567E}\x{567F}\x{5680}\x{5681}\x{5682}\x{5683}\x{5684}\x{5685}' . + '\x{5686}\x{5687}\x{568A}\x{568C}\x{568D}\x{568E}\x{568F}\x{5690}\x{5691}' . + '\x{5692}\x{5693}\x{5694}\x{5695}\x{5697}\x{5698}\x{5699}\x{569A}\x{569B}' . + '\x{569C}\x{569D}\x{569F}\x{56A0}\x{56A1}\x{56A3}\x{56A4}\x{56A5}\x{56A6}' . + '\x{56A7}\x{56A8}\x{56A9}\x{56AA}\x{56AB}\x{56AC}\x{56AD}\x{56AE}\x{56AF}' . + '\x{56B0}\x{56B1}\x{56B2}\x{56B3}\x{56B4}\x{56B5}\x{56B6}\x{56B7}\x{56B8}' . + '\x{56B9}\x{56BB}\x{56BC}\x{56BD}\x{56BE}\x{56BF}\x{56C0}\x{56C1}\x{56C2}' . + '\x{56C3}\x{56C4}\x{56C5}\x{56C6}\x{56C7}\x{56C8}\x{56C9}\x{56CA}\x{56CB}' . + '\x{56CC}\x{56CD}\x{56CE}\x{56D0}\x{56D1}\x{56D2}\x{56D3}\x{56D4}\x{56D5}' . + '\x{56D6}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DC}\x{56DD}\x{56DE}\x{56DF}' . + '\x{56E0}\x{56E1}\x{56E2}\x{56E3}\x{56E4}\x{56E5}\x{56E7}\x{56E8}\x{56E9}' . + '\x{56EA}\x{56EB}\x{56EC}\x{56ED}\x{56EE}\x{56EF}\x{56F0}\x{56F1}\x{56F2}' . + '\x{56F3}\x{56F4}\x{56F5}\x{56F7}\x{56F9}\x{56FA}\x{56FD}\x{56FE}\x{56FF}' . + '\x{5700}\x{5701}\x{5702}\x{5703}\x{5704}\x{5706}\x{5707}\x{5708}\x{5709}' . + '\x{570A}\x{570B}\x{570C}\x{570D}\x{570E}\x{570F}\x{5710}\x{5712}\x{5713}' . + '\x{5714}\x{5715}\x{5716}\x{5718}\x{5719}\x{571A}\x{571B}\x{571C}\x{571D}' . + '\x{571E}\x{571F}\x{5720}\x{5722}\x{5723}\x{5725}\x{5726}\x{5727}\x{5728}' . + '\x{5729}\x{572A}\x{572B}\x{572C}\x{572D}\x{572E}\x{572F}\x{5730}\x{5731}' . + '\x{5732}\x{5733}\x{5734}\x{5735}\x{5736}\x{5737}\x{5738}\x{5739}\x{573A}' . + '\x{573B}\x{573C}\x{573E}\x{573F}\x{5740}\x{5741}\x{5742}\x{5744}\x{5745}' . + '\x{5746}\x{5747}\x{5749}\x{574A}\x{574B}\x{574C}\x{574D}\x{574E}\x{574F}' . + '\x{5750}\x{5751}\x{5752}\x{5753}\x{5754}\x{5757}\x{5759}\x{575A}\x{575B}' . + '\x{575C}\x{575D}\x{575E}\x{575F}\x{5760}\x{5761}\x{5762}\x{5764}\x{5765}' . + '\x{5766}\x{5767}\x{5768}\x{5769}\x{576A}\x{576B}\x{576C}\x{576D}\x{576F}' . + '\x{5770}\x{5771}\x{5772}\x{5773}\x{5774}\x{5775}\x{5776}\x{5777}\x{5779}' . + '\x{577A}\x{577B}\x{577C}\x{577D}\x{577E}\x{577F}\x{5780}\x{5782}\x{5783}' . + '\x{5784}\x{5785}\x{5786}\x{5788}\x{5789}\x{578A}\x{578B}\x{578C}\x{578D}' . + '\x{578E}\x{578F}\x{5790}\x{5791}\x{5792}\x{5793}\x{5794}\x{5795}\x{5797}' . + '\x{5798}\x{5799}\x{579A}\x{579B}\x{579C}\x{579D}\x{579E}\x{579F}\x{57A0}' . + '\x{57A1}\x{57A2}\x{57A3}\x{57A4}\x{57A5}\x{57A6}\x{57A7}\x{57A9}\x{57AA}' . + '\x{57AB}\x{57AC}\x{57AD}\x{57AE}\x{57AF}\x{57B0}\x{57B1}\x{57B2}\x{57B3}' . + '\x{57B4}\x{57B5}\x{57B6}\x{57B7}\x{57B8}\x{57B9}\x{57BA}\x{57BB}\x{57BC}' . + '\x{57BD}\x{57BE}\x{57BF}\x{57C0}\x{57C1}\x{57C2}\x{57C3}\x{57C4}\x{57C5}' . + '\x{57C6}\x{57C7}\x{57C8}\x{57C9}\x{57CB}\x{57CC}\x{57CD}\x{57CE}\x{57CF}' . + '\x{57D0}\x{57D2}\x{57D3}\x{57D4}\x{57D5}\x{57D6}\x{57D8}\x{57D9}\x{57DA}' . + '\x{57DC}\x{57DD}\x{57DF}\x{57E0}\x{57E1}\x{57E2}\x{57E3}\x{57E4}\x{57E5}' . + '\x{57E6}\x{57E7}\x{57E8}\x{57E9}\x{57EA}\x{57EB}\x{57EC}\x{57ED}\x{57EE}' . + '\x{57EF}\x{57F0}\x{57F1}\x{57F2}\x{57F3}\x{57F4}\x{57F5}\x{57F6}\x{57F7}' . + '\x{57F8}\x{57F9}\x{57FA}\x{57FB}\x{57FC}\x{57FD}\x{57FE}\x{57FF}\x{5800}' . + '\x{5801}\x{5802}\x{5803}\x{5804}\x{5805}\x{5806}\x{5807}\x{5808}\x{5809}' . + '\x{580A}\x{580B}\x{580C}\x{580D}\x{580E}\x{580F}\x{5810}\x{5811}\x{5812}' . + '\x{5813}\x{5814}\x{5815}\x{5816}\x{5819}\x{581A}\x{581B}\x{581C}\x{581D}' . + '\x{581E}\x{581F}\x{5820}\x{5821}\x{5822}\x{5823}\x{5824}\x{5825}\x{5826}' . + '\x{5827}\x{5828}\x{5829}\x{582A}\x{582B}\x{582C}\x{582D}\x{582E}\x{582F}' . + '\x{5830}\x{5831}\x{5832}\x{5833}\x{5834}\x{5835}\x{5836}\x{5837}\x{5838}' . + '\x{5839}\x{583A}\x{583B}\x{583C}\x{583D}\x{583E}\x{583F}\x{5840}\x{5842}' . + '\x{5843}\x{5844}\x{5845}\x{5846}\x{5847}\x{5848}\x{5849}\x{584A}\x{584B}' . + '\x{584C}\x{584D}\x{584E}\x{584F}\x{5851}\x{5852}\x{5853}\x{5854}\x{5855}' . + '\x{5857}\x{5858}\x{5859}\x{585A}\x{585B}\x{585C}\x{585D}\x{585E}\x{585F}' . + '\x{5861}\x{5862}\x{5863}\x{5864}\x{5865}\x{5868}\x{5869}\x{586A}\x{586B}' . + '\x{586C}\x{586D}\x{586E}\x{586F}\x{5870}\x{5871}\x{5872}\x{5873}\x{5874}' . + '\x{5875}\x{5876}\x{5878}\x{5879}\x{587A}\x{587B}\x{587C}\x{587D}\x{587E}' . + '\x{587F}\x{5880}\x{5881}\x{5882}\x{5883}\x{5884}\x{5885}\x{5886}\x{5887}' . + '\x{5888}\x{5889}\x{588A}\x{588B}\x{588C}\x{588D}\x{588E}\x{588F}\x{5890}' . + '\x{5891}\x{5892}\x{5893}\x{5894}\x{5896}\x{5897}\x{5898}\x{5899}\x{589A}' . + '\x{589B}\x{589C}\x{589D}\x{589E}\x{589F}\x{58A0}\x{58A1}\x{58A2}\x{58A3}' . + '\x{58A4}\x{58A5}\x{58A6}\x{58A7}\x{58A8}\x{58A9}\x{58AB}\x{58AC}\x{58AD}' . + '\x{58AE}\x{58AF}\x{58B0}\x{58B1}\x{58B2}\x{58B3}\x{58B4}\x{58B7}\x{58B8}' . + '\x{58B9}\x{58BA}\x{58BB}\x{58BC}\x{58BD}\x{58BE}\x{58BF}\x{58C1}\x{58C2}' . + '\x{58C5}\x{58C6}\x{58C7}\x{58C8}\x{58C9}\x{58CA}\x{58CB}\x{58CE}\x{58CF}' . + '\x{58D1}\x{58D2}\x{58D3}\x{58D4}\x{58D5}\x{58D6}\x{58D7}\x{58D8}\x{58D9}' . + '\x{58DA}\x{58DB}\x{58DD}\x{58DE}\x{58DF}\x{58E0}\x{58E2}\x{58E3}\x{58E4}' . + '\x{58E5}\x{58E7}\x{58E8}\x{58E9}\x{58EA}\x{58EB}\x{58EC}\x{58ED}\x{58EE}' . + '\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F3}\x{58F4}\x{58F6}\x{58F7}\x{58F8}' . + '\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{58FE}\x{58FF}\x{5900}\x{5902}' . + '\x{5903}\x{5904}\x{5906}\x{5907}\x{5909}\x{590A}\x{590B}\x{590C}\x{590D}' . + '\x{590E}\x{590F}\x{5910}\x{5912}\x{5914}\x{5915}\x{5916}\x{5917}\x{5918}' . + '\x{5919}\x{591A}\x{591B}\x{591C}\x{591D}\x{591E}\x{591F}\x{5920}\x{5921}' . + '\x{5922}\x{5924}\x{5925}\x{5926}\x{5927}\x{5928}\x{5929}\x{592A}\x{592B}' . + '\x{592C}\x{592D}\x{592E}\x{592F}\x{5930}\x{5931}\x{5932}\x{5934}\x{5935}' . + '\x{5937}\x{5938}\x{5939}\x{593A}\x{593B}\x{593C}\x{593D}\x{593E}\x{593F}' . + '\x{5940}\x{5941}\x{5942}\x{5943}\x{5944}\x{5945}\x{5946}\x{5947}\x{5948}' . + '\x{5949}\x{594A}\x{594B}\x{594C}\x{594D}\x{594E}\x{594F}\x{5950}\x{5951}' . + '\x{5952}\x{5953}\x{5954}\x{5955}\x{5956}\x{5957}\x{5958}\x{595A}\x{595C}' . + '\x{595D}\x{595E}\x{595F}\x{5960}\x{5961}\x{5962}\x{5963}\x{5964}\x{5965}' . + '\x{5966}\x{5967}\x{5968}\x{5969}\x{596A}\x{596B}\x{596C}\x{596D}\x{596E}' . + '\x{596F}\x{5970}\x{5971}\x{5972}\x{5973}\x{5974}\x{5975}\x{5976}\x{5977}' . + '\x{5978}\x{5979}\x{597A}\x{597B}\x{597C}\x{597D}\x{597E}\x{597F}\x{5980}' . + '\x{5981}\x{5982}\x{5983}\x{5984}\x{5985}\x{5986}\x{5987}\x{5988}\x{5989}' . + '\x{598A}\x{598B}\x{598C}\x{598D}\x{598E}\x{598F}\x{5990}\x{5991}\x{5992}' . + '\x{5993}\x{5994}\x{5995}\x{5996}\x{5997}\x{5998}\x{5999}\x{599A}\x{599C}' . + '\x{599D}\x{599E}\x{599F}\x{59A0}\x{59A1}\x{59A2}\x{59A3}\x{59A4}\x{59A5}' . + '\x{59A6}\x{59A7}\x{59A8}\x{59A9}\x{59AA}\x{59AB}\x{59AC}\x{59AD}\x{59AE}' . + '\x{59AF}\x{59B0}\x{59B1}\x{59B2}\x{59B3}\x{59B4}\x{59B5}\x{59B6}\x{59B8}' . + '\x{59B9}\x{59BA}\x{59BB}\x{59BC}\x{59BD}\x{59BE}\x{59BF}\x{59C0}\x{59C1}' . + '\x{59C2}\x{59C3}\x{59C4}\x{59C5}\x{59C6}\x{59C7}\x{59C8}\x{59C9}\x{59CA}' . + '\x{59CB}\x{59CC}\x{59CD}\x{59CE}\x{59CF}\x{59D0}\x{59D1}\x{59D2}\x{59D3}' . + '\x{59D4}\x{59D5}\x{59D6}\x{59D7}\x{59D8}\x{59D9}\x{59DA}\x{59DB}\x{59DC}' . + '\x{59DD}\x{59DE}\x{59DF}\x{59E0}\x{59E1}\x{59E2}\x{59E3}\x{59E4}\x{59E5}' . + '\x{59E6}\x{59E8}\x{59E9}\x{59EA}\x{59EB}\x{59EC}\x{59ED}\x{59EE}\x{59EF}' . + '\x{59F0}\x{59F1}\x{59F2}\x{59F3}\x{59F4}\x{59F5}\x{59F6}\x{59F7}\x{59F8}' . + '\x{59F9}\x{59FA}\x{59FB}\x{59FC}\x{59FD}\x{59FE}\x{59FF}\x{5A00}\x{5A01}' . + '\x{5A02}\x{5A03}\x{5A04}\x{5A05}\x{5A06}\x{5A07}\x{5A08}\x{5A09}\x{5A0A}' . + '\x{5A0B}\x{5A0C}\x{5A0D}\x{5A0E}\x{5A0F}\x{5A10}\x{5A11}\x{5A12}\x{5A13}' . + '\x{5A14}\x{5A15}\x{5A16}\x{5A17}\x{5A18}\x{5A19}\x{5A1A}\x{5A1B}\x{5A1C}' . + '\x{5A1D}\x{5A1E}\x{5A1F}\x{5A20}\x{5A21}\x{5A22}\x{5A23}\x{5A25}\x{5A27}' . + '\x{5A28}\x{5A29}\x{5A2A}\x{5A2B}\x{5A2D}\x{5A2E}\x{5A2F}\x{5A31}\x{5A32}' . + '\x{5A33}\x{5A34}\x{5A35}\x{5A36}\x{5A37}\x{5A38}\x{5A39}\x{5A3A}\x{5A3B}' . + '\x{5A3C}\x{5A3D}\x{5A3E}\x{5A3F}\x{5A40}\x{5A41}\x{5A42}\x{5A43}\x{5A44}' . + '\x{5A45}\x{5A46}\x{5A47}\x{5A48}\x{5A49}\x{5A4A}\x{5A4B}\x{5A4C}\x{5A4D}' . + '\x{5A4E}\x{5A4F}\x{5A50}\x{5A51}\x{5A52}\x{5A53}\x{5A55}\x{5A56}\x{5A57}' . + '\x{5A58}\x{5A5A}\x{5A5B}\x{5A5C}\x{5A5D}\x{5A5E}\x{5A5F}\x{5A60}\x{5A61}' . + '\x{5A62}\x{5A63}\x{5A64}\x{5A65}\x{5A66}\x{5A67}\x{5A68}\x{5A69}\x{5A6A}' . + '\x{5A6B}\x{5A6C}\x{5A6D}\x{5A6E}\x{5A70}\x{5A72}\x{5A73}\x{5A74}\x{5A75}' . + '\x{5A76}\x{5A77}\x{5A78}\x{5A79}\x{5A7A}\x{5A7B}\x{5A7C}\x{5A7D}\x{5A7E}' . + '\x{5A7F}\x{5A80}\x{5A81}\x{5A82}\x{5A83}\x{5A84}\x{5A85}\x{5A86}\x{5A88}' . + '\x{5A89}\x{5A8A}\x{5A8B}\x{5A8C}\x{5A8E}\x{5A8F}\x{5A90}\x{5A91}\x{5A92}' . + '\x{5A93}\x{5A94}\x{5A95}\x{5A96}\x{5A97}\x{5A98}\x{5A99}\x{5A9A}\x{5A9B}' . + '\x{5A9C}\x{5A9D}\x{5A9E}\x{5A9F}\x{5AA0}\x{5AA1}\x{5AA2}\x{5AA3}\x{5AA4}' . + '\x{5AA5}\x{5AA6}\x{5AA7}\x{5AA8}\x{5AA9}\x{5AAA}\x{5AAC}\x{5AAD}\x{5AAE}' . + '\x{5AAF}\x{5AB0}\x{5AB1}\x{5AB2}\x{5AB3}\x{5AB4}\x{5AB5}\x{5AB6}\x{5AB7}' . + '\x{5AB8}\x{5AB9}\x{5ABA}\x{5ABB}\x{5ABC}\x{5ABD}\x{5ABE}\x{5ABF}\x{5AC0}' . + '\x{5AC1}\x{5AC2}\x{5AC3}\x{5AC4}\x{5AC5}\x{5AC6}\x{5AC7}\x{5AC8}\x{5AC9}' . + '\x{5ACA}\x{5ACB}\x{5ACC}\x{5ACD}\x{5ACE}\x{5ACF}\x{5AD1}\x{5AD2}\x{5AD4}' . + '\x{5AD5}\x{5AD6}\x{5AD7}\x{5AD8}\x{5AD9}\x{5ADA}\x{5ADB}\x{5ADC}\x{5ADD}' . + '\x{5ADE}\x{5ADF}\x{5AE0}\x{5AE1}\x{5AE2}\x{5AE3}\x{5AE4}\x{5AE5}\x{5AE6}' . + '\x{5AE7}\x{5AE8}\x{5AE9}\x{5AEA}\x{5AEB}\x{5AEC}\x{5AED}\x{5AEE}\x{5AF1}' . + '\x{5AF2}\x{5AF3}\x{5AF4}\x{5AF5}\x{5AF6}\x{5AF7}\x{5AF8}\x{5AF9}\x{5AFA}' . + '\x{5AFB}\x{5AFC}\x{5AFD}\x{5AFE}\x{5AFF}\x{5B00}\x{5B01}\x{5B02}\x{5B03}' . + '\x{5B04}\x{5B05}\x{5B06}\x{5B07}\x{5B08}\x{5B09}\x{5B0B}\x{5B0C}\x{5B0E}' . + '\x{5B0F}\x{5B10}\x{5B11}\x{5B12}\x{5B13}\x{5B14}\x{5B15}\x{5B16}\x{5B17}' . + '\x{5B18}\x{5B19}\x{5B1A}\x{5B1B}\x{5B1C}\x{5B1D}\x{5B1E}\x{5B1F}\x{5B20}' . + '\x{5B21}\x{5B22}\x{5B23}\x{5B24}\x{5B25}\x{5B26}\x{5B27}\x{5B28}\x{5B29}' . + '\x{5B2A}\x{5B2B}\x{5B2C}\x{5B2D}\x{5B2E}\x{5B2F}\x{5B30}\x{5B31}\x{5B32}' . + '\x{5B33}\x{5B34}\x{5B35}\x{5B36}\x{5B37}\x{5B38}\x{5B3A}\x{5B3B}\x{5B3C}' . + '\x{5B3D}\x{5B3E}\x{5B3F}\x{5B40}\x{5B41}\x{5B42}\x{5B43}\x{5B44}\x{5B45}' . + '\x{5B47}\x{5B48}\x{5B49}\x{5B4A}\x{5B4B}\x{5B4C}\x{5B4D}\x{5B4E}\x{5B50}' . + '\x{5B51}\x{5B53}\x{5B54}\x{5B55}\x{5B56}\x{5B57}\x{5B58}\x{5B59}\x{5B5A}' . + '\x{5B5B}\x{5B5C}\x{5B5D}\x{5B5E}\x{5B5F}\x{5B62}\x{5B63}\x{5B64}\x{5B65}' . + '\x{5B66}\x{5B67}\x{5B68}\x{5B69}\x{5B6A}\x{5B6B}\x{5B6C}\x{5B6D}\x{5B6E}' . + '\x{5B70}\x{5B71}\x{5B72}\x{5B73}\x{5B74}\x{5B75}\x{5B76}\x{5B77}\x{5B78}' . + '\x{5B7A}\x{5B7B}\x{5B7C}\x{5B7D}\x{5B7F}\x{5B80}\x{5B81}\x{5B82}\x{5B83}' . + '\x{5B84}\x{5B85}\x{5B87}\x{5B88}\x{5B89}\x{5B8A}\x{5B8B}\x{5B8C}\x{5B8D}' . + '\x{5B8E}\x{5B8F}\x{5B91}\x{5B92}\x{5B93}\x{5B94}\x{5B95}\x{5B96}\x{5B97}' . + '\x{5B98}\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9E}\x{5B9F}\x{5BA0}' . + '\x{5BA1}\x{5BA2}\x{5BA3}\x{5BA4}\x{5BA5}\x{5BA6}\x{5BA7}\x{5BA8}\x{5BAA}' . + '\x{5BAB}\x{5BAC}\x{5BAD}\x{5BAE}\x{5BAF}\x{5BB0}\x{5BB1}\x{5BB3}\x{5BB4}' . + '\x{5BB5}\x{5BB6}\x{5BB8}\x{5BB9}\x{5BBA}\x{5BBB}\x{5BBD}\x{5BBE}\x{5BBF}' . + '\x{5BC0}\x{5BC1}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BCA}' . + '\x{5BCB}\x{5BCC}\x{5BCD}\x{5BCE}\x{5BCF}\x{5BD0}\x{5BD1}\x{5BD2}\x{5BD3}' . + '\x{5BD4}\x{5BD5}\x{5BD6}\x{5BD8}\x{5BD9}\x{5BDB}\x{5BDC}\x{5BDD}\x{5BDE}' . + '\x{5BDF}\x{5BE0}\x{5BE1}\x{5BE2}\x{5BE3}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}' . + '\x{5BE8}\x{5BE9}\x{5BEA}\x{5BEB}\x{5BEC}\x{5BED}\x{5BEE}\x{5BEF}\x{5BF0}' . + '\x{5BF1}\x{5BF2}\x{5BF3}\x{5BF4}\x{5BF5}\x{5BF6}\x{5BF7}\x{5BF8}\x{5BF9}' . + '\x{5BFA}\x{5BFB}\x{5BFC}\x{5BFD}\x{5BFF}\x{5C01}\x{5C03}\x{5C04}\x{5C05}' . + '\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}\x{5C0B}\x{5C0C}\x{5C0D}\x{5C0E}' . + '\x{5C0F}\x{5C10}\x{5C11}\x{5C12}\x{5C13}\x{5C14}\x{5C15}\x{5C16}\x{5C17}' . + '\x{5C18}\x{5C19}\x{5C1A}\x{5C1C}\x{5C1D}\x{5C1E}\x{5C1F}\x{5C20}\x{5C21}' . + '\x{5C22}\x{5C24}\x{5C25}\x{5C27}\x{5C28}\x{5C2A}\x{5C2B}\x{5C2C}\x{5C2D}' . + '\x{5C2E}\x{5C2F}\x{5C30}\x{5C31}\x{5C32}\x{5C33}\x{5C34}\x{5C35}\x{5C37}' . + '\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}' . + '\x{5C41}\x{5C42}\x{5C43}\x{5C44}\x{5C45}\x{5C46}\x{5C47}\x{5C48}\x{5C49}' . + '\x{5C4A}\x{5C4B}\x{5C4C}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C52}' . + '\x{5C53}\x{5C54}\x{5C55}\x{5C56}\x{5C57}\x{5C58}\x{5C59}\x{5C5B}\x{5C5C}' . + '\x{5C5D}\x{5C5E}\x{5C5F}\x{5C60}\x{5C61}\x{5C62}\x{5C63}\x{5C64}\x{5C65}' . + '\x{5C66}\x{5C67}\x{5C68}\x{5C69}\x{5C6A}\x{5C6B}\x{5C6C}\x{5C6D}\x{5C6E}' . + '\x{5C6F}\x{5C70}\x{5C71}\x{5C72}\x{5C73}\x{5C74}\x{5C75}\x{5C76}\x{5C77}' . + '\x{5C78}\x{5C79}\x{5C7A}\x{5C7B}\x{5C7C}\x{5C7D}\x{5C7E}\x{5C7F}\x{5C80}' . + '\x{5C81}\x{5C82}\x{5C83}\x{5C84}\x{5C86}\x{5C87}\x{5C88}\x{5C89}\x{5C8A}' . + '\x{5C8B}\x{5C8C}\x{5C8D}\x{5C8E}\x{5C8F}\x{5C90}\x{5C91}\x{5C92}\x{5C93}' . + '\x{5C94}\x{5C95}\x{5C96}\x{5C97}\x{5C98}\x{5C99}\x{5C9A}\x{5C9B}\x{5C9C}' . + '\x{5C9D}\x{5C9E}\x{5C9F}\x{5CA0}\x{5CA1}\x{5CA2}\x{5CA3}\x{5CA4}\x{5CA5}' . + '\x{5CA6}\x{5CA7}\x{5CA8}\x{5CA9}\x{5CAA}\x{5CAB}\x{5CAC}\x{5CAD}\x{5CAE}' . + '\x{5CAF}\x{5CB0}\x{5CB1}\x{5CB2}\x{5CB3}\x{5CB5}\x{5CB6}\x{5CB7}\x{5CB8}' . + '\x{5CBA}\x{5CBB}\x{5CBC}\x{5CBD}\x{5CBE}\x{5CBF}\x{5CC1}\x{5CC2}\x{5CC3}' . + '\x{5CC4}\x{5CC5}\x{5CC6}\x{5CC7}\x{5CC8}\x{5CC9}\x{5CCA}\x{5CCB}\x{5CCC}' . + '\x{5CCD}\x{5CCE}\x{5CCF}\x{5CD0}\x{5CD1}\x{5CD2}\x{5CD3}\x{5CD4}\x{5CD6}' . + '\x{5CD7}\x{5CD8}\x{5CD9}\x{5CDA}\x{5CDB}\x{5CDC}\x{5CDE}\x{5CDF}\x{5CE0}' . + '\x{5CE1}\x{5CE2}\x{5CE3}\x{5CE4}\x{5CE5}\x{5CE6}\x{5CE7}\x{5CE8}\x{5CE9}' . + '\x{5CEA}\x{5CEB}\x{5CEC}\x{5CED}\x{5CEE}\x{5CEF}\x{5CF0}\x{5CF1}\x{5CF2}' . + '\x{5CF3}\x{5CF4}\x{5CF6}\x{5CF7}\x{5CF8}\x{5CF9}\x{5CFA}\x{5CFB}\x{5CFC}' . + '\x{5CFD}\x{5CFE}\x{5CFF}\x{5D00}\x{5D01}\x{5D02}\x{5D03}\x{5D04}\x{5D05}' . + '\x{5D06}\x{5D07}\x{5D08}\x{5D09}\x{5D0A}\x{5D0B}\x{5D0C}\x{5D0D}\x{5D0E}' . + '\x{5D0F}\x{5D10}\x{5D11}\x{5D12}\x{5D13}\x{5D14}\x{5D15}\x{5D16}\x{5D17}' . + '\x{5D18}\x{5D19}\x{5D1A}\x{5D1B}\x{5D1C}\x{5D1D}\x{5D1E}\x{5D1F}\x{5D20}' . + '\x{5D21}\x{5D22}\x{5D23}\x{5D24}\x{5D25}\x{5D26}\x{5D27}\x{5D28}\x{5D29}' . + '\x{5D2A}\x{5D2C}\x{5D2D}\x{5D2E}\x{5D30}\x{5D31}\x{5D32}\x{5D33}\x{5D34}' . + '\x{5D35}\x{5D36}\x{5D37}\x{5D38}\x{5D39}\x{5D3A}\x{5D3C}\x{5D3D}\x{5D3E}' . + '\x{5D3F}\x{5D40}\x{5D41}\x{5D42}\x{5D43}\x{5D44}\x{5D45}\x{5D46}\x{5D47}' . + '\x{5D48}\x{5D49}\x{5D4A}\x{5D4B}\x{5D4C}\x{5D4D}\x{5D4E}\x{5D4F}\x{5D50}' . + '\x{5D51}\x{5D52}\x{5D54}\x{5D55}\x{5D56}\x{5D58}\x{5D59}\x{5D5A}\x{5D5B}' . + '\x{5D5D}\x{5D5E}\x{5D5F}\x{5D61}\x{5D62}\x{5D63}\x{5D64}\x{5D65}\x{5D66}' . + '\x{5D67}\x{5D68}\x{5D69}\x{5D6A}\x{5D6B}\x{5D6C}\x{5D6D}\x{5D6E}\x{5D6F}' . + '\x{5D70}\x{5D71}\x{5D72}\x{5D73}\x{5D74}\x{5D75}\x{5D76}\x{5D77}\x{5D78}' . + '\x{5D79}\x{5D7A}\x{5D7B}\x{5D7C}\x{5D7D}\x{5D7E}\x{5D7F}\x{5D80}\x{5D81}' . + '\x{5D82}\x{5D84}\x{5D85}\x{5D86}\x{5D87}\x{5D88}\x{5D89}\x{5D8A}\x{5D8B}' . + '\x{5D8C}\x{5D8D}\x{5D8E}\x{5D8F}\x{5D90}\x{5D91}\x{5D92}\x{5D93}\x{5D94}' . + '\x{5D95}\x{5D97}\x{5D98}\x{5D99}\x{5D9A}\x{5D9B}\x{5D9C}\x{5D9D}\x{5D9E}' . + '\x{5D9F}\x{5DA0}\x{5DA1}\x{5DA2}\x{5DA5}\x{5DA6}\x{5DA7}\x{5DA8}\x{5DA9}' . + '\x{5DAA}\x{5DAC}\x{5DAD}\x{5DAE}\x{5DAF}\x{5DB0}\x{5DB1}\x{5DB2}\x{5DB4}' . + '\x{5DB5}\x{5DB6}\x{5DB7}\x{5DB8}\x{5DBA}\x{5DBB}\x{5DBC}\x{5DBD}\x{5DBE}' . + '\x{5DBF}\x{5DC0}\x{5DC1}\x{5DC2}\x{5DC3}\x{5DC5}\x{5DC6}\x{5DC7}\x{5DC8}' . + '\x{5DC9}\x{5DCA}\x{5DCB}\x{5DCC}\x{5DCD}\x{5DCE}\x{5DCF}\x{5DD0}\x{5DD1}' . + '\x{5DD2}\x{5DD3}\x{5DD4}\x{5DD5}\x{5DD6}\x{5DD8}\x{5DD9}\x{5DDB}\x{5DDD}' . + '\x{5DDE}\x{5DDF}\x{5DE0}\x{5DE1}\x{5DE2}\x{5DE3}\x{5DE4}\x{5DE5}\x{5DE6}' . + '\x{5DE7}\x{5DE8}\x{5DE9}\x{5DEA}\x{5DEB}\x{5DEC}\x{5DED}\x{5DEE}\x{5DEF}' . + '\x{5DF0}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DF8}\x{5DF9}' . + '\x{5DFA}\x{5DFB}\x{5DFC}\x{5DFD}\x{5DFE}\x{5DFF}\x{5E00}\x{5E01}\x{5E02}' . + '\x{5E03}\x{5E04}\x{5E05}\x{5E06}\x{5E07}\x{5E08}\x{5E09}\x{5E0A}\x{5E0B}' . + '\x{5E0C}\x{5E0D}\x{5E0E}\x{5E0F}\x{5E10}\x{5E11}\x{5E13}\x{5E14}\x{5E15}' . + '\x{5E16}\x{5E17}\x{5E18}\x{5E19}\x{5E1A}\x{5E1B}\x{5E1C}\x{5E1D}\x{5E1E}' . + '\x{5E1F}\x{5E20}\x{5E21}\x{5E22}\x{5E23}\x{5E24}\x{5E25}\x{5E26}\x{5E27}' . + '\x{5E28}\x{5E29}\x{5E2A}\x{5E2B}\x{5E2C}\x{5E2D}\x{5E2E}\x{5E2F}\x{5E30}' . + '\x{5E31}\x{5E32}\x{5E33}\x{5E34}\x{5E35}\x{5E36}\x{5E37}\x{5E38}\x{5E39}' . + '\x{5E3A}\x{5E3B}\x{5E3C}\x{5E3D}\x{5E3E}\x{5E40}\x{5E41}\x{5E42}\x{5E43}' . + '\x{5E44}\x{5E45}\x{5E46}\x{5E47}\x{5E49}\x{5E4A}\x{5E4B}\x{5E4C}\x{5E4D}' . + '\x{5E4E}\x{5E4F}\x{5E50}\x{5E52}\x{5E53}\x{5E54}\x{5E55}\x{5E56}\x{5E57}' . + '\x{5E58}\x{5E59}\x{5E5A}\x{5E5B}\x{5E5C}\x{5E5D}\x{5E5E}\x{5E5F}\x{5E60}' . + '\x{5E61}\x{5E62}\x{5E63}\x{5E64}\x{5E65}\x{5E66}\x{5E67}\x{5E68}\x{5E69}' . + '\x{5E6A}\x{5E6B}\x{5E6C}\x{5E6D}\x{5E6E}\x{5E6F}\x{5E70}\x{5E71}\x{5E72}' . + '\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E77}\x{5E78}\x{5E79}\x{5E7A}\x{5E7B}' . + '\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E80}\x{5E81}\x{5E82}\x{5E83}\x{5E84}' . + '\x{5E85}\x{5E86}\x{5E87}\x{5E88}\x{5E89}\x{5E8A}\x{5E8B}\x{5E8C}\x{5E8D}' . + '\x{5E8E}\x{5E8F}\x{5E90}\x{5E91}\x{5E93}\x{5E94}\x{5E95}\x{5E96}\x{5E97}' . + '\x{5E98}\x{5E99}\x{5E9A}\x{5E9B}\x{5E9C}\x{5E9D}\x{5E9E}\x{5E9F}\x{5EA0}' . + '\x{5EA1}\x{5EA2}\x{5EA3}\x{5EA4}\x{5EA5}\x{5EA6}\x{5EA7}\x{5EA8}\x{5EA9}' . + '\x{5EAA}\x{5EAB}\x{5EAC}\x{5EAD}\x{5EAE}\x{5EAF}\x{5EB0}\x{5EB1}\x{5EB2}' . + '\x{5EB3}\x{5EB4}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EB9}\x{5EBB}\x{5EBC}' . + '\x{5EBD}\x{5EBE}\x{5EBF}\x{5EC1}\x{5EC2}\x{5EC3}\x{5EC4}\x{5EC5}\x{5EC6}' . + '\x{5EC7}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECB}\x{5ECC}\x{5ECD}\x{5ECE}\x{5ECF}' . + '\x{5ED0}\x{5ED1}\x{5ED2}\x{5ED3}\x{5ED4}\x{5ED5}\x{5ED6}\x{5ED7}\x{5ED8}' . + '\x{5ED9}\x{5EDA}\x{5EDB}\x{5EDC}\x{5EDD}\x{5EDE}\x{5EDF}\x{5EE0}\x{5EE1}' . + '\x{5EE2}\x{5EE3}\x{5EE4}\x{5EE5}\x{5EE6}\x{5EE7}\x{5EE8}\x{5EE9}\x{5EEA}' . + '\x{5EEC}\x{5EED}\x{5EEE}\x{5EEF}\x{5EF0}\x{5EF1}\x{5EF2}\x{5EF3}\x{5EF4}' . + '\x{5EF5}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}\x{5EFB}\x{5EFC}\x{5EFD}\x{5EFE}' . + '\x{5EFF}\x{5F00}\x{5F01}\x{5F02}\x{5F03}\x{5F04}\x{5F05}\x{5F06}\x{5F07}' . + '\x{5F08}\x{5F0A}\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F11}\x{5F12}\x{5F13}' . + '\x{5F14}\x{5F15}\x{5F16}\x{5F17}\x{5F18}\x{5F19}\x{5F1A}\x{5F1B}\x{5F1C}' . + '\x{5F1D}\x{5F1E}\x{5F1F}\x{5F20}\x{5F21}\x{5F22}\x{5F23}\x{5F24}\x{5F25}' . + '\x{5F26}\x{5F27}\x{5F28}\x{5F29}\x{5F2A}\x{5F2B}\x{5F2C}\x{5F2D}\x{5F2E}' . + '\x{5F2F}\x{5F30}\x{5F31}\x{5F32}\x{5F33}\x{5F34}\x{5F35}\x{5F36}\x{5F37}' . + '\x{5F38}\x{5F39}\x{5F3A}\x{5F3C}\x{5F3E}\x{5F3F}\x{5F40}\x{5F41}\x{5F42}' . + '\x{5F43}\x{5F44}\x{5F45}\x{5F46}\x{5F47}\x{5F48}\x{5F49}\x{5F4A}\x{5F4B}' . + '\x{5F4C}\x{5F4D}\x{5F4E}\x{5F4F}\x{5F50}\x{5F51}\x{5F52}\x{5F53}\x{5F54}' . + '\x{5F55}\x{5F56}\x{5F57}\x{5F58}\x{5F59}\x{5F5A}\x{5F5B}\x{5F5C}\x{5F5D}' . + '\x{5F5E}\x{5F5F}\x{5F60}\x{5F61}\x{5F62}\x{5F63}\x{5F64}\x{5F65}\x{5F66}' . + '\x{5F67}\x{5F68}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}\x{5F6D}\x{5F6E}\x{5F6F}' . + '\x{5F70}\x{5F71}\x{5F72}\x{5F73}\x{5F74}\x{5F75}\x{5F76}\x{5F77}\x{5F78}' . + '\x{5F79}\x{5F7A}\x{5F7B}\x{5F7C}\x{5F7D}\x{5F7E}\x{5F7F}\x{5F80}\x{5F81}' . + '\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F86}\x{5F87}\x{5F88}\x{5F89}\x{5F8A}' . + '\x{5F8B}\x{5F8C}\x{5F8D}\x{5F8E}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F94}' . + '\x{5F95}\x{5F96}\x{5F97}\x{5F98}\x{5F99}\x{5F9B}\x{5F9C}\x{5F9D}\x{5F9E}' . + '\x{5F9F}\x{5FA0}\x{5FA1}\x{5FA2}\x{5FA5}\x{5FA6}\x{5FA7}\x{5FA8}\x{5FA9}' . + '\x{5FAA}\x{5FAB}\x{5FAC}\x{5FAD}\x{5FAE}\x{5FAF}\x{5FB1}\x{5FB2}\x{5FB3}' . + '\x{5FB4}\x{5FB5}\x{5FB6}\x{5FB7}\x{5FB8}\x{5FB9}\x{5FBA}\x{5FBB}\x{5FBC}' . + '\x{5FBD}\x{5FBE}\x{5FBF}\x{5FC0}\x{5FC1}\x{5FC3}\x{5FC4}\x{5FC5}\x{5FC6}' . + '\x{5FC7}\x{5FC8}\x{5FC9}\x{5FCA}\x{5FCB}\x{5FCC}\x{5FCD}\x{5FCF}\x{5FD0}' . + '\x{5FD1}\x{5FD2}\x{5FD3}\x{5FD4}\x{5FD5}\x{5FD6}\x{5FD7}\x{5FD8}\x{5FD9}' . + '\x{5FDA}\x{5FDC}\x{5FDD}\x{5FDE}\x{5FE0}\x{5FE1}\x{5FE3}\x{5FE4}\x{5FE5}' . + '\x{5FE6}\x{5FE7}\x{5FE8}\x{5FE9}\x{5FEA}\x{5FEB}\x{5FED}\x{5FEE}\x{5FEF}' . + '\x{5FF0}\x{5FF1}\x{5FF2}\x{5FF3}\x{5FF4}\x{5FF5}\x{5FF6}\x{5FF7}\x{5FF8}' . + '\x{5FF9}\x{5FFA}\x{5FFB}\x{5FFD}\x{5FFE}\x{5FFF}\x{6000}\x{6001}\x{6002}' . + '\x{6003}\x{6004}\x{6005}\x{6006}\x{6007}\x{6008}\x{6009}\x{600A}\x{600B}' . + '\x{600C}\x{600D}\x{600E}\x{600F}\x{6010}\x{6011}\x{6012}\x{6013}\x{6014}' . + '\x{6015}\x{6016}\x{6017}\x{6018}\x{6019}\x{601A}\x{601B}\x{601C}\x{601D}' . + '\x{601E}\x{601F}\x{6020}\x{6021}\x{6022}\x{6024}\x{6025}\x{6026}\x{6027}' . + '\x{6028}\x{6029}\x{602A}\x{602B}\x{602C}\x{602D}\x{602E}\x{602F}\x{6030}' . + '\x{6031}\x{6032}\x{6033}\x{6034}\x{6035}\x{6036}\x{6037}\x{6038}\x{6039}' . + '\x{603A}\x{603B}\x{603C}\x{603D}\x{603E}\x{603F}\x{6040}\x{6041}\x{6042}' . + '\x{6043}\x{6044}\x{6045}\x{6046}\x{6047}\x{6048}\x{6049}\x{604A}\x{604B}' . + '\x{604C}\x{604D}\x{604E}\x{604F}\x{6050}\x{6051}\x{6052}\x{6053}\x{6054}' . + '\x{6055}\x{6057}\x{6058}\x{6059}\x{605A}\x{605B}\x{605C}\x{605D}\x{605E}' . + '\x{605F}\x{6062}\x{6063}\x{6064}\x{6065}\x{6066}\x{6067}\x{6068}\x{6069}' . + '\x{606A}\x{606B}\x{606C}\x{606D}\x{606E}\x{606F}\x{6070}\x{6072}\x{6073}' . + '\x{6075}\x{6076}\x{6077}\x{6078}\x{6079}\x{607A}\x{607B}\x{607C}\x{607D}' . + '\x{607E}\x{607F}\x{6080}\x{6081}\x{6082}\x{6083}\x{6084}\x{6085}\x{6086}' . + '\x{6087}\x{6088}\x{6089}\x{608A}\x{608B}\x{608C}\x{608D}\x{608E}\x{608F}' . + '\x{6090}\x{6092}\x{6094}\x{6095}\x{6096}\x{6097}\x{6098}\x{6099}\x{609A}' . + '\x{609B}\x{609C}\x{609D}\x{609E}\x{609F}\x{60A0}\x{60A1}\x{60A2}\x{60A3}' . + '\x{60A4}\x{60A6}\x{60A7}\x{60A8}\x{60AA}\x{60AB}\x{60AC}\x{60AD}\x{60AE}' . + '\x{60AF}\x{60B0}\x{60B1}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B7}' . + '\x{60B8}\x{60B9}\x{60BA}\x{60BB}\x{60BC}\x{60BD}\x{60BE}\x{60BF}\x{60C0}' . + '\x{60C1}\x{60C2}\x{60C3}\x{60C4}\x{60C5}\x{60C6}\x{60C7}\x{60C8}\x{60C9}' . + '\x{60CA}\x{60CB}\x{60CC}\x{60CD}\x{60CE}\x{60CF}\x{60D0}\x{60D1}\x{60D3}' . + '\x{60D4}\x{60D5}\x{60D7}\x{60D8}\x{60D9}\x{60DA}\x{60DB}\x{60DC}\x{60DD}' . + '\x{60DF}\x{60E0}\x{60E1}\x{60E2}\x{60E4}\x{60E6}\x{60E7}\x{60E8}\x{60E9}' . + '\x{60EA}\x{60EB}\x{60EC}\x{60ED}\x{60EE}\x{60EF}\x{60F0}\x{60F1}\x{60F2}' . + '\x{60F3}\x{60F4}\x{60F5}\x{60F6}\x{60F7}\x{60F8}\x{60F9}\x{60FA}\x{60FB}' . + '\x{60FC}\x{60FE}\x{60FF}\x{6100}\x{6101}\x{6103}\x{6104}\x{6105}\x{6106}' . + '\x{6108}\x{6109}\x{610A}\x{610B}\x{610C}\x{610D}\x{610E}\x{610F}\x{6110}' . + '\x{6112}\x{6113}\x{6114}\x{6115}\x{6116}\x{6117}\x{6118}\x{6119}\x{611A}' . + '\x{611B}\x{611C}\x{611D}\x{611F}\x{6120}\x{6122}\x{6123}\x{6124}\x{6125}' . + '\x{6126}\x{6127}\x{6128}\x{6129}\x{612A}\x{612B}\x{612C}\x{612D}\x{612E}' . + '\x{612F}\x{6130}\x{6132}\x{6134}\x{6136}\x{6137}\x{613A}\x{613B}\x{613C}' . + '\x{613D}\x{613E}\x{613F}\x{6140}\x{6141}\x{6142}\x{6143}\x{6144}\x{6145}' . + '\x{6146}\x{6147}\x{6148}\x{6149}\x{614A}\x{614B}\x{614C}\x{614D}\x{614E}' . + '\x{614F}\x{6150}\x{6151}\x{6152}\x{6153}\x{6154}\x{6155}\x{6156}\x{6157}' . + '\x{6158}\x{6159}\x{615A}\x{615B}\x{615C}\x{615D}\x{615E}\x{615F}\x{6161}' . + '\x{6162}\x{6163}\x{6164}\x{6165}\x{6166}\x{6167}\x{6168}\x{6169}\x{616A}' . + '\x{616B}\x{616C}\x{616D}\x{616E}\x{6170}\x{6171}\x{6172}\x{6173}\x{6174}' . + '\x{6175}\x{6176}\x{6177}\x{6178}\x{6179}\x{617A}\x{617C}\x{617E}\x{6180}' . + '\x{6181}\x{6182}\x{6183}\x{6184}\x{6185}\x{6187}\x{6188}\x{6189}\x{618A}' . + '\x{618B}\x{618C}\x{618D}\x{618E}\x{618F}\x{6190}\x{6191}\x{6192}\x{6193}' . + '\x{6194}\x{6195}\x{6196}\x{6198}\x{6199}\x{619A}\x{619B}\x{619D}\x{619E}' . + '\x{619F}\x{61A0}\x{61A1}\x{61A2}\x{61A3}\x{61A4}\x{61A5}\x{61A6}\x{61A7}' . + '\x{61A8}\x{61A9}\x{61AA}\x{61AB}\x{61AC}\x{61AD}\x{61AE}\x{61AF}\x{61B0}' . + '\x{61B1}\x{61B2}\x{61B3}\x{61B4}\x{61B5}\x{61B6}\x{61B7}\x{61B8}\x{61BA}' . + '\x{61BC}\x{61BD}\x{61BE}\x{61BF}\x{61C0}\x{61C1}\x{61C2}\x{61C3}\x{61C4}' . + '\x{61C5}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' . + '\x{61CE}\x{61CF}\x{61D0}\x{61D1}\x{61D2}\x{61D4}\x{61D6}\x{61D7}\x{61D8}' . + '\x{61D9}\x{61DA}\x{61DB}\x{61DC}\x{61DD}\x{61DE}\x{61DF}\x{61E0}\x{61E1}' . + '\x{61E2}\x{61E3}\x{61E4}\x{61E5}\x{61E6}\x{61E7}\x{61E8}\x{61E9}\x{61EA}' . + '\x{61EB}\x{61ED}\x{61EE}\x{61F0}\x{61F1}\x{61F2}\x{61F3}\x{61F5}\x{61F6}' . + '\x{61F7}\x{61F8}\x{61F9}\x{61FA}\x{61FB}\x{61FC}\x{61FD}\x{61FE}\x{61FF}' . + '\x{6200}\x{6201}\x{6202}\x{6203}\x{6204}\x{6206}\x{6207}\x{6208}\x{6209}' . + '\x{620A}\x{620B}\x{620C}\x{620D}\x{620E}\x{620F}\x{6210}\x{6211}\x{6212}' . + '\x{6213}\x{6214}\x{6215}\x{6216}\x{6217}\x{6218}\x{6219}\x{621A}\x{621B}' . + '\x{621C}\x{621D}\x{621E}\x{621F}\x{6220}\x{6221}\x{6222}\x{6223}\x{6224}' . + '\x{6225}\x{6226}\x{6227}\x{6228}\x{6229}\x{622A}\x{622B}\x{622C}\x{622D}' . + '\x{622E}\x{622F}\x{6230}\x{6231}\x{6232}\x{6233}\x{6234}\x{6236}\x{6237}' . + '\x{6238}\x{623A}\x{623B}\x{623C}\x{623D}\x{623E}\x{623F}\x{6240}\x{6241}' . + '\x{6242}\x{6243}\x{6244}\x{6245}\x{6246}\x{6247}\x{6248}\x{6249}\x{624A}' . + '\x{624B}\x{624C}\x{624D}\x{624E}\x{624F}\x{6250}\x{6251}\x{6252}\x{6253}' . + '\x{6254}\x{6255}\x{6256}\x{6258}\x{6259}\x{625A}\x{625B}\x{625C}\x{625D}' . + '\x{625E}\x{625F}\x{6260}\x{6261}\x{6262}\x{6263}\x{6264}\x{6265}\x{6266}' . + '\x{6267}\x{6268}\x{6269}\x{626A}\x{626B}\x{626C}\x{626D}\x{626E}\x{626F}' . + '\x{6270}\x{6271}\x{6272}\x{6273}\x{6274}\x{6275}\x{6276}\x{6277}\x{6278}' . + '\x{6279}\x{627A}\x{627B}\x{627C}\x{627D}\x{627E}\x{627F}\x{6280}\x{6281}' . + '\x{6283}\x{6284}\x{6285}\x{6286}\x{6287}\x{6288}\x{6289}\x{628A}\x{628B}' . + '\x{628C}\x{628E}\x{628F}\x{6290}\x{6291}\x{6292}\x{6293}\x{6294}\x{6295}' . + '\x{6296}\x{6297}\x{6298}\x{6299}\x{629A}\x{629B}\x{629C}\x{629E}\x{629F}' . + '\x{62A0}\x{62A1}\x{62A2}\x{62A3}\x{62A4}\x{62A5}\x{62A7}\x{62A8}\x{62A9}' . + '\x{62AA}\x{62AB}\x{62AC}\x{62AD}\x{62AE}\x{62AF}\x{62B0}\x{62B1}\x{62B2}' . + '\x{62B3}\x{62B4}\x{62B5}\x{62B6}\x{62B7}\x{62B8}\x{62B9}\x{62BA}\x{62BB}' . + '\x{62BC}\x{62BD}\x{62BE}\x{62BF}\x{62C0}\x{62C1}\x{62C2}\x{62C3}\x{62C4}' . + '\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CB}\x{62CC}\x{62CD}' . + '\x{62CE}\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D5}\x{62D6}' . + '\x{62D7}\x{62D8}\x{62D9}\x{62DA}\x{62DB}\x{62DC}\x{62DD}\x{62DF}\x{62E0}' . + '\x{62E1}\x{62E2}\x{62E3}\x{62E4}\x{62E5}\x{62E6}\x{62E7}\x{62E8}\x{62E9}' . + '\x{62EB}\x{62EC}\x{62ED}\x{62EE}\x{62EF}\x{62F0}\x{62F1}\x{62F2}\x{62F3}' . + '\x{62F4}\x{62F5}\x{62F6}\x{62F7}\x{62F8}\x{62F9}\x{62FA}\x{62FB}\x{62FC}' . + '\x{62FD}\x{62FE}\x{62FF}\x{6300}\x{6301}\x{6302}\x{6303}\x{6304}\x{6305}' . + '\x{6306}\x{6307}\x{6308}\x{6309}\x{630B}\x{630C}\x{630D}\x{630E}\x{630F}' . + '\x{6310}\x{6311}\x{6312}\x{6313}\x{6314}\x{6315}\x{6316}\x{6318}\x{6319}' . + '\x{631A}\x{631B}\x{631C}\x{631D}\x{631E}\x{631F}\x{6320}\x{6321}\x{6322}' . + '\x{6323}\x{6324}\x{6325}\x{6326}\x{6327}\x{6328}\x{6329}\x{632A}\x{632B}' . + '\x{632C}\x{632D}\x{632E}\x{632F}\x{6330}\x{6332}\x{6333}\x{6334}\x{6336}' . + '\x{6338}\x{6339}\x{633A}\x{633B}\x{633C}\x{633D}\x{633E}\x{6340}\x{6341}' . + '\x{6342}\x{6343}\x{6344}\x{6345}\x{6346}\x{6347}\x{6348}\x{6349}\x{634A}' . + '\x{634B}\x{634C}\x{634D}\x{634E}\x{634F}\x{6350}\x{6351}\x{6352}\x{6353}' . + '\x{6354}\x{6355}\x{6356}\x{6357}\x{6358}\x{6359}\x{635A}\x{635C}\x{635D}' . + '\x{635E}\x{635F}\x{6360}\x{6361}\x{6362}\x{6363}\x{6364}\x{6365}\x{6366}' . + '\x{6367}\x{6368}\x{6369}\x{636A}\x{636B}\x{636C}\x{636D}\x{636E}\x{636F}' . + '\x{6370}\x{6371}\x{6372}\x{6373}\x{6374}\x{6375}\x{6376}\x{6377}\x{6378}' . + '\x{6379}\x{637A}\x{637B}\x{637C}\x{637D}\x{637E}\x{6380}\x{6381}\x{6382}' . + '\x{6383}\x{6384}\x{6385}\x{6386}\x{6387}\x{6388}\x{6389}\x{638A}\x{638C}' . + '\x{638D}\x{638E}\x{638F}\x{6390}\x{6391}\x{6392}\x{6394}\x{6395}\x{6396}' . + '\x{6397}\x{6398}\x{6399}\x{639A}\x{639B}\x{639C}\x{639D}\x{639E}\x{639F}' . + '\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A4}\x{63A5}\x{63A6}\x{63A7}\x{63A8}' . + '\x{63A9}\x{63AA}\x{63AB}\x{63AC}\x{63AD}\x{63AE}\x{63AF}\x{63B0}\x{63B1}' . + '\x{63B2}\x{63B3}\x{63B4}\x{63B5}\x{63B6}\x{63B7}\x{63B8}\x{63B9}\x{63BA}' . + '\x{63BC}\x{63BD}\x{63BE}\x{63BF}\x{63C0}\x{63C1}\x{63C2}\x{63C3}\x{63C4}' . + '\x{63C5}\x{63C6}\x{63C7}\x{63C8}\x{63C9}\x{63CA}\x{63CB}\x{63CC}\x{63CD}' . + '\x{63CE}\x{63CF}\x{63D0}\x{63D2}\x{63D3}\x{63D4}\x{63D5}\x{63D6}\x{63D7}' . + '\x{63D8}\x{63D9}\x{63DA}\x{63DB}\x{63DC}\x{63DD}\x{63DE}\x{63DF}\x{63E0}' . + '\x{63E1}\x{63E2}\x{63E3}\x{63E4}\x{63E5}\x{63E6}\x{63E7}\x{63E8}\x{63E9}' . + '\x{63EA}\x{63EB}\x{63EC}\x{63ED}\x{63EE}\x{63EF}\x{63F0}\x{63F1}\x{63F2}' . + '\x{63F3}\x{63F4}\x{63F5}\x{63F6}\x{63F7}\x{63F8}\x{63F9}\x{63FA}\x{63FB}' . + '\x{63FC}\x{63FD}\x{63FE}\x{63FF}\x{6400}\x{6401}\x{6402}\x{6403}\x{6404}' . + '\x{6405}\x{6406}\x{6408}\x{6409}\x{640A}\x{640B}\x{640C}\x{640D}\x{640E}' . + '\x{640F}\x{6410}\x{6411}\x{6412}\x{6413}\x{6414}\x{6415}\x{6416}\x{6417}' . + '\x{6418}\x{6419}\x{641A}\x{641B}\x{641C}\x{641D}\x{641E}\x{641F}\x{6420}' . + '\x{6421}\x{6422}\x{6423}\x{6424}\x{6425}\x{6426}\x{6427}\x{6428}\x{6429}' . + '\x{642A}\x{642B}\x{642C}\x{642D}\x{642E}\x{642F}\x{6430}\x{6431}\x{6432}' . + '\x{6433}\x{6434}\x{6435}\x{6436}\x{6437}\x{6438}\x{6439}\x{643A}\x{643D}' . + '\x{643E}\x{643F}\x{6440}\x{6441}\x{6443}\x{6444}\x{6445}\x{6446}\x{6447}' . + '\x{6448}\x{644A}\x{644B}\x{644C}\x{644D}\x{644E}\x{644F}\x{6450}\x{6451}' . + '\x{6452}\x{6453}\x{6454}\x{6455}\x{6456}\x{6457}\x{6458}\x{6459}\x{645B}' . + '\x{645C}\x{645D}\x{645E}\x{645F}\x{6460}\x{6461}\x{6462}\x{6463}\x{6464}' . + '\x{6465}\x{6466}\x{6467}\x{6468}\x{6469}\x{646A}\x{646B}\x{646C}\x{646D}' . + '\x{646E}\x{646F}\x{6470}\x{6471}\x{6472}\x{6473}\x{6474}\x{6475}\x{6476}' . + '\x{6477}\x{6478}\x{6479}\x{647A}\x{647B}\x{647C}\x{647D}\x{647F}\x{6480}' . + '\x{6481}\x{6482}\x{6483}\x{6484}\x{6485}\x{6487}\x{6488}\x{6489}\x{648A}' . + '\x{648B}\x{648C}\x{648D}\x{648E}\x{648F}\x{6490}\x{6491}\x{6492}\x{6493}' . + '\x{6494}\x{6495}\x{6496}\x{6497}\x{6498}\x{6499}\x{649A}\x{649B}\x{649C}' . + '\x{649D}\x{649E}\x{649F}\x{64A0}\x{64A2}\x{64A3}\x{64A4}\x{64A5}\x{64A6}' . + '\x{64A7}\x{64A8}\x{64A9}\x{64AA}\x{64AB}\x{64AC}\x{64AD}\x{64AE}\x{64B0}' . + '\x{64B1}\x{64B2}\x{64B3}\x{64B4}\x{64B5}\x{64B7}\x{64B8}\x{64B9}\x{64BA}' . + '\x{64BB}\x{64BC}\x{64BD}\x{64BE}\x{64BF}\x{64C0}\x{64C1}\x{64C2}\x{64C3}' . + '\x{64C4}\x{64C5}\x{64C6}\x{64C7}\x{64C9}\x{64CA}\x{64CB}\x{64CC}\x{64CD}' . + '\x{64CE}\x{64CF}\x{64D0}\x{64D1}\x{64D2}\x{64D3}\x{64D4}\x{64D6}\x{64D7}' . + '\x{64D8}\x{64D9}\x{64DA}\x{64DB}\x{64DC}\x{64DD}\x{64DE}\x{64DF}\x{64E0}' . + '\x{64E2}\x{64E3}\x{64E4}\x{64E6}\x{64E7}\x{64E8}\x{64E9}\x{64EA}\x{64EB}' . + '\x{64EC}\x{64ED}\x{64EF}\x{64F0}\x{64F1}\x{64F2}\x{64F3}\x{64F4}\x{64F6}' . + '\x{64F7}\x{64F8}\x{64FA}\x{64FB}\x{64FC}\x{64FD}\x{64FE}\x{64FF}\x{6500}' . + '\x{6501}\x{6503}\x{6504}\x{6505}\x{6506}\x{6507}\x{6508}\x{6509}\x{650B}' . + '\x{650C}\x{650D}\x{650E}\x{650F}\x{6510}\x{6511}\x{6512}\x{6513}\x{6514}' . + '\x{6515}\x{6516}\x{6517}\x{6518}\x{6519}\x{651A}\x{651B}\x{651C}\x{651D}' . + '\x{651E}\x{6520}\x{6521}\x{6522}\x{6523}\x{6524}\x{6525}\x{6526}\x{6527}' . + '\x{6529}\x{652A}\x{652B}\x{652C}\x{652D}\x{652E}\x{652F}\x{6530}\x{6531}' . + '\x{6532}\x{6533}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}\x{653A}' . + '\x{653B}\x{653C}\x{653D}\x{653E}\x{653F}\x{6541}\x{6543}\x{6544}\x{6545}' . + '\x{6546}\x{6547}\x{6548}\x{6549}\x{654A}\x{654B}\x{654C}\x{654D}\x{654E}' . + '\x{654F}\x{6550}\x{6551}\x{6552}\x{6553}\x{6554}\x{6555}\x{6556}\x{6557}' . + '\x{6558}\x{6559}\x{655B}\x{655C}\x{655D}\x{655E}\x{6560}\x{6561}\x{6562}' . + '\x{6563}\x{6564}\x{6565}\x{6566}\x{6567}\x{6568}\x{6569}\x{656A}\x{656B}' . + '\x{656C}\x{656E}\x{656F}\x{6570}\x{6571}\x{6572}\x{6573}\x{6574}\x{6575}' . + '\x{6576}\x{6577}\x{6578}\x{6579}\x{657A}\x{657B}\x{657C}\x{657E}\x{657F}' . + '\x{6580}\x{6581}\x{6582}\x{6583}\x{6584}\x{6585}\x{6586}\x{6587}\x{6588}' . + '\x{6589}\x{658B}\x{658C}\x{658D}\x{658E}\x{658F}\x{6590}\x{6591}\x{6592}' . + '\x{6593}\x{6594}\x{6595}\x{6596}\x{6597}\x{6598}\x{6599}\x{659B}\x{659C}' . + '\x{659D}\x{659E}\x{659F}\x{65A0}\x{65A1}\x{65A2}\x{65A3}\x{65A4}\x{65A5}' . + '\x{65A6}\x{65A7}\x{65A8}\x{65A9}\x{65AA}\x{65AB}\x{65AC}\x{65AD}\x{65AE}' . + '\x{65AF}\x{65B0}\x{65B1}\x{65B2}\x{65B3}\x{65B4}\x{65B6}\x{65B7}\x{65B8}' . + '\x{65B9}\x{65BA}\x{65BB}\x{65BC}\x{65BD}\x{65BF}\x{65C0}\x{65C1}\x{65C2}' . + '\x{65C3}\x{65C4}\x{65C5}\x{65C6}\x{65C7}\x{65CA}\x{65CB}\x{65CC}\x{65CD}' . + '\x{65CE}\x{65CF}\x{65D0}\x{65D2}\x{65D3}\x{65D4}\x{65D5}\x{65D6}\x{65D7}' . + '\x{65DA}\x{65DB}\x{65DD}\x{65DE}\x{65DF}\x{65E0}\x{65E1}\x{65E2}\x{65E3}' . + '\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}\x{65EB}\x{65EC}\x{65ED}\x{65EE}' . + '\x{65EF}\x{65F0}\x{65F1}\x{65F2}\x{65F3}\x{65F4}\x{65F5}\x{65F6}\x{65F7}' . + '\x{65F8}\x{65FA}\x{65FB}\x{65FC}\x{65FD}\x{6600}\x{6601}\x{6602}\x{6603}' . + '\x{6604}\x{6605}\x{6606}\x{6607}\x{6608}\x{6609}\x{660A}\x{660B}\x{660C}' . + '\x{660D}\x{660E}\x{660F}\x{6610}\x{6611}\x{6612}\x{6613}\x{6614}\x{6615}' . + '\x{6616}\x{6618}\x{6619}\x{661A}\x{661B}\x{661C}\x{661D}\x{661F}\x{6620}' . + '\x{6621}\x{6622}\x{6623}\x{6624}\x{6625}\x{6626}\x{6627}\x{6628}\x{6629}' . + '\x{662A}\x{662B}\x{662D}\x{662E}\x{662F}\x{6630}\x{6631}\x{6632}\x{6633}' . + '\x{6634}\x{6635}\x{6636}\x{6639}\x{663A}\x{663C}\x{663D}\x{663E}\x{6640}' . + '\x{6641}\x{6642}\x{6643}\x{6644}\x{6645}\x{6646}\x{6647}\x{6649}\x{664A}' . + '\x{664B}\x{664C}\x{664E}\x{664F}\x{6650}\x{6651}\x{6652}\x{6653}\x{6654}' . + '\x{6655}\x{6656}\x{6657}\x{6658}\x{6659}\x{665A}\x{665B}\x{665C}\x{665D}' . + '\x{665E}\x{665F}\x{6661}\x{6662}\x{6664}\x{6665}\x{6666}\x{6668}\x{6669}' . + '\x{666A}\x{666B}\x{666C}\x{666D}\x{666E}\x{666F}\x{6670}\x{6671}\x{6672}' . + '\x{6673}\x{6674}\x{6675}\x{6676}\x{6677}\x{6678}\x{6679}\x{667A}\x{667B}' . + '\x{667C}\x{667D}\x{667E}\x{667F}\x{6680}\x{6681}\x{6682}\x{6683}\x{6684}' . + '\x{6685}\x{6686}\x{6687}\x{6688}\x{6689}\x{668A}\x{668B}\x{668C}\x{668D}' . + '\x{668E}\x{668F}\x{6690}\x{6691}\x{6693}\x{6694}\x{6695}\x{6696}\x{6697}' . + '\x{6698}\x{6699}\x{669A}\x{669B}\x{669D}\x{669F}\x{66A0}\x{66A1}\x{66A2}' . + '\x{66A3}\x{66A4}\x{66A5}\x{66A6}\x{66A7}\x{66A8}\x{66A9}\x{66AA}\x{66AB}' . + '\x{66AE}\x{66AF}\x{66B0}\x{66B1}\x{66B2}\x{66B3}\x{66B4}\x{66B5}\x{66B6}' . + '\x{66B7}\x{66B8}\x{66B9}\x{66BA}\x{66BB}\x{66BC}\x{66BD}\x{66BE}\x{66BF}' . + '\x{66C0}\x{66C1}\x{66C2}\x{66C3}\x{66C4}\x{66C5}\x{66C6}\x{66C7}\x{66C8}' . + '\x{66C9}\x{66CA}\x{66CB}\x{66CC}\x{66CD}\x{66CE}\x{66CF}\x{66D1}\x{66D2}' . + '\x{66D4}\x{66D5}\x{66D6}\x{66D8}\x{66D9}\x{66DA}\x{66DB}\x{66DC}\x{66DD}' . + '\x{66DE}\x{66E0}\x{66E1}\x{66E2}\x{66E3}\x{66E4}\x{66E5}\x{66E6}\x{66E7}' . + '\x{66E8}\x{66E9}\x{66EA}\x{66EB}\x{66EC}\x{66ED}\x{66EE}\x{66F0}\x{66F1}' . + '\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F6}\x{66F7}\x{66F8}\x{66F9}\x{66FA}' . + '\x{66FB}\x{66FC}\x{66FE}\x{66FF}\x{6700}\x{6701}\x{6703}\x{6704}\x{6705}' . + '\x{6706}\x{6708}\x{6709}\x{670A}\x{670B}\x{670C}\x{670D}\x{670E}\x{670F}' . + '\x{6710}\x{6711}\x{6712}\x{6713}\x{6714}\x{6715}\x{6716}\x{6717}\x{6718}' . + '\x{671A}\x{671B}\x{671C}\x{671D}\x{671E}\x{671F}\x{6720}\x{6721}\x{6722}' . + '\x{6723}\x{6725}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}\x{672D}' . + '\x{672E}\x{672F}\x{6730}\x{6731}\x{6732}\x{6733}\x{6734}\x{6735}\x{6736}' . + '\x{6737}\x{6738}\x{6739}\x{673A}\x{673B}\x{673C}\x{673D}\x{673E}\x{673F}' . + '\x{6740}\x{6741}\x{6742}\x{6743}\x{6744}\x{6745}\x{6746}\x{6747}\x{6748}' . + '\x{6749}\x{674A}\x{674B}\x{674C}\x{674D}\x{674E}\x{674F}\x{6750}\x{6751}' . + '\x{6752}\x{6753}\x{6754}\x{6755}\x{6756}\x{6757}\x{6758}\x{6759}\x{675A}' . + '\x{675B}\x{675C}\x{675D}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' . + '\x{6764}\x{6765}\x{6766}\x{6768}\x{6769}\x{676A}\x{676B}\x{676C}\x{676D}' . + '\x{676E}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}\x{6774}\x{6775}\x{6776}' . + '\x{6777}\x{6778}\x{6779}\x{677A}\x{677B}\x{677C}\x{677D}\x{677E}\x{677F}' . + '\x{6780}\x{6781}\x{6782}\x{6783}\x{6784}\x{6785}\x{6786}\x{6787}\x{6789}' . + '\x{678A}\x{678B}\x{678C}\x{678D}\x{678E}\x{678F}\x{6790}\x{6791}\x{6792}' . + '\x{6793}\x{6794}\x{6795}\x{6797}\x{6798}\x{6799}\x{679A}\x{679B}\x{679C}' . + '\x{679D}\x{679E}\x{679F}\x{67A0}\x{67A1}\x{67A2}\x{67A3}\x{67A4}\x{67A5}' . + '\x{67A6}\x{67A7}\x{67A8}\x{67AA}\x{67AB}\x{67AC}\x{67AD}\x{67AE}\x{67AF}' . + '\x{67B0}\x{67B1}\x{67B2}\x{67B3}\x{67B4}\x{67B5}\x{67B6}\x{67B7}\x{67B8}' . + '\x{67B9}\x{67BA}\x{67BB}\x{67BC}\x{67BE}\x{67C0}\x{67C1}\x{67C2}\x{67C3}' . + '\x{67C4}\x{67C5}\x{67C6}\x{67C7}\x{67C8}\x{67C9}\x{67CA}\x{67CB}\x{67CC}' . + '\x{67CD}\x{67CE}\x{67CF}\x{67D0}\x{67D1}\x{67D2}\x{67D3}\x{67D4}\x{67D6}' . + '\x{67D8}\x{67D9}\x{67DA}\x{67DB}\x{67DC}\x{67DD}\x{67DE}\x{67DF}\x{67E0}' . + '\x{67E1}\x{67E2}\x{67E3}\x{67E4}\x{67E5}\x{67E6}\x{67E7}\x{67E8}\x{67E9}' . + '\x{67EA}\x{67EB}\x{67EC}\x{67ED}\x{67EE}\x{67EF}\x{67F0}\x{67F1}\x{67F2}' . + '\x{67F3}\x{67F4}\x{67F5}\x{67F6}\x{67F7}\x{67F8}\x{67FA}\x{67FB}\x{67FC}' . + '\x{67FD}\x{67FE}\x{67FF}\x{6800}\x{6802}\x{6803}\x{6804}\x{6805}\x{6806}' . + '\x{6807}\x{6808}\x{6809}\x{680A}\x{680B}\x{680C}\x{680D}\x{680E}\x{680F}' . + '\x{6810}\x{6811}\x{6812}\x{6813}\x{6814}\x{6816}\x{6817}\x{6818}\x{6819}' . + '\x{681A}\x{681B}\x{681C}\x{681D}\x{681F}\x{6820}\x{6821}\x{6822}\x{6823}' . + '\x{6824}\x{6825}\x{6826}\x{6828}\x{6829}\x{682A}\x{682B}\x{682C}\x{682D}' . + '\x{682E}\x{682F}\x{6831}\x{6832}\x{6833}\x{6834}\x{6835}\x{6836}\x{6837}' . + '\x{6838}\x{6839}\x{683A}\x{683B}\x{683C}\x{683D}\x{683E}\x{683F}\x{6840}' . + '\x{6841}\x{6842}\x{6843}\x{6844}\x{6845}\x{6846}\x{6847}\x{6848}\x{6849}' . + '\x{684A}\x{684B}\x{684C}\x{684D}\x{684E}\x{684F}\x{6850}\x{6851}\x{6852}' . + '\x{6853}\x{6854}\x{6855}\x{6856}\x{6857}\x{685B}\x{685D}\x{6860}\x{6861}' . + '\x{6862}\x{6863}\x{6864}\x{6865}\x{6866}\x{6867}\x{6868}\x{6869}\x{686A}' . + '\x{686B}\x{686C}\x{686D}\x{686E}\x{686F}\x{6870}\x{6871}\x{6872}\x{6873}' . + '\x{6874}\x{6875}\x{6876}\x{6877}\x{6878}\x{6879}\x{687B}\x{687C}\x{687D}' . + '\x{687E}\x{687F}\x{6880}\x{6881}\x{6882}\x{6883}\x{6884}\x{6885}\x{6886}' . + '\x{6887}\x{6888}\x{6889}\x{688A}\x{688B}\x{688C}\x{688D}\x{688E}\x{688F}' . + '\x{6890}\x{6891}\x{6892}\x{6893}\x{6894}\x{6896}\x{6897}\x{6898}\x{689A}' . + '\x{689B}\x{689C}\x{689D}\x{689E}\x{689F}\x{68A0}\x{68A1}\x{68A2}\x{68A3}' . + '\x{68A4}\x{68A6}\x{68A7}\x{68A8}\x{68A9}\x{68AA}\x{68AB}\x{68AC}\x{68AD}' . + '\x{68AE}\x{68AF}\x{68B0}\x{68B1}\x{68B2}\x{68B3}\x{68B4}\x{68B5}\x{68B6}' . + '\x{68B7}\x{68B9}\x{68BB}\x{68BC}\x{68BD}\x{68BE}\x{68BF}\x{68C0}\x{68C1}' . + '\x{68C2}\x{68C4}\x{68C6}\x{68C7}\x{68C8}\x{68C9}\x{68CA}\x{68CB}\x{68CC}' . + '\x{68CD}\x{68CE}\x{68CF}\x{68D0}\x{68D1}\x{68D2}\x{68D3}\x{68D4}\x{68D5}' . + '\x{68D6}\x{68D7}\x{68D8}\x{68DA}\x{68DB}\x{68DC}\x{68DD}\x{68DE}\x{68DF}' . + '\x{68E0}\x{68E1}\x{68E3}\x{68E4}\x{68E6}\x{68E7}\x{68E8}\x{68E9}\x{68EA}' . + '\x{68EB}\x{68EC}\x{68ED}\x{68EE}\x{68EF}\x{68F0}\x{68F1}\x{68F2}\x{68F3}' . + '\x{68F4}\x{68F5}\x{68F6}\x{68F7}\x{68F8}\x{68F9}\x{68FA}\x{68FB}\x{68FC}' . + '\x{68FD}\x{68FE}\x{68FF}\x{6901}\x{6902}\x{6903}\x{6904}\x{6905}\x{6906}' . + '\x{6907}\x{6908}\x{690A}\x{690B}\x{690C}\x{690D}\x{690E}\x{690F}\x{6910}' . + '\x{6911}\x{6912}\x{6913}\x{6914}\x{6915}\x{6916}\x{6917}\x{6918}\x{6919}' . + '\x{691A}\x{691B}\x{691C}\x{691D}\x{691E}\x{691F}\x{6920}\x{6921}\x{6922}' . + '\x{6923}\x{6924}\x{6925}\x{6926}\x{6927}\x{6928}\x{6929}\x{692A}\x{692B}' . + '\x{692C}\x{692D}\x{692E}\x{692F}\x{6930}\x{6931}\x{6932}\x{6933}\x{6934}' . + '\x{6935}\x{6936}\x{6937}\x{6938}\x{6939}\x{693A}\x{693B}\x{693C}\x{693D}' . + '\x{693F}\x{6940}\x{6941}\x{6942}\x{6943}\x{6944}\x{6945}\x{6946}\x{6947}' . + '\x{6948}\x{6949}\x{694A}\x{694B}\x{694C}\x{694E}\x{694F}\x{6950}\x{6951}' . + '\x{6952}\x{6953}\x{6954}\x{6955}\x{6956}\x{6957}\x{6958}\x{6959}\x{695A}' . + '\x{695B}\x{695C}\x{695D}\x{695E}\x{695F}\x{6960}\x{6961}\x{6962}\x{6963}' . + '\x{6964}\x{6965}\x{6966}\x{6967}\x{6968}\x{6969}\x{696A}\x{696B}\x{696C}' . + '\x{696D}\x{696E}\x{696F}\x{6970}\x{6971}\x{6972}\x{6973}\x{6974}\x{6975}' . + '\x{6976}\x{6977}\x{6978}\x{6979}\x{697A}\x{697B}\x{697C}\x{697D}\x{697E}' . + '\x{697F}\x{6980}\x{6981}\x{6982}\x{6983}\x{6984}\x{6985}\x{6986}\x{6987}' . + '\x{6988}\x{6989}\x{698A}\x{698B}\x{698C}\x{698D}\x{698E}\x{698F}\x{6990}' . + '\x{6991}\x{6992}\x{6993}\x{6994}\x{6995}\x{6996}\x{6997}\x{6998}\x{6999}' . + '\x{699A}\x{699B}\x{699C}\x{699D}\x{699E}\x{69A0}\x{69A1}\x{69A3}\x{69A4}' . + '\x{69A5}\x{69A6}\x{69A7}\x{69A8}\x{69A9}\x{69AA}\x{69AB}\x{69AC}\x{69AD}' . + '\x{69AE}\x{69AF}\x{69B0}\x{69B1}\x{69B2}\x{69B3}\x{69B4}\x{69B5}\x{69B6}' . + '\x{69B7}\x{69B8}\x{69B9}\x{69BA}\x{69BB}\x{69BC}\x{69BD}\x{69BE}\x{69BF}' . + '\x{69C1}\x{69C2}\x{69C3}\x{69C4}\x{69C5}\x{69C6}\x{69C7}\x{69C8}\x{69C9}' . + '\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}\x{69CF}\x{69D0}\x{69D3}\x{69D4}' . + '\x{69D8}\x{69D9}\x{69DA}\x{69DB}\x{69DC}\x{69DD}\x{69DE}\x{69DF}\x{69E0}' . + '\x{69E1}\x{69E2}\x{69E3}\x{69E4}\x{69E5}\x{69E6}\x{69E7}\x{69E8}\x{69E9}' . + '\x{69EA}\x{69EB}\x{69EC}\x{69ED}\x{69EE}\x{69EF}\x{69F0}\x{69F1}\x{69F2}' . + '\x{69F3}\x{69F4}\x{69F5}\x{69F6}\x{69F7}\x{69F8}\x{69FA}\x{69FB}\x{69FC}' . + '\x{69FD}\x{69FE}\x{69FF}\x{6A00}\x{6A01}\x{6A02}\x{6A04}\x{6A05}\x{6A06}' . + '\x{6A07}\x{6A08}\x{6A09}\x{6A0A}\x{6A0B}\x{6A0D}\x{6A0E}\x{6A0F}\x{6A10}' . + '\x{6A11}\x{6A12}\x{6A13}\x{6A14}\x{6A15}\x{6A16}\x{6A17}\x{6A18}\x{6A19}' . + '\x{6A1A}\x{6A1B}\x{6A1D}\x{6A1E}\x{6A1F}\x{6A20}\x{6A21}\x{6A22}\x{6A23}' . + '\x{6A25}\x{6A26}\x{6A27}\x{6A28}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2C}\x{6A2D}' . + '\x{6A2E}\x{6A2F}\x{6A30}\x{6A31}\x{6A32}\x{6A33}\x{6A34}\x{6A35}\x{6A36}' . + '\x{6A38}\x{6A39}\x{6A3A}\x{6A3B}\x{6A3C}\x{6A3D}\x{6A3E}\x{6A3F}\x{6A40}' . + '\x{6A41}\x{6A42}\x{6A43}\x{6A44}\x{6A45}\x{6A46}\x{6A47}\x{6A48}\x{6A49}' . + '\x{6A4B}\x{6A4C}\x{6A4D}\x{6A4E}\x{6A4F}\x{6A50}\x{6A51}\x{6A52}\x{6A54}' . + '\x{6A55}\x{6A56}\x{6A57}\x{6A58}\x{6A59}\x{6A5A}\x{6A5B}\x{6A5D}\x{6A5E}' . + '\x{6A5F}\x{6A60}\x{6A61}\x{6A62}\x{6A63}\x{6A64}\x{6A65}\x{6A66}\x{6A67}' . + '\x{6A68}\x{6A69}\x{6A6A}\x{6A6B}\x{6A6C}\x{6A6D}\x{6A6F}\x{6A71}\x{6A72}' . + '\x{6A73}\x{6A74}\x{6A75}\x{6A76}\x{6A77}\x{6A78}\x{6A79}\x{6A7A}\x{6A7B}' . + '\x{6A7C}\x{6A7D}\x{6A7E}\x{6A7F}\x{6A80}\x{6A81}\x{6A82}\x{6A83}\x{6A84}' . + '\x{6A85}\x{6A87}\x{6A88}\x{6A89}\x{6A8B}\x{6A8C}\x{6A8D}\x{6A8E}\x{6A90}' . + '\x{6A91}\x{6A92}\x{6A93}\x{6A94}\x{6A95}\x{6A96}\x{6A97}\x{6A98}\x{6A9A}' . + '\x{6A9B}\x{6A9C}\x{6A9E}\x{6A9F}\x{6AA0}\x{6AA1}\x{6AA2}\x{6AA3}\x{6AA4}' . + '\x{6AA5}\x{6AA6}\x{6AA7}\x{6AA8}\x{6AA9}\x{6AAB}\x{6AAC}\x{6AAD}\x{6AAE}' . + '\x{6AAF}\x{6AB0}\x{6AB2}\x{6AB3}\x{6AB4}\x{6AB5}\x{6AB6}\x{6AB7}\x{6AB8}' . + '\x{6AB9}\x{6ABA}\x{6ABB}\x{6ABC}\x{6ABD}\x{6ABF}\x{6AC1}\x{6AC2}\x{6AC3}' . + '\x{6AC5}\x{6AC6}\x{6AC7}\x{6ACA}\x{6ACB}\x{6ACC}\x{6ACD}\x{6ACE}\x{6ACF}' . + '\x{6AD0}\x{6AD1}\x{6AD2}\x{6AD3}\x{6AD4}\x{6AD5}\x{6AD6}\x{6AD7}\x{6AD9}' . + '\x{6ADA}\x{6ADB}\x{6ADC}\x{6ADD}\x{6ADE}\x{6ADF}\x{6AE0}\x{6AE1}\x{6AE2}' . + '\x{6AE3}\x{6AE4}\x{6AE5}\x{6AE6}\x{6AE7}\x{6AE8}\x{6AEA}\x{6AEB}\x{6AEC}' . + '\x{6AED}\x{6AEE}\x{6AEF}\x{6AF0}\x{6AF1}\x{6AF2}\x{6AF3}\x{6AF4}\x{6AF5}' . + '\x{6AF6}\x{6AF7}\x{6AF8}\x{6AF9}\x{6AFA}\x{6AFB}\x{6AFC}\x{6AFD}\x{6AFE}' . + '\x{6AFF}\x{6B00}\x{6B01}\x{6B02}\x{6B03}\x{6B04}\x{6B05}\x{6B06}\x{6B07}' . + '\x{6B08}\x{6B09}\x{6B0A}\x{6B0B}\x{6B0C}\x{6B0D}\x{6B0F}\x{6B10}\x{6B11}' . + '\x{6B12}\x{6B13}\x{6B14}\x{6B15}\x{6B16}\x{6B17}\x{6B18}\x{6B19}\x{6B1A}' . + '\x{6B1C}\x{6B1D}\x{6B1E}\x{6B1F}\x{6B20}\x{6B21}\x{6B22}\x{6B23}\x{6B24}' . + '\x{6B25}\x{6B26}\x{6B27}\x{6B28}\x{6B29}\x{6B2A}\x{6B2B}\x{6B2C}\x{6B2D}' . + '\x{6B2F}\x{6B30}\x{6B31}\x{6B32}\x{6B33}\x{6B34}\x{6B36}\x{6B37}\x{6B38}' . + '\x{6B39}\x{6B3A}\x{6B3B}\x{6B3C}\x{6B3D}\x{6B3E}\x{6B3F}\x{6B41}\x{6B42}' . + '\x{6B43}\x{6B44}\x{6B45}\x{6B46}\x{6B47}\x{6B48}\x{6B49}\x{6B4A}\x{6B4B}' . + '\x{6B4C}\x{6B4D}\x{6B4E}\x{6B4F}\x{6B50}\x{6B51}\x{6B52}\x{6B53}\x{6B54}' . + '\x{6B55}\x{6B56}\x{6B59}\x{6B5A}\x{6B5B}\x{6B5C}\x{6B5E}\x{6B5F}\x{6B60}' . + '\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B65}\x{6B66}\x{6B67}\x{6B69}\x{6B6A}' . + '\x{6B6B}\x{6B6D}\x{6B6F}\x{6B70}\x{6B72}\x{6B73}\x{6B74}\x{6B76}\x{6B77}' . + '\x{6B78}\x{6B79}\x{6B7A}\x{6B7B}\x{6B7C}\x{6B7E}\x{6B7F}\x{6B80}\x{6B81}' . + '\x{6B82}\x{6B83}\x{6B84}\x{6B85}\x{6B86}\x{6B87}\x{6B88}\x{6B89}\x{6B8A}' . + '\x{6B8B}\x{6B8C}\x{6B8D}\x{6B8E}\x{6B8F}\x{6B90}\x{6B91}\x{6B92}\x{6B93}' . + '\x{6B94}\x{6B95}\x{6B96}\x{6B97}\x{6B98}\x{6B99}\x{6B9A}\x{6B9B}\x{6B9C}' . + '\x{6B9D}\x{6B9E}\x{6B9F}\x{6BA0}\x{6BA1}\x{6BA2}\x{6BA3}\x{6BA4}\x{6BA5}' . + '\x{6BA6}\x{6BA7}\x{6BA8}\x{6BA9}\x{6BAA}\x{6BAB}\x{6BAC}\x{6BAD}\x{6BAE}' . + '\x{6BAF}\x{6BB0}\x{6BB2}\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB6}\x{6BB7}\x{6BB9}' . + '\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBD}\x{6BBE}\x{6BBF}\x{6BC0}\x{6BC1}\x{6BC2}' . + '\x{6BC3}\x{6BC4}\x{6BC5}\x{6BC6}\x{6BC7}\x{6BC8}\x{6BC9}\x{6BCA}\x{6BCB}' . + '\x{6BCC}\x{6BCD}\x{6BCE}\x{6BCF}\x{6BD0}\x{6BD1}\x{6BD2}\x{6BD3}\x{6BD4}' . + '\x{6BD5}\x{6BD6}\x{6BD7}\x{6BD8}\x{6BD9}\x{6BDA}\x{6BDB}\x{6BDC}\x{6BDD}' . + '\x{6BDE}\x{6BDF}\x{6BE0}\x{6BE1}\x{6BE2}\x{6BE3}\x{6BE4}\x{6BE5}\x{6BE6}' . + '\x{6BE7}\x{6BE8}\x{6BEA}\x{6BEB}\x{6BEC}\x{6BED}\x{6BEE}\x{6BEF}\x{6BF0}' . + '\x{6BF2}\x{6BF3}\x{6BF5}\x{6BF6}\x{6BF7}\x{6BF8}\x{6BF9}\x{6BFB}\x{6BFC}' . + '\x{6BFD}\x{6BFE}\x{6BFF}\x{6C00}\x{6C01}\x{6C02}\x{6C03}\x{6C04}\x{6C05}' . + '\x{6C06}\x{6C07}\x{6C08}\x{6C09}\x{6C0B}\x{6C0C}\x{6C0D}\x{6C0E}\x{6C0F}' . + '\x{6C10}\x{6C11}\x{6C12}\x{6C13}\x{6C14}\x{6C15}\x{6C16}\x{6C18}\x{6C19}' . + '\x{6C1A}\x{6C1B}\x{6C1D}\x{6C1E}\x{6C1F}\x{6C20}\x{6C21}\x{6C22}\x{6C23}' . + '\x{6C24}\x{6C25}\x{6C26}\x{6C27}\x{6C28}\x{6C29}\x{6C2A}\x{6C2B}\x{6C2C}' . + '\x{6C2E}\x{6C2F}\x{6C30}\x{6C31}\x{6C32}\x{6C33}\x{6C34}\x{6C35}\x{6C36}' . + '\x{6C37}\x{6C38}\x{6C3A}\x{6C3B}\x{6C3D}\x{6C3E}\x{6C3F}\x{6C40}\x{6C41}' . + '\x{6C42}\x{6C43}\x{6C44}\x{6C46}\x{6C47}\x{6C48}\x{6C49}\x{6C4A}\x{6C4B}' . + '\x{6C4C}\x{6C4D}\x{6C4E}\x{6C4F}\x{6C50}\x{6C51}\x{6C52}\x{6C53}\x{6C54}' . + '\x{6C55}\x{6C56}\x{6C57}\x{6C58}\x{6C59}\x{6C5A}\x{6C5B}\x{6C5C}\x{6C5D}' . + '\x{6C5E}\x{6C5F}\x{6C60}\x{6C61}\x{6C62}\x{6C63}\x{6C64}\x{6C65}\x{6C66}' . + '\x{6C67}\x{6C68}\x{6C69}\x{6C6A}\x{6C6B}\x{6C6D}\x{6C6F}\x{6C70}\x{6C71}' . + '\x{6C72}\x{6C73}\x{6C74}\x{6C75}\x{6C76}\x{6C77}\x{6C78}\x{6C79}\x{6C7A}' . + '\x{6C7B}\x{6C7C}\x{6C7D}\x{6C7E}\x{6C7F}\x{6C80}\x{6C81}\x{6C82}\x{6C83}' . + '\x{6C84}\x{6C85}\x{6C86}\x{6C87}\x{6C88}\x{6C89}\x{6C8A}\x{6C8B}\x{6C8C}' . + '\x{6C8D}\x{6C8E}\x{6C8F}\x{6C90}\x{6C91}\x{6C92}\x{6C93}\x{6C94}\x{6C95}' . + '\x{6C96}\x{6C97}\x{6C98}\x{6C99}\x{6C9A}\x{6C9B}\x{6C9C}\x{6C9D}\x{6C9E}' . + '\x{6C9F}\x{6CA1}\x{6CA2}\x{6CA3}\x{6CA4}\x{6CA5}\x{6CA6}\x{6CA7}\x{6CA8}' . + '\x{6CA9}\x{6CAA}\x{6CAB}\x{6CAC}\x{6CAD}\x{6CAE}\x{6CAF}\x{6CB0}\x{6CB1}' . + '\x{6CB2}\x{6CB3}\x{6CB4}\x{6CB5}\x{6CB6}\x{6CB7}\x{6CB8}\x{6CB9}\x{6CBA}' . + '\x{6CBB}\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC0}\x{6CC1}\x{6CC2}\x{6CC3}' . + '\x{6CC4}\x{6CC5}\x{6CC6}\x{6CC7}\x{6CC8}\x{6CC9}\x{6CCA}\x{6CCB}\x{6CCC}' . + '\x{6CCD}\x{6CCE}\x{6CCF}\x{6CD0}\x{6CD1}\x{6CD2}\x{6CD3}\x{6CD4}\x{6CD5}' . + '\x{6CD6}\x{6CD7}\x{6CD9}\x{6CDA}\x{6CDB}\x{6CDC}\x{6CDD}\x{6CDE}\x{6CDF}' . + '\x{6CE0}\x{6CE1}\x{6CE2}\x{6CE3}\x{6CE4}\x{6CE5}\x{6CE6}\x{6CE7}\x{6CE8}' . + '\x{6CE9}\x{6CEA}\x{6CEB}\x{6CEC}\x{6CED}\x{6CEE}\x{6CEF}\x{6CF0}\x{6CF1}' . + '\x{6CF2}\x{6CF3}\x{6CF5}\x{6CF6}\x{6CF7}\x{6CF8}\x{6CF9}\x{6CFA}\x{6CFB}' . + '\x{6CFC}\x{6CFD}\x{6CFE}\x{6CFF}\x{6D00}\x{6D01}\x{6D03}\x{6D04}\x{6D05}' . + '\x{6D06}\x{6D07}\x{6D08}\x{6D09}\x{6D0A}\x{6D0B}\x{6D0C}\x{6D0D}\x{6D0E}' . + '\x{6D0F}\x{6D10}\x{6D11}\x{6D12}\x{6D13}\x{6D14}\x{6D15}\x{6D16}\x{6D17}' . + '\x{6D18}\x{6D19}\x{6D1A}\x{6D1B}\x{6D1D}\x{6D1E}\x{6D1F}\x{6D20}\x{6D21}' . + '\x{6D22}\x{6D23}\x{6D25}\x{6D26}\x{6D27}\x{6D28}\x{6D29}\x{6D2A}\x{6D2B}' . + '\x{6D2C}\x{6D2D}\x{6D2E}\x{6D2F}\x{6D30}\x{6D31}\x{6D32}\x{6D33}\x{6D34}' . + '\x{6D35}\x{6D36}\x{6D37}\x{6D38}\x{6D39}\x{6D3A}\x{6D3B}\x{6D3C}\x{6D3D}' . + '\x{6D3E}\x{6D3F}\x{6D40}\x{6D41}\x{6D42}\x{6D43}\x{6D44}\x{6D45}\x{6D46}' . + '\x{6D47}\x{6D48}\x{6D49}\x{6D4A}\x{6D4B}\x{6D4C}\x{6D4D}\x{6D4E}\x{6D4F}' . + '\x{6D50}\x{6D51}\x{6D52}\x{6D53}\x{6D54}\x{6D55}\x{6D56}\x{6D57}\x{6D58}' . + '\x{6D59}\x{6D5A}\x{6D5B}\x{6D5C}\x{6D5D}\x{6D5E}\x{6D5F}\x{6D60}\x{6D61}' . + '\x{6D62}\x{6D63}\x{6D64}\x{6D65}\x{6D66}\x{6D67}\x{6D68}\x{6D69}\x{6D6A}' . + '\x{6D6B}\x{6D6C}\x{6D6D}\x{6D6E}\x{6D6F}\x{6D70}\x{6D72}\x{6D73}\x{6D74}' . + '\x{6D75}\x{6D76}\x{6D77}\x{6D78}\x{6D79}\x{6D7A}\x{6D7B}\x{6D7C}\x{6D7D}' . + '\x{6D7E}\x{6D7F}\x{6D80}\x{6D82}\x{6D83}\x{6D84}\x{6D85}\x{6D86}\x{6D87}' . + '\x{6D88}\x{6D89}\x{6D8A}\x{6D8B}\x{6D8C}\x{6D8D}\x{6D8E}\x{6D8F}\x{6D90}' . + '\x{6D91}\x{6D92}\x{6D93}\x{6D94}\x{6D95}\x{6D97}\x{6D98}\x{6D99}\x{6D9A}' . + '\x{6D9B}\x{6D9D}\x{6D9E}\x{6D9F}\x{6DA0}\x{6DA1}\x{6DA2}\x{6DA3}\x{6DA4}' . + '\x{6DA5}\x{6DA6}\x{6DA7}\x{6DA8}\x{6DA9}\x{6DAA}\x{6DAB}\x{6DAC}\x{6DAD}' . + '\x{6DAE}\x{6DAF}\x{6DB2}\x{6DB3}\x{6DB4}\x{6DB5}\x{6DB7}\x{6DB8}\x{6DB9}' . + '\x{6DBA}\x{6DBB}\x{6DBC}\x{6DBD}\x{6DBE}\x{6DBF}\x{6DC0}\x{6DC1}\x{6DC2}' . + '\x{6DC3}\x{6DC4}\x{6DC5}\x{6DC6}\x{6DC7}\x{6DC8}\x{6DC9}\x{6DCA}\x{6DCB}' . + '\x{6DCC}\x{6DCD}\x{6DCE}\x{6DCF}\x{6DD0}\x{6DD1}\x{6DD2}\x{6DD3}\x{6DD4}' . + '\x{6DD5}\x{6DD6}\x{6DD7}\x{6DD8}\x{6DD9}\x{6DDA}\x{6DDB}\x{6DDC}\x{6DDD}' . + '\x{6DDE}\x{6DDF}\x{6DE0}\x{6DE1}\x{6DE2}\x{6DE3}\x{6DE4}\x{6DE5}\x{6DE6}' . + '\x{6DE7}\x{6DE8}\x{6DE9}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DED}\x{6DEE}\x{6DEF}' . + '\x{6DF0}\x{6DF1}\x{6DF2}\x{6DF3}\x{6DF4}\x{6DF5}\x{6DF6}\x{6DF7}\x{6DF8}' . + '\x{6DF9}\x{6DFA}\x{6DFB}\x{6DFC}\x{6DFD}\x{6E00}\x{6E03}\x{6E04}\x{6E05}' . + '\x{6E07}\x{6E08}\x{6E09}\x{6E0A}\x{6E0B}\x{6E0C}\x{6E0D}\x{6E0E}\x{6E0F}' . + '\x{6E10}\x{6E11}\x{6E14}\x{6E15}\x{6E16}\x{6E17}\x{6E19}\x{6E1A}\x{6E1B}' . + '\x{6E1C}\x{6E1D}\x{6E1E}\x{6E1F}\x{6E20}\x{6E21}\x{6E22}\x{6E23}\x{6E24}' . + '\x{6E25}\x{6E26}\x{6E27}\x{6E28}\x{6E29}\x{6E2B}\x{6E2C}\x{6E2D}\x{6E2E}' . + '\x{6E2F}\x{6E30}\x{6E31}\x{6E32}\x{6E33}\x{6E34}\x{6E35}\x{6E36}\x{6E37}' . + '\x{6E38}\x{6E39}\x{6E3A}\x{6E3B}\x{6E3C}\x{6E3D}\x{6E3E}\x{6E3F}\x{6E40}' . + '\x{6E41}\x{6E42}\x{6E43}\x{6E44}\x{6E45}\x{6E46}\x{6E47}\x{6E48}\x{6E49}' . + '\x{6E4A}\x{6E4B}\x{6E4D}\x{6E4E}\x{6E4F}\x{6E50}\x{6E51}\x{6E52}\x{6E53}' . + '\x{6E54}\x{6E55}\x{6E56}\x{6E57}\x{6E58}\x{6E59}\x{6E5A}\x{6E5B}\x{6E5C}' . + '\x{6E5D}\x{6E5E}\x{6E5F}\x{6E60}\x{6E61}\x{6E62}\x{6E63}\x{6E64}\x{6E65}' . + '\x{6E66}\x{6E67}\x{6E68}\x{6E69}\x{6E6A}\x{6E6B}\x{6E6D}\x{6E6E}\x{6E6F}' . + '\x{6E70}\x{6E71}\x{6E72}\x{6E73}\x{6E74}\x{6E75}\x{6E77}\x{6E78}\x{6E79}' . + '\x{6E7E}\x{6E7F}\x{6E80}\x{6E81}\x{6E82}\x{6E83}\x{6E84}\x{6E85}\x{6E86}' . + '\x{6E87}\x{6E88}\x{6E89}\x{6E8A}\x{6E8D}\x{6E8E}\x{6E8F}\x{6E90}\x{6E91}' . + '\x{6E92}\x{6E93}\x{6E94}\x{6E96}\x{6E97}\x{6E98}\x{6E99}\x{6E9A}\x{6E9B}' . + '\x{6E9C}\x{6E9D}\x{6E9E}\x{6E9F}\x{6EA0}\x{6EA1}\x{6EA2}\x{6EA3}\x{6EA4}' . + '\x{6EA5}\x{6EA6}\x{6EA7}\x{6EA8}\x{6EA9}\x{6EAA}\x{6EAB}\x{6EAC}\x{6EAD}' . + '\x{6EAE}\x{6EAF}\x{6EB0}\x{6EB1}\x{6EB2}\x{6EB3}\x{6EB4}\x{6EB5}\x{6EB6}' . + '\x{6EB7}\x{6EB8}\x{6EB9}\x{6EBA}\x{6EBB}\x{6EBC}\x{6EBD}\x{6EBE}\x{6EBF}' . + '\x{6EC0}\x{6EC1}\x{6EC2}\x{6EC3}\x{6EC4}\x{6EC5}\x{6EC6}\x{6EC7}\x{6EC8}' . + '\x{6EC9}\x{6ECA}\x{6ECB}\x{6ECC}\x{6ECD}\x{6ECE}\x{6ECF}\x{6ED0}\x{6ED1}' . + '\x{6ED2}\x{6ED3}\x{6ED4}\x{6ED5}\x{6ED6}\x{6ED7}\x{6ED8}\x{6ED9}\x{6EDA}' . + '\x{6EDC}\x{6EDE}\x{6EDF}\x{6EE0}\x{6EE1}\x{6EE2}\x{6EE4}\x{6EE5}\x{6EE6}' . + '\x{6EE7}\x{6EE8}\x{6EE9}\x{6EEA}\x{6EEB}\x{6EEC}\x{6EED}\x{6EEE}\x{6EEF}' . + '\x{6EF0}\x{6EF1}\x{6EF2}\x{6EF3}\x{6EF4}\x{6EF5}\x{6EF6}\x{6EF7}\x{6EF8}' . + '\x{6EF9}\x{6EFA}\x{6EFB}\x{6EFC}\x{6EFD}\x{6EFE}\x{6EFF}\x{6F00}\x{6F01}' . + '\x{6F02}\x{6F03}\x{6F05}\x{6F06}\x{6F07}\x{6F08}\x{6F09}\x{6F0A}\x{6F0C}' . + '\x{6F0D}\x{6F0E}\x{6F0F}\x{6F10}\x{6F11}\x{6F12}\x{6F13}\x{6F14}\x{6F15}' . + '\x{6F16}\x{6F17}\x{6F18}\x{6F19}\x{6F1A}\x{6F1B}\x{6F1C}\x{6F1D}\x{6F1E}' . + '\x{6F1F}\x{6F20}\x{6F21}\x{6F22}\x{6F23}\x{6F24}\x{6F25}\x{6F26}\x{6F27}' . + '\x{6F28}\x{6F29}\x{6F2A}\x{6F2B}\x{6F2C}\x{6F2D}\x{6F2E}\x{6F2F}\x{6F30}' . + '\x{6F31}\x{6F32}\x{6F33}\x{6F34}\x{6F35}\x{6F36}\x{6F37}\x{6F38}\x{6F39}' . + '\x{6F3A}\x{6F3B}\x{6F3C}\x{6F3D}\x{6F3E}\x{6F3F}\x{6F40}\x{6F41}\x{6F43}' . + '\x{6F44}\x{6F45}\x{6F46}\x{6F47}\x{6F49}\x{6F4B}\x{6F4C}\x{6F4D}\x{6F4E}' . + '\x{6F4F}\x{6F50}\x{6F51}\x{6F52}\x{6F53}\x{6F54}\x{6F55}\x{6F56}\x{6F57}' . + '\x{6F58}\x{6F59}\x{6F5A}\x{6F5B}\x{6F5C}\x{6F5D}\x{6F5E}\x{6F5F}\x{6F60}' . + '\x{6F61}\x{6F62}\x{6F63}\x{6F64}\x{6F65}\x{6F66}\x{6F67}\x{6F68}\x{6F69}' . + '\x{6F6A}\x{6F6B}\x{6F6C}\x{6F6D}\x{6F6E}\x{6F6F}\x{6F70}\x{6F71}\x{6F72}' . + '\x{6F73}\x{6F74}\x{6F75}\x{6F76}\x{6F77}\x{6F78}\x{6F7A}\x{6F7B}\x{6F7C}' . + '\x{6F7D}\x{6F7E}\x{6F7F}\x{6F80}\x{6F81}\x{6F82}\x{6F83}\x{6F84}\x{6F85}' . + '\x{6F86}\x{6F87}\x{6F88}\x{6F89}\x{6F8A}\x{6F8B}\x{6F8C}\x{6F8D}\x{6F8E}' . + '\x{6F8F}\x{6F90}\x{6F91}\x{6F92}\x{6F93}\x{6F94}\x{6F95}\x{6F96}\x{6F97}' . + '\x{6F99}\x{6F9B}\x{6F9C}\x{6F9D}\x{6F9E}\x{6FA0}\x{6FA1}\x{6FA2}\x{6FA3}' . + '\x{6FA4}\x{6FA5}\x{6FA6}\x{6FA7}\x{6FA8}\x{6FA9}\x{6FAA}\x{6FAB}\x{6FAC}' . + '\x{6FAD}\x{6FAE}\x{6FAF}\x{6FB0}\x{6FB1}\x{6FB2}\x{6FB3}\x{6FB4}\x{6FB5}' . + '\x{6FB6}\x{6FB8}\x{6FB9}\x{6FBA}\x{6FBB}\x{6FBC}\x{6FBD}\x{6FBE}\x{6FBF}' . + '\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC4}\x{6FC6}\x{6FC7}\x{6FC8}\x{6FC9}' . + '\x{6FCA}\x{6FCB}\x{6FCC}\x{6FCD}\x{6FCE}\x{6FCF}\x{6FD1}\x{6FD2}\x{6FD4}' . + '\x{6FD5}\x{6FD6}\x{6FD7}\x{6FD8}\x{6FD9}\x{6FDA}\x{6FDB}\x{6FDC}\x{6FDD}' . + '\x{6FDE}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE2}\x{6FE3}\x{6FE4}\x{6FE5}\x{6FE6}' . + '\x{6FE7}\x{6FE8}\x{6FE9}\x{6FEA}\x{6FEB}\x{6FEC}\x{6FED}\x{6FEE}\x{6FEF}' . + '\x{6FF0}\x{6FF1}\x{6FF2}\x{6FF3}\x{6FF4}\x{6FF6}\x{6FF7}\x{6FF8}\x{6FF9}' . + '\x{6FFA}\x{6FFB}\x{6FFC}\x{6FFE}\x{6FFF}\x{7000}\x{7001}\x{7002}\x{7003}' . + '\x{7004}\x{7005}\x{7006}\x{7007}\x{7008}\x{7009}\x{700A}\x{700B}\x{700C}' . + '\x{700D}\x{700E}\x{700F}\x{7011}\x{7012}\x{7014}\x{7015}\x{7016}\x{7017}' . + '\x{7018}\x{7019}\x{701A}\x{701B}\x{701C}\x{701D}\x{701F}\x{7020}\x{7021}' . + '\x{7022}\x{7023}\x{7024}\x{7025}\x{7026}\x{7027}\x{7028}\x{7029}\x{702A}' . + '\x{702B}\x{702C}\x{702D}\x{702E}\x{702F}\x{7030}\x{7031}\x{7032}\x{7033}' . + '\x{7034}\x{7035}\x{7036}\x{7037}\x{7038}\x{7039}\x{703A}\x{703B}\x{703C}' . + '\x{703D}\x{703E}\x{703F}\x{7040}\x{7041}\x{7042}\x{7043}\x{7044}\x{7045}' . + '\x{7046}\x{7048}\x{7049}\x{704A}\x{704C}\x{704D}\x{704F}\x{7050}\x{7051}' . + '\x{7052}\x{7053}\x{7054}\x{7055}\x{7056}\x{7057}\x{7058}\x{7059}\x{705A}' . + '\x{705B}\x{705C}\x{705D}\x{705E}\x{705F}\x{7060}\x{7061}\x{7062}\x{7063}' . + '\x{7064}\x{7065}\x{7066}\x{7067}\x{7068}\x{7069}\x{706A}\x{706B}\x{706C}' . + '\x{706D}\x{706E}\x{706F}\x{7070}\x{7071}\x{7074}\x{7075}\x{7076}\x{7077}' . + '\x{7078}\x{7079}\x{707A}\x{707C}\x{707D}\x{707E}\x{707F}\x{7080}\x{7082}' . + '\x{7083}\x{7084}\x{7085}\x{7086}\x{7087}\x{7088}\x{7089}\x{708A}\x{708B}' . + '\x{708C}\x{708E}\x{708F}\x{7090}\x{7091}\x{7092}\x{7093}\x{7094}\x{7095}' . + '\x{7096}\x{7098}\x{7099}\x{709A}\x{709C}\x{709D}\x{709E}\x{709F}\x{70A0}' . + '\x{70A1}\x{70A2}\x{70A3}\x{70A4}\x{70A5}\x{70A6}\x{70A7}\x{70A8}\x{70A9}' . + '\x{70AB}\x{70AC}\x{70AD}\x{70AE}\x{70AF}\x{70B0}\x{70B1}\x{70B3}\x{70B4}' . + '\x{70B5}\x{70B7}\x{70B8}\x{70B9}\x{70BA}\x{70BB}\x{70BC}\x{70BD}\x{70BE}' . + '\x{70BF}\x{70C0}\x{70C1}\x{70C2}\x{70C3}\x{70C4}\x{70C5}\x{70C6}\x{70C7}' . + '\x{70C8}\x{70C9}\x{70CA}\x{70CB}\x{70CC}\x{70CD}\x{70CE}\x{70CF}\x{70D0}' . + '\x{70D1}\x{70D2}\x{70D3}\x{70D4}\x{70D6}\x{70D7}\x{70D8}\x{70D9}\x{70DA}' . + '\x{70DB}\x{70DC}\x{70DD}\x{70DE}\x{70DF}\x{70E0}\x{70E1}\x{70E2}\x{70E3}' . + '\x{70E4}\x{70E5}\x{70E6}\x{70E7}\x{70E8}\x{70E9}\x{70EA}\x{70EB}\x{70EC}' . + '\x{70ED}\x{70EE}\x{70EF}\x{70F0}\x{70F1}\x{70F2}\x{70F3}\x{70F4}\x{70F5}' . + '\x{70F6}\x{70F7}\x{70F8}\x{70F9}\x{70FA}\x{70FB}\x{70FC}\x{70FD}\x{70FF}' . + '\x{7100}\x{7101}\x{7102}\x{7103}\x{7104}\x{7105}\x{7106}\x{7107}\x{7109}' . + '\x{710A}\x{710B}\x{710C}\x{710D}\x{710E}\x{710F}\x{7110}\x{7111}\x{7112}' . + '\x{7113}\x{7115}\x{7116}\x{7117}\x{7118}\x{7119}\x{711A}\x{711B}\x{711C}' . + '\x{711D}\x{711E}\x{711F}\x{7120}\x{7121}\x{7122}\x{7123}\x{7125}\x{7126}' . + '\x{7127}\x{7128}\x{7129}\x{712A}\x{712B}\x{712C}\x{712D}\x{712E}\x{712F}' . + '\x{7130}\x{7131}\x{7132}\x{7135}\x{7136}\x{7137}\x{7138}\x{7139}\x{713A}' . + '\x{713B}\x{713D}\x{713E}\x{713F}\x{7140}\x{7141}\x{7142}\x{7143}\x{7144}' . + '\x{7145}\x{7146}\x{7147}\x{7148}\x{7149}\x{714A}\x{714B}\x{714C}\x{714D}' . + '\x{714E}\x{714F}\x{7150}\x{7151}\x{7152}\x{7153}\x{7154}\x{7156}\x{7158}' . + '\x{7159}\x{715A}\x{715B}\x{715C}\x{715D}\x{715E}\x{715F}\x{7160}\x{7161}' . + '\x{7162}\x{7163}\x{7164}\x{7165}\x{7166}\x{7167}\x{7168}\x{7169}\x{716A}' . + '\x{716C}\x{716E}\x{716F}\x{7170}\x{7171}\x{7172}\x{7173}\x{7174}\x{7175}' . + '\x{7176}\x{7177}\x{7178}\x{7179}\x{717A}\x{717B}\x{717C}\x{717D}\x{717E}' . + '\x{717F}\x{7180}\x{7181}\x{7182}\x{7183}\x{7184}\x{7185}\x{7186}\x{7187}' . + '\x{7188}\x{7189}\x{718A}\x{718B}\x{718C}\x{718E}\x{718F}\x{7190}\x{7191}' . + '\x{7192}\x{7193}\x{7194}\x{7195}\x{7197}\x{7198}\x{7199}\x{719A}\x{719B}' . + '\x{719C}\x{719D}\x{719E}\x{719F}\x{71A0}\x{71A1}\x{71A2}\x{71A3}\x{71A4}' . + '\x{71A5}\x{71A7}\x{71A8}\x{71A9}\x{71AA}\x{71AC}\x{71AD}\x{71AE}\x{71AF}' . + '\x{71B0}\x{71B1}\x{71B2}\x{71B3}\x{71B4}\x{71B5}\x{71B7}\x{71B8}\x{71B9}' . + '\x{71BA}\x{71BB}\x{71BC}\x{71BD}\x{71BE}\x{71BF}\x{71C0}\x{71C1}\x{71C2}' . + '\x{71C3}\x{71C4}\x{71C5}\x{71C6}\x{71C7}\x{71C8}\x{71C9}\x{71CA}\x{71CB}' . + '\x{71CD}\x{71CE}\x{71CF}\x{71D0}\x{71D1}\x{71D2}\x{71D4}\x{71D5}\x{71D6}' . + '\x{71D7}\x{71D8}\x{71D9}\x{71DA}\x{71DB}\x{71DC}\x{71DD}\x{71DE}\x{71DF}' . + '\x{71E0}\x{71E1}\x{71E2}\x{71E3}\x{71E4}\x{71E5}\x{71E6}\x{71E7}\x{71E8}' . + '\x{71E9}\x{71EA}\x{71EB}\x{71EC}\x{71ED}\x{71EE}\x{71EF}\x{71F0}\x{71F1}' . + '\x{71F2}\x{71F4}\x{71F5}\x{71F6}\x{71F7}\x{71F8}\x{71F9}\x{71FB}\x{71FC}' . + '\x{71FD}\x{71FE}\x{71FF}\x{7201}\x{7202}\x{7203}\x{7204}\x{7205}\x{7206}' . + '\x{7207}\x{7208}\x{7209}\x{720A}\x{720C}\x{720D}\x{720E}\x{720F}\x{7210}' . + '\x{7212}\x{7213}\x{7214}\x{7216}\x{7218}\x{7219}\x{721A}\x{721B}\x{721C}' . + '\x{721D}\x{721E}\x{721F}\x{7221}\x{7222}\x{7223}\x{7226}\x{7227}\x{7228}' . + '\x{7229}\x{722A}\x{722B}\x{722C}\x{722D}\x{722E}\x{7230}\x{7231}\x{7232}' . + '\x{7233}\x{7235}\x{7236}\x{7237}\x{7238}\x{7239}\x{723A}\x{723B}\x{723C}' . + '\x{723D}\x{723E}\x{723F}\x{7240}\x{7241}\x{7242}\x{7243}\x{7244}\x{7246}' . + '\x{7247}\x{7248}\x{7249}\x{724A}\x{724B}\x{724C}\x{724D}\x{724F}\x{7251}' . + '\x{7252}\x{7253}\x{7254}\x{7256}\x{7257}\x{7258}\x{7259}\x{725A}\x{725B}' . + '\x{725C}\x{725D}\x{725E}\x{725F}\x{7260}\x{7261}\x{7262}\x{7263}\x{7264}' . + '\x{7265}\x{7266}\x{7267}\x{7268}\x{7269}\x{726A}\x{726B}\x{726C}\x{726D}' . + '\x{726E}\x{726F}\x{7270}\x{7271}\x{7272}\x{7273}\x{7274}\x{7275}\x{7276}' . + '\x{7277}\x{7278}\x{7279}\x{727A}\x{727B}\x{727C}\x{727D}\x{727E}\x{727F}' . + '\x{7280}\x{7281}\x{7282}\x{7283}\x{7284}\x{7285}\x{7286}\x{7287}\x{7288}' . + '\x{7289}\x{728A}\x{728B}\x{728C}\x{728D}\x{728E}\x{728F}\x{7290}\x{7291}' . + '\x{7292}\x{7293}\x{7294}\x{7295}\x{7296}\x{7297}\x{7298}\x{7299}\x{729A}' . + '\x{729B}\x{729C}\x{729D}\x{729E}\x{729F}\x{72A1}\x{72A2}\x{72A3}\x{72A4}' . + '\x{72A5}\x{72A6}\x{72A7}\x{72A8}\x{72A9}\x{72AA}\x{72AC}\x{72AD}\x{72AE}' . + '\x{72AF}\x{72B0}\x{72B1}\x{72B2}\x{72B3}\x{72B4}\x{72B5}\x{72B6}\x{72B7}' . + '\x{72B8}\x{72B9}\x{72BA}\x{72BB}\x{72BC}\x{72BD}\x{72BF}\x{72C0}\x{72C1}' . + '\x{72C2}\x{72C3}\x{72C4}\x{72C5}\x{72C6}\x{72C7}\x{72C8}\x{72C9}\x{72CA}' . + '\x{72CB}\x{72CC}\x{72CD}\x{72CE}\x{72CF}\x{72D0}\x{72D1}\x{72D2}\x{72D3}' . + '\x{72D4}\x{72D5}\x{72D6}\x{72D7}\x{72D8}\x{72D9}\x{72DA}\x{72DB}\x{72DC}' . + '\x{72DD}\x{72DE}\x{72DF}\x{72E0}\x{72E1}\x{72E2}\x{72E3}\x{72E4}\x{72E5}' . + '\x{72E6}\x{72E7}\x{72E8}\x{72E9}\x{72EA}\x{72EB}\x{72EC}\x{72ED}\x{72EE}' . + '\x{72EF}\x{72F0}\x{72F1}\x{72F2}\x{72F3}\x{72F4}\x{72F5}\x{72F6}\x{72F7}' . + '\x{72F8}\x{72F9}\x{72FA}\x{72FB}\x{72FC}\x{72FD}\x{72FE}\x{72FF}\x{7300}' . + '\x{7301}\x{7303}\x{7304}\x{7305}\x{7306}\x{7307}\x{7308}\x{7309}\x{730A}' . + '\x{730B}\x{730C}\x{730D}\x{730E}\x{730F}\x{7311}\x{7312}\x{7313}\x{7314}' . + '\x{7315}\x{7316}\x{7317}\x{7318}\x{7319}\x{731A}\x{731B}\x{731C}\x{731D}' . + '\x{731E}\x{7320}\x{7321}\x{7322}\x{7323}\x{7324}\x{7325}\x{7326}\x{7327}' . + '\x{7329}\x{732A}\x{732B}\x{732C}\x{732D}\x{732E}\x{7330}\x{7331}\x{7332}' . + '\x{7333}\x{7334}\x{7335}\x{7336}\x{7337}\x{7338}\x{7339}\x{733A}\x{733B}' . + '\x{733C}\x{733D}\x{733E}\x{733F}\x{7340}\x{7341}\x{7342}\x{7343}\x{7344}' . + '\x{7345}\x{7346}\x{7347}\x{7348}\x{7349}\x{734A}\x{734B}\x{734C}\x{734D}' . + '\x{734E}\x{7350}\x{7351}\x{7352}\x{7354}\x{7355}\x{7356}\x{7357}\x{7358}' . + '\x{7359}\x{735A}\x{735B}\x{735C}\x{735D}\x{735E}\x{735F}\x{7360}\x{7361}' . + '\x{7362}\x{7364}\x{7365}\x{7366}\x{7367}\x{7368}\x{7369}\x{736A}\x{736B}' . + '\x{736C}\x{736D}\x{736E}\x{736F}\x{7370}\x{7371}\x{7372}\x{7373}\x{7374}' . + '\x{7375}\x{7376}\x{7377}\x{7378}\x{7379}\x{737A}\x{737B}\x{737C}\x{737D}' . + '\x{737E}\x{737F}\x{7380}\x{7381}\x{7382}\x{7383}\x{7384}\x{7385}\x{7386}' . + '\x{7387}\x{7388}\x{7389}\x{738A}\x{738B}\x{738C}\x{738D}\x{738E}\x{738F}' . + '\x{7390}\x{7391}\x{7392}\x{7393}\x{7394}\x{7395}\x{7396}\x{7397}\x{7398}' . + '\x{7399}\x{739A}\x{739B}\x{739D}\x{739E}\x{739F}\x{73A0}\x{73A1}\x{73A2}' . + '\x{73A3}\x{73A4}\x{73A5}\x{73A6}\x{73A7}\x{73A8}\x{73A9}\x{73AA}\x{73AB}' . + '\x{73AC}\x{73AD}\x{73AE}\x{73AF}\x{73B0}\x{73B1}\x{73B2}\x{73B3}\x{73B4}' . + '\x{73B5}\x{73B6}\x{73B7}\x{73B8}\x{73B9}\x{73BA}\x{73BB}\x{73BC}\x{73BD}' . + '\x{73BE}\x{73BF}\x{73C0}\x{73C2}\x{73C3}\x{73C4}\x{73C5}\x{73C6}\x{73C7}' . + '\x{73C8}\x{73C9}\x{73CA}\x{73CB}\x{73CC}\x{73CD}\x{73CE}\x{73CF}\x{73D0}' . + '\x{73D1}\x{73D2}\x{73D3}\x{73D4}\x{73D5}\x{73D6}\x{73D7}\x{73D8}\x{73D9}' . + '\x{73DA}\x{73DB}\x{73DC}\x{73DD}\x{73DE}\x{73DF}\x{73E0}\x{73E2}\x{73E3}' . + '\x{73E5}\x{73E6}\x{73E7}\x{73E8}\x{73E9}\x{73EA}\x{73EB}\x{73EC}\x{73ED}' . + '\x{73EE}\x{73EF}\x{73F0}\x{73F1}\x{73F2}\x{73F4}\x{73F5}\x{73F6}\x{73F7}' . + '\x{73F8}\x{73F9}\x{73FA}\x{73FC}\x{73FD}\x{73FE}\x{73FF}\x{7400}\x{7401}' . + '\x{7402}\x{7403}\x{7404}\x{7405}\x{7406}\x{7407}\x{7408}\x{7409}\x{740A}' . + '\x{740B}\x{740C}\x{740D}\x{740E}\x{740F}\x{7410}\x{7411}\x{7412}\x{7413}' . + '\x{7414}\x{7415}\x{7416}\x{7417}\x{7419}\x{741A}\x{741B}\x{741C}\x{741D}' . + '\x{741E}\x{741F}\x{7420}\x{7421}\x{7422}\x{7423}\x{7424}\x{7425}\x{7426}' . + '\x{7427}\x{7428}\x{7429}\x{742A}\x{742B}\x{742C}\x{742D}\x{742E}\x{742F}' . + '\x{7430}\x{7431}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{7437}\x{7438}' . + '\x{743A}\x{743B}\x{743C}\x{743D}\x{743F}\x{7440}\x{7441}\x{7442}\x{7443}' . + '\x{7444}\x{7445}\x{7446}\x{7448}\x{744A}\x{744B}\x{744C}\x{744D}\x{744E}' . + '\x{744F}\x{7450}\x{7451}\x{7452}\x{7453}\x{7454}\x{7455}\x{7456}\x{7457}' . + '\x{7459}\x{745A}\x{745B}\x{745C}\x{745D}\x{745E}\x{745F}\x{7461}\x{7462}' . + '\x{7463}\x{7464}\x{7465}\x{7466}\x{7467}\x{7468}\x{7469}\x{746A}\x{746B}' . + '\x{746C}\x{746D}\x{746E}\x{746F}\x{7470}\x{7471}\x{7472}\x{7473}\x{7474}' . + '\x{7475}\x{7476}\x{7477}\x{7478}\x{7479}\x{747A}\x{747C}\x{747D}\x{747E}' . + '\x{747F}\x{7480}\x{7481}\x{7482}\x{7483}\x{7485}\x{7486}\x{7487}\x{7488}' . + '\x{7489}\x{748A}\x{748B}\x{748C}\x{748D}\x{748E}\x{748F}\x{7490}\x{7491}' . + '\x{7492}\x{7493}\x{7494}\x{7495}\x{7497}\x{7498}\x{7499}\x{749A}\x{749B}' . + '\x{749C}\x{749E}\x{749F}\x{74A0}\x{74A1}\x{74A3}\x{74A4}\x{74A5}\x{74A6}' . + '\x{74A7}\x{74A8}\x{74A9}\x{74AA}\x{74AB}\x{74AC}\x{74AD}\x{74AE}\x{74AF}' . + '\x{74B0}\x{74B1}\x{74B2}\x{74B3}\x{74B4}\x{74B5}\x{74B6}\x{74B7}\x{74B8}' . + '\x{74B9}\x{74BA}\x{74BB}\x{74BC}\x{74BD}\x{74BE}\x{74BF}\x{74C0}\x{74C1}' . + '\x{74C2}\x{74C3}\x{74C4}\x{74C5}\x{74C6}\x{74CA}\x{74CB}\x{74CD}\x{74CE}' . + '\x{74CF}\x{74D0}\x{74D1}\x{74D2}\x{74D3}\x{74D4}\x{74D5}\x{74D6}\x{74D7}' . + '\x{74D8}\x{74D9}\x{74DA}\x{74DB}\x{74DC}\x{74DD}\x{74DE}\x{74DF}\x{74E0}' . + '\x{74E1}\x{74E2}\x{74E3}\x{74E4}\x{74E5}\x{74E6}\x{74E7}\x{74E8}\x{74E9}' . + '\x{74EA}\x{74EC}\x{74ED}\x{74EE}\x{74EF}\x{74F0}\x{74F1}\x{74F2}\x{74F3}' . + '\x{74F4}\x{74F5}\x{74F6}\x{74F7}\x{74F8}\x{74F9}\x{74FA}\x{74FB}\x{74FC}' . + '\x{74FD}\x{74FE}\x{74FF}\x{7500}\x{7501}\x{7502}\x{7503}\x{7504}\x{7505}' . + '\x{7506}\x{7507}\x{7508}\x{7509}\x{750A}\x{750B}\x{750C}\x{750D}\x{750F}' . + '\x{7510}\x{7511}\x{7512}\x{7513}\x{7514}\x{7515}\x{7516}\x{7517}\x{7518}' . + '\x{7519}\x{751A}\x{751B}\x{751C}\x{751D}\x{751E}\x{751F}\x{7521}\x{7522}' . + '\x{7523}\x{7524}\x{7525}\x{7526}\x{7527}\x{7528}\x{7529}\x{752A}\x{752B}' . + '\x{752C}\x{752D}\x{752E}\x{752F}\x{7530}\x{7531}\x{7532}\x{7533}\x{7535}' . + '\x{7536}\x{7537}\x{7538}\x{7539}\x{753A}\x{753B}\x{753C}\x{753D}\x{753E}' . + '\x{753F}\x{7540}\x{7542}\x{7543}\x{7544}\x{7545}\x{7546}\x{7547}\x{7548}' . + '\x{7549}\x{754B}\x{754C}\x{754D}\x{754E}\x{754F}\x{7550}\x{7551}\x{7553}' . + '\x{7554}\x{7556}\x{7557}\x{7558}\x{7559}\x{755A}\x{755B}\x{755C}\x{755D}' . + '\x{755F}\x{7560}\x{7562}\x{7563}\x{7564}\x{7565}\x{7566}\x{7567}\x{7568}' . + '\x{7569}\x{756A}\x{756B}\x{756C}\x{756D}\x{756E}\x{756F}\x{7570}\x{7572}' . + '\x{7574}\x{7575}\x{7576}\x{7577}\x{7578}\x{7579}\x{757C}\x{757D}\x{757E}' . + '\x{757F}\x{7580}\x{7581}\x{7582}\x{7583}\x{7584}\x{7586}\x{7587}\x{7588}' . + '\x{7589}\x{758A}\x{758B}\x{758C}\x{758D}\x{758F}\x{7590}\x{7591}\x{7592}' . + '\x{7593}\x{7594}\x{7595}\x{7596}\x{7597}\x{7598}\x{7599}\x{759A}\x{759B}' . + '\x{759C}\x{759D}\x{759E}\x{759F}\x{75A0}\x{75A1}\x{75A2}\x{75A3}\x{75A4}' . + '\x{75A5}\x{75A6}\x{75A7}\x{75A8}\x{75AA}\x{75AB}\x{75AC}\x{75AD}\x{75AE}' . + '\x{75AF}\x{75B0}\x{75B1}\x{75B2}\x{75B3}\x{75B4}\x{75B5}\x{75B6}\x{75B8}' . + '\x{75B9}\x{75BA}\x{75BB}\x{75BC}\x{75BD}\x{75BE}\x{75BF}\x{75C0}\x{75C1}' . + '\x{75C2}\x{75C3}\x{75C4}\x{75C5}\x{75C6}\x{75C7}\x{75C8}\x{75C9}\x{75CA}' . + '\x{75CB}\x{75CC}\x{75CD}\x{75CE}\x{75CF}\x{75D0}\x{75D1}\x{75D2}\x{75D3}' . + '\x{75D4}\x{75D5}\x{75D6}\x{75D7}\x{75D8}\x{75D9}\x{75DA}\x{75DB}\x{75DD}' . + '\x{75DE}\x{75DF}\x{75E0}\x{75E1}\x{75E2}\x{75E3}\x{75E4}\x{75E5}\x{75E6}' . + '\x{75E7}\x{75E8}\x{75EA}\x{75EB}\x{75EC}\x{75ED}\x{75EF}\x{75F0}\x{75F1}' . + '\x{75F2}\x{75F3}\x{75F4}\x{75F5}\x{75F6}\x{75F7}\x{75F8}\x{75F9}\x{75FA}' . + '\x{75FB}\x{75FC}\x{75FD}\x{75FE}\x{75FF}\x{7600}\x{7601}\x{7602}\x{7603}' . + '\x{7604}\x{7605}\x{7606}\x{7607}\x{7608}\x{7609}\x{760A}\x{760B}\x{760C}' . + '\x{760D}\x{760E}\x{760F}\x{7610}\x{7611}\x{7612}\x{7613}\x{7614}\x{7615}' . + '\x{7616}\x{7617}\x{7618}\x{7619}\x{761A}\x{761B}\x{761C}\x{761D}\x{761E}' . + '\x{761F}\x{7620}\x{7621}\x{7622}\x{7623}\x{7624}\x{7625}\x{7626}\x{7627}' . + '\x{7628}\x{7629}\x{762A}\x{762B}\x{762D}\x{762E}\x{762F}\x{7630}\x{7631}' . + '\x{7632}\x{7633}\x{7634}\x{7635}\x{7636}\x{7637}\x{7638}\x{7639}\x{763A}' . + '\x{763B}\x{763C}\x{763D}\x{763E}\x{763F}\x{7640}\x{7641}\x{7642}\x{7643}' . + '\x{7646}\x{7647}\x{7648}\x{7649}\x{764A}\x{764B}\x{764C}\x{764D}\x{764F}' . + '\x{7650}\x{7652}\x{7653}\x{7654}\x{7656}\x{7657}\x{7658}\x{7659}\x{765A}' . + '\x{765B}\x{765C}\x{765D}\x{765E}\x{765F}\x{7660}\x{7661}\x{7662}\x{7663}' . + '\x{7664}\x{7665}\x{7666}\x{7667}\x{7668}\x{7669}\x{766A}\x{766B}\x{766C}' . + '\x{766D}\x{766E}\x{766F}\x{7670}\x{7671}\x{7672}\x{7674}\x{7675}\x{7676}' . + '\x{7677}\x{7678}\x{7679}\x{767B}\x{767C}\x{767D}\x{767E}\x{767F}\x{7680}' . + '\x{7681}\x{7682}\x{7683}\x{7684}\x{7685}\x{7686}\x{7687}\x{7688}\x{7689}' . + '\x{768A}\x{768B}\x{768C}\x{768E}\x{768F}\x{7690}\x{7691}\x{7692}\x{7693}' . + '\x{7694}\x{7695}\x{7696}\x{7697}\x{7698}\x{7699}\x{769A}\x{769B}\x{769C}' . + '\x{769D}\x{769E}\x{769F}\x{76A0}\x{76A3}\x{76A4}\x{76A6}\x{76A7}\x{76A9}' . + '\x{76AA}\x{76AB}\x{76AC}\x{76AD}\x{76AE}\x{76AF}\x{76B0}\x{76B1}\x{76B2}' . + '\x{76B4}\x{76B5}\x{76B7}\x{76B8}\x{76BA}\x{76BB}\x{76BC}\x{76BD}\x{76BE}' . + '\x{76BF}\x{76C0}\x{76C2}\x{76C3}\x{76C4}\x{76C5}\x{76C6}\x{76C7}\x{76C8}' . + '\x{76C9}\x{76CA}\x{76CD}\x{76CE}\x{76CF}\x{76D0}\x{76D1}\x{76D2}\x{76D3}' . + '\x{76D4}\x{76D5}\x{76D6}\x{76D7}\x{76D8}\x{76DA}\x{76DB}\x{76DC}\x{76DD}' . + '\x{76DE}\x{76DF}\x{76E0}\x{76E1}\x{76E2}\x{76E3}\x{76E4}\x{76E5}\x{76E6}' . + '\x{76E7}\x{76E8}\x{76E9}\x{76EA}\x{76EC}\x{76ED}\x{76EE}\x{76EF}\x{76F0}' . + '\x{76F1}\x{76F2}\x{76F3}\x{76F4}\x{76F5}\x{76F6}\x{76F7}\x{76F8}\x{76F9}' . + '\x{76FA}\x{76FB}\x{76FC}\x{76FD}\x{76FE}\x{76FF}\x{7701}\x{7703}\x{7704}' . + '\x{7705}\x{7706}\x{7707}\x{7708}\x{7709}\x{770A}\x{770B}\x{770C}\x{770D}' . + '\x{770F}\x{7710}\x{7711}\x{7712}\x{7713}\x{7714}\x{7715}\x{7716}\x{7717}' . + '\x{7718}\x{7719}\x{771A}\x{771B}\x{771C}\x{771D}\x{771E}\x{771F}\x{7720}' . + '\x{7722}\x{7723}\x{7725}\x{7726}\x{7727}\x{7728}\x{7729}\x{772A}\x{772C}' . + '\x{772D}\x{772E}\x{772F}\x{7730}\x{7731}\x{7732}\x{7733}\x{7734}\x{7735}' . + '\x{7736}\x{7737}\x{7738}\x{7739}\x{773A}\x{773B}\x{773C}\x{773D}\x{773E}' . + '\x{7740}\x{7741}\x{7743}\x{7744}\x{7745}\x{7746}\x{7747}\x{7748}\x{7749}' . + '\x{774A}\x{774B}\x{774C}\x{774D}\x{774E}\x{774F}\x{7750}\x{7751}\x{7752}' . + '\x{7753}\x{7754}\x{7755}\x{7756}\x{7757}\x{7758}\x{7759}\x{775A}\x{775B}' . + '\x{775C}\x{775D}\x{775E}\x{775F}\x{7760}\x{7761}\x{7762}\x{7763}\x{7765}' . + '\x{7766}\x{7767}\x{7768}\x{7769}\x{776A}\x{776B}\x{776C}\x{776D}\x{776E}' . + '\x{776F}\x{7770}\x{7771}\x{7772}\x{7773}\x{7774}\x{7775}\x{7776}\x{7777}' . + '\x{7778}\x{7779}\x{777A}\x{777B}\x{777C}\x{777D}\x{777E}\x{777F}\x{7780}' . + '\x{7781}\x{7782}\x{7783}\x{7784}\x{7785}\x{7786}\x{7787}\x{7788}\x{7789}' . + '\x{778A}\x{778B}\x{778C}\x{778D}\x{778E}\x{778F}\x{7790}\x{7791}\x{7792}' . + '\x{7793}\x{7794}\x{7795}\x{7797}\x{7798}\x{7799}\x{779A}\x{779B}\x{779C}' . + '\x{779D}\x{779E}\x{779F}\x{77A0}\x{77A1}\x{77A2}\x{77A3}\x{77A5}\x{77A6}' . + '\x{77A7}\x{77A8}\x{77A9}\x{77AA}\x{77AB}\x{77AC}\x{77AD}\x{77AE}\x{77AF}' . + '\x{77B0}\x{77B1}\x{77B2}\x{77B3}\x{77B4}\x{77B5}\x{77B6}\x{77B7}\x{77B8}' . + '\x{77B9}\x{77BA}\x{77BB}\x{77BC}\x{77BD}\x{77BF}\x{77C0}\x{77C2}\x{77C3}' . + '\x{77C4}\x{77C5}\x{77C6}\x{77C7}\x{77C8}\x{77C9}\x{77CA}\x{77CB}\x{77CC}' . + '\x{77CD}\x{77CE}\x{77CF}\x{77D0}\x{77D1}\x{77D3}\x{77D4}\x{77D5}\x{77D6}' . + '\x{77D7}\x{77D8}\x{77D9}\x{77DA}\x{77DB}\x{77DC}\x{77DE}\x{77DF}\x{77E0}' . + '\x{77E1}\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E8}\x{77E9}\x{77EA}\x{77EB}' . + '\x{77EC}\x{77ED}\x{77EE}\x{77EF}\x{77F0}\x{77F1}\x{77F2}\x{77F3}\x{77F6}' . + '\x{77F7}\x{77F8}\x{77F9}\x{77FA}\x{77FB}\x{77FC}\x{77FD}\x{77FE}\x{77FF}' . + '\x{7800}\x{7801}\x{7802}\x{7803}\x{7804}\x{7805}\x{7806}\x{7808}\x{7809}' . + '\x{780A}\x{780B}\x{780C}\x{780D}\x{780E}\x{780F}\x{7810}\x{7811}\x{7812}' . + '\x{7813}\x{7814}\x{7815}\x{7816}\x{7817}\x{7818}\x{7819}\x{781A}\x{781B}' . + '\x{781C}\x{781D}\x{781E}\x{781F}\x{7820}\x{7821}\x{7822}\x{7823}\x{7825}' . + '\x{7826}\x{7827}\x{7828}\x{7829}\x{782A}\x{782B}\x{782C}\x{782D}\x{782E}' . + '\x{782F}\x{7830}\x{7831}\x{7832}\x{7833}\x{7834}\x{7835}\x{7837}\x{7838}' . + '\x{7839}\x{783A}\x{783B}\x{783C}\x{783D}\x{783E}\x{7840}\x{7841}\x{7843}' . + '\x{7844}\x{7845}\x{7847}\x{7848}\x{7849}\x{784A}\x{784C}\x{784D}\x{784E}' . + '\x{7850}\x{7851}\x{7852}\x{7853}\x{7854}\x{7855}\x{7856}\x{7857}\x{7858}' . + '\x{7859}\x{785A}\x{785B}\x{785C}\x{785D}\x{785E}\x{785F}\x{7860}\x{7861}' . + '\x{7862}\x{7863}\x{7864}\x{7865}\x{7866}\x{7867}\x{7868}\x{7869}\x{786A}' . + '\x{786B}\x{786C}\x{786D}\x{786E}\x{786F}\x{7870}\x{7871}\x{7872}\x{7873}' . + '\x{7874}\x{7875}\x{7877}\x{7878}\x{7879}\x{787A}\x{787B}\x{787C}\x{787D}' . + '\x{787E}\x{787F}\x{7880}\x{7881}\x{7882}\x{7883}\x{7884}\x{7885}\x{7886}' . + '\x{7887}\x{7889}\x{788A}\x{788B}\x{788C}\x{788D}\x{788E}\x{788F}\x{7890}' . + '\x{7891}\x{7892}\x{7893}\x{7894}\x{7895}\x{7896}\x{7897}\x{7898}\x{7899}' . + '\x{789A}\x{789B}\x{789C}\x{789D}\x{789E}\x{789F}\x{78A0}\x{78A1}\x{78A2}' . + '\x{78A3}\x{78A4}\x{78A5}\x{78A6}\x{78A7}\x{78A8}\x{78A9}\x{78AA}\x{78AB}' . + '\x{78AC}\x{78AD}\x{78AE}\x{78AF}\x{78B0}\x{78B1}\x{78B2}\x{78B3}\x{78B4}' . + '\x{78B5}\x{78B6}\x{78B7}\x{78B8}\x{78B9}\x{78BA}\x{78BB}\x{78BC}\x{78BD}' . + '\x{78BE}\x{78BF}\x{78C0}\x{78C1}\x{78C3}\x{78C4}\x{78C5}\x{78C6}\x{78C8}' . + '\x{78C9}\x{78CA}\x{78CB}\x{78CC}\x{78CD}\x{78CE}\x{78CF}\x{78D0}\x{78D1}' . + '\x{78D3}\x{78D4}\x{78D5}\x{78D6}\x{78D7}\x{78D8}\x{78D9}\x{78DA}\x{78DB}' . + '\x{78DC}\x{78DD}\x{78DE}\x{78DF}\x{78E0}\x{78E1}\x{78E2}\x{78E3}\x{78E4}' . + '\x{78E5}\x{78E6}\x{78E7}\x{78E8}\x{78E9}\x{78EA}\x{78EB}\x{78EC}\x{78ED}' . + '\x{78EE}\x{78EF}\x{78F1}\x{78F2}\x{78F3}\x{78F4}\x{78F5}\x{78F6}\x{78F7}' . + '\x{78F9}\x{78FA}\x{78FB}\x{78FC}\x{78FD}\x{78FE}\x{78FF}\x{7901}\x{7902}' . + '\x{7903}\x{7904}\x{7905}\x{7906}\x{7907}\x{7909}\x{790A}\x{790B}\x{790C}' . + '\x{790E}\x{790F}\x{7910}\x{7911}\x{7912}\x{7913}\x{7914}\x{7916}\x{7917}' . + '\x{7918}\x{7919}\x{791A}\x{791B}\x{791C}\x{791D}\x{791E}\x{7921}\x{7922}' . + '\x{7923}\x{7924}\x{7925}\x{7926}\x{7927}\x{7928}\x{7929}\x{792A}\x{792B}' . + '\x{792C}\x{792D}\x{792E}\x{792F}\x{7930}\x{7931}\x{7933}\x{7934}\x{7935}' . + '\x{7937}\x{7938}\x{7939}\x{793A}\x{793B}\x{793C}\x{793D}\x{793E}\x{793F}' . + '\x{7940}\x{7941}\x{7942}\x{7943}\x{7944}\x{7945}\x{7946}\x{7947}\x{7948}' . + '\x{7949}\x{794A}\x{794B}\x{794C}\x{794D}\x{794E}\x{794F}\x{7950}\x{7951}' . + '\x{7952}\x{7953}\x{7954}\x{7955}\x{7956}\x{7957}\x{7958}\x{795A}\x{795B}' . + '\x{795C}\x{795D}\x{795E}\x{795F}\x{7960}\x{7961}\x{7962}\x{7963}\x{7964}' . + '\x{7965}\x{7966}\x{7967}\x{7968}\x{7969}\x{796A}\x{796B}\x{796D}\x{796F}' . + '\x{7970}\x{7971}\x{7972}\x{7973}\x{7974}\x{7977}\x{7978}\x{7979}\x{797A}' . + '\x{797B}\x{797C}\x{797D}\x{797E}\x{797F}\x{7980}\x{7981}\x{7982}\x{7983}' . + '\x{7984}\x{7985}\x{7988}\x{7989}\x{798A}\x{798B}\x{798C}\x{798D}\x{798E}' . + '\x{798F}\x{7990}\x{7991}\x{7992}\x{7993}\x{7994}\x{7995}\x{7996}\x{7997}' . + '\x{7998}\x{7999}\x{799A}\x{799B}\x{799C}\x{799F}\x{79A0}\x{79A1}\x{79A2}' . + '\x{79A3}\x{79A4}\x{79A5}\x{79A6}\x{79A7}\x{79A8}\x{79AA}\x{79AB}\x{79AC}' . + '\x{79AD}\x{79AE}\x{79AF}\x{79B0}\x{79B1}\x{79B2}\x{79B3}\x{79B4}\x{79B5}' . + '\x{79B6}\x{79B7}\x{79B8}\x{79B9}\x{79BA}\x{79BB}\x{79BD}\x{79BE}\x{79BF}' . + '\x{79C0}\x{79C1}\x{79C2}\x{79C3}\x{79C5}\x{79C6}\x{79C8}\x{79C9}\x{79CA}' . + '\x{79CB}\x{79CD}\x{79CE}\x{79CF}\x{79D0}\x{79D1}\x{79D2}\x{79D3}\x{79D5}' . + '\x{79D6}\x{79D8}\x{79D9}\x{79DA}\x{79DB}\x{79DC}\x{79DD}\x{79DE}\x{79DF}' . + '\x{79E0}\x{79E1}\x{79E2}\x{79E3}\x{79E4}\x{79E5}\x{79E6}\x{79E7}\x{79E8}' . + '\x{79E9}\x{79EA}\x{79EB}\x{79EC}\x{79ED}\x{79EE}\x{79EF}\x{79F0}\x{79F1}' . + '\x{79F2}\x{79F3}\x{79F4}\x{79F5}\x{79F6}\x{79F7}\x{79F8}\x{79F9}\x{79FA}' . + '\x{79FB}\x{79FC}\x{79FD}\x{79FE}\x{79FF}\x{7A00}\x{7A02}\x{7A03}\x{7A04}' . + '\x{7A05}\x{7A06}\x{7A08}\x{7A0A}\x{7A0B}\x{7A0C}\x{7A0D}\x{7A0E}\x{7A0F}' . + '\x{7A10}\x{7A11}\x{7A12}\x{7A13}\x{7A14}\x{7A15}\x{7A16}\x{7A17}\x{7A18}' . + '\x{7A19}\x{7A1A}\x{7A1B}\x{7A1C}\x{7A1D}\x{7A1E}\x{7A1F}\x{7A20}\x{7A21}' . + '\x{7A22}\x{7A23}\x{7A24}\x{7A25}\x{7A26}\x{7A27}\x{7A28}\x{7A29}\x{7A2A}' . + '\x{7A2B}\x{7A2D}\x{7A2E}\x{7A2F}\x{7A30}\x{7A31}\x{7A32}\x{7A33}\x{7A34}' . + '\x{7A35}\x{7A37}\x{7A39}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' . + '\x{7A41}\x{7A42}\x{7A43}\x{7A44}\x{7A45}\x{7A46}\x{7A47}\x{7A48}\x{7A49}' . + '\x{7A4A}\x{7A4B}\x{7A4C}\x{7A4D}\x{7A4E}\x{7A50}\x{7A51}\x{7A52}\x{7A53}' . + '\x{7A54}\x{7A55}\x{7A56}\x{7A57}\x{7A58}\x{7A59}\x{7A5A}\x{7A5B}\x{7A5C}' . + '\x{7A5D}\x{7A5E}\x{7A5F}\x{7A60}\x{7A61}\x{7A62}\x{7A65}\x{7A66}\x{7A67}' . + '\x{7A68}\x{7A69}\x{7A6B}\x{7A6C}\x{7A6D}\x{7A6E}\x{7A70}\x{7A71}\x{7A72}' . + '\x{7A73}\x{7A74}\x{7A75}\x{7A76}\x{7A77}\x{7A78}\x{7A79}\x{7A7A}\x{7A7B}' . + '\x{7A7C}\x{7A7D}\x{7A7E}\x{7A7F}\x{7A80}\x{7A81}\x{7A83}\x{7A84}\x{7A85}' . + '\x{7A86}\x{7A87}\x{7A88}\x{7A89}\x{7A8A}\x{7A8B}\x{7A8C}\x{7A8D}\x{7A8E}' . + '\x{7A8F}\x{7A90}\x{7A91}\x{7A92}\x{7A93}\x{7A94}\x{7A95}\x{7A96}\x{7A97}' . + '\x{7A98}\x{7A99}\x{7A9C}\x{7A9D}\x{7A9E}\x{7A9F}\x{7AA0}\x{7AA1}\x{7AA2}' . + '\x{7AA3}\x{7AA4}\x{7AA5}\x{7AA6}\x{7AA7}\x{7AA8}\x{7AA9}\x{7AAA}\x{7AAB}' . + '\x{7AAC}\x{7AAD}\x{7AAE}\x{7AAF}\x{7AB0}\x{7AB1}\x{7AB2}\x{7AB3}\x{7AB4}' . + '\x{7AB5}\x{7AB6}\x{7AB7}\x{7AB8}\x{7ABA}\x{7ABE}\x{7ABF}\x{7AC0}\x{7AC1}' . + '\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}\x{7AC9}\x{7ACA}\x{7ACB}\x{7ACC}\x{7ACD}' . + '\x{7ACE}\x{7ACF}\x{7AD0}\x{7AD1}\x{7AD2}\x{7AD3}\x{7AD4}\x{7AD5}\x{7AD6}' . + '\x{7AD8}\x{7AD9}\x{7ADB}\x{7ADC}\x{7ADD}\x{7ADE}\x{7ADF}\x{7AE0}\x{7AE1}' . + '\x{7AE2}\x{7AE3}\x{7AE4}\x{7AE5}\x{7AE6}\x{7AE7}\x{7AE8}\x{7AEA}\x{7AEB}' . + '\x{7AEC}\x{7AED}\x{7AEE}\x{7AEF}\x{7AF0}\x{7AF1}\x{7AF2}\x{7AF3}\x{7AF4}' . + '\x{7AF6}\x{7AF7}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFB}\x{7AFD}\x{7AFE}\x{7AFF}' . + '\x{7B00}\x{7B01}\x{7B02}\x{7B03}\x{7B04}\x{7B05}\x{7B06}\x{7B08}\x{7B09}' . + '\x{7B0A}\x{7B0B}\x{7B0C}\x{7B0D}\x{7B0E}\x{7B0F}\x{7B10}\x{7B11}\x{7B12}' . + '\x{7B13}\x{7B14}\x{7B15}\x{7B16}\x{7B17}\x{7B18}\x{7B19}\x{7B1A}\x{7B1B}' . + '\x{7B1C}\x{7B1D}\x{7B1E}\x{7B20}\x{7B21}\x{7B22}\x{7B23}\x{7B24}\x{7B25}' . + '\x{7B26}\x{7B28}\x{7B2A}\x{7B2B}\x{7B2C}\x{7B2D}\x{7B2E}\x{7B2F}\x{7B30}' . + '\x{7B31}\x{7B32}\x{7B33}\x{7B34}\x{7B35}\x{7B36}\x{7B37}\x{7B38}\x{7B39}' . + '\x{7B3A}\x{7B3B}\x{7B3C}\x{7B3D}\x{7B3E}\x{7B3F}\x{7B40}\x{7B41}\x{7B43}' . + '\x{7B44}\x{7B45}\x{7B46}\x{7B47}\x{7B48}\x{7B49}\x{7B4A}\x{7B4B}\x{7B4C}' . + '\x{7B4D}\x{7B4E}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B55}\x{7B56}' . + '\x{7B57}\x{7B58}\x{7B59}\x{7B5A}\x{7B5B}\x{7B5C}\x{7B5D}\x{7B5E}\x{7B5F}' . + '\x{7B60}\x{7B61}\x{7B62}\x{7B63}\x{7B64}\x{7B65}\x{7B66}\x{7B67}\x{7B68}' . + '\x{7B69}\x{7B6A}\x{7B6B}\x{7B6C}\x{7B6D}\x{7B6E}\x{7B70}\x{7B71}\x{7B72}' . + '\x{7B73}\x{7B74}\x{7B75}\x{7B76}\x{7B77}\x{7B78}\x{7B79}\x{7B7B}\x{7B7C}' . + '\x{7B7D}\x{7B7E}\x{7B7F}\x{7B80}\x{7B81}\x{7B82}\x{7B83}\x{7B84}\x{7B85}' . + '\x{7B87}\x{7B88}\x{7B89}\x{7B8A}\x{7B8B}\x{7B8C}\x{7B8D}\x{7B8E}\x{7B8F}' . + '\x{7B90}\x{7B91}\x{7B93}\x{7B94}\x{7B95}\x{7B96}\x{7B97}\x{7B98}\x{7B99}' . + '\x{7B9A}\x{7B9B}\x{7B9C}\x{7B9D}\x{7B9E}\x{7B9F}\x{7BA0}\x{7BA1}\x{7BA2}' . + '\x{7BA4}\x{7BA6}\x{7BA7}\x{7BA8}\x{7BA9}\x{7BAA}\x{7BAB}\x{7BAC}\x{7BAD}' . + '\x{7BAE}\x{7BAF}\x{7BB1}\x{7BB3}\x{7BB4}\x{7BB5}\x{7BB6}\x{7BB7}\x{7BB8}' . + '\x{7BB9}\x{7BBA}\x{7BBB}\x{7BBC}\x{7BBD}\x{7BBE}\x{7BBF}\x{7BC0}\x{7BC1}' . + '\x{7BC2}\x{7BC3}\x{7BC4}\x{7BC5}\x{7BC6}\x{7BC7}\x{7BC8}\x{7BC9}\x{7BCA}' . + '\x{7BCB}\x{7BCC}\x{7BCD}\x{7BCE}\x{7BD0}\x{7BD1}\x{7BD2}\x{7BD3}\x{7BD4}' . + '\x{7BD5}\x{7BD6}\x{7BD7}\x{7BD8}\x{7BD9}\x{7BDA}\x{7BDB}\x{7BDC}\x{7BDD}' . + '\x{7BDE}\x{7BDF}\x{7BE0}\x{7BE1}\x{7BE2}\x{7BE3}\x{7BE4}\x{7BE5}\x{7BE6}' . + '\x{7BE7}\x{7BE8}\x{7BE9}\x{7BEA}\x{7BEB}\x{7BEC}\x{7BED}\x{7BEE}\x{7BEF}' . + '\x{7BF0}\x{7BF1}\x{7BF2}\x{7BF3}\x{7BF4}\x{7BF5}\x{7BF6}\x{7BF7}\x{7BF8}' . + '\x{7BF9}\x{7BFB}\x{7BFC}\x{7BFD}\x{7BFE}\x{7BFF}\x{7C00}\x{7C01}\x{7C02}' . + '\x{7C03}\x{7C04}\x{7C05}\x{7C06}\x{7C07}\x{7C08}\x{7C09}\x{7C0A}\x{7C0B}' . + '\x{7C0C}\x{7C0D}\x{7C0E}\x{7C0F}\x{7C10}\x{7C11}\x{7C12}\x{7C13}\x{7C15}' . + '\x{7C16}\x{7C17}\x{7C18}\x{7C19}\x{7C1A}\x{7C1C}\x{7C1D}\x{7C1E}\x{7C1F}' . + '\x{7C20}\x{7C21}\x{7C22}\x{7C23}\x{7C24}\x{7C25}\x{7C26}\x{7C27}\x{7C28}' . + '\x{7C29}\x{7C2A}\x{7C2B}\x{7C2C}\x{7C2D}\x{7C30}\x{7C31}\x{7C32}\x{7C33}' . + '\x{7C34}\x{7C35}\x{7C36}\x{7C37}\x{7C38}\x{7C39}\x{7C3A}\x{7C3B}\x{7C3C}' . + '\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C41}\x{7C42}\x{7C43}\x{7C44}\x{7C45}' . + '\x{7C46}\x{7C47}\x{7C48}\x{7C49}\x{7C4A}\x{7C4B}\x{7C4C}\x{7C4D}\x{7C4E}' . + '\x{7C50}\x{7C51}\x{7C53}\x{7C54}\x{7C56}\x{7C57}\x{7C58}\x{7C59}\x{7C5A}' . + '\x{7C5B}\x{7C5C}\x{7C5E}\x{7C5F}\x{7C60}\x{7C61}\x{7C62}\x{7C63}\x{7C64}' . + '\x{7C65}\x{7C66}\x{7C67}\x{7C68}\x{7C69}\x{7C6A}\x{7C6B}\x{7C6C}\x{7C6D}' . + '\x{7C6E}\x{7C6F}\x{7C70}\x{7C71}\x{7C72}\x{7C73}\x{7C74}\x{7C75}\x{7C77}' . + '\x{7C78}\x{7C79}\x{7C7A}\x{7C7B}\x{7C7C}\x{7C7D}\x{7C7E}\x{7C7F}\x{7C80}' . + '\x{7C81}\x{7C82}\x{7C84}\x{7C85}\x{7C86}\x{7C88}\x{7C89}\x{7C8A}\x{7C8B}' . + '\x{7C8C}\x{7C8D}\x{7C8E}\x{7C8F}\x{7C90}\x{7C91}\x{7C92}\x{7C94}\x{7C95}' . + '\x{7C96}\x{7C97}\x{7C98}\x{7C99}\x{7C9B}\x{7C9C}\x{7C9D}\x{7C9E}\x{7C9F}' . + '\x{7CA0}\x{7CA1}\x{7CA2}\x{7CA3}\x{7CA4}\x{7CA5}\x{7CA6}\x{7CA7}\x{7CA8}' . + '\x{7CA9}\x{7CAA}\x{7CAD}\x{7CAE}\x{7CAF}\x{7CB0}\x{7CB1}\x{7CB2}\x{7CB3}' . + '\x{7CB4}\x{7CB5}\x{7CB6}\x{7CB7}\x{7CB8}\x{7CB9}\x{7CBA}\x{7CBB}\x{7CBC}' . + '\x{7CBD}\x{7CBE}\x{7CBF}\x{7CC0}\x{7CC1}\x{7CC2}\x{7CC3}\x{7CC4}\x{7CC5}' . + '\x{7CC6}\x{7CC7}\x{7CC8}\x{7CC9}\x{7CCA}\x{7CCB}\x{7CCC}\x{7CCD}\x{7CCE}' . + '\x{7CCF}\x{7CD0}\x{7CD1}\x{7CD2}\x{7CD4}\x{7CD5}\x{7CD6}\x{7CD7}\x{7CD8}' . + '\x{7CD9}\x{7CDC}\x{7CDD}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE4}\x{7CE7}' . + '\x{7CE8}\x{7CE9}\x{7CEA}\x{7CEB}\x{7CEC}\x{7CED}\x{7CEE}\x{7CEF}\x{7CF0}' . + '\x{7CF1}\x{7CF2}\x{7CF3}\x{7CF4}\x{7CF5}\x{7CF6}\x{7CF7}\x{7CF8}\x{7CF9}' . + '\x{7CFA}\x{7CFB}\x{7CFD}\x{7CFE}\x{7D00}\x{7D01}\x{7D02}\x{7D03}\x{7D04}' . + '\x{7D05}\x{7D06}\x{7D07}\x{7D08}\x{7D09}\x{7D0A}\x{7D0B}\x{7D0C}\x{7D0D}' . + '\x{7D0E}\x{7D0F}\x{7D10}\x{7D11}\x{7D12}\x{7D13}\x{7D14}\x{7D15}\x{7D16}' . + '\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D1D}\x{7D1E}\x{7D1F}' . + '\x{7D20}\x{7D21}\x{7D22}\x{7D24}\x{7D25}\x{7D26}\x{7D27}\x{7D28}\x{7D29}' . + '\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D31}\x{7D32}\x{7D33}\x{7D34}' . + '\x{7D35}\x{7D36}\x{7D37}\x{7D38}\x{7D39}\x{7D3A}\x{7D3B}\x{7D3C}\x{7D3D}' . + '\x{7D3E}\x{7D3F}\x{7D40}\x{7D41}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}' . + '\x{7D47}\x{7D49}\x{7D4A}\x{7D4B}\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D51}' . + '\x{7D52}\x{7D53}\x{7D54}\x{7D55}\x{7D56}\x{7D57}\x{7D58}\x{7D59}\x{7D5B}' . + '\x{7D5C}\x{7D5D}\x{7D5E}\x{7D5F}\x{7D60}\x{7D61}\x{7D62}\x{7D63}\x{7D65}' . + '\x{7D66}\x{7D67}\x{7D68}\x{7D69}\x{7D6A}\x{7D6B}\x{7D6C}\x{7D6D}\x{7D6E}' . + '\x{7D6F}\x{7D70}\x{7D71}\x{7D72}\x{7D73}\x{7D74}\x{7D75}\x{7D76}\x{7D77}' . + '\x{7D79}\x{7D7A}\x{7D7B}\x{7D7C}\x{7D7D}\x{7D7E}\x{7D7F}\x{7D80}\x{7D81}' . + '\x{7D83}\x{7D84}\x{7D85}\x{7D86}\x{7D87}\x{7D88}\x{7D89}\x{7D8A}\x{7D8B}' . + '\x{7D8C}\x{7D8D}\x{7D8E}\x{7D8F}\x{7D90}\x{7D91}\x{7D92}\x{7D93}\x{7D94}' . + '\x{7D96}\x{7D97}\x{7D99}\x{7D9B}\x{7D9C}\x{7D9D}\x{7D9E}\x{7D9F}\x{7DA0}' . + '\x{7DA1}\x{7DA2}\x{7DA3}\x{7DA5}\x{7DA6}\x{7DA7}\x{7DA9}\x{7DAA}\x{7DAB}' . + '\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}\x{7DB1}\x{7DB2}\x{7DB3}\x{7DB4}' . + '\x{7DB5}\x{7DB6}\x{7DB7}\x{7DB8}\x{7DB9}\x{7DBA}\x{7DBB}\x{7DBC}\x{7DBD}' . + '\x{7DBE}\x{7DBF}\x{7DC0}\x{7DC1}\x{7DC2}\x{7DC3}\x{7DC4}\x{7DC5}\x{7DC6}' . + '\x{7DC7}\x{7DC8}\x{7DC9}\x{7DCA}\x{7DCB}\x{7DCC}\x{7DCE}\x{7DCF}\x{7DD0}' . + '\x{7DD1}\x{7DD2}\x{7DD4}\x{7DD5}\x{7DD6}\x{7DD7}\x{7DD8}\x{7DD9}\x{7DDA}' . + '\x{7DDB}\x{7DDD}\x{7DDE}\x{7DDF}\x{7DE0}\x{7DE1}\x{7DE2}\x{7DE3}\x{7DE6}' . + '\x{7DE7}\x{7DE8}\x{7DE9}\x{7DEA}\x{7DEC}\x{7DED}\x{7DEE}\x{7DEF}\x{7DF0}' . + '\x{7DF1}\x{7DF2}\x{7DF3}\x{7DF4}\x{7DF5}\x{7DF6}\x{7DF7}\x{7DF8}\x{7DF9}' . + '\x{7DFA}\x{7DFB}\x{7DFC}\x{7E00}\x{7E01}\x{7E02}\x{7E03}\x{7E04}\x{7E05}' . + '\x{7E06}\x{7E07}\x{7E08}\x{7E09}\x{7E0A}\x{7E0B}\x{7E0C}\x{7E0D}\x{7E0E}' . + '\x{7E0F}\x{7E10}\x{7E11}\x{7E12}\x{7E13}\x{7E14}\x{7E15}\x{7E16}\x{7E17}' . + '\x{7E19}\x{7E1A}\x{7E1B}\x{7E1C}\x{7E1D}\x{7E1E}\x{7E1F}\x{7E20}\x{7E21}' . + '\x{7E22}\x{7E23}\x{7E24}\x{7E25}\x{7E26}\x{7E27}\x{7E28}\x{7E29}\x{7E2A}' . + '\x{7E2B}\x{7E2C}\x{7E2D}\x{7E2E}\x{7E2F}\x{7E30}\x{7E31}\x{7E32}\x{7E33}' . + '\x{7E34}\x{7E35}\x{7E36}\x{7E37}\x{7E38}\x{7E39}\x{7E3A}\x{7E3B}\x{7E3C}' . + '\x{7E3D}\x{7E3E}\x{7E3F}\x{7E40}\x{7E41}\x{7E42}\x{7E43}\x{7E44}\x{7E45}' . + '\x{7E46}\x{7E47}\x{7E48}\x{7E49}\x{7E4C}\x{7E4D}\x{7E4E}\x{7E4F}\x{7E50}' . + '\x{7E51}\x{7E52}\x{7E53}\x{7E54}\x{7E55}\x{7E56}\x{7E57}\x{7E58}\x{7E59}' . + '\x{7E5A}\x{7E5C}\x{7E5D}\x{7E5E}\x{7E5F}\x{7E60}\x{7E61}\x{7E62}\x{7E63}' . + '\x{7E65}\x{7E66}\x{7E67}\x{7E68}\x{7E69}\x{7E6A}\x{7E6B}\x{7E6C}\x{7E6D}' . + '\x{7E6E}\x{7E6F}\x{7E70}\x{7E71}\x{7E72}\x{7E73}\x{7E74}\x{7E75}\x{7E76}' . + '\x{7E77}\x{7E78}\x{7E79}\x{7E7A}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7E}\x{7E7F}' . + '\x{7E80}\x{7E81}\x{7E82}\x{7E83}\x{7E84}\x{7E85}\x{7E86}\x{7E87}\x{7E88}' . + '\x{7E89}\x{7E8A}\x{7E8B}\x{7E8C}\x{7E8D}\x{7E8E}\x{7E8F}\x{7E90}\x{7E91}' . + '\x{7E92}\x{7E93}\x{7E94}\x{7E95}\x{7E96}\x{7E97}\x{7E98}\x{7E99}\x{7E9A}' . + '\x{7E9B}\x{7E9C}\x{7E9E}\x{7E9F}\x{7EA0}\x{7EA1}\x{7EA2}\x{7EA3}\x{7EA4}' . + '\x{7EA5}\x{7EA6}\x{7EA7}\x{7EA8}\x{7EA9}\x{7EAA}\x{7EAB}\x{7EAC}\x{7EAD}' . + '\x{7EAE}\x{7EAF}\x{7EB0}\x{7EB1}\x{7EB2}\x{7EB3}\x{7EB4}\x{7EB5}\x{7EB6}' . + '\x{7EB7}\x{7EB8}\x{7EB9}\x{7EBA}\x{7EBB}\x{7EBC}\x{7EBD}\x{7EBE}\x{7EBF}' . + '\x{7EC0}\x{7EC1}\x{7EC2}\x{7EC3}\x{7EC4}\x{7EC5}\x{7EC6}\x{7EC7}\x{7EC8}' . + '\x{7EC9}\x{7ECA}\x{7ECB}\x{7ECC}\x{7ECD}\x{7ECE}\x{7ECF}\x{7ED0}\x{7ED1}' . + '\x{7ED2}\x{7ED3}\x{7ED4}\x{7ED5}\x{7ED6}\x{7ED7}\x{7ED8}\x{7ED9}\x{7EDA}' . + '\x{7EDB}\x{7EDC}\x{7EDD}\x{7EDE}\x{7EDF}\x{7EE0}\x{7EE1}\x{7EE2}\x{7EE3}' . + '\x{7EE4}\x{7EE5}\x{7EE6}\x{7EE7}\x{7EE8}\x{7EE9}\x{7EEA}\x{7EEB}\x{7EEC}' . + '\x{7EED}\x{7EEE}\x{7EEF}\x{7EF0}\x{7EF1}\x{7EF2}\x{7EF3}\x{7EF4}\x{7EF5}' . + '\x{7EF6}\x{7EF7}\x{7EF8}\x{7EF9}\x{7EFA}\x{7EFB}\x{7EFC}\x{7EFD}\x{7EFE}' . + '\x{7EFF}\x{7F00}\x{7F01}\x{7F02}\x{7F03}\x{7F04}\x{7F05}\x{7F06}\x{7F07}' . + '\x{7F08}\x{7F09}\x{7F0A}\x{7F0B}\x{7F0C}\x{7F0D}\x{7F0E}\x{7F0F}\x{7F10}' . + '\x{7F11}\x{7F12}\x{7F13}\x{7F14}\x{7F15}\x{7F16}\x{7F17}\x{7F18}\x{7F19}' . + '\x{7F1A}\x{7F1B}\x{7F1C}\x{7F1D}\x{7F1E}\x{7F1F}\x{7F20}\x{7F21}\x{7F22}' . + '\x{7F23}\x{7F24}\x{7F25}\x{7F26}\x{7F27}\x{7F28}\x{7F29}\x{7F2A}\x{7F2B}' . + '\x{7F2C}\x{7F2D}\x{7F2E}\x{7F2F}\x{7F30}\x{7F31}\x{7F32}\x{7F33}\x{7F34}' . + '\x{7F35}\x{7F36}\x{7F37}\x{7F38}\x{7F39}\x{7F3A}\x{7F3D}\x{7F3E}\x{7F3F}' . + '\x{7F40}\x{7F42}\x{7F43}\x{7F44}\x{7F45}\x{7F47}\x{7F48}\x{7F49}\x{7F4A}' . + '\x{7F4B}\x{7F4C}\x{7F4D}\x{7F4E}\x{7F4F}\x{7F50}\x{7F51}\x{7F52}\x{7F53}' . + '\x{7F54}\x{7F55}\x{7F56}\x{7F57}\x{7F58}\x{7F5A}\x{7F5B}\x{7F5C}\x{7F5D}' . + '\x{7F5E}\x{7F5F}\x{7F60}\x{7F61}\x{7F62}\x{7F63}\x{7F64}\x{7F65}\x{7F66}' . + '\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6C}\x{7F6D}\x{7F6E}\x{7F6F}' . + '\x{7F70}\x{7F71}\x{7F72}\x{7F73}\x{7F74}\x{7F75}\x{7F76}\x{7F77}\x{7F78}' . + '\x{7F79}\x{7F7A}\x{7F7B}\x{7F7C}\x{7F7D}\x{7F7E}\x{7F7F}\x{7F80}\x{7F81}' . + '\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}\x{7F88}\x{7F89}\x{7F8A}\x{7F8B}' . + '\x{7F8C}\x{7F8D}\x{7F8E}\x{7F8F}\x{7F91}\x{7F92}\x{7F93}\x{7F94}\x{7F95}' . + '\x{7F96}\x{7F98}\x{7F9A}\x{7F9B}\x{7F9C}\x{7F9D}\x{7F9E}\x{7F9F}\x{7FA0}' . + '\x{7FA1}\x{7FA2}\x{7FA3}\x{7FA4}\x{7FA5}\x{7FA6}\x{7FA7}\x{7FA8}\x{7FA9}' . + '\x{7FAA}\x{7FAB}\x{7FAC}\x{7FAD}\x{7FAE}\x{7FAF}\x{7FB0}\x{7FB1}\x{7FB2}' . + '\x{7FB3}\x{7FB5}\x{7FB6}\x{7FB7}\x{7FB8}\x{7FB9}\x{7FBA}\x{7FBB}\x{7FBC}' . + '\x{7FBD}\x{7FBE}\x{7FBF}\x{7FC0}\x{7FC1}\x{7FC2}\x{7FC3}\x{7FC4}\x{7FC5}' . + '\x{7FC6}\x{7FC7}\x{7FC8}\x{7FC9}\x{7FCA}\x{7FCB}\x{7FCC}\x{7FCD}\x{7FCE}' . + '\x{7FCF}\x{7FD0}\x{7FD1}\x{7FD2}\x{7FD3}\x{7FD4}\x{7FD5}\x{7FD7}\x{7FD8}' . + '\x{7FD9}\x{7FDA}\x{7FDB}\x{7FDC}\x{7FDE}\x{7FDF}\x{7FE0}\x{7FE1}\x{7FE2}' . + '\x{7FE3}\x{7FE5}\x{7FE6}\x{7FE7}\x{7FE8}\x{7FE9}\x{7FEA}\x{7FEB}\x{7FEC}' . + '\x{7FED}\x{7FEE}\x{7FEF}\x{7FF0}\x{7FF1}\x{7FF2}\x{7FF3}\x{7FF4}\x{7FF5}' . + '\x{7FF6}\x{7FF7}\x{7FF8}\x{7FF9}\x{7FFA}\x{7FFB}\x{7FFC}\x{7FFD}\x{7FFE}' . + '\x{7FFF}\x{8000}\x{8001}\x{8002}\x{8003}\x{8004}\x{8005}\x{8006}\x{8007}' . + '\x{8008}\x{8009}\x{800B}\x{800C}\x{800D}\x{800E}\x{800F}\x{8010}\x{8011}' . + '\x{8012}\x{8013}\x{8014}\x{8015}\x{8016}\x{8017}\x{8018}\x{8019}\x{801A}' . + '\x{801B}\x{801C}\x{801D}\x{801E}\x{801F}\x{8020}\x{8021}\x{8022}\x{8023}' . + '\x{8024}\x{8025}\x{8026}\x{8027}\x{8028}\x{8029}\x{802A}\x{802B}\x{802C}' . + '\x{802D}\x{802E}\x{8030}\x{8031}\x{8032}\x{8033}\x{8034}\x{8035}\x{8036}' . + '\x{8037}\x{8038}\x{8039}\x{803A}\x{803B}\x{803D}\x{803E}\x{803F}\x{8041}' . + '\x{8042}\x{8043}\x{8044}\x{8045}\x{8046}\x{8047}\x{8048}\x{8049}\x{804A}' . + '\x{804B}\x{804C}\x{804D}\x{804E}\x{804F}\x{8050}\x{8051}\x{8052}\x{8053}' . + '\x{8054}\x{8055}\x{8056}\x{8057}\x{8058}\x{8059}\x{805A}\x{805B}\x{805C}' . + '\x{805D}\x{805E}\x{805F}\x{8060}\x{8061}\x{8062}\x{8063}\x{8064}\x{8065}' . + '\x{8067}\x{8068}\x{8069}\x{806A}\x{806B}\x{806C}\x{806D}\x{806E}\x{806F}' . + '\x{8070}\x{8071}\x{8072}\x{8073}\x{8074}\x{8075}\x{8076}\x{8077}\x{8078}' . + '\x{8079}\x{807A}\x{807B}\x{807C}\x{807D}\x{807E}\x{807F}\x{8080}\x{8081}' . + '\x{8082}\x{8083}\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808A}\x{808B}' . + '\x{808C}\x{808D}\x{808F}\x{8090}\x{8091}\x{8092}\x{8093}\x{8095}\x{8096}' . + '\x{8097}\x{8098}\x{8099}\x{809A}\x{809B}\x{809C}\x{809D}\x{809E}\x{809F}' . + '\x{80A0}\x{80A1}\x{80A2}\x{80A3}\x{80A4}\x{80A5}\x{80A9}\x{80AA}\x{80AB}' . + '\x{80AD}\x{80AE}\x{80AF}\x{80B0}\x{80B1}\x{80B2}\x{80B4}\x{80B5}\x{80B6}' . + '\x{80B7}\x{80B8}\x{80BA}\x{80BB}\x{80BC}\x{80BD}\x{80BE}\x{80BF}\x{80C0}' . + '\x{80C1}\x{80C2}\x{80C3}\x{80C4}\x{80C5}\x{80C6}\x{80C7}\x{80C8}\x{80C9}' . + '\x{80CA}\x{80CB}\x{80CC}\x{80CD}\x{80CE}\x{80CF}\x{80D0}\x{80D1}\x{80D2}' . + '\x{80D3}\x{80D4}\x{80D5}\x{80D6}\x{80D7}\x{80D8}\x{80D9}\x{80DA}\x{80DB}' . + '\x{80DC}\x{80DD}\x{80DE}\x{80E0}\x{80E1}\x{80E2}\x{80E3}\x{80E4}\x{80E5}' . + '\x{80E6}\x{80E7}\x{80E8}\x{80E9}\x{80EA}\x{80EB}\x{80EC}\x{80ED}\x{80EE}' . + '\x{80EF}\x{80F0}\x{80F1}\x{80F2}\x{80F3}\x{80F4}\x{80F5}\x{80F6}\x{80F7}' . + '\x{80F8}\x{80F9}\x{80FA}\x{80FB}\x{80FC}\x{80FD}\x{80FE}\x{80FF}\x{8100}' . + '\x{8101}\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{810B}' . + '\x{810C}\x{810D}\x{810E}\x{810F}\x{8110}\x{8111}\x{8112}\x{8113}\x{8114}' . + '\x{8115}\x{8116}\x{8118}\x{8119}\x{811A}\x{811B}\x{811C}\x{811D}\x{811E}' . + '\x{811F}\x{8120}\x{8121}\x{8122}\x{8123}\x{8124}\x{8125}\x{8126}\x{8127}' . + '\x{8128}\x{8129}\x{812A}\x{812B}\x{812C}\x{812D}\x{812E}\x{812F}\x{8130}' . + '\x{8131}\x{8132}\x{8136}\x{8137}\x{8138}\x{8139}\x{813A}\x{813B}\x{813C}' . + '\x{813D}\x{813E}\x{813F}\x{8140}\x{8141}\x{8142}\x{8143}\x{8144}\x{8145}' . + '\x{8146}\x{8147}\x{8148}\x{8149}\x{814A}\x{814B}\x{814C}\x{814D}\x{814E}' . + '\x{814F}\x{8150}\x{8151}\x{8152}\x{8153}\x{8154}\x{8155}\x{8156}\x{8157}' . + '\x{8158}\x{8159}\x{815A}\x{815B}\x{815C}\x{815D}\x{815E}\x{8160}\x{8161}' . + '\x{8162}\x{8163}\x{8164}\x{8165}\x{8166}\x{8167}\x{8168}\x{8169}\x{816A}' . + '\x{816B}\x{816C}\x{816D}\x{816E}\x{816F}\x{8170}\x{8171}\x{8172}\x{8173}' . + '\x{8174}\x{8175}\x{8176}\x{8177}\x{8178}\x{8179}\x{817A}\x{817B}\x{817C}' . + '\x{817D}\x{817E}\x{817F}\x{8180}\x{8181}\x{8182}\x{8183}\x{8185}\x{8186}' . + '\x{8187}\x{8188}\x{8189}\x{818A}\x{818B}\x{818C}\x{818D}\x{818E}\x{818F}' . + '\x{8191}\x{8192}\x{8193}\x{8194}\x{8195}\x{8197}\x{8198}\x{8199}\x{819A}' . + '\x{819B}\x{819C}\x{819D}\x{819E}\x{819F}\x{81A0}\x{81A1}\x{81A2}\x{81A3}' . + '\x{81A4}\x{81A5}\x{81A6}\x{81A7}\x{81A8}\x{81A9}\x{81AA}\x{81AB}\x{81AC}' . + '\x{81AD}\x{81AE}\x{81AF}\x{81B0}\x{81B1}\x{81B2}\x{81B3}\x{81B4}\x{81B5}' . + '\x{81B6}\x{81B7}\x{81B8}\x{81B9}\x{81BA}\x{81BB}\x{81BC}\x{81BD}\x{81BE}' . + '\x{81BF}\x{81C0}\x{81C1}\x{81C2}\x{81C3}\x{81C4}\x{81C5}\x{81C6}\x{81C7}' . + '\x{81C8}\x{81C9}\x{81CA}\x{81CC}\x{81CD}\x{81CE}\x{81CF}\x{81D0}\x{81D1}' . + '\x{81D2}\x{81D4}\x{81D5}\x{81D6}\x{81D7}\x{81D8}\x{81D9}\x{81DA}\x{81DB}' . + '\x{81DC}\x{81DD}\x{81DE}\x{81DF}\x{81E0}\x{81E1}\x{81E2}\x{81E3}\x{81E5}' . + '\x{81E6}\x{81E7}\x{81E8}\x{81E9}\x{81EA}\x{81EB}\x{81EC}\x{81ED}\x{81EE}' . + '\x{81F1}\x{81F2}\x{81F3}\x{81F4}\x{81F5}\x{81F6}\x{81F7}\x{81F8}\x{81F9}' . + '\x{81FA}\x{81FB}\x{81FC}\x{81FD}\x{81FE}\x{81FF}\x{8200}\x{8201}\x{8202}' . + '\x{8203}\x{8204}\x{8205}\x{8206}\x{8207}\x{8208}\x{8209}\x{820A}\x{820B}' . + '\x{820C}\x{820D}\x{820E}\x{820F}\x{8210}\x{8211}\x{8212}\x{8214}\x{8215}' . + '\x{8216}\x{8218}\x{8219}\x{821A}\x{821B}\x{821C}\x{821D}\x{821E}\x{821F}' . + '\x{8220}\x{8221}\x{8222}\x{8223}\x{8225}\x{8226}\x{8227}\x{8228}\x{8229}' . + '\x{822A}\x{822B}\x{822C}\x{822D}\x{822F}\x{8230}\x{8231}\x{8232}\x{8233}' . + '\x{8234}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{823A}\x{823B}\x{823C}' . + '\x{823D}\x{823E}\x{823F}\x{8240}\x{8242}\x{8243}\x{8244}\x{8245}\x{8246}' . + '\x{8247}\x{8248}\x{8249}\x{824A}\x{824B}\x{824C}\x{824D}\x{824E}\x{824F}' . + '\x{8250}\x{8251}\x{8252}\x{8253}\x{8254}\x{8255}\x{8256}\x{8257}\x{8258}' . + '\x{8259}\x{825A}\x{825B}\x{825C}\x{825D}\x{825E}\x{825F}\x{8260}\x{8261}' . + '\x{8263}\x{8264}\x{8266}\x{8267}\x{8268}\x{8269}\x{826A}\x{826B}\x{826C}' . + '\x{826D}\x{826E}\x{826F}\x{8270}\x{8271}\x{8272}\x{8273}\x{8274}\x{8275}' . + '\x{8276}\x{8277}\x{8278}\x{8279}\x{827A}\x{827B}\x{827C}\x{827D}\x{827E}' . + '\x{827F}\x{8280}\x{8281}\x{8282}\x{8283}\x{8284}\x{8285}\x{8286}\x{8287}' . + '\x{8288}\x{8289}\x{828A}\x{828B}\x{828D}\x{828E}\x{828F}\x{8290}\x{8291}' . + '\x{8292}\x{8293}\x{8294}\x{8295}\x{8296}\x{8297}\x{8298}\x{8299}\x{829A}' . + '\x{829B}\x{829C}\x{829D}\x{829E}\x{829F}\x{82A0}\x{82A1}\x{82A2}\x{82A3}' . + '\x{82A4}\x{82A5}\x{82A6}\x{82A7}\x{82A8}\x{82A9}\x{82AA}\x{82AB}\x{82AC}' . + '\x{82AD}\x{82AE}\x{82AF}\x{82B0}\x{82B1}\x{82B3}\x{82B4}\x{82B5}\x{82B6}' . + '\x{82B7}\x{82B8}\x{82B9}\x{82BA}\x{82BB}\x{82BC}\x{82BD}\x{82BE}\x{82BF}' . + '\x{82C0}\x{82C1}\x{82C2}\x{82C3}\x{82C4}\x{82C5}\x{82C6}\x{82C7}\x{82C8}' . + '\x{82C9}\x{82CA}\x{82CB}\x{82CC}\x{82CD}\x{82CE}\x{82CF}\x{82D0}\x{82D1}' . + '\x{82D2}\x{82D3}\x{82D4}\x{82D5}\x{82D6}\x{82D7}\x{82D8}\x{82D9}\x{82DA}' . + '\x{82DB}\x{82DC}\x{82DD}\x{82DE}\x{82DF}\x{82E0}\x{82E1}\x{82E3}\x{82E4}' . + '\x{82E5}\x{82E6}\x{82E7}\x{82E8}\x{82E9}\x{82EA}\x{82EB}\x{82EC}\x{82ED}' . + '\x{82EE}\x{82EF}\x{82F0}\x{82F1}\x{82F2}\x{82F3}\x{82F4}\x{82F5}\x{82F6}' . + '\x{82F7}\x{82F8}\x{82F9}\x{82FA}\x{82FB}\x{82FD}\x{82FE}\x{82FF}\x{8300}' . + '\x{8301}\x{8302}\x{8303}\x{8304}\x{8305}\x{8306}\x{8307}\x{8308}\x{8309}' . + '\x{830B}\x{830C}\x{830D}\x{830E}\x{830F}\x{8311}\x{8312}\x{8313}\x{8314}' . + '\x{8315}\x{8316}\x{8317}\x{8318}\x{8319}\x{831A}\x{831B}\x{831C}\x{831D}' . + '\x{831E}\x{831F}\x{8320}\x{8321}\x{8322}\x{8323}\x{8324}\x{8325}\x{8326}' . + '\x{8327}\x{8328}\x{8329}\x{832A}\x{832B}\x{832C}\x{832D}\x{832E}\x{832F}' . + '\x{8331}\x{8332}\x{8333}\x{8334}\x{8335}\x{8336}\x{8337}\x{8338}\x{8339}' . + '\x{833A}\x{833B}\x{833C}\x{833D}\x{833E}\x{833F}\x{8340}\x{8341}\x{8342}' . + '\x{8343}\x{8344}\x{8345}\x{8346}\x{8347}\x{8348}\x{8349}\x{834A}\x{834B}' . + '\x{834C}\x{834D}\x{834E}\x{834F}\x{8350}\x{8351}\x{8352}\x{8353}\x{8354}' . + '\x{8356}\x{8357}\x{8358}\x{8359}\x{835A}\x{835B}\x{835C}\x{835D}\x{835E}' . + '\x{835F}\x{8360}\x{8361}\x{8362}\x{8363}\x{8364}\x{8365}\x{8366}\x{8367}' . + '\x{8368}\x{8369}\x{836A}\x{836B}\x{836C}\x{836D}\x{836E}\x{836F}\x{8370}' . + '\x{8371}\x{8372}\x{8373}\x{8374}\x{8375}\x{8376}\x{8377}\x{8378}\x{8379}' . + '\x{837A}\x{837B}\x{837C}\x{837D}\x{837E}\x{837F}\x{8380}\x{8381}\x{8382}' . + '\x{8383}\x{8384}\x{8385}\x{8386}\x{8387}\x{8388}\x{8389}\x{838A}\x{838B}' . + '\x{838C}\x{838D}\x{838E}\x{838F}\x{8390}\x{8391}\x{8392}\x{8393}\x{8394}' . + '\x{8395}\x{8396}\x{8397}\x{8398}\x{8399}\x{839A}\x{839B}\x{839C}\x{839D}' . + '\x{839E}\x{83A0}\x{83A1}\x{83A2}\x{83A3}\x{83A4}\x{83A5}\x{83A6}\x{83A7}' . + '\x{83A8}\x{83A9}\x{83AA}\x{83AB}\x{83AC}\x{83AD}\x{83AE}\x{83AF}\x{83B0}' . + '\x{83B1}\x{83B2}\x{83B3}\x{83B4}\x{83B6}\x{83B7}\x{83B8}\x{83B9}\x{83BA}' . + '\x{83BB}\x{83BC}\x{83BD}\x{83BF}\x{83C0}\x{83C1}\x{83C2}\x{83C3}\x{83C4}' . + '\x{83C5}\x{83C6}\x{83C7}\x{83C8}\x{83C9}\x{83CA}\x{83CB}\x{83CC}\x{83CD}' . + '\x{83CE}\x{83CF}\x{83D0}\x{83D1}\x{83D2}\x{83D3}\x{83D4}\x{83D5}\x{83D6}' . + '\x{83D7}\x{83D8}\x{83D9}\x{83DA}\x{83DB}\x{83DC}\x{83DD}\x{83DE}\x{83DF}' . + '\x{83E0}\x{83E1}\x{83E2}\x{83E3}\x{83E4}\x{83E5}\x{83E7}\x{83E8}\x{83E9}' . + '\x{83EA}\x{83EB}\x{83EC}\x{83EE}\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F3}' . + '\x{83F4}\x{83F5}\x{83F6}\x{83F7}\x{83F8}\x{83F9}\x{83FA}\x{83FB}\x{83FC}' . + '\x{83FD}\x{83FE}\x{83FF}\x{8400}\x{8401}\x{8402}\x{8403}\x{8404}\x{8405}' . + '\x{8406}\x{8407}\x{8408}\x{8409}\x{840A}\x{840B}\x{840C}\x{840D}\x{840E}' . + '\x{840F}\x{8410}\x{8411}\x{8412}\x{8413}\x{8415}\x{8418}\x{8419}\x{841A}' . + '\x{841B}\x{841C}\x{841D}\x{841E}\x{8421}\x{8422}\x{8423}\x{8424}\x{8425}' . + '\x{8426}\x{8427}\x{8428}\x{8429}\x{842A}\x{842B}\x{842C}\x{842D}\x{842E}' . + '\x{842F}\x{8430}\x{8431}\x{8432}\x{8433}\x{8434}\x{8435}\x{8436}\x{8437}' . + '\x{8438}\x{8439}\x{843A}\x{843B}\x{843C}\x{843D}\x{843E}\x{843F}\x{8440}' . + '\x{8441}\x{8442}\x{8443}\x{8444}\x{8445}\x{8446}\x{8447}\x{8448}\x{8449}' . + '\x{844A}\x{844B}\x{844C}\x{844D}\x{844E}\x{844F}\x{8450}\x{8451}\x{8452}' . + '\x{8453}\x{8454}\x{8455}\x{8456}\x{8457}\x{8459}\x{845A}\x{845B}\x{845C}' . + '\x{845D}\x{845E}\x{845F}\x{8460}\x{8461}\x{8462}\x{8463}\x{8464}\x{8465}' . + '\x{8466}\x{8467}\x{8468}\x{8469}\x{846A}\x{846B}\x{846C}\x{846D}\x{846E}' . + '\x{846F}\x{8470}\x{8471}\x{8472}\x{8473}\x{8474}\x{8475}\x{8476}\x{8477}' . + '\x{8478}\x{8479}\x{847A}\x{847B}\x{847C}\x{847D}\x{847E}\x{847F}\x{8480}' . + '\x{8481}\x{8482}\x{8484}\x{8485}\x{8486}\x{8487}\x{8488}\x{8489}\x{848A}' . + '\x{848B}\x{848C}\x{848D}\x{848E}\x{848F}\x{8490}\x{8491}\x{8492}\x{8493}' . + '\x{8494}\x{8496}\x{8497}\x{8498}\x{8499}\x{849A}\x{849B}\x{849C}\x{849D}' . + '\x{849E}\x{849F}\x{84A0}\x{84A1}\x{84A2}\x{84A3}\x{84A4}\x{84A5}\x{84A6}' . + '\x{84A7}\x{84A8}\x{84A9}\x{84AA}\x{84AB}\x{84AC}\x{84AE}\x{84AF}\x{84B0}' . + '\x{84B1}\x{84B2}\x{84B3}\x{84B4}\x{84B5}\x{84B6}\x{84B8}\x{84B9}\x{84BA}' . + '\x{84BB}\x{84BC}\x{84BD}\x{84BE}\x{84BF}\x{84C0}\x{84C1}\x{84C2}\x{84C4}' . + '\x{84C5}\x{84C6}\x{84C7}\x{84C8}\x{84C9}\x{84CA}\x{84CB}\x{84CC}\x{84CD}' . + '\x{84CE}\x{84CF}\x{84D0}\x{84D1}\x{84D2}\x{84D3}\x{84D4}\x{84D5}\x{84D6}' . + '\x{84D7}\x{84D8}\x{84D9}\x{84DB}\x{84DC}\x{84DD}\x{84DE}\x{84DF}\x{84E0}' . + '\x{84E1}\x{84E2}\x{84E3}\x{84E4}\x{84E5}\x{84E6}\x{84E7}\x{84E8}\x{84E9}' . + '\x{84EA}\x{84EB}\x{84EC}\x{84EE}\x{84EF}\x{84F0}\x{84F1}\x{84F2}\x{84F3}' . + '\x{84F4}\x{84F5}\x{84F6}\x{84F7}\x{84F8}\x{84F9}\x{84FA}\x{84FB}\x{84FC}' . + '\x{84FD}\x{84FE}\x{84FF}\x{8500}\x{8501}\x{8502}\x{8503}\x{8504}\x{8506}' . + '\x{8507}\x{8508}\x{8509}\x{850A}\x{850B}\x{850C}\x{850D}\x{850E}\x{850F}' . + '\x{8511}\x{8512}\x{8513}\x{8514}\x{8515}\x{8516}\x{8517}\x{8518}\x{8519}' . + '\x{851A}\x{851B}\x{851C}\x{851D}\x{851E}\x{851F}\x{8520}\x{8521}\x{8522}' . + '\x{8523}\x{8524}\x{8525}\x{8526}\x{8527}\x{8528}\x{8529}\x{852A}\x{852B}' . + '\x{852C}\x{852D}\x{852E}\x{852F}\x{8530}\x{8531}\x{8534}\x{8535}\x{8536}' . + '\x{8537}\x{8538}\x{8539}\x{853A}\x{853B}\x{853C}\x{853D}\x{853E}\x{853F}' . + '\x{8540}\x{8541}\x{8542}\x{8543}\x{8544}\x{8545}\x{8546}\x{8547}\x{8548}' . + '\x{8549}\x{854A}\x{854B}\x{854D}\x{854E}\x{854F}\x{8551}\x{8552}\x{8553}' . + '\x{8554}\x{8555}\x{8556}\x{8557}\x{8558}\x{8559}\x{855A}\x{855B}\x{855C}' . + '\x{855D}\x{855E}\x{855F}\x{8560}\x{8561}\x{8562}\x{8563}\x{8564}\x{8565}' . + '\x{8566}\x{8567}\x{8568}\x{8569}\x{856A}\x{856B}\x{856C}\x{856D}\x{856E}' . + '\x{856F}\x{8570}\x{8571}\x{8572}\x{8573}\x{8574}\x{8575}\x{8576}\x{8577}' . + '\x{8578}\x{8579}\x{857A}\x{857B}\x{857C}\x{857D}\x{857E}\x{8580}\x{8581}' . + '\x{8582}\x{8583}\x{8584}\x{8585}\x{8586}\x{8587}\x{8588}\x{8589}\x{858A}' . + '\x{858B}\x{858C}\x{858D}\x{858E}\x{858F}\x{8590}\x{8591}\x{8592}\x{8594}' . + '\x{8595}\x{8596}\x{8598}\x{8599}\x{859A}\x{859B}\x{859C}\x{859D}\x{859E}' . + '\x{859F}\x{85A0}\x{85A1}\x{85A2}\x{85A3}\x{85A4}\x{85A5}\x{85A6}\x{85A7}' . + '\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AD}\x{85AE}\x{85AF}\x{85B0}' . + '\x{85B1}\x{85B3}\x{85B4}\x{85B5}\x{85B6}\x{85B7}\x{85B8}\x{85B9}\x{85BA}' . + '\x{85BC}\x{85BD}\x{85BE}\x{85BF}\x{85C0}\x{85C1}\x{85C2}\x{85C3}\x{85C4}' . + '\x{85C5}\x{85C6}\x{85C7}\x{85C8}\x{85C9}\x{85CA}\x{85CB}\x{85CD}\x{85CE}' . + '\x{85CF}\x{85D0}\x{85D1}\x{85D2}\x{85D3}\x{85D4}\x{85D5}\x{85D6}\x{85D7}' . + '\x{85D8}\x{85D9}\x{85DA}\x{85DB}\x{85DC}\x{85DD}\x{85DE}\x{85DF}\x{85E0}' . + '\x{85E1}\x{85E2}\x{85E3}\x{85E4}\x{85E5}\x{85E6}\x{85E7}\x{85E8}\x{85E9}' . + '\x{85EA}\x{85EB}\x{85EC}\x{85ED}\x{85EF}\x{85F0}\x{85F1}\x{85F2}\x{85F4}' . + '\x{85F5}\x{85F6}\x{85F7}\x{85F8}\x{85F9}\x{85FA}\x{85FB}\x{85FD}\x{85FE}' . + '\x{85FF}\x{8600}\x{8601}\x{8602}\x{8604}\x{8605}\x{8606}\x{8607}\x{8608}' . + '\x{8609}\x{860A}\x{860B}\x{860C}\x{860F}\x{8611}\x{8612}\x{8613}\x{8614}' . + '\x{8616}\x{8617}\x{8618}\x{8619}\x{861A}\x{861B}\x{861C}\x{861E}\x{861F}' . + '\x{8620}\x{8621}\x{8622}\x{8623}\x{8624}\x{8625}\x{8626}\x{8627}\x{8628}' . + '\x{8629}\x{862A}\x{862B}\x{862C}\x{862D}\x{862E}\x{862F}\x{8630}\x{8631}' . + '\x{8632}\x{8633}\x{8634}\x{8635}\x{8636}\x{8638}\x{8639}\x{863A}\x{863B}' . + '\x{863C}\x{863D}\x{863E}\x{863F}\x{8640}\x{8641}\x{8642}\x{8643}\x{8644}' . + '\x{8645}\x{8646}\x{8647}\x{8648}\x{8649}\x{864A}\x{864B}\x{864C}\x{864D}' . + '\x{864E}\x{864F}\x{8650}\x{8651}\x{8652}\x{8653}\x{8654}\x{8655}\x{8656}' . + '\x{8658}\x{8659}\x{865A}\x{865B}\x{865C}\x{865D}\x{865E}\x{865F}\x{8660}' . + '\x{8661}\x{8662}\x{8663}\x{8664}\x{8665}\x{8666}\x{8667}\x{8668}\x{8669}' . + '\x{866A}\x{866B}\x{866C}\x{866D}\x{866E}\x{866F}\x{8670}\x{8671}\x{8672}' . + '\x{8673}\x{8674}\x{8676}\x{8677}\x{8678}\x{8679}\x{867A}\x{867B}\x{867C}' . + '\x{867D}\x{867E}\x{867F}\x{8680}\x{8681}\x{8682}\x{8683}\x{8684}\x{8685}' . + '\x{8686}\x{8687}\x{8688}\x{868A}\x{868B}\x{868C}\x{868D}\x{868E}\x{868F}' . + '\x{8690}\x{8691}\x{8693}\x{8694}\x{8695}\x{8696}\x{8697}\x{8698}\x{8699}' . + '\x{869A}\x{869B}\x{869C}\x{869D}\x{869E}\x{869F}\x{86A1}\x{86A2}\x{86A3}' . + '\x{86A4}\x{86A5}\x{86A7}\x{86A8}\x{86A9}\x{86AA}\x{86AB}\x{86AC}\x{86AD}' . + '\x{86AE}\x{86AF}\x{86B0}\x{86B1}\x{86B2}\x{86B3}\x{86B4}\x{86B5}\x{86B6}' . + '\x{86B7}\x{86B8}\x{86B9}\x{86BA}\x{86BB}\x{86BC}\x{86BD}\x{86BE}\x{86BF}' . + '\x{86C0}\x{86C1}\x{86C2}\x{86C3}\x{86C4}\x{86C5}\x{86C6}\x{86C7}\x{86C8}' . + '\x{86C9}\x{86CA}\x{86CB}\x{86CC}\x{86CE}\x{86CF}\x{86D0}\x{86D1}\x{86D2}' . + '\x{86D3}\x{86D4}\x{86D6}\x{86D7}\x{86D8}\x{86D9}\x{86DA}\x{86DB}\x{86DC}' . + '\x{86DD}\x{86DE}\x{86DF}\x{86E1}\x{86E2}\x{86E3}\x{86E4}\x{86E5}\x{86E6}' . + '\x{86E8}\x{86E9}\x{86EA}\x{86EB}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F0}' . + '\x{86F1}\x{86F2}\x{86F3}\x{86F4}\x{86F5}\x{86F6}\x{86F7}\x{86F8}\x{86F9}' . + '\x{86FA}\x{86FB}\x{86FC}\x{86FE}\x{86FF}\x{8700}\x{8701}\x{8702}\x{8703}' . + '\x{8704}\x{8705}\x{8706}\x{8707}\x{8708}\x{8709}\x{870A}\x{870B}\x{870C}' . + '\x{870D}\x{870E}\x{870F}\x{8710}\x{8711}\x{8712}\x{8713}\x{8714}\x{8715}' . + '\x{8716}\x{8717}\x{8718}\x{8719}\x{871A}\x{871B}\x{871C}\x{871E}\x{871F}' . + '\x{8720}\x{8721}\x{8722}\x{8723}\x{8724}\x{8725}\x{8726}\x{8727}\x{8728}' . + '\x{8729}\x{872A}\x{872B}\x{872C}\x{872D}\x{872E}\x{8730}\x{8731}\x{8732}' . + '\x{8733}\x{8734}\x{8735}\x{8736}\x{8737}\x{8738}\x{8739}\x{873A}\x{873B}' . + '\x{873C}\x{873E}\x{873F}\x{8740}\x{8741}\x{8742}\x{8743}\x{8744}\x{8746}' . + '\x{8747}\x{8748}\x{8749}\x{874A}\x{874C}\x{874D}\x{874E}\x{874F}\x{8750}' . + '\x{8751}\x{8752}\x{8753}\x{8754}\x{8755}\x{8756}\x{8757}\x{8758}\x{8759}' . + '\x{875A}\x{875B}\x{875C}\x{875D}\x{875E}\x{875F}\x{8760}\x{8761}\x{8762}' . + '\x{8763}\x{8764}\x{8765}\x{8766}\x{8767}\x{8768}\x{8769}\x{876A}\x{876B}' . + '\x{876C}\x{876D}\x{876E}\x{876F}\x{8770}\x{8772}\x{8773}\x{8774}\x{8775}' . + '\x{8776}\x{8777}\x{8778}\x{8779}\x{877A}\x{877B}\x{877C}\x{877D}\x{877E}' . + '\x{8780}\x{8781}\x{8782}\x{8783}\x{8784}\x{8785}\x{8786}\x{8787}\x{8788}' . + '\x{8789}\x{878A}\x{878B}\x{878C}\x{878D}\x{878F}\x{8790}\x{8791}\x{8792}' . + '\x{8793}\x{8794}\x{8795}\x{8796}\x{8797}\x{8798}\x{879A}\x{879B}\x{879C}' . + '\x{879D}\x{879E}\x{879F}\x{87A0}\x{87A1}\x{87A2}\x{87A3}\x{87A4}\x{87A5}' . + '\x{87A6}\x{87A7}\x{87A8}\x{87A9}\x{87AA}\x{87AB}\x{87AC}\x{87AD}\x{87AE}' . + '\x{87AF}\x{87B0}\x{87B1}\x{87B2}\x{87B3}\x{87B4}\x{87B5}\x{87B6}\x{87B7}' . + '\x{87B8}\x{87B9}\x{87BA}\x{87BB}\x{87BC}\x{87BD}\x{87BE}\x{87BF}\x{87C0}' . + '\x{87C1}\x{87C2}\x{87C3}\x{87C4}\x{87C5}\x{87C6}\x{87C7}\x{87C8}\x{87C9}' . + '\x{87CA}\x{87CB}\x{87CC}\x{87CD}\x{87CE}\x{87CF}\x{87D0}\x{87D1}\x{87D2}' . + '\x{87D3}\x{87D4}\x{87D5}\x{87D6}\x{87D7}\x{87D8}\x{87D9}\x{87DB}\x{87DC}' . + '\x{87DD}\x{87DE}\x{87DF}\x{87E0}\x{87E1}\x{87E2}\x{87E3}\x{87E4}\x{87E5}' . + '\x{87E6}\x{87E7}\x{87E8}\x{87E9}\x{87EA}\x{87EB}\x{87EC}\x{87ED}\x{87EE}' . + '\x{87EF}\x{87F1}\x{87F2}\x{87F3}\x{87F4}\x{87F5}\x{87F6}\x{87F7}\x{87F8}' . + '\x{87F9}\x{87FA}\x{87FB}\x{87FC}\x{87FD}\x{87FE}\x{87FF}\x{8800}\x{8801}' . + '\x{8802}\x{8803}\x{8804}\x{8805}\x{8806}\x{8808}\x{8809}\x{880A}\x{880B}' . + '\x{880C}\x{880D}\x{880E}\x{880F}\x{8810}\x{8811}\x{8813}\x{8814}\x{8815}' . + '\x{8816}\x{8817}\x{8818}\x{8819}\x{881A}\x{881B}\x{881C}\x{881D}\x{881E}' . + '\x{881F}\x{8820}\x{8821}\x{8822}\x{8823}\x{8824}\x{8825}\x{8826}\x{8827}' . + '\x{8828}\x{8829}\x{882A}\x{882B}\x{882C}\x{882E}\x{882F}\x{8830}\x{8831}' . + '\x{8832}\x{8833}\x{8834}\x{8835}\x{8836}\x{8837}\x{8838}\x{8839}\x{883B}' . + '\x{883C}\x{883D}\x{883E}\x{883F}\x{8840}\x{8841}\x{8842}\x{8843}\x{8844}' . + '\x{8845}\x{8846}\x{8848}\x{8849}\x{884A}\x{884B}\x{884C}\x{884D}\x{884E}' . + '\x{884F}\x{8850}\x{8851}\x{8852}\x{8853}\x{8854}\x{8855}\x{8856}\x{8857}' . + '\x{8859}\x{885A}\x{885B}\x{885D}\x{885E}\x{8860}\x{8861}\x{8862}\x{8863}' . + '\x{8864}\x{8865}\x{8866}\x{8867}\x{8868}\x{8869}\x{886A}\x{886B}\x{886C}' . + '\x{886D}\x{886E}\x{886F}\x{8870}\x{8871}\x{8872}\x{8873}\x{8874}\x{8875}' . + '\x{8876}\x{8877}\x{8878}\x{8879}\x{887B}\x{887C}\x{887D}\x{887E}\x{887F}' . + '\x{8880}\x{8881}\x{8882}\x{8883}\x{8884}\x{8885}\x{8886}\x{8887}\x{8888}' . + '\x{8889}\x{888A}\x{888B}\x{888C}\x{888D}\x{888E}\x{888F}\x{8890}\x{8891}' . + '\x{8892}\x{8893}\x{8894}\x{8895}\x{8896}\x{8897}\x{8898}\x{8899}\x{889A}' . + '\x{889B}\x{889C}\x{889D}\x{889E}\x{889F}\x{88A0}\x{88A1}\x{88A2}\x{88A3}' . + '\x{88A4}\x{88A5}\x{88A6}\x{88A7}\x{88A8}\x{88A9}\x{88AA}\x{88AB}\x{88AC}' . + '\x{88AD}\x{88AE}\x{88AF}\x{88B0}\x{88B1}\x{88B2}\x{88B3}\x{88B4}\x{88B6}' . + '\x{88B7}\x{88B8}\x{88B9}\x{88BA}\x{88BB}\x{88BC}\x{88BD}\x{88BE}\x{88BF}' . + '\x{88C0}\x{88C1}\x{88C2}\x{88C3}\x{88C4}\x{88C5}\x{88C6}\x{88C7}\x{88C8}' . + '\x{88C9}\x{88CA}\x{88CB}\x{88CC}\x{88CD}\x{88CE}\x{88CF}\x{88D0}\x{88D1}' . + '\x{88D2}\x{88D3}\x{88D4}\x{88D5}\x{88D6}\x{88D7}\x{88D8}\x{88D9}\x{88DA}' . + '\x{88DB}\x{88DC}\x{88DD}\x{88DE}\x{88DF}\x{88E0}\x{88E1}\x{88E2}\x{88E3}' . + '\x{88E4}\x{88E5}\x{88E7}\x{88E8}\x{88EA}\x{88EB}\x{88EC}\x{88EE}\x{88EF}' . + '\x{88F0}\x{88F1}\x{88F2}\x{88F3}\x{88F4}\x{88F5}\x{88F6}\x{88F7}\x{88F8}' . + '\x{88F9}\x{88FA}\x{88FB}\x{88FC}\x{88FD}\x{88FE}\x{88FF}\x{8900}\x{8901}' . + '\x{8902}\x{8904}\x{8905}\x{8906}\x{8907}\x{8908}\x{8909}\x{890A}\x{890B}' . + '\x{890C}\x{890D}\x{890E}\x{8910}\x{8911}\x{8912}\x{8913}\x{8914}\x{8915}' . + '\x{8916}\x{8917}\x{8918}\x{8919}\x{891A}\x{891B}\x{891C}\x{891D}\x{891E}' . + '\x{891F}\x{8920}\x{8921}\x{8922}\x{8923}\x{8925}\x{8926}\x{8927}\x{8928}' . + '\x{8929}\x{892A}\x{892B}\x{892C}\x{892D}\x{892E}\x{892F}\x{8930}\x{8931}' . + '\x{8932}\x{8933}\x{8934}\x{8935}\x{8936}\x{8937}\x{8938}\x{8939}\x{893A}' . + '\x{893B}\x{893C}\x{893D}\x{893E}\x{893F}\x{8940}\x{8941}\x{8942}\x{8943}' . + '\x{8944}\x{8945}\x{8946}\x{8947}\x{8948}\x{8949}\x{894A}\x{894B}\x{894C}' . + '\x{894E}\x{894F}\x{8950}\x{8951}\x{8952}\x{8953}\x{8954}\x{8955}\x{8956}' . + '\x{8957}\x{8958}\x{8959}\x{895A}\x{895B}\x{895C}\x{895D}\x{895E}\x{895F}' . + '\x{8960}\x{8961}\x{8962}\x{8963}\x{8964}\x{8966}\x{8967}\x{8968}\x{8969}' . + '\x{896A}\x{896B}\x{896C}\x{896D}\x{896E}\x{896F}\x{8970}\x{8971}\x{8972}' . + '\x{8973}\x{8974}\x{8976}\x{8977}\x{8978}\x{8979}\x{897A}\x{897B}\x{897C}' . + '\x{897E}\x{897F}\x{8980}\x{8981}\x{8982}\x{8983}\x{8984}\x{8985}\x{8986}' . + '\x{8987}\x{8988}\x{8989}\x{898A}\x{898B}\x{898C}\x{898E}\x{898F}\x{8991}' . + '\x{8992}\x{8993}\x{8995}\x{8996}\x{8997}\x{8998}\x{899A}\x{899B}\x{899C}' . + '\x{899D}\x{899E}\x{899F}\x{89A0}\x{89A1}\x{89A2}\x{89A3}\x{89A4}\x{89A5}' . + '\x{89A6}\x{89A7}\x{89A8}\x{89AA}\x{89AB}\x{89AC}\x{89AD}\x{89AE}\x{89AF}' . + '\x{89B1}\x{89B2}\x{89B3}\x{89B5}\x{89B6}\x{89B7}\x{89B8}\x{89B9}\x{89BA}' . + '\x{89BD}\x{89BE}\x{89BF}\x{89C0}\x{89C1}\x{89C2}\x{89C3}\x{89C4}\x{89C5}' . + '\x{89C6}\x{89C7}\x{89C8}\x{89C9}\x{89CA}\x{89CB}\x{89CC}\x{89CD}\x{89CE}' . + '\x{89CF}\x{89D0}\x{89D1}\x{89D2}\x{89D3}\x{89D4}\x{89D5}\x{89D6}\x{89D7}' . + '\x{89D8}\x{89D9}\x{89DA}\x{89DB}\x{89DC}\x{89DD}\x{89DE}\x{89DF}\x{89E0}' . + '\x{89E1}\x{89E2}\x{89E3}\x{89E4}\x{89E5}\x{89E6}\x{89E7}\x{89E8}\x{89E9}' . + '\x{89EA}\x{89EB}\x{89EC}\x{89ED}\x{89EF}\x{89F0}\x{89F1}\x{89F2}\x{89F3}' . + '\x{89F4}\x{89F6}\x{89F7}\x{89F8}\x{89FA}\x{89FB}\x{89FC}\x{89FE}\x{89FF}' . + '\x{8A00}\x{8A01}\x{8A02}\x{8A03}\x{8A04}\x{8A07}\x{8A08}\x{8A09}\x{8A0A}' . + '\x{8A0B}\x{8A0C}\x{8A0D}\x{8A0E}\x{8A0F}\x{8A10}\x{8A11}\x{8A12}\x{8A13}' . + '\x{8A15}\x{8A16}\x{8A17}\x{8A18}\x{8A1A}\x{8A1B}\x{8A1C}\x{8A1D}\x{8A1E}' . + '\x{8A1F}\x{8A22}\x{8A23}\x{8A24}\x{8A25}\x{8A26}\x{8A27}\x{8A28}\x{8A29}' . + '\x{8A2A}\x{8A2C}\x{8A2D}\x{8A2E}\x{8A2F}\x{8A30}\x{8A31}\x{8A32}\x{8A34}' . + '\x{8A35}\x{8A36}\x{8A37}\x{8A38}\x{8A39}\x{8A3A}\x{8A3B}\x{8A3C}\x{8A3E}' . + '\x{8A3F}\x{8A40}\x{8A41}\x{8A42}\x{8A43}\x{8A44}\x{8A45}\x{8A46}\x{8A47}' . + '\x{8A48}\x{8A49}\x{8A4A}\x{8A4C}\x{8A4D}\x{8A4E}\x{8A4F}\x{8A50}\x{8A51}' . + '\x{8A52}\x{8A53}\x{8A54}\x{8A55}\x{8A56}\x{8A57}\x{8A58}\x{8A59}\x{8A5A}' . + '\x{8A5B}\x{8A5C}\x{8A5D}\x{8A5E}\x{8A5F}\x{8A60}\x{8A61}\x{8A62}\x{8A63}' . + '\x{8A65}\x{8A66}\x{8A67}\x{8A68}\x{8A69}\x{8A6A}\x{8A6B}\x{8A6C}\x{8A6D}' . + '\x{8A6E}\x{8A6F}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A74}\x{8A75}\x{8A76}' . + '\x{8A77}\x{8A79}\x{8A7A}\x{8A7B}\x{8A7C}\x{8A7E}\x{8A7F}\x{8A80}\x{8A81}' . + '\x{8A82}\x{8A83}\x{8A84}\x{8A85}\x{8A86}\x{8A87}\x{8A89}\x{8A8A}\x{8A8B}' . + '\x{8A8C}\x{8A8D}\x{8A8E}\x{8A8F}\x{8A90}\x{8A91}\x{8A92}\x{8A93}\x{8A94}' . + '\x{8A95}\x{8A96}\x{8A97}\x{8A98}\x{8A99}\x{8A9A}\x{8A9B}\x{8A9C}\x{8A9D}' . + '\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA2}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA7}' . + '\x{8AA8}\x{8AA9}\x{8AAA}\x{8AAB}\x{8AAC}\x{8AAE}\x{8AB0}\x{8AB1}\x{8AB2}' . + '\x{8AB3}\x{8AB4}\x{8AB5}\x{8AB6}\x{8AB8}\x{8AB9}\x{8ABA}\x{8ABB}\x{8ABC}' . + '\x{8ABD}\x{8ABE}\x{8ABF}\x{8AC0}\x{8AC1}\x{8AC2}\x{8AC3}\x{8AC4}\x{8AC5}' . + '\x{8AC6}\x{8AC7}\x{8AC8}\x{8AC9}\x{8ACA}\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACE}' . + '\x{8ACF}\x{8AD1}\x{8AD2}\x{8AD3}\x{8AD4}\x{8AD5}\x{8AD6}\x{8AD7}\x{8AD8}' . + '\x{8AD9}\x{8ADA}\x{8ADB}\x{8ADC}\x{8ADD}\x{8ADE}\x{8ADF}\x{8AE0}\x{8AE1}' . + '\x{8AE2}\x{8AE3}\x{8AE4}\x{8AE5}\x{8AE6}\x{8AE7}\x{8AE8}\x{8AE9}\x{8AEA}' . + '\x{8AEB}\x{8AED}\x{8AEE}\x{8AEF}\x{8AF0}\x{8AF1}\x{8AF2}\x{8AF3}\x{8AF4}' . + '\x{8AF5}\x{8AF6}\x{8AF7}\x{8AF8}\x{8AF9}\x{8AFA}\x{8AFB}\x{8AFC}\x{8AFD}' . + '\x{8AFE}\x{8AFF}\x{8B00}\x{8B01}\x{8B02}\x{8B03}\x{8B04}\x{8B05}\x{8B06}' . + '\x{8B07}\x{8B08}\x{8B09}\x{8B0A}\x{8B0B}\x{8B0D}\x{8B0E}\x{8B0F}\x{8B10}' . + '\x{8B11}\x{8B12}\x{8B13}\x{8B14}\x{8B15}\x{8B16}\x{8B17}\x{8B18}\x{8B19}' . + '\x{8B1A}\x{8B1B}\x{8B1C}\x{8B1D}\x{8B1E}\x{8B1F}\x{8B20}\x{8B21}\x{8B22}' . + '\x{8B23}\x{8B24}\x{8B25}\x{8B26}\x{8B27}\x{8B28}\x{8B2A}\x{8B2B}\x{8B2C}' . + '\x{8B2D}\x{8B2E}\x{8B2F}\x{8B30}\x{8B31}\x{8B33}\x{8B34}\x{8B35}\x{8B36}' . + '\x{8B37}\x{8B39}\x{8B3A}\x{8B3B}\x{8B3C}\x{8B3D}\x{8B3E}\x{8B40}\x{8B41}' . + '\x{8B42}\x{8B43}\x{8B44}\x{8B45}\x{8B46}\x{8B47}\x{8B48}\x{8B49}\x{8B4A}' . + '\x{8B4B}\x{8B4C}\x{8B4D}\x{8B4E}\x{8B4F}\x{8B50}\x{8B51}\x{8B52}\x{8B53}' . + '\x{8B54}\x{8B55}\x{8B56}\x{8B57}\x{8B58}\x{8B59}\x{8B5A}\x{8B5B}\x{8B5C}' . + '\x{8B5D}\x{8B5E}\x{8B5F}\x{8B60}\x{8B63}\x{8B64}\x{8B65}\x{8B66}\x{8B67}' . + '\x{8B68}\x{8B6A}\x{8B6B}\x{8B6C}\x{8B6D}\x{8B6E}\x{8B6F}\x{8B70}\x{8B71}' . + '\x{8B73}\x{8B74}\x{8B76}\x{8B77}\x{8B78}\x{8B79}\x{8B7A}\x{8B7B}\x{8B7D}' . + '\x{8B7E}\x{8B7F}\x{8B80}\x{8B82}\x{8B83}\x{8B84}\x{8B85}\x{8B86}\x{8B88}' . + '\x{8B89}\x{8B8A}\x{8B8B}\x{8B8C}\x{8B8E}\x{8B90}\x{8B91}\x{8B92}\x{8B93}' . + '\x{8B94}\x{8B95}\x{8B96}\x{8B97}\x{8B98}\x{8B99}\x{8B9A}\x{8B9C}\x{8B9D}' . + '\x{8B9E}\x{8B9F}\x{8BA0}\x{8BA1}\x{8BA2}\x{8BA3}\x{8BA4}\x{8BA5}\x{8BA6}' . + '\x{8BA7}\x{8BA8}\x{8BA9}\x{8BAA}\x{8BAB}\x{8BAC}\x{8BAD}\x{8BAE}\x{8BAF}' . + '\x{8BB0}\x{8BB1}\x{8BB2}\x{8BB3}\x{8BB4}\x{8BB5}\x{8BB6}\x{8BB7}\x{8BB8}' . + '\x{8BB9}\x{8BBA}\x{8BBB}\x{8BBC}\x{8BBD}\x{8BBE}\x{8BBF}\x{8BC0}\x{8BC1}' . + '\x{8BC2}\x{8BC3}\x{8BC4}\x{8BC5}\x{8BC6}\x{8BC7}\x{8BC8}\x{8BC9}\x{8BCA}' . + '\x{8BCB}\x{8BCC}\x{8BCD}\x{8BCE}\x{8BCF}\x{8BD0}\x{8BD1}\x{8BD2}\x{8BD3}' . + '\x{8BD4}\x{8BD5}\x{8BD6}\x{8BD7}\x{8BD8}\x{8BD9}\x{8BDA}\x{8BDB}\x{8BDC}' . + '\x{8BDD}\x{8BDE}\x{8BDF}\x{8BE0}\x{8BE1}\x{8BE2}\x{8BE3}\x{8BE4}\x{8BE5}' . + '\x{8BE6}\x{8BE7}\x{8BE8}\x{8BE9}\x{8BEA}\x{8BEB}\x{8BEC}\x{8BED}\x{8BEE}' . + '\x{8BEF}\x{8BF0}\x{8BF1}\x{8BF2}\x{8BF3}\x{8BF4}\x{8BF5}\x{8BF6}\x{8BF7}' . + '\x{8BF8}\x{8BF9}\x{8BFA}\x{8BFB}\x{8BFC}\x{8BFD}\x{8BFE}\x{8BFF}\x{8C00}' . + '\x{8C01}\x{8C02}\x{8C03}\x{8C04}\x{8C05}\x{8C06}\x{8C07}\x{8C08}\x{8C09}' . + '\x{8C0A}\x{8C0B}\x{8C0C}\x{8C0D}\x{8C0E}\x{8C0F}\x{8C10}\x{8C11}\x{8C12}' . + '\x{8C13}\x{8C14}\x{8C15}\x{8C16}\x{8C17}\x{8C18}\x{8C19}\x{8C1A}\x{8C1B}' . + '\x{8C1C}\x{8C1D}\x{8C1E}\x{8C1F}\x{8C20}\x{8C21}\x{8C22}\x{8C23}\x{8C24}' . + '\x{8C25}\x{8C26}\x{8C27}\x{8C28}\x{8C29}\x{8C2A}\x{8C2B}\x{8C2C}\x{8C2D}' . + '\x{8C2E}\x{8C2F}\x{8C30}\x{8C31}\x{8C32}\x{8C33}\x{8C34}\x{8C35}\x{8C36}' . + '\x{8C37}\x{8C39}\x{8C3A}\x{8C3B}\x{8C3C}\x{8C3D}\x{8C3E}\x{8C3F}\x{8C41}' . + '\x{8C42}\x{8C43}\x{8C45}\x{8C46}\x{8C47}\x{8C48}\x{8C49}\x{8C4A}\x{8C4B}' . + '\x{8C4C}\x{8C4D}\x{8C4E}\x{8C4F}\x{8C50}\x{8C54}\x{8C55}\x{8C56}\x{8C57}' . + '\x{8C59}\x{8C5A}\x{8C5B}\x{8C5C}\x{8C5D}\x{8C5E}\x{8C5F}\x{8C60}\x{8C61}' . + '\x{8C62}\x{8C63}\x{8C64}\x{8C65}\x{8C66}\x{8C67}\x{8C68}\x{8C69}\x{8C6A}' . + '\x{8C6B}\x{8C6C}\x{8C6D}\x{8C6E}\x{8C6F}\x{8C70}\x{8C71}\x{8C72}\x{8C73}' . + '\x{8C75}\x{8C76}\x{8C77}\x{8C78}\x{8C79}\x{8C7A}\x{8C7B}\x{8C7D}\x{8C7E}' . + '\x{8C80}\x{8C81}\x{8C82}\x{8C84}\x{8C85}\x{8C86}\x{8C88}\x{8C89}\x{8C8A}' . + '\x{8C8C}\x{8C8D}\x{8C8F}\x{8C90}\x{8C91}\x{8C92}\x{8C93}\x{8C94}\x{8C95}' . + '\x{8C96}\x{8C97}\x{8C98}\x{8C99}\x{8C9A}\x{8C9C}\x{8C9D}\x{8C9E}\x{8C9F}' . + '\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA3}\x{8CA4}\x{8CA5}\x{8CA7}\x{8CA8}\x{8CA9}' . + '\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}\x{8CB1}\x{8CB2}' . + '\x{8CB3}\x{8CB4}\x{8CB5}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CB9}\x{8CBA}\x{8CBB}' . + '\x{8CBC}\x{8CBD}\x{8CBE}\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}' . + '\x{8CC5}\x{8CC6}\x{8CC7}\x{8CC8}\x{8CC9}\x{8CCA}\x{8CCC}\x{8CCE}\x{8CCF}' . + '\x{8CD0}\x{8CD1}\x{8CD2}\x{8CD3}\x{8CD4}\x{8CD5}\x{8CD7}\x{8CD9}\x{8CDA}' . + '\x{8CDB}\x{8CDC}\x{8CDD}\x{8CDE}\x{8CDF}\x{8CE0}\x{8CE1}\x{8CE2}\x{8CE3}' . + '\x{8CE4}\x{8CE5}\x{8CE6}\x{8CE7}\x{8CE8}\x{8CEA}\x{8CEB}\x{8CEC}\x{8CED}' . + '\x{8CEE}\x{8CEF}\x{8CF0}\x{8CF1}\x{8CF2}\x{8CF3}\x{8CF4}\x{8CF5}\x{8CF6}' . + '\x{8CF8}\x{8CF9}\x{8CFA}\x{8CFB}\x{8CFC}\x{8CFD}\x{8CFE}\x{8CFF}\x{8D00}' . + '\x{8D02}\x{8D03}\x{8D04}\x{8D05}\x{8D06}\x{8D07}\x{8D08}\x{8D09}\x{8D0A}' . + '\x{8D0B}\x{8D0C}\x{8D0D}\x{8D0E}\x{8D0F}\x{8D10}\x{8D13}\x{8D14}\x{8D15}' . + '\x{8D16}\x{8D17}\x{8D18}\x{8D19}\x{8D1A}\x{8D1B}\x{8D1C}\x{8D1D}\x{8D1E}' . + '\x{8D1F}\x{8D20}\x{8D21}\x{8D22}\x{8D23}\x{8D24}\x{8D25}\x{8D26}\x{8D27}' . + '\x{8D28}\x{8D29}\x{8D2A}\x{8D2B}\x{8D2C}\x{8D2D}\x{8D2E}\x{8D2F}\x{8D30}' . + '\x{8D31}\x{8D32}\x{8D33}\x{8D34}\x{8D35}\x{8D36}\x{8D37}\x{8D38}\x{8D39}' . + '\x{8D3A}\x{8D3B}\x{8D3C}\x{8D3D}\x{8D3E}\x{8D3F}\x{8D40}\x{8D41}\x{8D42}' . + '\x{8D43}\x{8D44}\x{8D45}\x{8D46}\x{8D47}\x{8D48}\x{8D49}\x{8D4A}\x{8D4B}' . + '\x{8D4C}\x{8D4D}\x{8D4E}\x{8D4F}\x{8D50}\x{8D51}\x{8D52}\x{8D53}\x{8D54}' . + '\x{8D55}\x{8D56}\x{8D57}\x{8D58}\x{8D59}\x{8D5A}\x{8D5B}\x{8D5C}\x{8D5D}' . + '\x{8D5E}\x{8D5F}\x{8D60}\x{8D61}\x{8D62}\x{8D63}\x{8D64}\x{8D65}\x{8D66}' . + '\x{8D67}\x{8D68}\x{8D69}\x{8D6A}\x{8D6B}\x{8D6C}\x{8D6D}\x{8D6E}\x{8D6F}' . + '\x{8D70}\x{8D71}\x{8D72}\x{8D73}\x{8D74}\x{8D75}\x{8D76}\x{8D77}\x{8D78}' . + '\x{8D79}\x{8D7A}\x{8D7B}\x{8D7D}\x{8D7E}\x{8D7F}\x{8D80}\x{8D81}\x{8D82}' . + '\x{8D83}\x{8D84}\x{8D85}\x{8D86}\x{8D87}\x{8D88}\x{8D89}\x{8D8A}\x{8D8B}' . + '\x{8D8C}\x{8D8D}\x{8D8E}\x{8D8F}\x{8D90}\x{8D91}\x{8D92}\x{8D93}\x{8D94}' . + '\x{8D95}\x{8D96}\x{8D97}\x{8D98}\x{8D99}\x{8D9A}\x{8D9B}\x{8D9C}\x{8D9D}' . + '\x{8D9E}\x{8D9F}\x{8DA0}\x{8DA1}\x{8DA2}\x{8DA3}\x{8DA4}\x{8DA5}\x{8DA7}' . + '\x{8DA8}\x{8DA9}\x{8DAA}\x{8DAB}\x{8DAC}\x{8DAD}\x{8DAE}\x{8DAF}\x{8DB0}' . + '\x{8DB1}\x{8DB2}\x{8DB3}\x{8DB4}\x{8DB5}\x{8DB6}\x{8DB7}\x{8DB8}\x{8DB9}' . + '\x{8DBA}\x{8DBB}\x{8DBC}\x{8DBD}\x{8DBE}\x{8DBF}\x{8DC1}\x{8DC2}\x{8DC3}' . + '\x{8DC4}\x{8DC5}\x{8DC6}\x{8DC7}\x{8DC8}\x{8DC9}\x{8DCA}\x{8DCB}\x{8DCC}' . + '\x{8DCD}\x{8DCE}\x{8DCF}\x{8DD0}\x{8DD1}\x{8DD2}\x{8DD3}\x{8DD4}\x{8DD5}' . + '\x{8DD6}\x{8DD7}\x{8DD8}\x{8DD9}\x{8DDA}\x{8DDB}\x{8DDC}\x{8DDD}\x{8DDE}' . + '\x{8DDF}\x{8DE0}\x{8DE1}\x{8DE2}\x{8DE3}\x{8DE4}\x{8DE6}\x{8DE7}\x{8DE8}' . + '\x{8DE9}\x{8DEA}\x{8DEB}\x{8DEC}\x{8DED}\x{8DEE}\x{8DEF}\x{8DF0}\x{8DF1}' . + '\x{8DF2}\x{8DF3}\x{8DF4}\x{8DF5}\x{8DF6}\x{8DF7}\x{8DF8}\x{8DF9}\x{8DFA}' . + '\x{8DFB}\x{8DFC}\x{8DFD}\x{8DFE}\x{8DFF}\x{8E00}\x{8E02}\x{8E03}\x{8E04}' . + '\x{8E05}\x{8E06}\x{8E07}\x{8E08}\x{8E09}\x{8E0A}\x{8E0C}\x{8E0D}\x{8E0E}' . + '\x{8E0F}\x{8E10}\x{8E11}\x{8E12}\x{8E13}\x{8E14}\x{8E15}\x{8E16}\x{8E17}' . + '\x{8E18}\x{8E19}\x{8E1A}\x{8E1B}\x{8E1C}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E20}' . + '\x{8E21}\x{8E22}\x{8E23}\x{8E24}\x{8E25}\x{8E26}\x{8E27}\x{8E28}\x{8E29}' . + '\x{8E2A}\x{8E2B}\x{8E2C}\x{8E2D}\x{8E2E}\x{8E2F}\x{8E30}\x{8E31}\x{8E33}' . + '\x{8E34}\x{8E35}\x{8E36}\x{8E37}\x{8E38}\x{8E39}\x{8E3A}\x{8E3B}\x{8E3C}' . + '\x{8E3D}\x{8E3E}\x{8E3F}\x{8E40}\x{8E41}\x{8E42}\x{8E43}\x{8E44}\x{8E45}' . + '\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4B}\x{8E4C}\x{8E4D}\x{8E4E}\x{8E50}' . + '\x{8E51}\x{8E52}\x{8E53}\x{8E54}\x{8E55}\x{8E56}\x{8E57}\x{8E58}\x{8E59}' . + '\x{8E5A}\x{8E5B}\x{8E5C}\x{8E5D}\x{8E5E}\x{8E5F}\x{8E60}\x{8E61}\x{8E62}' . + '\x{8E63}\x{8E64}\x{8E65}\x{8E66}\x{8E67}\x{8E68}\x{8E69}\x{8E6A}\x{8E6B}' . + '\x{8E6C}\x{8E6D}\x{8E6F}\x{8E70}\x{8E71}\x{8E72}\x{8E73}\x{8E74}\x{8E76}' . + '\x{8E78}\x{8E7A}\x{8E7B}\x{8E7C}\x{8E7D}\x{8E7E}\x{8E7F}\x{8E80}\x{8E81}' . + '\x{8E82}\x{8E83}\x{8E84}\x{8E85}\x{8E86}\x{8E87}\x{8E88}\x{8E89}\x{8E8A}' . + '\x{8E8B}\x{8E8C}\x{8E8D}\x{8E8E}\x{8E8F}\x{8E90}\x{8E91}\x{8E92}\x{8E93}' . + '\x{8E94}\x{8E95}\x{8E96}\x{8E97}\x{8E98}\x{8E9A}\x{8E9C}\x{8E9D}\x{8E9E}' . + '\x{8E9F}\x{8EA0}\x{8EA1}\x{8EA3}\x{8EA4}\x{8EA5}\x{8EA6}\x{8EA7}\x{8EA8}' . + '\x{8EA9}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAD}\x{8EAE}\x{8EAF}\x{8EB0}\x{8EB1}' . + '\x{8EB2}\x{8EB4}\x{8EB5}\x{8EB8}\x{8EB9}\x{8EBA}\x{8EBB}\x{8EBC}\x{8EBD}' . + '\x{8EBE}\x{8EBF}\x{8EC0}\x{8EC2}\x{8EC3}\x{8EC5}\x{8EC6}\x{8EC7}\x{8EC8}' . + '\x{8EC9}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ECE}\x{8ECF}\x{8ED0}\x{8ED1}' . + '\x{8ED2}\x{8ED3}\x{8ED4}\x{8ED5}\x{8ED6}\x{8ED7}\x{8ED8}\x{8EDA}\x{8EDB}' . + '\x{8EDC}\x{8EDD}\x{8EDE}\x{8EDF}\x{8EE0}\x{8EE1}\x{8EE4}\x{8EE5}\x{8EE6}' . + '\x{8EE7}\x{8EE8}\x{8EE9}\x{8EEA}\x{8EEB}\x{8EEC}\x{8EED}\x{8EEE}\x{8EEF}' . + '\x{8EF1}\x{8EF2}\x{8EF3}\x{8EF4}\x{8EF5}\x{8EF6}\x{8EF7}\x{8EF8}\x{8EF9}' . + '\x{8EFA}\x{8EFB}\x{8EFC}\x{8EFD}\x{8EFE}\x{8EFF}\x{8F00}\x{8F01}\x{8F02}' . + '\x{8F03}\x{8F04}\x{8F05}\x{8F06}\x{8F07}\x{8F08}\x{8F09}\x{8F0A}\x{8F0B}' . + '\x{8F0D}\x{8F0E}\x{8F10}\x{8F11}\x{8F12}\x{8F13}\x{8F14}\x{8F15}\x{8F16}' . + '\x{8F17}\x{8F18}\x{8F1A}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1E}\x{8F1F}\x{8F20}' . + '\x{8F21}\x{8F22}\x{8F23}\x{8F24}\x{8F25}\x{8F26}\x{8F27}\x{8F28}\x{8F29}' . + '\x{8F2A}\x{8F2B}\x{8F2C}\x{8F2E}\x{8F2F}\x{8F30}\x{8F31}\x{8F32}\x{8F33}' . + '\x{8F34}\x{8F35}\x{8F36}\x{8F37}\x{8F38}\x{8F39}\x{8F3B}\x{8F3C}\x{8F3D}' . + '\x{8F3E}\x{8F3F}\x{8F40}\x{8F42}\x{8F43}\x{8F44}\x{8F45}\x{8F46}\x{8F47}' . + '\x{8F48}\x{8F49}\x{8F4A}\x{8F4B}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F4F}\x{8F50}' . + '\x{8F51}\x{8F52}\x{8F53}\x{8F54}\x{8F55}\x{8F56}\x{8F57}\x{8F58}\x{8F59}' . + '\x{8F5A}\x{8F5B}\x{8F5D}\x{8F5E}\x{8F5F}\x{8F60}\x{8F61}\x{8F62}\x{8F63}' . + '\x{8F64}\x{8F65}\x{8F66}\x{8F67}\x{8F68}\x{8F69}\x{8F6A}\x{8F6B}\x{8F6C}' . + '\x{8F6D}\x{8F6E}\x{8F6F}\x{8F70}\x{8F71}\x{8F72}\x{8F73}\x{8F74}\x{8F75}' . + '\x{8F76}\x{8F77}\x{8F78}\x{8F79}\x{8F7A}\x{8F7B}\x{8F7C}\x{8F7D}\x{8F7E}' . + '\x{8F7F}\x{8F80}\x{8F81}\x{8F82}\x{8F83}\x{8F84}\x{8F85}\x{8F86}\x{8F87}' . + '\x{8F88}\x{8F89}\x{8F8A}\x{8F8B}\x{8F8C}\x{8F8D}\x{8F8E}\x{8F8F}\x{8F90}' . + '\x{8F91}\x{8F92}\x{8F93}\x{8F94}\x{8F95}\x{8F96}\x{8F97}\x{8F98}\x{8F99}' . + '\x{8F9A}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA0}\x{8FA1}\x{8FA2}\x{8FA3}' . + '\x{8FA5}\x{8FA6}\x{8FA7}\x{8FA8}\x{8FA9}\x{8FAA}\x{8FAB}\x{8FAC}\x{8FAD}' . + '\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB4}\x{8FB5}\x{8FB6}\x{8FB7}' . + '\x{8FB8}\x{8FB9}\x{8FBB}\x{8FBC}\x{8FBD}\x{8FBE}\x{8FBF}\x{8FC0}\x{8FC1}' . + '\x{8FC2}\x{8FC4}\x{8FC5}\x{8FC6}\x{8FC7}\x{8FC8}\x{8FC9}\x{8FCB}\x{8FCC}' . + '\x{8FCD}\x{8FCE}\x{8FCF}\x{8FD0}\x{8FD1}\x{8FD2}\x{8FD3}\x{8FD4}\x{8FD5}' . + '\x{8FD6}\x{8FD7}\x{8FD8}\x{8FD9}\x{8FDA}\x{8FDB}\x{8FDC}\x{8FDD}\x{8FDE}' . + '\x{8FDF}\x{8FE0}\x{8FE1}\x{8FE2}\x{8FE3}\x{8FE4}\x{8FE5}\x{8FE6}\x{8FE8}' . + '\x{8FE9}\x{8FEA}\x{8FEB}\x{8FEC}\x{8FED}\x{8FEE}\x{8FEF}\x{8FF0}\x{8FF1}' . + '\x{8FF2}\x{8FF3}\x{8FF4}\x{8FF5}\x{8FF6}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}' . + '\x{8FFB}\x{8FFC}\x{8FFD}\x{8FFE}\x{8FFF}\x{9000}\x{9001}\x{9002}\x{9003}' . + '\x{9004}\x{9005}\x{9006}\x{9007}\x{9008}\x{9009}\x{900A}\x{900B}\x{900C}' . + '\x{900D}\x{900F}\x{9010}\x{9011}\x{9012}\x{9013}\x{9014}\x{9015}\x{9016}' . + '\x{9017}\x{9018}\x{9019}\x{901A}\x{901B}\x{901C}\x{901D}\x{901E}\x{901F}' . + '\x{9020}\x{9021}\x{9022}\x{9023}\x{9024}\x{9025}\x{9026}\x{9027}\x{9028}' . + '\x{9029}\x{902B}\x{902D}\x{902E}\x{902F}\x{9030}\x{9031}\x{9032}\x{9033}' . + '\x{9034}\x{9035}\x{9036}\x{9038}\x{903A}\x{903B}\x{903C}\x{903D}\x{903E}' . + '\x{903F}\x{9041}\x{9042}\x{9043}\x{9044}\x{9045}\x{9047}\x{9048}\x{9049}' . + '\x{904A}\x{904B}\x{904C}\x{904D}\x{904E}\x{904F}\x{9050}\x{9051}\x{9052}' . + '\x{9053}\x{9054}\x{9055}\x{9056}\x{9057}\x{9058}\x{9059}\x{905A}\x{905B}' . + '\x{905C}\x{905D}\x{905E}\x{905F}\x{9060}\x{9061}\x{9062}\x{9063}\x{9064}' . + '\x{9065}\x{9066}\x{9067}\x{9068}\x{9069}\x{906A}\x{906B}\x{906C}\x{906D}' . + '\x{906E}\x{906F}\x{9070}\x{9071}\x{9072}\x{9073}\x{9074}\x{9075}\x{9076}' . + '\x{9077}\x{9078}\x{9079}\x{907A}\x{907B}\x{907C}\x{907D}\x{907E}\x{907F}' . + '\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9085}\x{9086}\x{9087}\x{9088}' . + '\x{9089}\x{908A}\x{908B}\x{908C}\x{908D}\x{908E}\x{908F}\x{9090}\x{9091}' . + '\x{9092}\x{9093}\x{9094}\x{9095}\x{9096}\x{9097}\x{9098}\x{9099}\x{909A}' . + '\x{909B}\x{909C}\x{909D}\x{909E}\x{909F}\x{90A0}\x{90A1}\x{90A2}\x{90A3}' . + '\x{90A4}\x{90A5}\x{90A6}\x{90A7}\x{90A8}\x{90A9}\x{90AA}\x{90AC}\x{90AD}' . + '\x{90AE}\x{90AF}\x{90B0}\x{90B1}\x{90B2}\x{90B3}\x{90B4}\x{90B5}\x{90B6}' . + '\x{90B7}\x{90B8}\x{90B9}\x{90BA}\x{90BB}\x{90BC}\x{90BD}\x{90BE}\x{90BF}' . + '\x{90C0}\x{90C1}\x{90C2}\x{90C3}\x{90C4}\x{90C5}\x{90C6}\x{90C7}\x{90C8}' . + '\x{90C9}\x{90CA}\x{90CB}\x{90CE}\x{90CF}\x{90D0}\x{90D1}\x{90D3}\x{90D4}' . + '\x{90D5}\x{90D6}\x{90D7}\x{90D8}\x{90D9}\x{90DA}\x{90DB}\x{90DC}\x{90DD}' . + '\x{90DE}\x{90DF}\x{90E0}\x{90E1}\x{90E2}\x{90E3}\x{90E4}\x{90E5}\x{90E6}' . + '\x{90E7}\x{90E8}\x{90E9}\x{90EA}\x{90EB}\x{90EC}\x{90ED}\x{90EE}\x{90EF}' . + '\x{90F0}\x{90F1}\x{90F2}\x{90F3}\x{90F4}\x{90F5}\x{90F7}\x{90F8}\x{90F9}' . + '\x{90FA}\x{90FB}\x{90FC}\x{90FD}\x{90FE}\x{90FF}\x{9100}\x{9101}\x{9102}' . + '\x{9103}\x{9104}\x{9105}\x{9106}\x{9107}\x{9108}\x{9109}\x{910B}\x{910C}' . + '\x{910D}\x{910E}\x{910F}\x{9110}\x{9111}\x{9112}\x{9113}\x{9114}\x{9115}' . + '\x{9116}\x{9117}\x{9118}\x{9119}\x{911A}\x{911B}\x{911C}\x{911D}\x{911E}' . + '\x{911F}\x{9120}\x{9121}\x{9122}\x{9123}\x{9124}\x{9125}\x{9126}\x{9127}' . + '\x{9128}\x{9129}\x{912A}\x{912B}\x{912C}\x{912D}\x{912E}\x{912F}\x{9130}' . + '\x{9131}\x{9132}\x{9133}\x{9134}\x{9135}\x{9136}\x{9137}\x{9138}\x{9139}' . + '\x{913A}\x{913B}\x{913E}\x{913F}\x{9140}\x{9141}\x{9142}\x{9143}\x{9144}' . + '\x{9145}\x{9146}\x{9147}\x{9148}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}' . + '\x{914E}\x{914F}\x{9150}\x{9151}\x{9152}\x{9153}\x{9154}\x{9155}\x{9156}' . + '\x{9157}\x{9158}\x{915A}\x{915B}\x{915C}\x{915D}\x{915E}\x{915F}\x{9160}' . + '\x{9161}\x{9162}\x{9163}\x{9164}\x{9165}\x{9166}\x{9167}\x{9168}\x{9169}' . + '\x{916A}\x{916B}\x{916C}\x{916D}\x{916E}\x{916F}\x{9170}\x{9171}\x{9172}' . + '\x{9173}\x{9174}\x{9175}\x{9176}\x{9177}\x{9178}\x{9179}\x{917A}\x{917C}' . + '\x{917D}\x{917E}\x{917F}\x{9180}\x{9181}\x{9182}\x{9183}\x{9184}\x{9185}' . + '\x{9186}\x{9187}\x{9188}\x{9189}\x{918A}\x{918B}\x{918C}\x{918D}\x{918E}' . + '\x{918F}\x{9190}\x{9191}\x{9192}\x{9193}\x{9194}\x{9196}\x{9199}\x{919A}' . + '\x{919B}\x{919C}\x{919D}\x{919E}\x{919F}\x{91A0}\x{91A1}\x{91A2}\x{91A3}' . + '\x{91A5}\x{91A6}\x{91A7}\x{91A8}\x{91AA}\x{91AB}\x{91AC}\x{91AD}\x{91AE}' . + '\x{91AF}\x{91B0}\x{91B1}\x{91B2}\x{91B3}\x{91B4}\x{91B5}\x{91B6}\x{91B7}' . + '\x{91B9}\x{91BA}\x{91BB}\x{91BC}\x{91BD}\x{91BE}\x{91C0}\x{91C1}\x{91C2}' . + '\x{91C3}\x{91C5}\x{91C6}\x{91C7}\x{91C9}\x{91CA}\x{91CB}\x{91CC}\x{91CD}' . + '\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D2}\x{91D3}\x{91D4}\x{91D5}\x{91D7}' . + '\x{91D8}\x{91D9}\x{91DA}\x{91DB}\x{91DC}\x{91DD}\x{91DE}\x{91DF}\x{91E2}' . + '\x{91E3}\x{91E4}\x{91E5}\x{91E6}\x{91E7}\x{91E8}\x{91E9}\x{91EA}\x{91EB}' . + '\x{91EC}\x{91ED}\x{91EE}\x{91F0}\x{91F1}\x{91F2}\x{91F3}\x{91F4}\x{91F5}' . + '\x{91F7}\x{91F8}\x{91F9}\x{91FA}\x{91FB}\x{91FD}\x{91FE}\x{91FF}\x{9200}' . + '\x{9201}\x{9202}\x{9203}\x{9204}\x{9205}\x{9206}\x{9207}\x{9208}\x{9209}' . + '\x{920A}\x{920B}\x{920C}\x{920D}\x{920E}\x{920F}\x{9210}\x{9211}\x{9212}' . + '\x{9214}\x{9215}\x{9216}\x{9217}\x{9218}\x{9219}\x{921A}\x{921B}\x{921C}' . + '\x{921D}\x{921E}\x{9220}\x{9221}\x{9223}\x{9224}\x{9225}\x{9226}\x{9227}' . + '\x{9228}\x{9229}\x{922A}\x{922B}\x{922D}\x{922E}\x{922F}\x{9230}\x{9231}' . + '\x{9232}\x{9233}\x{9234}\x{9235}\x{9236}\x{9237}\x{9238}\x{9239}\x{923A}' . + '\x{923B}\x{923C}\x{923D}\x{923E}\x{923F}\x{9240}\x{9241}\x{9242}\x{9245}' . + '\x{9246}\x{9247}\x{9248}\x{9249}\x{924A}\x{924B}\x{924C}\x{924D}\x{924E}' . + '\x{924F}\x{9250}\x{9251}\x{9252}\x{9253}\x{9254}\x{9255}\x{9256}\x{9257}' . + '\x{9258}\x{9259}\x{925A}\x{925B}\x{925C}\x{925D}\x{925E}\x{925F}\x{9260}' . + '\x{9261}\x{9262}\x{9263}\x{9264}\x{9265}\x{9266}\x{9267}\x{9268}\x{926B}' . + '\x{926C}\x{926D}\x{926E}\x{926F}\x{9270}\x{9272}\x{9273}\x{9274}\x{9275}' . + '\x{9276}\x{9277}\x{9278}\x{9279}\x{927A}\x{927B}\x{927C}\x{927D}\x{927E}' . + '\x{927F}\x{9280}\x{9282}\x{9283}\x{9285}\x{9286}\x{9287}\x{9288}\x{9289}' . + '\x{928A}\x{928B}\x{928C}\x{928D}\x{928E}\x{928F}\x{9290}\x{9291}\x{9292}' . + '\x{9293}\x{9294}\x{9295}\x{9296}\x{9297}\x{9298}\x{9299}\x{929A}\x{929B}' . + '\x{929C}\x{929D}\x{929F}\x{92A0}\x{92A1}\x{92A2}\x{92A3}\x{92A4}\x{92A5}' . + '\x{92A6}\x{92A7}\x{92A8}\x{92A9}\x{92AA}\x{92AB}\x{92AC}\x{92AD}\x{92AE}' . + '\x{92AF}\x{92B0}\x{92B1}\x{92B2}\x{92B3}\x{92B4}\x{92B5}\x{92B6}\x{92B7}' . + '\x{92B8}\x{92B9}\x{92BA}\x{92BB}\x{92BC}\x{92BE}\x{92BF}\x{92C0}\x{92C1}' . + '\x{92C2}\x{92C3}\x{92C4}\x{92C5}\x{92C6}\x{92C7}\x{92C8}\x{92C9}\x{92CA}' . + '\x{92CB}\x{92CC}\x{92CD}\x{92CE}\x{92CF}\x{92D0}\x{92D1}\x{92D2}\x{92D3}' . + '\x{92D5}\x{92D6}\x{92D7}\x{92D8}\x{92D9}\x{92DA}\x{92DC}\x{92DD}\x{92DE}' . + '\x{92DF}\x{92E0}\x{92E1}\x{92E3}\x{92E4}\x{92E5}\x{92E6}\x{92E7}\x{92E8}' . + '\x{92E9}\x{92EA}\x{92EB}\x{92EC}\x{92ED}\x{92EE}\x{92EF}\x{92F0}\x{92F1}' . + '\x{92F2}\x{92F3}\x{92F4}\x{92F5}\x{92F6}\x{92F7}\x{92F8}\x{92F9}\x{92FA}' . + '\x{92FB}\x{92FC}\x{92FD}\x{92FE}\x{92FF}\x{9300}\x{9301}\x{9302}\x{9303}' . + '\x{9304}\x{9305}\x{9306}\x{9307}\x{9308}\x{9309}\x{930A}\x{930B}\x{930C}' . + '\x{930D}\x{930E}\x{930F}\x{9310}\x{9311}\x{9312}\x{9313}\x{9314}\x{9315}' . + '\x{9316}\x{9317}\x{9318}\x{9319}\x{931A}\x{931B}\x{931D}\x{931E}\x{931F}' . + '\x{9320}\x{9321}\x{9322}\x{9323}\x{9324}\x{9325}\x{9326}\x{9327}\x{9328}' . + '\x{9329}\x{932A}\x{932B}\x{932D}\x{932E}\x{932F}\x{9332}\x{9333}\x{9334}' . + '\x{9335}\x{9336}\x{9337}\x{9338}\x{9339}\x{933A}\x{933B}\x{933C}\x{933D}' . + '\x{933E}\x{933F}\x{9340}\x{9341}\x{9342}\x{9343}\x{9344}\x{9345}\x{9346}' . + '\x{9347}\x{9348}\x{9349}\x{934A}\x{934B}\x{934C}\x{934D}\x{934E}\x{934F}' . + '\x{9350}\x{9351}\x{9352}\x{9353}\x{9354}\x{9355}\x{9356}\x{9357}\x{9358}' . + '\x{9359}\x{935A}\x{935B}\x{935C}\x{935D}\x{935E}\x{935F}\x{9360}\x{9361}' . + '\x{9363}\x{9364}\x{9365}\x{9366}\x{9367}\x{9369}\x{936A}\x{936C}\x{936D}' . + '\x{936E}\x{9370}\x{9371}\x{9372}\x{9374}\x{9375}\x{9376}\x{9377}\x{9379}' . + '\x{937A}\x{937B}\x{937C}\x{937D}\x{937E}\x{9380}\x{9382}\x{9383}\x{9384}' . + '\x{9385}\x{9386}\x{9387}\x{9388}\x{9389}\x{938A}\x{938C}\x{938D}\x{938E}' . + '\x{938F}\x{9390}\x{9391}\x{9392}\x{9393}\x{9394}\x{9395}\x{9396}\x{9397}' . + '\x{9398}\x{9399}\x{939A}\x{939B}\x{939D}\x{939E}\x{939F}\x{93A1}\x{93A2}' . + '\x{93A3}\x{93A4}\x{93A5}\x{93A6}\x{93A7}\x{93A8}\x{93A9}\x{93AA}\x{93AC}' . + '\x{93AD}\x{93AE}\x{93AF}\x{93B0}\x{93B1}\x{93B2}\x{93B3}\x{93B4}\x{93B5}' . + '\x{93B6}\x{93B7}\x{93B8}\x{93B9}\x{93BA}\x{93BC}\x{93BD}\x{93BE}\x{93BF}' . + '\x{93C0}\x{93C1}\x{93C2}\x{93C3}\x{93C4}\x{93C5}\x{93C6}\x{93C7}\x{93C8}' . + '\x{93C9}\x{93CA}\x{93CB}\x{93CC}\x{93CD}\x{93CE}\x{93CF}\x{93D0}\x{93D1}' . + '\x{93D2}\x{93D3}\x{93D4}\x{93D5}\x{93D6}\x{93D7}\x{93D8}\x{93D9}\x{93DA}' . + '\x{93DB}\x{93DC}\x{93DD}\x{93DE}\x{93DF}\x{93E1}\x{93E2}\x{93E3}\x{93E4}' . + '\x{93E6}\x{93E7}\x{93E8}\x{93E9}\x{93EA}\x{93EB}\x{93EC}\x{93ED}\x{93EE}' . + '\x{93EF}\x{93F0}\x{93F1}\x{93F2}\x{93F4}\x{93F5}\x{93F6}\x{93F7}\x{93F8}' . + '\x{93F9}\x{93FA}\x{93FB}\x{93FC}\x{93FD}\x{93FE}\x{93FF}\x{9400}\x{9401}' . + '\x{9403}\x{9404}\x{9405}\x{9406}\x{9407}\x{9408}\x{9409}\x{940A}\x{940B}' . + '\x{940C}\x{940D}\x{940E}\x{940F}\x{9410}\x{9411}\x{9412}\x{9413}\x{9414}' . + '\x{9415}\x{9416}\x{9418}\x{9419}\x{941B}\x{941D}\x{9420}\x{9422}\x{9423}' . + '\x{9425}\x{9426}\x{9427}\x{9428}\x{9429}\x{942A}\x{942B}\x{942C}\x{942D}' . + '\x{942E}\x{942F}\x{9430}\x{9431}\x{9432}\x{9433}\x{9434}\x{9435}\x{9436}' . + '\x{9437}\x{9438}\x{9439}\x{943A}\x{943B}\x{943C}\x{943D}\x{943E}\x{943F}' . + '\x{9440}\x{9441}\x{9442}\x{9444}\x{9445}\x{9446}\x{9447}\x{9448}\x{9449}' . + '\x{944A}\x{944B}\x{944C}\x{944D}\x{944F}\x{9450}\x{9451}\x{9452}\x{9453}' . + '\x{9454}\x{9455}\x{9456}\x{9457}\x{9458}\x{9459}\x{945B}\x{945C}\x{945D}' . + '\x{945E}\x{945F}\x{9460}\x{9461}\x{9462}\x{9463}\x{9464}\x{9465}\x{9466}' . + '\x{9467}\x{9468}\x{9469}\x{946A}\x{946B}\x{946D}\x{946E}\x{946F}\x{9470}' . + '\x{9471}\x{9472}\x{9473}\x{9474}\x{9475}\x{9476}\x{9477}\x{9478}\x{9479}' . + '\x{947A}\x{947C}\x{947D}\x{947E}\x{947F}\x{9480}\x{9481}\x{9482}\x{9483}' . + '\x{9484}\x{9485}\x{9486}\x{9487}\x{9488}\x{9489}\x{948A}\x{948B}\x{948C}' . + '\x{948D}\x{948E}\x{948F}\x{9490}\x{9491}\x{9492}\x{9493}\x{9494}\x{9495}' . + '\x{9496}\x{9497}\x{9498}\x{9499}\x{949A}\x{949B}\x{949C}\x{949D}\x{949E}' . + '\x{949F}\x{94A0}\x{94A1}\x{94A2}\x{94A3}\x{94A4}\x{94A5}\x{94A6}\x{94A7}' . + '\x{94A8}\x{94A9}\x{94AA}\x{94AB}\x{94AC}\x{94AD}\x{94AE}\x{94AF}\x{94B0}' . + '\x{94B1}\x{94B2}\x{94B3}\x{94B4}\x{94B5}\x{94B6}\x{94B7}\x{94B8}\x{94B9}' . + '\x{94BA}\x{94BB}\x{94BC}\x{94BD}\x{94BE}\x{94BF}\x{94C0}\x{94C1}\x{94C2}' . + '\x{94C3}\x{94C4}\x{94C5}\x{94C6}\x{94C7}\x{94C8}\x{94C9}\x{94CA}\x{94CB}' . + '\x{94CC}\x{94CD}\x{94CE}\x{94CF}\x{94D0}\x{94D1}\x{94D2}\x{94D3}\x{94D4}' . + '\x{94D5}\x{94D6}\x{94D7}\x{94D8}\x{94D9}\x{94DA}\x{94DB}\x{94DC}\x{94DD}' . + '\x{94DE}\x{94DF}\x{94E0}\x{94E1}\x{94E2}\x{94E3}\x{94E4}\x{94E5}\x{94E6}' . + '\x{94E7}\x{94E8}\x{94E9}\x{94EA}\x{94EB}\x{94EC}\x{94ED}\x{94EE}\x{94EF}' . + '\x{94F0}\x{94F1}\x{94F2}\x{94F3}\x{94F4}\x{94F5}\x{94F6}\x{94F7}\x{94F8}' . + '\x{94F9}\x{94FA}\x{94FB}\x{94FC}\x{94FD}\x{94FE}\x{94FF}\x{9500}\x{9501}' . + '\x{9502}\x{9503}\x{9504}\x{9505}\x{9506}\x{9507}\x{9508}\x{9509}\x{950A}' . + '\x{950B}\x{950C}\x{950D}\x{950E}\x{950F}\x{9510}\x{9511}\x{9512}\x{9513}' . + '\x{9514}\x{9515}\x{9516}\x{9517}\x{9518}\x{9519}\x{951A}\x{951B}\x{951C}' . + '\x{951D}\x{951E}\x{951F}\x{9520}\x{9521}\x{9522}\x{9523}\x{9524}\x{9525}' . + '\x{9526}\x{9527}\x{9528}\x{9529}\x{952A}\x{952B}\x{952C}\x{952D}\x{952E}' . + '\x{952F}\x{9530}\x{9531}\x{9532}\x{9533}\x{9534}\x{9535}\x{9536}\x{9537}' . + '\x{9538}\x{9539}\x{953A}\x{953B}\x{953C}\x{953D}\x{953E}\x{953F}\x{9540}' . + '\x{9541}\x{9542}\x{9543}\x{9544}\x{9545}\x{9546}\x{9547}\x{9548}\x{9549}' . + '\x{954A}\x{954B}\x{954C}\x{954D}\x{954E}\x{954F}\x{9550}\x{9551}\x{9552}' . + '\x{9553}\x{9554}\x{9555}\x{9556}\x{9557}\x{9558}\x{9559}\x{955A}\x{955B}' . + '\x{955C}\x{955D}\x{955E}\x{955F}\x{9560}\x{9561}\x{9562}\x{9563}\x{9564}' . + '\x{9565}\x{9566}\x{9567}\x{9568}\x{9569}\x{956A}\x{956B}\x{956C}\x{956D}' . + '\x{956E}\x{956F}\x{9570}\x{9571}\x{9572}\x{9573}\x{9574}\x{9575}\x{9576}' . + '\x{9577}\x{957A}\x{957B}\x{957C}\x{957D}\x{957F}\x{9580}\x{9581}\x{9582}' . + '\x{9583}\x{9584}\x{9586}\x{9587}\x{9588}\x{9589}\x{958A}\x{958B}\x{958C}' . + '\x{958D}\x{958E}\x{958F}\x{9590}\x{9591}\x{9592}\x{9593}\x{9594}\x{9595}' . + '\x{9596}\x{9598}\x{9599}\x{959A}\x{959B}\x{959C}\x{959D}\x{959E}\x{959F}' . + '\x{95A1}\x{95A2}\x{95A3}\x{95A4}\x{95A5}\x{95A6}\x{95A7}\x{95A8}\x{95A9}' . + '\x{95AA}\x{95AB}\x{95AC}\x{95AD}\x{95AE}\x{95AF}\x{95B0}\x{95B1}\x{95B2}' . + '\x{95B5}\x{95B6}\x{95B7}\x{95B9}\x{95BA}\x{95BB}\x{95BC}\x{95BD}\x{95BE}' . + '\x{95BF}\x{95C0}\x{95C2}\x{95C3}\x{95C4}\x{95C5}\x{95C6}\x{95C7}\x{95C8}' . + '\x{95C9}\x{95CA}\x{95CB}\x{95CC}\x{95CD}\x{95CE}\x{95CF}\x{95D0}\x{95D1}' . + '\x{95D2}\x{95D3}\x{95D4}\x{95D5}\x{95D6}\x{95D7}\x{95D8}\x{95DA}\x{95DB}' . + '\x{95DC}\x{95DE}\x{95DF}\x{95E0}\x{95E1}\x{95E2}\x{95E3}\x{95E4}\x{95E5}' . + '\x{95E6}\x{95E7}\x{95E8}\x{95E9}\x{95EA}\x{95EB}\x{95EC}\x{95ED}\x{95EE}' . + '\x{95EF}\x{95F0}\x{95F1}\x{95F2}\x{95F3}\x{95F4}\x{95F5}\x{95F6}\x{95F7}' . + '\x{95F8}\x{95F9}\x{95FA}\x{95FB}\x{95FC}\x{95FD}\x{95FE}\x{95FF}\x{9600}' . + '\x{9601}\x{9602}\x{9603}\x{9604}\x{9605}\x{9606}\x{9607}\x{9608}\x{9609}' . + '\x{960A}\x{960B}\x{960C}\x{960D}\x{960E}\x{960F}\x{9610}\x{9611}\x{9612}' . + '\x{9613}\x{9614}\x{9615}\x{9616}\x{9617}\x{9618}\x{9619}\x{961A}\x{961B}' . + '\x{961C}\x{961D}\x{961E}\x{961F}\x{9620}\x{9621}\x{9622}\x{9623}\x{9624}' . + '\x{9627}\x{9628}\x{962A}\x{962B}\x{962C}\x{962D}\x{962E}\x{962F}\x{9630}' . + '\x{9631}\x{9632}\x{9633}\x{9634}\x{9635}\x{9636}\x{9637}\x{9638}\x{9639}' . + '\x{963A}\x{963B}\x{963C}\x{963D}\x{963F}\x{9640}\x{9641}\x{9642}\x{9643}' . + '\x{9644}\x{9645}\x{9646}\x{9647}\x{9648}\x{9649}\x{964A}\x{964B}\x{964C}' . + '\x{964D}\x{964E}\x{964F}\x{9650}\x{9651}\x{9652}\x{9653}\x{9654}\x{9655}' . + '\x{9658}\x{9659}\x{965A}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9660}' . + '\x{9661}\x{9662}\x{9663}\x{9664}\x{9666}\x{9667}\x{9668}\x{9669}\x{966A}' . + '\x{966B}\x{966C}\x{966D}\x{966E}\x{966F}\x{9670}\x{9671}\x{9672}\x{9673}' . + '\x{9674}\x{9675}\x{9676}\x{9677}\x{9678}\x{967C}\x{967D}\x{967E}\x{9680}' . + '\x{9683}\x{9684}\x{9685}\x{9686}\x{9687}\x{9688}\x{9689}\x{968A}\x{968B}' . + '\x{968D}\x{968E}\x{968F}\x{9690}\x{9691}\x{9692}\x{9693}\x{9694}\x{9695}' . + '\x{9697}\x{9698}\x{9699}\x{969B}\x{969C}\x{969E}\x{96A0}\x{96A1}\x{96A2}' . + '\x{96A3}\x{96A4}\x{96A5}\x{96A6}\x{96A7}\x{96A8}\x{96A9}\x{96AA}\x{96AC}' . + '\x{96AD}\x{96AE}\x{96B0}\x{96B1}\x{96B3}\x{96B4}\x{96B6}\x{96B7}\x{96B8}' . + '\x{96B9}\x{96BA}\x{96BB}\x{96BC}\x{96BD}\x{96BE}\x{96BF}\x{96C0}\x{96C1}' . + '\x{96C2}\x{96C3}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C8}\x{96C9}\x{96CA}' . + '\x{96CB}\x{96CC}\x{96CD}\x{96CE}\x{96CF}\x{96D0}\x{96D1}\x{96D2}\x{96D3}' . + '\x{96D4}\x{96D5}\x{96D6}\x{96D7}\x{96D8}\x{96D9}\x{96DA}\x{96DB}\x{96DC}' . + '\x{96DD}\x{96DE}\x{96DF}\x{96E0}\x{96E1}\x{96E2}\x{96E3}\x{96E5}\x{96E8}' . + '\x{96E9}\x{96EA}\x{96EB}\x{96EC}\x{96ED}\x{96EE}\x{96EF}\x{96F0}\x{96F1}' . + '\x{96F2}\x{96F3}\x{96F4}\x{96F5}\x{96F6}\x{96F7}\x{96F8}\x{96F9}\x{96FA}' . + '\x{96FB}\x{96FD}\x{96FE}\x{96FF}\x{9700}\x{9701}\x{9702}\x{9703}\x{9704}' . + '\x{9705}\x{9706}\x{9707}\x{9708}\x{9709}\x{970A}\x{970B}\x{970C}\x{970D}' . + '\x{970E}\x{970F}\x{9710}\x{9711}\x{9712}\x{9713}\x{9715}\x{9716}\x{9718}' . + '\x{9719}\x{971C}\x{971D}\x{971E}\x{971F}\x{9720}\x{9721}\x{9722}\x{9723}' . + '\x{9724}\x{9725}\x{9726}\x{9727}\x{9728}\x{9729}\x{972A}\x{972B}\x{972C}' . + '\x{972D}\x{972E}\x{972F}\x{9730}\x{9731}\x{9732}\x{9735}\x{9736}\x{9738}' . + '\x{9739}\x{973A}\x{973B}\x{973C}\x{973D}\x{973E}\x{973F}\x{9742}\x{9743}' . + '\x{9744}\x{9745}\x{9746}\x{9747}\x{9748}\x{9749}\x{974A}\x{974B}\x{974C}' . + '\x{974E}\x{974F}\x{9750}\x{9751}\x{9752}\x{9753}\x{9754}\x{9755}\x{9756}' . + '\x{9758}\x{9759}\x{975A}\x{975B}\x{975C}\x{975D}\x{975E}\x{975F}\x{9760}' . + '\x{9761}\x{9762}\x{9765}\x{9766}\x{9767}\x{9768}\x{9769}\x{976A}\x{976B}' . + '\x{976C}\x{976D}\x{976E}\x{976F}\x{9770}\x{9772}\x{9773}\x{9774}\x{9776}' . + '\x{9777}\x{9778}\x{9779}\x{977A}\x{977B}\x{977C}\x{977D}\x{977E}\x{977F}' . + '\x{9780}\x{9781}\x{9782}\x{9783}\x{9784}\x{9785}\x{9786}\x{9788}\x{978A}' . + '\x{978B}\x{978C}\x{978D}\x{978E}\x{978F}\x{9790}\x{9791}\x{9792}\x{9793}' . + '\x{9794}\x{9795}\x{9796}\x{9797}\x{9798}\x{9799}\x{979A}\x{979C}\x{979D}' . + '\x{979E}\x{979F}\x{97A0}\x{97A1}\x{97A2}\x{97A3}\x{97A4}\x{97A5}\x{97A6}' . + '\x{97A7}\x{97A8}\x{97AA}\x{97AB}\x{97AC}\x{97AD}\x{97AE}\x{97AF}\x{97B2}' . + '\x{97B3}\x{97B4}\x{97B6}\x{97B7}\x{97B8}\x{97B9}\x{97BA}\x{97BB}\x{97BC}' . + '\x{97BD}\x{97BF}\x{97C1}\x{97C2}\x{97C3}\x{97C4}\x{97C5}\x{97C6}\x{97C7}' . + '\x{97C8}\x{97C9}\x{97CA}\x{97CB}\x{97CC}\x{97CD}\x{97CE}\x{97CF}\x{97D0}' . + '\x{97D1}\x{97D3}\x{97D4}\x{97D5}\x{97D6}\x{97D7}\x{97D8}\x{97D9}\x{97DA}' . + '\x{97DB}\x{97DC}\x{97DD}\x{97DE}\x{97DF}\x{97E0}\x{97E1}\x{97E2}\x{97E3}' . + '\x{97E4}\x{97E5}\x{97E6}\x{97E7}\x{97E8}\x{97E9}\x{97EA}\x{97EB}\x{97EC}' . + '\x{97ED}\x{97EE}\x{97EF}\x{97F0}\x{97F1}\x{97F2}\x{97F3}\x{97F4}\x{97F5}' . + '\x{97F6}\x{97F7}\x{97F8}\x{97F9}\x{97FA}\x{97FB}\x{97FD}\x{97FE}\x{97FF}' . + '\x{9800}\x{9801}\x{9802}\x{9803}\x{9804}\x{9805}\x{9806}\x{9807}\x{9808}' . + '\x{9809}\x{980A}\x{980B}\x{980C}\x{980D}\x{980E}\x{980F}\x{9810}\x{9811}' . + '\x{9812}\x{9813}\x{9814}\x{9815}\x{9816}\x{9817}\x{9818}\x{9819}\x{981A}' . + '\x{981B}\x{981C}\x{981D}\x{981E}\x{9820}\x{9821}\x{9822}\x{9823}\x{9824}' . + '\x{9826}\x{9827}\x{9828}\x{9829}\x{982B}\x{982D}\x{982E}\x{982F}\x{9830}' . + '\x{9831}\x{9832}\x{9834}\x{9835}\x{9836}\x{9837}\x{9838}\x{9839}\x{983B}' . + '\x{983C}\x{983D}\x{983F}\x{9840}\x{9841}\x{9843}\x{9844}\x{9845}\x{9846}' . + '\x{9848}\x{9849}\x{984A}\x{984C}\x{984D}\x{984E}\x{984F}\x{9850}\x{9851}' . + '\x{9852}\x{9853}\x{9854}\x{9855}\x{9857}\x{9858}\x{9859}\x{985A}\x{985B}' . + '\x{985C}\x{985D}\x{985E}\x{985F}\x{9860}\x{9861}\x{9862}\x{9863}\x{9864}' . + '\x{9865}\x{9867}\x{9869}\x{986A}\x{986B}\x{986C}\x{986D}\x{986E}\x{986F}' . + '\x{9870}\x{9871}\x{9872}\x{9873}\x{9874}\x{9875}\x{9876}\x{9877}\x{9878}' . + '\x{9879}\x{987A}\x{987B}\x{987C}\x{987D}\x{987E}\x{987F}\x{9880}\x{9881}' . + '\x{9882}\x{9883}\x{9884}\x{9885}\x{9886}\x{9887}\x{9888}\x{9889}\x{988A}' . + '\x{988B}\x{988C}\x{988D}\x{988E}\x{988F}\x{9890}\x{9891}\x{9892}\x{9893}' . + '\x{9894}\x{9895}\x{9896}\x{9897}\x{9898}\x{9899}\x{989A}\x{989B}\x{989C}' . + '\x{989D}\x{989E}\x{989F}\x{98A0}\x{98A1}\x{98A2}\x{98A3}\x{98A4}\x{98A5}' . + '\x{98A6}\x{98A7}\x{98A8}\x{98A9}\x{98AA}\x{98AB}\x{98AC}\x{98AD}\x{98AE}' . + '\x{98AF}\x{98B0}\x{98B1}\x{98B2}\x{98B3}\x{98B4}\x{98B5}\x{98B6}\x{98B8}' . + '\x{98B9}\x{98BA}\x{98BB}\x{98BC}\x{98BD}\x{98BE}\x{98BF}\x{98C0}\x{98C1}' . + '\x{98C2}\x{98C3}\x{98C4}\x{98C5}\x{98C6}\x{98C8}\x{98C9}\x{98CB}\x{98CC}' . + '\x{98CD}\x{98CE}\x{98CF}\x{98D0}\x{98D1}\x{98D2}\x{98D3}\x{98D4}\x{98D5}' . + '\x{98D6}\x{98D7}\x{98D8}\x{98D9}\x{98DA}\x{98DB}\x{98DC}\x{98DD}\x{98DE}' . + '\x{98DF}\x{98E0}\x{98E2}\x{98E3}\x{98E5}\x{98E6}\x{98E7}\x{98E8}\x{98E9}' . + '\x{98EA}\x{98EB}\x{98ED}\x{98EF}\x{98F0}\x{98F2}\x{98F3}\x{98F4}\x{98F5}' . + '\x{98F6}\x{98F7}\x{98F9}\x{98FA}\x{98FC}\x{98FD}\x{98FE}\x{98FF}\x{9900}' . + '\x{9901}\x{9902}\x{9903}\x{9904}\x{9905}\x{9906}\x{9907}\x{9908}\x{9909}' . + '\x{990A}\x{990B}\x{990C}\x{990D}\x{990E}\x{990F}\x{9910}\x{9911}\x{9912}' . + '\x{9913}\x{9914}\x{9915}\x{9916}\x{9917}\x{9918}\x{991A}\x{991B}\x{991C}' . + '\x{991D}\x{991E}\x{991F}\x{9920}\x{9921}\x{9922}\x{9923}\x{9924}\x{9925}' . + '\x{9926}\x{9927}\x{9928}\x{9929}\x{992A}\x{992B}\x{992C}\x{992D}\x{992E}' . + '\x{992F}\x{9930}\x{9931}\x{9932}\x{9933}\x{9934}\x{9935}\x{9936}\x{9937}' . + '\x{9938}\x{9939}\x{993A}\x{993C}\x{993D}\x{993E}\x{993F}\x{9940}\x{9941}' . + '\x{9942}\x{9943}\x{9945}\x{9946}\x{9947}\x{9948}\x{9949}\x{994A}\x{994B}' . + '\x{994C}\x{994E}\x{994F}\x{9950}\x{9951}\x{9952}\x{9953}\x{9954}\x{9955}' . + '\x{9956}\x{9957}\x{9958}\x{9959}\x{995B}\x{995C}\x{995E}\x{995F}\x{9960}' . + '\x{9961}\x{9962}\x{9963}\x{9964}\x{9965}\x{9966}\x{9967}\x{9968}\x{9969}' . + '\x{996A}\x{996B}\x{996C}\x{996D}\x{996E}\x{996F}\x{9970}\x{9971}\x{9972}' . + '\x{9973}\x{9974}\x{9975}\x{9976}\x{9977}\x{9978}\x{9979}\x{997A}\x{997B}' . + '\x{997C}\x{997D}\x{997E}\x{997F}\x{9980}\x{9981}\x{9982}\x{9983}\x{9984}' . + '\x{9985}\x{9986}\x{9987}\x{9988}\x{9989}\x{998A}\x{998B}\x{998C}\x{998D}' . + '\x{998E}\x{998F}\x{9990}\x{9991}\x{9992}\x{9993}\x{9994}\x{9995}\x{9996}' . + '\x{9997}\x{9998}\x{9999}\x{999A}\x{999B}\x{999C}\x{999D}\x{999E}\x{999F}' . + '\x{99A0}\x{99A1}\x{99A2}\x{99A3}\x{99A4}\x{99A5}\x{99A6}\x{99A7}\x{99A8}' . + '\x{99A9}\x{99AA}\x{99AB}\x{99AC}\x{99AD}\x{99AE}\x{99AF}\x{99B0}\x{99B1}' . + '\x{99B2}\x{99B3}\x{99B4}\x{99B5}\x{99B6}\x{99B7}\x{99B8}\x{99B9}\x{99BA}' . + '\x{99BB}\x{99BC}\x{99BD}\x{99BE}\x{99C0}\x{99C1}\x{99C2}\x{99C3}\x{99C4}' . + '\x{99C6}\x{99C7}\x{99C8}\x{99C9}\x{99CA}\x{99CB}\x{99CC}\x{99CD}\x{99CE}' . + '\x{99CF}\x{99D0}\x{99D1}\x{99D2}\x{99D3}\x{99D4}\x{99D5}\x{99D6}\x{99D7}' . + '\x{99D8}\x{99D9}\x{99DA}\x{99DB}\x{99DC}\x{99DD}\x{99DE}\x{99DF}\x{99E1}' . + '\x{99E2}\x{99E3}\x{99E4}\x{99E5}\x{99E7}\x{99E8}\x{99E9}\x{99EA}\x{99EC}' . + '\x{99ED}\x{99EE}\x{99EF}\x{99F0}\x{99F1}\x{99F2}\x{99F3}\x{99F4}\x{99F6}' . + '\x{99F7}\x{99F8}\x{99F9}\x{99FA}\x{99FB}\x{99FC}\x{99FD}\x{99FE}\x{99FF}' . + '\x{9A00}\x{9A01}\x{9A02}\x{9A03}\x{9A04}\x{9A05}\x{9A06}\x{9A07}\x{9A08}' . + '\x{9A09}\x{9A0A}\x{9A0B}\x{9A0C}\x{9A0D}\x{9A0E}\x{9A0F}\x{9A11}\x{9A14}' . + '\x{9A15}\x{9A16}\x{9A19}\x{9A1A}\x{9A1B}\x{9A1C}\x{9A1D}\x{9A1E}\x{9A1F}' . + '\x{9A20}\x{9A21}\x{9A22}\x{9A23}\x{9A24}\x{9A25}\x{9A26}\x{9A27}\x{9A29}' . + '\x{9A2A}\x{9A2B}\x{9A2C}\x{9A2D}\x{9A2E}\x{9A2F}\x{9A30}\x{9A31}\x{9A32}' . + '\x{9A33}\x{9A34}\x{9A35}\x{9A36}\x{9A37}\x{9A38}\x{9A39}\x{9A3A}\x{9A3C}' . + '\x{9A3D}\x{9A3E}\x{9A3F}\x{9A40}\x{9A41}\x{9A42}\x{9A43}\x{9A44}\x{9A45}' . + '\x{9A46}\x{9A47}\x{9A48}\x{9A49}\x{9A4A}\x{9A4B}\x{9A4C}\x{9A4D}\x{9A4E}' . + '\x{9A4F}\x{9A50}\x{9A52}\x{9A53}\x{9A54}\x{9A55}\x{9A56}\x{9A57}\x{9A59}' . + '\x{9A5A}\x{9A5B}\x{9A5C}\x{9A5E}\x{9A5F}\x{9A60}\x{9A61}\x{9A62}\x{9A64}' . + '\x{9A65}\x{9A66}\x{9A67}\x{9A68}\x{9A69}\x{9A6A}\x{9A6B}\x{9A6C}\x{9A6D}' . + '\x{9A6E}\x{9A6F}\x{9A70}\x{9A71}\x{9A72}\x{9A73}\x{9A74}\x{9A75}\x{9A76}' . + '\x{9A77}\x{9A78}\x{9A79}\x{9A7A}\x{9A7B}\x{9A7C}\x{9A7D}\x{9A7E}\x{9A7F}' . + '\x{9A80}\x{9A81}\x{9A82}\x{9A83}\x{9A84}\x{9A85}\x{9A86}\x{9A87}\x{9A88}' . + '\x{9A89}\x{9A8A}\x{9A8B}\x{9A8C}\x{9A8D}\x{9A8E}\x{9A8F}\x{9A90}\x{9A91}' . + '\x{9A92}\x{9A93}\x{9A94}\x{9A95}\x{9A96}\x{9A97}\x{9A98}\x{9A99}\x{9A9A}' . + '\x{9A9B}\x{9A9C}\x{9A9D}\x{9A9E}\x{9A9F}\x{9AA0}\x{9AA1}\x{9AA2}\x{9AA3}' . + '\x{9AA4}\x{9AA5}\x{9AA6}\x{9AA7}\x{9AA8}\x{9AAA}\x{9AAB}\x{9AAC}\x{9AAD}' . + '\x{9AAE}\x{9AAF}\x{9AB0}\x{9AB1}\x{9AB2}\x{9AB3}\x{9AB4}\x{9AB5}\x{9AB6}' . + '\x{9AB7}\x{9AB8}\x{9AB9}\x{9ABA}\x{9ABB}\x{9ABC}\x{9ABE}\x{9ABF}\x{9AC0}' . + '\x{9AC1}\x{9AC2}\x{9AC3}\x{9AC4}\x{9AC5}\x{9AC6}\x{9AC7}\x{9AC9}\x{9ACA}' . + '\x{9ACB}\x{9ACC}\x{9ACD}\x{9ACE}\x{9ACF}\x{9AD0}\x{9AD1}\x{9AD2}\x{9AD3}' . + '\x{9AD4}\x{9AD5}\x{9AD6}\x{9AD8}\x{9AD9}\x{9ADA}\x{9ADB}\x{9ADC}\x{9ADD}' . + '\x{9ADE}\x{9ADF}\x{9AE1}\x{9AE2}\x{9AE3}\x{9AE5}\x{9AE6}\x{9AE7}\x{9AEA}' . + '\x{9AEB}\x{9AEC}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF2}\x{9AF3}\x{9AF4}' . + '\x{9AF5}\x{9AF6}\x{9AF7}\x{9AF8}\x{9AF9}\x{9AFA}\x{9AFB}\x{9AFC}\x{9AFD}' . + '\x{9AFE}\x{9AFF}\x{9B01}\x{9B03}\x{9B04}\x{9B05}\x{9B06}\x{9B07}\x{9B08}' . + '\x{9B0A}\x{9B0B}\x{9B0C}\x{9B0D}\x{9B0E}\x{9B0F}\x{9B10}\x{9B11}\x{9B12}' . + '\x{9B13}\x{9B15}\x{9B16}\x{9B17}\x{9B18}\x{9B19}\x{9B1A}\x{9B1C}\x{9B1D}' . + '\x{9B1E}\x{9B1F}\x{9B20}\x{9B21}\x{9B22}\x{9B23}\x{9B24}\x{9B25}\x{9B26}' . + '\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2B}\x{9B2C}\x{9B2D}\x{9B2E}\x{9B2F}' . + '\x{9B30}\x{9B31}\x{9B32}\x{9B33}\x{9B35}\x{9B36}\x{9B37}\x{9B38}\x{9B39}' . + '\x{9B3A}\x{9B3B}\x{9B3C}\x{9B3E}\x{9B3F}\x{9B41}\x{9B42}\x{9B43}\x{9B44}' . + '\x{9B45}\x{9B46}\x{9B47}\x{9B48}\x{9B49}\x{9B4A}\x{9B4B}\x{9B4C}\x{9B4D}' . + '\x{9B4E}\x{9B4F}\x{9B51}\x{9B52}\x{9B53}\x{9B54}\x{9B55}\x{9B56}\x{9B58}' . + '\x{9B59}\x{9B5A}\x{9B5B}\x{9B5C}\x{9B5D}\x{9B5E}\x{9B5F}\x{9B60}\x{9B61}' . + '\x{9B63}\x{9B64}\x{9B65}\x{9B66}\x{9B67}\x{9B68}\x{9B69}\x{9B6A}\x{9B6B}' . + '\x{9B6C}\x{9B6D}\x{9B6E}\x{9B6F}\x{9B70}\x{9B71}\x{9B73}\x{9B74}\x{9B75}' . + '\x{9B76}\x{9B77}\x{9B78}\x{9B79}\x{9B7A}\x{9B7B}\x{9B7C}\x{9B7D}\x{9B7E}' . + '\x{9B7F}\x{9B80}\x{9B81}\x{9B82}\x{9B83}\x{9B84}\x{9B85}\x{9B86}\x{9B87}' . + '\x{9B88}\x{9B8A}\x{9B8B}\x{9B8D}\x{9B8E}\x{9B8F}\x{9B90}\x{9B91}\x{9B92}' . + '\x{9B93}\x{9B94}\x{9B95}\x{9B96}\x{9B97}\x{9B98}\x{9B9A}\x{9B9B}\x{9B9C}' . + '\x{9B9D}\x{9B9E}\x{9B9F}\x{9BA0}\x{9BA1}\x{9BA2}\x{9BA3}\x{9BA4}\x{9BA5}' . + '\x{9BA6}\x{9BA7}\x{9BA8}\x{9BA9}\x{9BAA}\x{9BAB}\x{9BAC}\x{9BAD}\x{9BAE}' . + '\x{9BAF}\x{9BB0}\x{9BB1}\x{9BB2}\x{9BB3}\x{9BB4}\x{9BB5}\x{9BB6}\x{9BB7}' . + '\x{9BB8}\x{9BB9}\x{9BBA}\x{9BBB}\x{9BBC}\x{9BBD}\x{9BBE}\x{9BBF}\x{9BC0}' . + '\x{9BC1}\x{9BC3}\x{9BC4}\x{9BC5}\x{9BC6}\x{9BC7}\x{9BC8}\x{9BC9}\x{9BCA}' . + '\x{9BCB}\x{9BCC}\x{9BCD}\x{9BCE}\x{9BCF}\x{9BD0}\x{9BD1}\x{9BD2}\x{9BD3}' . + '\x{9BD4}\x{9BD5}\x{9BD6}\x{9BD7}\x{9BD8}\x{9BD9}\x{9BDA}\x{9BDB}\x{9BDC}' . + '\x{9BDD}\x{9BDE}\x{9BDF}\x{9BE0}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}\x{9BE5}' . + '\x{9BE6}\x{9BE7}\x{9BE8}\x{9BE9}\x{9BEA}\x{9BEB}\x{9BEC}\x{9BED}\x{9BEE}' . + '\x{9BEF}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF3}\x{9BF4}\x{9BF5}\x{9BF7}\x{9BF8}' . + '\x{9BF9}\x{9BFA}\x{9BFB}\x{9BFC}\x{9BFD}\x{9BFE}\x{9BFF}\x{9C02}\x{9C05}' . + '\x{9C06}\x{9C07}\x{9C08}\x{9C09}\x{9C0A}\x{9C0B}\x{9C0C}\x{9C0D}\x{9C0E}' . + '\x{9C0F}\x{9C10}\x{9C11}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C16}\x{9C17}' . + '\x{9C18}\x{9C19}\x{9C1A}\x{9C1B}\x{9C1C}\x{9C1D}\x{9C1E}\x{9C1F}\x{9C20}' . + '\x{9C21}\x{9C22}\x{9C23}\x{9C24}\x{9C25}\x{9C26}\x{9C27}\x{9C28}\x{9C29}' . + '\x{9C2A}\x{9C2B}\x{9C2C}\x{9C2D}\x{9C2F}\x{9C30}\x{9C31}\x{9C32}\x{9C33}' . + '\x{9C34}\x{9C35}\x{9C36}\x{9C37}\x{9C38}\x{9C39}\x{9C3A}\x{9C3B}\x{9C3C}' . + '\x{9C3D}\x{9C3E}\x{9C3F}\x{9C40}\x{9C41}\x{9C43}\x{9C44}\x{9C45}\x{9C46}' . + '\x{9C47}\x{9C48}\x{9C49}\x{9C4A}\x{9C4B}\x{9C4C}\x{9C4D}\x{9C4E}\x{9C50}' . + '\x{9C52}\x{9C53}\x{9C54}\x{9C55}\x{9C56}\x{9C57}\x{9C58}\x{9C59}\x{9C5A}' . + '\x{9C5B}\x{9C5C}\x{9C5D}\x{9C5E}\x{9C5F}\x{9C60}\x{9C62}\x{9C63}\x{9C65}' . + '\x{9C66}\x{9C67}\x{9C68}\x{9C69}\x{9C6A}\x{9C6B}\x{9C6C}\x{9C6D}\x{9C6E}' . + '\x{9C6F}\x{9C70}\x{9C71}\x{9C72}\x{9C73}\x{9C74}\x{9C75}\x{9C77}\x{9C78}' . + '\x{9C79}\x{9C7A}\x{9C7C}\x{9C7D}\x{9C7E}\x{9C7F}\x{9C80}\x{9C81}\x{9C82}' . + '\x{9C83}\x{9C84}\x{9C85}\x{9C86}\x{9C87}\x{9C88}\x{9C89}\x{9C8A}\x{9C8B}' . + '\x{9C8C}\x{9C8D}\x{9C8E}\x{9C8F}\x{9C90}\x{9C91}\x{9C92}\x{9C93}\x{9C94}' . + '\x{9C95}\x{9C96}\x{9C97}\x{9C98}\x{9C99}\x{9C9A}\x{9C9B}\x{9C9C}\x{9C9D}' . + '\x{9C9E}\x{9C9F}\x{9CA0}\x{9CA1}\x{9CA2}\x{9CA3}\x{9CA4}\x{9CA5}\x{9CA6}' . + '\x{9CA7}\x{9CA8}\x{9CA9}\x{9CAA}\x{9CAB}\x{9CAC}\x{9CAD}\x{9CAE}\x{9CAF}' . + '\x{9CB0}\x{9CB1}\x{9CB2}\x{9CB3}\x{9CB4}\x{9CB5}\x{9CB6}\x{9CB7}\x{9CB8}' . + '\x{9CB9}\x{9CBA}\x{9CBB}\x{9CBC}\x{9CBD}\x{9CBE}\x{9CBF}\x{9CC0}\x{9CC1}' . + '\x{9CC2}\x{9CC3}\x{9CC4}\x{9CC5}\x{9CC6}\x{9CC7}\x{9CC8}\x{9CC9}\x{9CCA}' . + '\x{9CCB}\x{9CCC}\x{9CCD}\x{9CCE}\x{9CCF}\x{9CD0}\x{9CD1}\x{9CD2}\x{9CD3}' . + '\x{9CD4}\x{9CD5}\x{9CD6}\x{9CD7}\x{9CD8}\x{9CD9}\x{9CDA}\x{9CDB}\x{9CDC}' . + '\x{9CDD}\x{9CDE}\x{9CDF}\x{9CE0}\x{9CE1}\x{9CE2}\x{9CE3}\x{9CE4}\x{9CE5}' . + '\x{9CE6}\x{9CE7}\x{9CE8}\x{9CE9}\x{9CEA}\x{9CEB}\x{9CEC}\x{9CED}\x{9CEE}' . + '\x{9CEF}\x{9CF0}\x{9CF1}\x{9CF2}\x{9CF3}\x{9CF4}\x{9CF5}\x{9CF6}\x{9CF7}' . + '\x{9CF8}\x{9CF9}\x{9CFA}\x{9CFB}\x{9CFC}\x{9CFD}\x{9CFE}\x{9CFF}\x{9D00}' . + '\x{9D01}\x{9D02}\x{9D03}\x{9D04}\x{9D05}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' . + '\x{9D0A}\x{9D0B}\x{9D0F}\x{9D10}\x{9D12}\x{9D13}\x{9D14}\x{9D15}\x{9D16}' . + '\x{9D17}\x{9D18}\x{9D19}\x{9D1A}\x{9D1B}\x{9D1C}\x{9D1D}\x{9D1E}\x{9D1F}' . + '\x{9D20}\x{9D21}\x{9D22}\x{9D23}\x{9D24}\x{9D25}\x{9D26}\x{9D28}\x{9D29}' . + '\x{9D2B}\x{9D2D}\x{9D2E}\x{9D2F}\x{9D30}\x{9D31}\x{9D32}\x{9D33}\x{9D34}' . + '\x{9D36}\x{9D37}\x{9D38}\x{9D39}\x{9D3A}\x{9D3B}\x{9D3D}\x{9D3E}\x{9D3F}' . + '\x{9D40}\x{9D41}\x{9D42}\x{9D43}\x{9D45}\x{9D46}\x{9D47}\x{9D48}\x{9D49}' . + '\x{9D4A}\x{9D4B}\x{9D4C}\x{9D4D}\x{9D4E}\x{9D4F}\x{9D50}\x{9D51}\x{9D52}' . + '\x{9D53}\x{9D54}\x{9D55}\x{9D56}\x{9D57}\x{9D58}\x{9D59}\x{9D5A}\x{9D5B}' . + '\x{9D5C}\x{9D5D}\x{9D5E}\x{9D5F}\x{9D60}\x{9D61}\x{9D62}\x{9D63}\x{9D64}' . + '\x{9D65}\x{9D66}\x{9D67}\x{9D68}\x{9D69}\x{9D6A}\x{9D6B}\x{9D6C}\x{9D6E}' . + '\x{9D6F}\x{9D70}\x{9D71}\x{9D72}\x{9D73}\x{9D74}\x{9D75}\x{9D76}\x{9D77}' . + '\x{9D78}\x{9D79}\x{9D7A}\x{9D7B}\x{9D7C}\x{9D7D}\x{9D7E}\x{9D7F}\x{9D80}' . + '\x{9D81}\x{9D82}\x{9D83}\x{9D84}\x{9D85}\x{9D86}\x{9D87}\x{9D88}\x{9D89}' . + '\x{9D8A}\x{9D8B}\x{9D8C}\x{9D8D}\x{9D8E}\x{9D90}\x{9D91}\x{9D92}\x{9D93}' . + '\x{9D94}\x{9D96}\x{9D97}\x{9D98}\x{9D99}\x{9D9A}\x{9D9B}\x{9D9C}\x{9D9D}' . + '\x{9D9E}\x{9D9F}\x{9DA0}\x{9DA1}\x{9DA2}\x{9DA3}\x{9DA4}\x{9DA5}\x{9DA6}' . + '\x{9DA7}\x{9DA8}\x{9DA9}\x{9DAA}\x{9DAB}\x{9DAC}\x{9DAD}\x{9DAF}\x{9DB0}' . + '\x{9DB1}\x{9DB2}\x{9DB3}\x{9DB4}\x{9DB5}\x{9DB6}\x{9DB7}\x{9DB8}\x{9DB9}' . + '\x{9DBA}\x{9DBB}\x{9DBC}\x{9DBE}\x{9DBF}\x{9DC1}\x{9DC2}\x{9DC3}\x{9DC4}' . + '\x{9DC5}\x{9DC7}\x{9DC8}\x{9DC9}\x{9DCA}\x{9DCB}\x{9DCC}\x{9DCD}\x{9DCE}' . + '\x{9DCF}\x{9DD0}\x{9DD1}\x{9DD2}\x{9DD3}\x{9DD4}\x{9DD5}\x{9DD6}\x{9DD7}' . + '\x{9DD8}\x{9DD9}\x{9DDA}\x{9DDB}\x{9DDC}\x{9DDD}\x{9DDE}\x{9DDF}\x{9DE0}' . + '\x{9DE1}\x{9DE2}\x{9DE3}\x{9DE4}\x{9DE5}\x{9DE6}\x{9DE7}\x{9DE8}\x{9DE9}' . + '\x{9DEB}\x{9DEC}\x{9DED}\x{9DEE}\x{9DEF}\x{9DF0}\x{9DF1}\x{9DF2}\x{9DF3}' . + '\x{9DF4}\x{9DF5}\x{9DF6}\x{9DF7}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFB}\x{9DFD}' . + '\x{9DFE}\x{9DFF}\x{9E00}\x{9E01}\x{9E02}\x{9E03}\x{9E04}\x{9E05}\x{9E06}' . + '\x{9E07}\x{9E08}\x{9E09}\x{9E0A}\x{9E0B}\x{9E0C}\x{9E0D}\x{9E0F}\x{9E10}' . + '\x{9E11}\x{9E12}\x{9E13}\x{9E14}\x{9E15}\x{9E17}\x{9E18}\x{9E19}\x{9E1A}' . + '\x{9E1B}\x{9E1D}\x{9E1E}\x{9E1F}\x{9E20}\x{9E21}\x{9E22}\x{9E23}\x{9E24}' . + '\x{9E25}\x{9E26}\x{9E27}\x{9E28}\x{9E29}\x{9E2A}\x{9E2B}\x{9E2C}\x{9E2D}' . + '\x{9E2E}\x{9E2F}\x{9E30}\x{9E31}\x{9E32}\x{9E33}\x{9E34}\x{9E35}\x{9E36}' . + '\x{9E37}\x{9E38}\x{9E39}\x{9E3A}\x{9E3B}\x{9E3C}\x{9E3D}\x{9E3E}\x{9E3F}' . + '\x{9E40}\x{9E41}\x{9E42}\x{9E43}\x{9E44}\x{9E45}\x{9E46}\x{9E47}\x{9E48}' . + '\x{9E49}\x{9E4A}\x{9E4B}\x{9E4C}\x{9E4D}\x{9E4E}\x{9E4F}\x{9E50}\x{9E51}' . + '\x{9E52}\x{9E53}\x{9E54}\x{9E55}\x{9E56}\x{9E57}\x{9E58}\x{9E59}\x{9E5A}' . + '\x{9E5B}\x{9E5C}\x{9E5D}\x{9E5E}\x{9E5F}\x{9E60}\x{9E61}\x{9E62}\x{9E63}' . + '\x{9E64}\x{9E65}\x{9E66}\x{9E67}\x{9E68}\x{9E69}\x{9E6A}\x{9E6B}\x{9E6C}' . + '\x{9E6D}\x{9E6E}\x{9E6F}\x{9E70}\x{9E71}\x{9E72}\x{9E73}\x{9E74}\x{9E75}' . + '\x{9E76}\x{9E77}\x{9E79}\x{9E7A}\x{9E7C}\x{9E7D}\x{9E7E}\x{9E7F}\x{9E80}' . + '\x{9E81}\x{9E82}\x{9E83}\x{9E84}\x{9E85}\x{9E86}\x{9E87}\x{9E88}\x{9E89}' . + '\x{9E8A}\x{9E8B}\x{9E8C}\x{9E8D}\x{9E8E}\x{9E91}\x{9E92}\x{9E93}\x{9E94}' . + '\x{9E96}\x{9E97}\x{9E99}\x{9E9A}\x{9E9B}\x{9E9C}\x{9E9D}\x{9E9F}\x{9EA0}' . + '\x{9EA1}\x{9EA3}\x{9EA4}\x{9EA5}\x{9EA6}\x{9EA7}\x{9EA8}\x{9EA9}\x{9EAA}' . + '\x{9EAD}\x{9EAE}\x{9EAF}\x{9EB0}\x{9EB2}\x{9EB3}\x{9EB4}\x{9EB5}\x{9EB6}' . + '\x{9EB7}\x{9EB8}\x{9EBB}\x{9EBC}\x{9EBD}\x{9EBE}\x{9EBF}\x{9EC0}\x{9EC1}' . + '\x{9EC2}\x{9EC3}\x{9EC4}\x{9EC5}\x{9EC6}\x{9EC7}\x{9EC8}\x{9EC9}\x{9ECA}' . + '\x{9ECB}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED1}\x{9ED2}\x{9ED3}' . + '\x{9ED4}\x{9ED5}\x{9ED6}\x{9ED7}\x{9ED8}\x{9ED9}\x{9EDA}\x{9EDB}\x{9EDC}' . + '\x{9EDD}\x{9EDE}\x{9EDF}\x{9EE0}\x{9EE1}\x{9EE2}\x{9EE3}\x{9EE4}\x{9EE5}' . + '\x{9EE6}\x{9EE7}\x{9EE8}\x{9EE9}\x{9EEA}\x{9EEB}\x{9EED}\x{9EEE}\x{9EEF}' . + '\x{9EF0}\x{9EF2}\x{9EF3}\x{9EF4}\x{9EF5}\x{9EF6}\x{9EF7}\x{9EF8}\x{9EF9}' . + '\x{9EFA}\x{9EFB}\x{9EFC}\x{9EFD}\x{9EFE}\x{9EFF}\x{9F00}\x{9F01}\x{9F02}' . + '\x{9F04}\x{9F05}\x{9F06}\x{9F07}\x{9F08}\x{9F09}\x{9F0A}\x{9F0B}\x{9F0C}' . + '\x{9F0D}\x{9F0E}\x{9F0F}\x{9F10}\x{9F12}\x{9F13}\x{9F15}\x{9F16}\x{9F17}' . + '\x{9F18}\x{9F19}\x{9F1A}\x{9F1B}\x{9F1C}\x{9F1D}\x{9F1E}\x{9F1F}\x{9F20}' . + '\x{9F22}\x{9F23}\x{9F24}\x{9F25}\x{9F27}\x{9F28}\x{9F29}\x{9F2A}\x{9F2B}' . + '\x{9F2C}\x{9F2D}\x{9F2E}\x{9F2F}\x{9F30}\x{9F31}\x{9F32}\x{9F33}\x{9F34}' . + '\x{9F35}\x{9F36}\x{9F37}\x{9F38}\x{9F39}\x{9F3A}\x{9F3B}\x{9F3C}\x{9F3D}' . + '\x{9F3E}\x{9F3F}\x{9F40}\x{9F41}\x{9F42}\x{9F43}\x{9F44}\x{9F46}\x{9F47}' . + '\x{9F48}\x{9F49}\x{9F4A}\x{9F4B}\x{9F4C}\x{9F4D}\x{9F4E}\x{9F4F}\x{9F50}' . + '\x{9F51}\x{9F52}\x{9F54}\x{9F55}\x{9F56}\x{9F57}\x{9F58}\x{9F59}\x{9F5A}' . + '\x{9F5B}\x{9F5C}\x{9F5D}\x{9F5E}\x{9F5F}\x{9F60}\x{9F61}\x{9F63}\x{9F64}' . + '\x{9F65}\x{9F66}\x{9F67}\x{9F68}\x{9F69}\x{9F6A}\x{9F6B}\x{9F6C}\x{9F6E}' . + '\x{9F6F}\x{9F70}\x{9F71}\x{9F72}\x{9F73}\x{9F74}\x{9F75}\x{9F76}\x{9F77}' . + '\x{9F78}\x{9F79}\x{9F7A}\x{9F7B}\x{9F7C}\x{9F7D}\x{9F7E}\x{9F7F}\x{9F80}' . + '\x{9F81}\x{9F82}\x{9F83}\x{9F84}\x{9F85}\x{9F86}\x{9F87}\x{9F88}\x{9F89}' . + '\x{9F8A}\x{9F8B}\x{9F8C}\x{9F8D}\x{9F8E}\x{9F8F}\x{9F90}\x{9F91}\x{9F92}' . + '\x{9F93}\x{9F94}\x{9F95}\x{9F96}\x{9F97}\x{9F98}\x{9F99}\x{9F9A}\x{9F9B}' . + '\x{9F9C}\x{9F9D}\x{9F9E}\x{9F9F}\x{9FA0}\x{9FA2}\x{9FA4}\x{9FA5}]{1,20}$/iu', +]; diff --git a/lib/laminas/laminas-validator/src/Hostname/Cn.php b/lib/laminas/laminas-validator/src/Hostname/Cn.php new file mode 100644 index 000000000..cab93d4b2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname/Cn.php @@ -0,0 +1,2184 @@ + '/^[\x{002d}0-9a-z\x{3447}\x{3473}\x{359E}\x{360E}\x{361A}\x{3918}\x{396E}\x{39CF}\x{39D0}' . + '\x{39DF}\x{3A73}\x{3B4E}\x{3C6E}\x{3CE0}\x{4056}\x{415F}\x{4337}\x{43AC}' . + '\x{43B1}\x{43DD}\x{44D6}\x{464C}\x{4661}\x{4723}\x{4729}\x{477C}\x{478D}' . + '\x{4947}\x{497A}\x{497D}\x{4982}\x{4983}\x{4985}\x{4986}\x{499B}\x{499F}' . + '\x{49B6}\x{49B7}\x{4C77}\x{4C9F}\x{4CA0}\x{4CA1}\x{4CA2}\x{4CA3}\x{4D13}' . + '\x{4D14}\x{4D15}\x{4D16}\x{4D17}\x{4D18}\x{4D19}\x{4DAE}\x{4E00}\x{4E01}' . + '\x{4E02}\x{4E03}\x{4E04}\x{4E05}\x{4E06}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' . + '\x{4E0B}\x{4E0C}\x{4E0D}\x{4E0E}\x{4E0F}\x{4E10}\x{4E11}\x{4E13}\x{4E14}' . + '\x{4E15}\x{4E16}\x{4E17}\x{4E18}\x{4E19}\x{4E1A}\x{4E1B}\x{4E1C}\x{4E1D}' . + '\x{4E1E}\x{4E1F}\x{4E20}\x{4E21}\x{4E22}\x{4E23}\x{4E24}\x{4E25}\x{4E26}' . + '\x{4E27}\x{4E28}\x{4E2A}\x{4E2B}\x{4E2C}\x{4E2D}\x{4E2E}\x{4E2F}\x{4E30}' . + '\x{4E31}\x{4E32}\x{4E33}\x{4E34}\x{4E35}\x{4E36}\x{4E37}\x{4E38}\x{4E39}' . + '\x{4E3A}\x{4E3B}\x{4E3C}\x{4E3D}\x{4E3E}\x{4E3F}\x{4E40}\x{4E41}\x{4E42}' . + '\x{4E43}\x{4E44}\x{4E45}\x{4E46}\x{4E47}\x{4E48}\x{4E49}\x{4E4A}\x{4E4B}' . + '\x{4E4C}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E50}\x{4E51}\x{4E52}\x{4E53}\x{4E54}' . + '\x{4E56}\x{4E57}\x{4E58}\x{4E59}\x{4E5A}\x{4E5B}\x{4E5C}\x{4E5D}\x{4E5E}' . + '\x{4E5F}\x{4E60}\x{4E61}\x{4E62}\x{4E63}\x{4E64}\x{4E65}\x{4E66}\x{4E67}' . + '\x{4E69}\x{4E6A}\x{4E6B}\x{4E6C}\x{4E6D}\x{4E6E}\x{4E6F}\x{4E70}\x{4E71}' . + '\x{4E72}\x{4E73}\x{4E74}\x{4E75}\x{4E76}\x{4E77}\x{4E78}\x{4E7A}\x{4E7B}' . + '\x{4E7C}\x{4E7D}\x{4E7E}\x{4E7F}\x{4E80}\x{4E81}\x{4E82}\x{4E83}\x{4E84}' . + '\x{4E85}\x{4E86}\x{4E87}\x{4E88}\x{4E89}\x{4E8B}\x{4E8C}\x{4E8D}\x{4E8E}' . + '\x{4E8F}\x{4E90}\x{4E91}\x{4E92}\x{4E93}\x{4E94}\x{4E95}\x{4E97}\x{4E98}' . + '\x{4E99}\x{4E9A}\x{4E9B}\x{4E9C}\x{4E9D}\x{4E9E}\x{4E9F}\x{4EA0}\x{4EA1}' . + '\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA7}\x{4EA8}\x{4EA9}\x{4EAA}\x{4EAB}' . + '\x{4EAC}\x{4EAD}\x{4EAE}\x{4EAF}\x{4EB0}\x{4EB1}\x{4EB2}\x{4EB3}\x{4EB4}' . + '\x{4EB5}\x{4EB6}\x{4EB7}\x{4EB8}\x{4EB9}\x{4EBA}\x{4EBB}\x{4EBD}\x{4EBE}' . + '\x{4EBF}\x{4EC0}\x{4EC1}\x{4EC2}\x{4EC3}\x{4EC4}\x{4EC5}\x{4EC6}\x{4EC7}' . + '\x{4EC8}\x{4EC9}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED0}\x{4ED1}' . + '\x{4ED2}\x{4ED3}\x{4ED4}\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDA}' . + '\x{4EDB}\x{4EDC}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE0}\x{4EE1}\x{4EE2}\x{4EE3}' . + '\x{4EE4}\x{4EE5}\x{4EE6}\x{4EE8}\x{4EE9}\x{4EEA}\x{4EEB}\x{4EEC}\x{4EEF}' . + '\x{4EF0}\x{4EF1}\x{4EF2}\x{4EF3}\x{4EF4}\x{4EF5}\x{4EF6}\x{4EF7}\x{4EFB}' . + '\x{4EFD}\x{4EFF}\x{4F00}\x{4F01}\x{4F02}\x{4F03}\x{4F04}\x{4F05}\x{4F06}' . + '\x{4F08}\x{4F09}\x{4F0A}\x{4F0B}\x{4F0C}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}' . + '\x{4F11}\x{4F12}\x{4F13}\x{4F14}\x{4F15}\x{4F17}\x{4F18}\x{4F19}\x{4F1A}' . + '\x{4F1B}\x{4F1C}\x{4F1D}\x{4F1E}\x{4F1F}\x{4F20}\x{4F21}\x{4F22}\x{4F23}' . + '\x{4F24}\x{4F25}\x{4F26}\x{4F27}\x{4F29}\x{4F2A}\x{4F2B}\x{4F2C}\x{4F2D}' . + '\x{4F2E}\x{4F2F}\x{4F30}\x{4F32}\x{4F33}\x{4F34}\x{4F36}\x{4F38}\x{4F39}' . + '\x{4F3A}\x{4F3B}\x{4F3C}\x{4F3D}\x{4F3E}\x{4F3F}\x{4F41}\x{4F42}\x{4F43}' . + '\x{4F45}\x{4F46}\x{4F47}\x{4F48}\x{4F49}\x{4F4A}\x{4F4B}\x{4F4C}\x{4F4D}' . + '\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}\x{4F52}\x{4F53}\x{4F54}\x{4F55}\x{4F56}' . + '\x{4F57}\x{4F58}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}\x{4F5F}' . + '\x{4F60}\x{4F61}\x{4F62}\x{4F63}\x{4F64}\x{4F65}\x{4F66}\x{4F67}\x{4F68}' . + '\x{4F69}\x{4F6A}\x{4F6B}\x{4F6C}\x{4F6D}\x{4F6E}\x{4F6F}\x{4F70}\x{4F72}' . + '\x{4F73}\x{4F74}\x{4F75}\x{4F76}\x{4F77}\x{4F78}\x{4F79}\x{4F7A}\x{4F7B}' . + '\x{4F7C}\x{4F7D}\x{4F7E}\x{4F7F}\x{4F80}\x{4F81}\x{4F82}\x{4F83}\x{4F84}' . + '\x{4F85}\x{4F86}\x{4F87}\x{4F88}\x{4F89}\x{4F8A}\x{4F8B}\x{4F8D}\x{4F8F}' . + '\x{4F90}\x{4F91}\x{4F92}\x{4F93}\x{4F94}\x{4F95}\x{4F96}\x{4F97}\x{4F98}' . + '\x{4F99}\x{4F9A}\x{4F9B}\x{4F9C}\x{4F9D}\x{4F9E}\x{4F9F}\x{4FA0}\x{4FA1}' . + '\x{4FA3}\x{4FA4}\x{4FA5}\x{4FA6}\x{4FA7}\x{4FA8}\x{4FA9}\x{4FAA}\x{4FAB}' . + '\x{4FAC}\x{4FAE}\x{4FAF}\x{4FB0}\x{4FB1}\x{4FB2}\x{4FB3}\x{4FB4}\x{4FB5}' . + '\x{4FB6}\x{4FB7}\x{4FB8}\x{4FB9}\x{4FBA}\x{4FBB}\x{4FBC}\x{4FBE}\x{4FBF}' . + '\x{4FC0}\x{4FC1}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FC5}\x{4FC7}\x{4FC9}\x{4FCA}' . + '\x{4FCB}\x{4FCD}\x{4FCE}\x{4FCF}\x{4FD0}\x{4FD1}\x{4FD2}\x{4FD3}\x{4FD4}' . + '\x{4FD5}\x{4FD6}\x{4FD7}\x{4FD8}\x{4FD9}\x{4FDA}\x{4FDB}\x{4FDC}\x{4FDD}' . + '\x{4FDE}\x{4FDF}\x{4FE0}\x{4FE1}\x{4FE3}\x{4FE4}\x{4FE5}\x{4FE6}\x{4FE7}' . + '\x{4FE8}\x{4FE9}\x{4FEA}\x{4FEB}\x{4FEC}\x{4FED}\x{4FEE}\x{4FEF}\x{4FF0}' . + '\x{4FF1}\x{4FF2}\x{4FF3}\x{4FF4}\x{4FF5}\x{4FF6}\x{4FF7}\x{4FF8}\x{4FF9}' . + '\x{4FFA}\x{4FFB}\x{4FFE}\x{4FFF}\x{5000}\x{5001}\x{5002}\x{5003}\x{5004}' . + '\x{5005}\x{5006}\x{5007}\x{5008}\x{5009}\x{500A}\x{500B}\x{500C}\x{500D}' . + '\x{500E}\x{500F}\x{5011}\x{5012}\x{5013}\x{5014}\x{5015}\x{5016}\x{5017}' . + '\x{5018}\x{5019}\x{501A}\x{501B}\x{501C}\x{501D}\x{501E}\x{501F}\x{5020}' . + '\x{5021}\x{5022}\x{5023}\x{5024}\x{5025}\x{5026}\x{5027}\x{5028}\x{5029}' . + '\x{502A}\x{502B}\x{502C}\x{502D}\x{502E}\x{502F}\x{5030}\x{5031}\x{5032}' . + '\x{5033}\x{5035}\x{5036}\x{5037}\x{5039}\x{503A}\x{503B}\x{503C}\x{503E}' . + '\x{503F}\x{5040}\x{5041}\x{5043}\x{5044}\x{5045}\x{5046}\x{5047}\x{5048}' . + '\x{5049}\x{504A}\x{504B}\x{504C}\x{504D}\x{504E}\x{504F}\x{5051}\x{5053}' . + '\x{5054}\x{5055}\x{5056}\x{5057}\x{5059}\x{505A}\x{505B}\x{505C}\x{505D}' . + '\x{505E}\x{505F}\x{5060}\x{5061}\x{5062}\x{5063}\x{5064}\x{5065}\x{5066}' . + '\x{5067}\x{5068}\x{5069}\x{506A}\x{506B}\x{506C}\x{506D}\x{506E}\x{506F}' . + '\x{5070}\x{5071}\x{5072}\x{5073}\x{5074}\x{5075}\x{5076}\x{5077}\x{5078}' . + '\x{5079}\x{507A}\x{507B}\x{507D}\x{507E}\x{507F}\x{5080}\x{5082}\x{5083}' . + '\x{5084}\x{5085}\x{5086}\x{5087}\x{5088}\x{5089}\x{508A}\x{508B}\x{508C}' . + '\x{508D}\x{508E}\x{508F}\x{5090}\x{5091}\x{5092}\x{5094}\x{5095}\x{5096}' . + '\x{5098}\x{5099}\x{509A}\x{509B}\x{509C}\x{509D}\x{509E}\x{50A2}\x{50A3}' . + '\x{50A4}\x{50A5}\x{50A6}\x{50A7}\x{50A8}\x{50A9}\x{50AA}\x{50AB}\x{50AC}' . + '\x{50AD}\x{50AE}\x{50AF}\x{50B0}\x{50B1}\x{50B2}\x{50B3}\x{50B4}\x{50B5}' . + '\x{50B6}\x{50B7}\x{50B8}\x{50BA}\x{50BB}\x{50BC}\x{50BD}\x{50BE}\x{50BF}' . + '\x{50C0}\x{50C1}\x{50C2}\x{50C4}\x{50C5}\x{50C6}\x{50C7}\x{50C8}\x{50C9}' . + '\x{50CA}\x{50CB}\x{50CC}\x{50CD}\x{50CE}\x{50CF}\x{50D0}\x{50D1}\x{50D2}' . + '\x{50D3}\x{50D4}\x{50D5}\x{50D6}\x{50D7}\x{50D9}\x{50DA}\x{50DB}\x{50DC}' . + '\x{50DD}\x{50DE}\x{50E0}\x{50E3}\x{50E4}\x{50E5}\x{50E6}\x{50E7}\x{50E8}' . + '\x{50E9}\x{50EA}\x{50EC}\x{50ED}\x{50EE}\x{50EF}\x{50F0}\x{50F1}\x{50F2}' . + '\x{50F3}\x{50F5}\x{50F6}\x{50F8}\x{50F9}\x{50FA}\x{50FB}\x{50FC}\x{50FD}' . + '\x{50FE}\x{50FF}\x{5100}\x{5101}\x{5102}\x{5103}\x{5104}\x{5105}\x{5106}' . + '\x{5107}\x{5108}\x{5109}\x{510A}\x{510B}\x{510C}\x{510D}\x{510E}\x{510F}' . + '\x{5110}\x{5111}\x{5112}\x{5113}\x{5114}\x{5115}\x{5116}\x{5117}\x{5118}' . + '\x{5119}\x{511A}\x{511C}\x{511D}\x{511E}\x{511F}\x{5120}\x{5121}\x{5122}' . + '\x{5123}\x{5124}\x{5125}\x{5126}\x{5127}\x{5129}\x{512A}\x{512C}\x{512D}' . + '\x{512E}\x{512F}\x{5130}\x{5131}\x{5132}\x{5133}\x{5134}\x{5135}\x{5136}' . + '\x{5137}\x{5138}\x{5139}\x{513A}\x{513B}\x{513C}\x{513D}\x{513E}\x{513F}' . + '\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' . + '\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5151}\x{5152}\x{5154}\x{5155}' . + '\x{5156}\x{5157}\x{5159}\x{515A}\x{515B}\x{515C}\x{515D}\x{515E}\x{515F}' . + '\x{5161}\x{5162}\x{5163}\x{5165}\x{5166}\x{5167}\x{5168}\x{5169}\x{516A}' . + '\x{516B}\x{516C}\x{516D}\x{516E}\x{516F}\x{5170}\x{5171}\x{5173}\x{5174}' . + '\x{5175}\x{5176}\x{5177}\x{5178}\x{5179}\x{517A}\x{517B}\x{517C}\x{517D}' . + '\x{517F}\x{5180}\x{5181}\x{5182}\x{5185}\x{5186}\x{5187}\x{5188}\x{5189}' . + '\x{518A}\x{518B}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}\x{5193}' . + '\x{5194}\x{5195}\x{5196}\x{5197}\x{5198}\x{5199}\x{519A}\x{519B}\x{519C}' . + '\x{519D}\x{519E}\x{519F}\x{51A0}\x{51A2}\x{51A4}\x{51A5}\x{51A6}\x{51A7}' . + '\x{51A8}\x{51AA}\x{51AB}\x{51AC}\x{51AE}\x{51AF}\x{51B0}\x{51B1}\x{51B2}' . + '\x{51B3}\x{51B5}\x{51B6}\x{51B7}\x{51B9}\x{51BB}\x{51BC}\x{51BD}\x{51BE}' . + '\x{51BF}\x{51C0}\x{51C1}\x{51C3}\x{51C4}\x{51C5}\x{51C6}\x{51C7}\x{51C8}' . + '\x{51C9}\x{51CA}\x{51CB}\x{51CC}\x{51CD}\x{51CE}\x{51CF}\x{51D0}\x{51D1}' . + '\x{51D4}\x{51D5}\x{51D6}\x{51D7}\x{51D8}\x{51D9}\x{51DA}\x{51DB}\x{51DC}' . + '\x{51DD}\x{51DE}\x{51E0}\x{51E1}\x{51E2}\x{51E3}\x{51E4}\x{51E5}\x{51E7}' . + '\x{51E8}\x{51E9}\x{51EA}\x{51EB}\x{51ED}\x{51EF}\x{51F0}\x{51F1}\x{51F3}' . + '\x{51F4}\x{51F5}\x{51F6}\x{51F7}\x{51F8}\x{51F9}\x{51FA}\x{51FB}\x{51FC}' . + '\x{51FD}\x{51FE}\x{51FF}\x{5200}\x{5201}\x{5202}\x{5203}\x{5204}\x{5205}' . + '\x{5206}\x{5207}\x{5208}\x{5209}\x{520A}\x{520B}\x{520C}\x{520D}\x{520E}' . + '\x{520F}\x{5210}\x{5211}\x{5212}\x{5213}\x{5214}\x{5215}\x{5216}\x{5217}' . + '\x{5218}\x{5219}\x{521A}\x{521B}\x{521C}\x{521D}\x{521E}\x{521F}\x{5220}' . + '\x{5221}\x{5222}\x{5223}\x{5224}\x{5225}\x{5226}\x{5228}\x{5229}\x{522A}' . + '\x{522B}\x{522C}\x{522D}\x{522E}\x{522F}\x{5230}\x{5231}\x{5232}\x{5233}' . + '\x{5234}\x{5235}\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{523C}' . + '\x{523D}\x{523E}\x{523F}\x{5240}\x{5241}\x{5242}\x{5243}\x{5244}\x{5245}' . + '\x{5246}\x{5247}\x{5248}\x{5249}\x{524A}\x{524B}\x{524C}\x{524D}\x{524E}' . + '\x{5250}\x{5251}\x{5252}\x{5254}\x{5255}\x{5256}\x{5257}\x{5258}\x{5259}' . + '\x{525A}\x{525B}\x{525C}\x{525D}\x{525E}\x{525F}\x{5260}\x{5261}\x{5262}' . + '\x{5263}\x{5264}\x{5265}\x{5267}\x{5268}\x{5269}\x{526A}\x{526B}\x{526C}' . + '\x{526D}\x{526E}\x{526F}\x{5270}\x{5272}\x{5273}\x{5274}\x{5275}\x{5276}' . + '\x{5277}\x{5278}\x{527A}\x{527B}\x{527C}\x{527D}\x{527E}\x{527F}\x{5280}' . + '\x{5281}\x{5282}\x{5283}\x{5284}\x{5286}\x{5287}\x{5288}\x{5289}\x{528A}' . + '\x{528B}\x{528C}\x{528D}\x{528F}\x{5290}\x{5291}\x{5292}\x{5293}\x{5294}' . + '\x{5295}\x{5296}\x{5297}\x{5298}\x{5299}\x{529A}\x{529B}\x{529C}\x{529D}' . + '\x{529E}\x{529F}\x{52A0}\x{52A1}\x{52A2}\x{52A3}\x{52A5}\x{52A6}\x{52A7}' . + '\x{52A8}\x{52A9}\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52AE}\x{52AF}\x{52B0}' . + '\x{52B1}\x{52B2}\x{52B3}\x{52B4}\x{52B5}\x{52B6}\x{52B7}\x{52B8}\x{52B9}' . + '\x{52BA}\x{52BB}\x{52BC}\x{52BD}\x{52BE}\x{52BF}\x{52C0}\x{52C1}\x{52C2}' . + '\x{52C3}\x{52C6}\x{52C7}\x{52C9}\x{52CA}\x{52CB}\x{52CD}\x{52CF}\x{52D0}' . + '\x{52D2}\x{52D3}\x{52D5}\x{52D6}\x{52D7}\x{52D8}\x{52D9}\x{52DA}\x{52DB}' . + '\x{52DC}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}\x{52E4}\x{52E6}' . + '\x{52E7}\x{52E8}\x{52E9}\x{52EA}\x{52EB}\x{52EC}\x{52ED}\x{52EF}\x{52F0}' . + '\x{52F1}\x{52F2}\x{52F3}\x{52F4}\x{52F5}\x{52F6}\x{52F7}\x{52F8}\x{52F9}' . + '\x{52FA}\x{52FB}\x{52FC}\x{52FD}\x{52FE}\x{52FF}\x{5300}\x{5301}\x{5302}' . + '\x{5305}\x{5306}\x{5307}\x{5308}\x{5309}\x{530A}\x{530B}\x{530C}\x{530D}' . + '\x{530E}\x{530F}\x{5310}\x{5311}\x{5312}\x{5313}\x{5314}\x{5315}\x{5316}' . + '\x{5317}\x{5319}\x{531A}\x{531C}\x{531D}\x{531F}\x{5320}\x{5321}\x{5322}' . + '\x{5323}\x{5324}\x{5325}\x{5326}\x{5328}\x{532A}\x{532B}\x{532C}\x{532D}' . + '\x{532E}\x{532F}\x{5330}\x{5331}\x{5333}\x{5334}\x{5337}\x{5339}\x{533A}' . + '\x{533B}\x{533C}\x{533D}\x{533E}\x{533F}\x{5340}\x{5341}\x{5343}\x{5344}' . + '\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}\x{534A}\x{534B}\x{534C}\x{534D}' . + '\x{534E}\x{534F}\x{5350}\x{5351}\x{5352}\x{5353}\x{5354}\x{5355}\x{5356}' . + '\x{5357}\x{5358}\x{5359}\x{535A}\x{535C}\x{535E}\x{535F}\x{5360}\x{5361}' . + '\x{5362}\x{5363}\x{5364}\x{5365}\x{5366}\x{5367}\x{5369}\x{536B}\x{536C}' . + '\x{536E}\x{536F}\x{5370}\x{5371}\x{5372}\x{5373}\x{5374}\x{5375}\x{5376}' . + '\x{5377}\x{5378}\x{5379}\x{537A}\x{537B}\x{537C}\x{537D}\x{537E}\x{537F}' . + '\x{5381}\x{5382}\x{5383}\x{5384}\x{5385}\x{5386}\x{5387}\x{5388}\x{5389}' . + '\x{538A}\x{538B}\x{538C}\x{538D}\x{538E}\x{538F}\x{5390}\x{5391}\x{5392}' . + '\x{5393}\x{5394}\x{5395}\x{5396}\x{5397}\x{5398}\x{5399}\x{539A}\x{539B}' . + '\x{539C}\x{539D}\x{539E}\x{539F}\x{53A0}\x{53A2}\x{53A3}\x{53A4}\x{53A5}' . + '\x{53A6}\x{53A7}\x{53A8}\x{53A9}\x{53AC}\x{53AD}\x{53AE}\x{53B0}\x{53B1}' . + '\x{53B2}\x{53B3}\x{53B4}\x{53B5}\x{53B6}\x{53B7}\x{53B8}\x{53B9}\x{53BB}' . + '\x{53BC}\x{53BD}\x{53BE}\x{53BF}\x{53C0}\x{53C1}\x{53C2}\x{53C3}\x{53C4}' . + '\x{53C6}\x{53C7}\x{53C8}\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}' . + '\x{53D0}\x{53D1}\x{53D2}\x{53D3}\x{53D4}\x{53D5}\x{53D6}\x{53D7}\x{53D8}' . + '\x{53D9}\x{53DB}\x{53DC}\x{53DF}\x{53E0}\x{53E1}\x{53E2}\x{53E3}\x{53E4}' . + '\x{53E5}\x{53E6}\x{53E8}\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}' . + '\x{53EF}\x{53F0}\x{53F1}\x{53F2}\x{53F3}\x{53F4}\x{53F5}\x{53F6}\x{53F7}' . + '\x{53F8}\x{53F9}\x{53FA}\x{53FB}\x{53FC}\x{53FD}\x{53FE}\x{5401}\x{5402}' . + '\x{5403}\x{5404}\x{5405}\x{5406}\x{5407}\x{5408}\x{5409}\x{540A}\x{540B}' . + '\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}\x{5411}\x{5412}\x{5413}\x{5414}' . + '\x{5415}\x{5416}\x{5417}\x{5418}\x{5419}\x{541B}\x{541C}\x{541D}\x{541E}' . + '\x{541F}\x{5420}\x{5421}\x{5423}\x{5424}\x{5425}\x{5426}\x{5427}\x{5428}' . + '\x{5429}\x{542A}\x{542B}\x{542C}\x{542D}\x{542E}\x{542F}\x{5430}\x{5431}' . + '\x{5432}\x{5433}\x{5434}\x{5435}\x{5436}\x{5437}\x{5438}\x{5439}\x{543A}' . + '\x{543B}\x{543C}\x{543D}\x{543E}\x{543F}\x{5440}\x{5441}\x{5442}\x{5443}' . + '\x{5444}\x{5445}\x{5446}\x{5447}\x{5448}\x{5449}\x{544A}\x{544B}\x{544D}' . + '\x{544E}\x{544F}\x{5450}\x{5451}\x{5452}\x{5453}\x{5454}\x{5455}\x{5456}' . + '\x{5457}\x{5458}\x{5459}\x{545A}\x{545B}\x{545C}\x{545E}\x{545F}\x{5460}' . + '\x{5461}\x{5462}\x{5463}\x{5464}\x{5465}\x{5466}\x{5467}\x{5468}\x{546A}' . + '\x{546B}\x{546C}\x{546D}\x{546E}\x{546F}\x{5470}\x{5471}\x{5472}\x{5473}' . + '\x{5474}\x{5475}\x{5476}\x{5477}\x{5478}\x{5479}\x{547A}\x{547B}\x{547C}' . + '\x{547D}\x{547E}\x{547F}\x{5480}\x{5481}\x{5482}\x{5483}\x{5484}\x{5485}' . + '\x{5486}\x{5487}\x{5488}\x{5489}\x{548B}\x{548C}\x{548D}\x{548E}\x{548F}' . + '\x{5490}\x{5491}\x{5492}\x{5493}\x{5494}\x{5495}\x{5496}\x{5497}\x{5498}' . + '\x{5499}\x{549A}\x{549B}\x{549C}\x{549D}\x{549E}\x{549F}\x{54A0}\x{54A1}' . + '\x{54A2}\x{54A3}\x{54A4}\x{54A5}\x{54A6}\x{54A7}\x{54A8}\x{54A9}\x{54AA}' . + '\x{54AB}\x{54AC}\x{54AD}\x{54AE}\x{54AF}\x{54B0}\x{54B1}\x{54B2}\x{54B3}' . + '\x{54B4}\x{54B6}\x{54B7}\x{54B8}\x{54B9}\x{54BA}\x{54BB}\x{54BC}\x{54BD}' . + '\x{54BE}\x{54BF}\x{54C0}\x{54C1}\x{54C2}\x{54C3}\x{54C4}\x{54C5}\x{54C6}' . + '\x{54C7}\x{54C8}\x{54C9}\x{54CA}\x{54CB}\x{54CC}\x{54CD}\x{54CE}\x{54CF}' . + '\x{54D0}\x{54D1}\x{54D2}\x{54D3}\x{54D4}\x{54D5}\x{54D6}\x{54D7}\x{54D8}' . + '\x{54D9}\x{54DA}\x{54DB}\x{54DC}\x{54DD}\x{54DE}\x{54DF}\x{54E0}\x{54E1}' . + '\x{54E2}\x{54E3}\x{54E4}\x{54E5}\x{54E6}\x{54E7}\x{54E8}\x{54E9}\x{54EA}' . + '\x{54EB}\x{54EC}\x{54ED}\x{54EE}\x{54EF}\x{54F0}\x{54F1}\x{54F2}\x{54F3}' . + '\x{54F4}\x{54F5}\x{54F7}\x{54F8}\x{54F9}\x{54FA}\x{54FB}\x{54FC}\x{54FD}' . + '\x{54FE}\x{54FF}\x{5500}\x{5501}\x{5502}\x{5503}\x{5504}\x{5505}\x{5506}' . + '\x{5507}\x{5508}\x{5509}\x{550A}\x{550B}\x{550C}\x{550D}\x{550E}\x{550F}' . + '\x{5510}\x{5511}\x{5512}\x{5513}\x{5514}\x{5516}\x{5517}\x{551A}\x{551B}' . + '\x{551C}\x{551D}\x{551E}\x{551F}\x{5520}\x{5521}\x{5522}\x{5523}\x{5524}' . + '\x{5525}\x{5526}\x{5527}\x{5528}\x{5529}\x{552A}\x{552B}\x{552C}\x{552D}' . + '\x{552E}\x{552F}\x{5530}\x{5531}\x{5532}\x{5533}\x{5534}\x{5535}\x{5536}' . + '\x{5537}\x{5538}\x{5539}\x{553A}\x{553B}\x{553C}\x{553D}\x{553E}\x{553F}' . + '\x{5540}\x{5541}\x{5542}\x{5543}\x{5544}\x{5545}\x{5546}\x{5548}\x{5549}' . + '\x{554A}\x{554B}\x{554C}\x{554D}\x{554E}\x{554F}\x{5550}\x{5551}\x{5552}' . + '\x{5553}\x{5554}\x{5555}\x{5556}\x{5557}\x{5558}\x{5559}\x{555A}\x{555B}' . + '\x{555C}\x{555D}\x{555E}\x{555F}\x{5561}\x{5562}\x{5563}\x{5564}\x{5565}' . + '\x{5566}\x{5567}\x{5568}\x{5569}\x{556A}\x{556B}\x{556C}\x{556D}\x{556E}' . + '\x{556F}\x{5570}\x{5571}\x{5572}\x{5573}\x{5574}\x{5575}\x{5576}\x{5577}' . + '\x{5578}\x{5579}\x{557B}\x{557C}\x{557D}\x{557E}\x{557F}\x{5580}\x{5581}' . + '\x{5582}\x{5583}\x{5584}\x{5585}\x{5586}\x{5587}\x{5588}\x{5589}\x{558A}' . + '\x{558B}\x{558C}\x{558D}\x{558E}\x{558F}\x{5590}\x{5591}\x{5592}\x{5593}' . + '\x{5594}\x{5595}\x{5596}\x{5597}\x{5598}\x{5599}\x{559A}\x{559B}\x{559C}' . + '\x{559D}\x{559E}\x{559F}\x{55A0}\x{55A1}\x{55A2}\x{55A3}\x{55A4}\x{55A5}' . + '\x{55A6}\x{55A7}\x{55A8}\x{55A9}\x{55AA}\x{55AB}\x{55AC}\x{55AD}\x{55AE}' . + '\x{55AF}\x{55B0}\x{55B1}\x{55B2}\x{55B3}\x{55B4}\x{55B5}\x{55B6}\x{55B7}' . + '\x{55B8}\x{55B9}\x{55BA}\x{55BB}\x{55BC}\x{55BD}\x{55BE}\x{55BF}\x{55C0}' . + '\x{55C1}\x{55C2}\x{55C3}\x{55C4}\x{55C5}\x{55C6}\x{55C7}\x{55C8}\x{55C9}' . + '\x{55CA}\x{55CB}\x{55CC}\x{55CD}\x{55CE}\x{55CF}\x{55D0}\x{55D1}\x{55D2}' . + '\x{55D3}\x{55D4}\x{55D5}\x{55D6}\x{55D7}\x{55D8}\x{55D9}\x{55DA}\x{55DB}' . + '\x{55DC}\x{55DD}\x{55DE}\x{55DF}\x{55E1}\x{55E2}\x{55E3}\x{55E4}\x{55E5}' . + '\x{55E6}\x{55E7}\x{55E8}\x{55E9}\x{55EA}\x{55EB}\x{55EC}\x{55ED}\x{55EE}' . + '\x{55EF}\x{55F0}\x{55F1}\x{55F2}\x{55F3}\x{55F4}\x{55F5}\x{55F6}\x{55F7}' . + '\x{55F9}\x{55FA}\x{55FB}\x{55FC}\x{55FD}\x{55FE}\x{55FF}\x{5600}\x{5601}' . + '\x{5602}\x{5603}\x{5604}\x{5606}\x{5607}\x{5608}\x{5609}\x{560C}\x{560D}' . + '\x{560E}\x{560F}\x{5610}\x{5611}\x{5612}\x{5613}\x{5614}\x{5615}\x{5616}' . + '\x{5617}\x{5618}\x{5619}\x{561A}\x{561B}\x{561C}\x{561D}\x{561E}\x{561F}' . + '\x{5621}\x{5622}\x{5623}\x{5624}\x{5625}\x{5626}\x{5627}\x{5628}\x{5629}' . + '\x{562A}\x{562C}\x{562D}\x{562E}\x{562F}\x{5630}\x{5631}\x{5632}\x{5633}' . + '\x{5634}\x{5635}\x{5636}\x{5638}\x{5639}\x{563A}\x{563B}\x{563D}\x{563E}' . + '\x{563F}\x{5640}\x{5641}\x{5642}\x{5643}\x{5645}\x{5646}\x{5647}\x{5648}' . + '\x{5649}\x{564A}\x{564C}\x{564D}\x{564E}\x{564F}\x{5650}\x{5652}\x{5653}' . + '\x{5654}\x{5655}\x{5657}\x{5658}\x{5659}\x{565A}\x{565B}\x{565C}\x{565D}' . + '\x{565E}\x{5660}\x{5662}\x{5663}\x{5664}\x{5665}\x{5666}\x{5667}\x{5668}' . + '\x{5669}\x{566A}\x{566B}\x{566C}\x{566D}\x{566E}\x{566F}\x{5670}\x{5671}' . + '\x{5672}\x{5673}\x{5674}\x{5676}\x{5677}\x{5678}\x{5679}\x{567A}\x{567B}' . + '\x{567C}\x{567E}\x{567F}\x{5680}\x{5681}\x{5682}\x{5683}\x{5684}\x{5685}' . + '\x{5686}\x{5687}\x{568A}\x{568C}\x{568D}\x{568E}\x{568F}\x{5690}\x{5691}' . + '\x{5692}\x{5693}\x{5694}\x{5695}\x{5697}\x{5698}\x{5699}\x{569A}\x{569B}' . + '\x{569C}\x{569D}\x{569F}\x{56A0}\x{56A1}\x{56A3}\x{56A4}\x{56A5}\x{56A6}' . + '\x{56A7}\x{56A8}\x{56A9}\x{56AA}\x{56AB}\x{56AC}\x{56AD}\x{56AE}\x{56AF}' . + '\x{56B0}\x{56B1}\x{56B2}\x{56B3}\x{56B4}\x{56B5}\x{56B6}\x{56B7}\x{56B8}' . + '\x{56B9}\x{56BB}\x{56BC}\x{56BD}\x{56BE}\x{56BF}\x{56C0}\x{56C1}\x{56C2}' . + '\x{56C3}\x{56C4}\x{56C5}\x{56C6}\x{56C7}\x{56C8}\x{56C9}\x{56CA}\x{56CB}' . + '\x{56CC}\x{56CD}\x{56CE}\x{56D0}\x{56D1}\x{56D2}\x{56D3}\x{56D4}\x{56D5}' . + '\x{56D6}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DC}\x{56DD}\x{56DE}\x{56DF}' . + '\x{56E0}\x{56E1}\x{56E2}\x{56E3}\x{56E4}\x{56E5}\x{56E7}\x{56E8}\x{56E9}' . + '\x{56EA}\x{56EB}\x{56EC}\x{56ED}\x{56EE}\x{56EF}\x{56F0}\x{56F1}\x{56F2}' . + '\x{56F3}\x{56F4}\x{56F5}\x{56F7}\x{56F9}\x{56FA}\x{56FD}\x{56FE}\x{56FF}' . + '\x{5700}\x{5701}\x{5702}\x{5703}\x{5704}\x{5706}\x{5707}\x{5708}\x{5709}' . + '\x{570A}\x{570B}\x{570C}\x{570D}\x{570E}\x{570F}\x{5710}\x{5712}\x{5713}' . + '\x{5714}\x{5715}\x{5716}\x{5718}\x{5719}\x{571A}\x{571B}\x{571C}\x{571D}' . + '\x{571E}\x{571F}\x{5720}\x{5722}\x{5723}\x{5725}\x{5726}\x{5727}\x{5728}' . + '\x{5729}\x{572A}\x{572B}\x{572C}\x{572D}\x{572E}\x{572F}\x{5730}\x{5731}' . + '\x{5732}\x{5733}\x{5734}\x{5735}\x{5736}\x{5737}\x{5738}\x{5739}\x{573A}' . + '\x{573B}\x{573C}\x{573E}\x{573F}\x{5740}\x{5741}\x{5742}\x{5744}\x{5745}' . + '\x{5746}\x{5747}\x{5749}\x{574A}\x{574B}\x{574C}\x{574D}\x{574E}\x{574F}' . + '\x{5750}\x{5751}\x{5752}\x{5753}\x{5754}\x{5757}\x{5759}\x{575A}\x{575B}' . + '\x{575C}\x{575D}\x{575E}\x{575F}\x{5760}\x{5761}\x{5762}\x{5764}\x{5765}' . + '\x{5766}\x{5767}\x{5768}\x{5769}\x{576A}\x{576B}\x{576C}\x{576D}\x{576F}' . + '\x{5770}\x{5771}\x{5772}\x{5773}\x{5774}\x{5775}\x{5776}\x{5777}\x{5779}' . + '\x{577A}\x{577B}\x{577C}\x{577D}\x{577E}\x{577F}\x{5780}\x{5782}\x{5783}' . + '\x{5784}\x{5785}\x{5786}\x{5788}\x{5789}\x{578A}\x{578B}\x{578C}\x{578D}' . + '\x{578E}\x{578F}\x{5790}\x{5791}\x{5792}\x{5793}\x{5794}\x{5795}\x{5797}' . + '\x{5798}\x{5799}\x{579A}\x{579B}\x{579C}\x{579D}\x{579E}\x{579F}\x{57A0}' . + '\x{57A1}\x{57A2}\x{57A3}\x{57A4}\x{57A5}\x{57A6}\x{57A7}\x{57A9}\x{57AA}' . + '\x{57AB}\x{57AC}\x{57AD}\x{57AE}\x{57AF}\x{57B0}\x{57B1}\x{57B2}\x{57B3}' . + '\x{57B4}\x{57B5}\x{57B6}\x{57B7}\x{57B8}\x{57B9}\x{57BA}\x{57BB}\x{57BC}' . + '\x{57BD}\x{57BE}\x{57BF}\x{57C0}\x{57C1}\x{57C2}\x{57C3}\x{57C4}\x{57C5}' . + '\x{57C6}\x{57C7}\x{57C8}\x{57C9}\x{57CB}\x{57CC}\x{57CD}\x{57CE}\x{57CF}' . + '\x{57D0}\x{57D2}\x{57D3}\x{57D4}\x{57D5}\x{57D6}\x{57D8}\x{57D9}\x{57DA}' . + '\x{57DC}\x{57DD}\x{57DF}\x{57E0}\x{57E1}\x{57E2}\x{57E3}\x{57E4}\x{57E5}' . + '\x{57E6}\x{57E7}\x{57E8}\x{57E9}\x{57EA}\x{57EB}\x{57EC}\x{57ED}\x{57EE}' . + '\x{57EF}\x{57F0}\x{57F1}\x{57F2}\x{57F3}\x{57F4}\x{57F5}\x{57F6}\x{57F7}' . + '\x{57F8}\x{57F9}\x{57FA}\x{57FB}\x{57FC}\x{57FD}\x{57FE}\x{57FF}\x{5800}' . + '\x{5801}\x{5802}\x{5803}\x{5804}\x{5805}\x{5806}\x{5807}\x{5808}\x{5809}' . + '\x{580A}\x{580B}\x{580C}\x{580D}\x{580E}\x{580F}\x{5810}\x{5811}\x{5812}' . + '\x{5813}\x{5814}\x{5815}\x{5816}\x{5819}\x{581A}\x{581B}\x{581C}\x{581D}' . + '\x{581E}\x{581F}\x{5820}\x{5821}\x{5822}\x{5823}\x{5824}\x{5825}\x{5826}' . + '\x{5827}\x{5828}\x{5829}\x{582A}\x{582B}\x{582C}\x{582D}\x{582E}\x{582F}' . + '\x{5830}\x{5831}\x{5832}\x{5833}\x{5834}\x{5835}\x{5836}\x{5837}\x{5838}' . + '\x{5839}\x{583A}\x{583B}\x{583C}\x{583D}\x{583E}\x{583F}\x{5840}\x{5842}' . + '\x{5843}\x{5844}\x{5845}\x{5846}\x{5847}\x{5848}\x{5849}\x{584A}\x{584B}' . + '\x{584C}\x{584D}\x{584E}\x{584F}\x{5851}\x{5852}\x{5853}\x{5854}\x{5855}' . + '\x{5857}\x{5858}\x{5859}\x{585A}\x{585B}\x{585C}\x{585D}\x{585E}\x{585F}' . + '\x{5861}\x{5862}\x{5863}\x{5864}\x{5865}\x{5868}\x{5869}\x{586A}\x{586B}' . + '\x{586C}\x{586D}\x{586E}\x{586F}\x{5870}\x{5871}\x{5872}\x{5873}\x{5874}' . + '\x{5875}\x{5876}\x{5878}\x{5879}\x{587A}\x{587B}\x{587C}\x{587D}\x{587E}' . + '\x{587F}\x{5880}\x{5881}\x{5882}\x{5883}\x{5884}\x{5885}\x{5886}\x{5887}' . + '\x{5888}\x{5889}\x{588A}\x{588B}\x{588C}\x{588D}\x{588E}\x{588F}\x{5890}' . + '\x{5891}\x{5892}\x{5893}\x{5894}\x{5896}\x{5897}\x{5898}\x{5899}\x{589A}' . + '\x{589B}\x{589C}\x{589D}\x{589E}\x{589F}\x{58A0}\x{58A1}\x{58A2}\x{58A3}' . + '\x{58A4}\x{58A5}\x{58A6}\x{58A7}\x{58A8}\x{58A9}\x{58AB}\x{58AC}\x{58AD}' . + '\x{58AE}\x{58AF}\x{58B0}\x{58B1}\x{58B2}\x{58B3}\x{58B4}\x{58B7}\x{58B8}' . + '\x{58B9}\x{58BA}\x{58BB}\x{58BC}\x{58BD}\x{58BE}\x{58BF}\x{58C1}\x{58C2}' . + '\x{58C5}\x{58C6}\x{58C7}\x{58C8}\x{58C9}\x{58CA}\x{58CB}\x{58CE}\x{58CF}' . + '\x{58D1}\x{58D2}\x{58D3}\x{58D4}\x{58D5}\x{58D6}\x{58D7}\x{58D8}\x{58D9}' . + '\x{58DA}\x{58DB}\x{58DD}\x{58DE}\x{58DF}\x{58E0}\x{58E2}\x{58E3}\x{58E4}' . + '\x{58E5}\x{58E7}\x{58E8}\x{58E9}\x{58EA}\x{58EB}\x{58EC}\x{58ED}\x{58EE}' . + '\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F3}\x{58F4}\x{58F6}\x{58F7}\x{58F8}' . + '\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{58FE}\x{58FF}\x{5900}\x{5902}' . + '\x{5903}\x{5904}\x{5906}\x{5907}\x{5909}\x{590A}\x{590B}\x{590C}\x{590D}' . + '\x{590E}\x{590F}\x{5910}\x{5912}\x{5914}\x{5915}\x{5916}\x{5917}\x{5918}' . + '\x{5919}\x{591A}\x{591B}\x{591C}\x{591D}\x{591E}\x{591F}\x{5920}\x{5921}' . + '\x{5922}\x{5924}\x{5925}\x{5926}\x{5927}\x{5928}\x{5929}\x{592A}\x{592B}' . + '\x{592C}\x{592D}\x{592E}\x{592F}\x{5930}\x{5931}\x{5932}\x{5934}\x{5935}' . + '\x{5937}\x{5938}\x{5939}\x{593A}\x{593B}\x{593C}\x{593D}\x{593E}\x{593F}' . + '\x{5940}\x{5941}\x{5942}\x{5943}\x{5944}\x{5945}\x{5946}\x{5947}\x{5948}' . + '\x{5949}\x{594A}\x{594B}\x{594C}\x{594D}\x{594E}\x{594F}\x{5950}\x{5951}' . + '\x{5952}\x{5953}\x{5954}\x{5955}\x{5956}\x{5957}\x{5958}\x{595A}\x{595C}' . + '\x{595D}\x{595E}\x{595F}\x{5960}\x{5961}\x{5962}\x{5963}\x{5964}\x{5965}' . + '\x{5966}\x{5967}\x{5968}\x{5969}\x{596A}\x{596B}\x{596C}\x{596D}\x{596E}' . + '\x{596F}\x{5970}\x{5971}\x{5972}\x{5973}\x{5974}\x{5975}\x{5976}\x{5977}' . + '\x{5978}\x{5979}\x{597A}\x{597B}\x{597C}\x{597D}\x{597E}\x{597F}\x{5980}' . + '\x{5981}\x{5982}\x{5983}\x{5984}\x{5985}\x{5986}\x{5987}\x{5988}\x{5989}' . + '\x{598A}\x{598B}\x{598C}\x{598D}\x{598E}\x{598F}\x{5990}\x{5991}\x{5992}' . + '\x{5993}\x{5994}\x{5995}\x{5996}\x{5997}\x{5998}\x{5999}\x{599A}\x{599C}' . + '\x{599D}\x{599E}\x{599F}\x{59A0}\x{59A1}\x{59A2}\x{59A3}\x{59A4}\x{59A5}' . + '\x{59A6}\x{59A7}\x{59A8}\x{59A9}\x{59AA}\x{59AB}\x{59AC}\x{59AD}\x{59AE}' . + '\x{59AF}\x{59B0}\x{59B1}\x{59B2}\x{59B3}\x{59B4}\x{59B5}\x{59B6}\x{59B8}' . + '\x{59B9}\x{59BA}\x{59BB}\x{59BC}\x{59BD}\x{59BE}\x{59BF}\x{59C0}\x{59C1}' . + '\x{59C2}\x{59C3}\x{59C4}\x{59C5}\x{59C6}\x{59C7}\x{59C8}\x{59C9}\x{59CA}' . + '\x{59CB}\x{59CC}\x{59CD}\x{59CE}\x{59CF}\x{59D0}\x{59D1}\x{59D2}\x{59D3}' . + '\x{59D4}\x{59D5}\x{59D6}\x{59D7}\x{59D8}\x{59D9}\x{59DA}\x{59DB}\x{59DC}' . + '\x{59DD}\x{59DE}\x{59DF}\x{59E0}\x{59E1}\x{59E2}\x{59E3}\x{59E4}\x{59E5}' . + '\x{59E6}\x{59E8}\x{59E9}\x{59EA}\x{59EB}\x{59EC}\x{59ED}\x{59EE}\x{59EF}' . + '\x{59F0}\x{59F1}\x{59F2}\x{59F3}\x{59F4}\x{59F5}\x{59F6}\x{59F7}\x{59F8}' . + '\x{59F9}\x{59FA}\x{59FB}\x{59FC}\x{59FD}\x{59FE}\x{59FF}\x{5A00}\x{5A01}' . + '\x{5A02}\x{5A03}\x{5A04}\x{5A05}\x{5A06}\x{5A07}\x{5A08}\x{5A09}\x{5A0A}' . + '\x{5A0B}\x{5A0C}\x{5A0D}\x{5A0E}\x{5A0F}\x{5A10}\x{5A11}\x{5A12}\x{5A13}' . + '\x{5A14}\x{5A15}\x{5A16}\x{5A17}\x{5A18}\x{5A19}\x{5A1A}\x{5A1B}\x{5A1C}' . + '\x{5A1D}\x{5A1E}\x{5A1F}\x{5A20}\x{5A21}\x{5A22}\x{5A23}\x{5A25}\x{5A27}' . + '\x{5A28}\x{5A29}\x{5A2A}\x{5A2B}\x{5A2D}\x{5A2E}\x{5A2F}\x{5A31}\x{5A32}' . + '\x{5A33}\x{5A34}\x{5A35}\x{5A36}\x{5A37}\x{5A38}\x{5A39}\x{5A3A}\x{5A3B}' . + '\x{5A3C}\x{5A3D}\x{5A3E}\x{5A3F}\x{5A40}\x{5A41}\x{5A42}\x{5A43}\x{5A44}' . + '\x{5A45}\x{5A46}\x{5A47}\x{5A48}\x{5A49}\x{5A4A}\x{5A4B}\x{5A4C}\x{5A4D}' . + '\x{5A4E}\x{5A4F}\x{5A50}\x{5A51}\x{5A52}\x{5A53}\x{5A55}\x{5A56}\x{5A57}' . + '\x{5A58}\x{5A5A}\x{5A5B}\x{5A5C}\x{5A5D}\x{5A5E}\x{5A5F}\x{5A60}\x{5A61}' . + '\x{5A62}\x{5A63}\x{5A64}\x{5A65}\x{5A66}\x{5A67}\x{5A68}\x{5A69}\x{5A6A}' . + '\x{5A6B}\x{5A6C}\x{5A6D}\x{5A6E}\x{5A70}\x{5A72}\x{5A73}\x{5A74}\x{5A75}' . + '\x{5A76}\x{5A77}\x{5A78}\x{5A79}\x{5A7A}\x{5A7B}\x{5A7C}\x{5A7D}\x{5A7E}' . + '\x{5A7F}\x{5A80}\x{5A81}\x{5A82}\x{5A83}\x{5A84}\x{5A85}\x{5A86}\x{5A88}' . + '\x{5A89}\x{5A8A}\x{5A8B}\x{5A8C}\x{5A8E}\x{5A8F}\x{5A90}\x{5A91}\x{5A92}' . + '\x{5A93}\x{5A94}\x{5A95}\x{5A96}\x{5A97}\x{5A98}\x{5A99}\x{5A9A}\x{5A9B}' . + '\x{5A9C}\x{5A9D}\x{5A9E}\x{5A9F}\x{5AA0}\x{5AA1}\x{5AA2}\x{5AA3}\x{5AA4}' . + '\x{5AA5}\x{5AA6}\x{5AA7}\x{5AA8}\x{5AA9}\x{5AAA}\x{5AAC}\x{5AAD}\x{5AAE}' . + '\x{5AAF}\x{5AB0}\x{5AB1}\x{5AB2}\x{5AB3}\x{5AB4}\x{5AB5}\x{5AB6}\x{5AB7}' . + '\x{5AB8}\x{5AB9}\x{5ABA}\x{5ABB}\x{5ABC}\x{5ABD}\x{5ABE}\x{5ABF}\x{5AC0}' . + '\x{5AC1}\x{5AC2}\x{5AC3}\x{5AC4}\x{5AC5}\x{5AC6}\x{5AC7}\x{5AC8}\x{5AC9}' . + '\x{5ACA}\x{5ACB}\x{5ACC}\x{5ACD}\x{5ACE}\x{5ACF}\x{5AD1}\x{5AD2}\x{5AD4}' . + '\x{5AD5}\x{5AD6}\x{5AD7}\x{5AD8}\x{5AD9}\x{5ADA}\x{5ADB}\x{5ADC}\x{5ADD}' . + '\x{5ADE}\x{5ADF}\x{5AE0}\x{5AE1}\x{5AE2}\x{5AE3}\x{5AE4}\x{5AE5}\x{5AE6}' . + '\x{5AE7}\x{5AE8}\x{5AE9}\x{5AEA}\x{5AEB}\x{5AEC}\x{5AED}\x{5AEE}\x{5AF1}' . + '\x{5AF2}\x{5AF3}\x{5AF4}\x{5AF5}\x{5AF6}\x{5AF7}\x{5AF8}\x{5AF9}\x{5AFA}' . + '\x{5AFB}\x{5AFC}\x{5AFD}\x{5AFE}\x{5AFF}\x{5B00}\x{5B01}\x{5B02}\x{5B03}' . + '\x{5B04}\x{5B05}\x{5B06}\x{5B07}\x{5B08}\x{5B09}\x{5B0B}\x{5B0C}\x{5B0E}' . + '\x{5B0F}\x{5B10}\x{5B11}\x{5B12}\x{5B13}\x{5B14}\x{5B15}\x{5B16}\x{5B17}' . + '\x{5B18}\x{5B19}\x{5B1A}\x{5B1B}\x{5B1C}\x{5B1D}\x{5B1E}\x{5B1F}\x{5B20}' . + '\x{5B21}\x{5B22}\x{5B23}\x{5B24}\x{5B25}\x{5B26}\x{5B27}\x{5B28}\x{5B29}' . + '\x{5B2A}\x{5B2B}\x{5B2C}\x{5B2D}\x{5B2E}\x{5B2F}\x{5B30}\x{5B31}\x{5B32}' . + '\x{5B33}\x{5B34}\x{5B35}\x{5B36}\x{5B37}\x{5B38}\x{5B3A}\x{5B3B}\x{5B3C}' . + '\x{5B3D}\x{5B3E}\x{5B3F}\x{5B40}\x{5B41}\x{5B42}\x{5B43}\x{5B44}\x{5B45}' . + '\x{5B47}\x{5B48}\x{5B49}\x{5B4A}\x{5B4B}\x{5B4C}\x{5B4D}\x{5B4E}\x{5B50}' . + '\x{5B51}\x{5B53}\x{5B54}\x{5B55}\x{5B56}\x{5B57}\x{5B58}\x{5B59}\x{5B5A}' . + '\x{5B5B}\x{5B5C}\x{5B5D}\x{5B5E}\x{5B5F}\x{5B62}\x{5B63}\x{5B64}\x{5B65}' . + '\x{5B66}\x{5B67}\x{5B68}\x{5B69}\x{5B6A}\x{5B6B}\x{5B6C}\x{5B6D}\x{5B6E}' . + '\x{5B70}\x{5B71}\x{5B72}\x{5B73}\x{5B74}\x{5B75}\x{5B76}\x{5B77}\x{5B78}' . + '\x{5B7A}\x{5B7B}\x{5B7C}\x{5B7D}\x{5B7F}\x{5B80}\x{5B81}\x{5B82}\x{5B83}' . + '\x{5B84}\x{5B85}\x{5B87}\x{5B88}\x{5B89}\x{5B8A}\x{5B8B}\x{5B8C}\x{5B8D}' . + '\x{5B8E}\x{5B8F}\x{5B91}\x{5B92}\x{5B93}\x{5B94}\x{5B95}\x{5B96}\x{5B97}' . + '\x{5B98}\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9E}\x{5B9F}\x{5BA0}' . + '\x{5BA1}\x{5BA2}\x{5BA3}\x{5BA4}\x{5BA5}\x{5BA6}\x{5BA7}\x{5BA8}\x{5BAA}' . + '\x{5BAB}\x{5BAC}\x{5BAD}\x{5BAE}\x{5BAF}\x{5BB0}\x{5BB1}\x{5BB3}\x{5BB4}' . + '\x{5BB5}\x{5BB6}\x{5BB8}\x{5BB9}\x{5BBA}\x{5BBB}\x{5BBD}\x{5BBE}\x{5BBF}' . + '\x{5BC0}\x{5BC1}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BCA}' . + '\x{5BCB}\x{5BCC}\x{5BCD}\x{5BCE}\x{5BCF}\x{5BD0}\x{5BD1}\x{5BD2}\x{5BD3}' . + '\x{5BD4}\x{5BD5}\x{5BD6}\x{5BD8}\x{5BD9}\x{5BDB}\x{5BDC}\x{5BDD}\x{5BDE}' . + '\x{5BDF}\x{5BE0}\x{5BE1}\x{5BE2}\x{5BE3}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}' . + '\x{5BE8}\x{5BE9}\x{5BEA}\x{5BEB}\x{5BEC}\x{5BED}\x{5BEE}\x{5BEF}\x{5BF0}' . + '\x{5BF1}\x{5BF2}\x{5BF3}\x{5BF4}\x{5BF5}\x{5BF6}\x{5BF7}\x{5BF8}\x{5BF9}' . + '\x{5BFA}\x{5BFB}\x{5BFC}\x{5BFD}\x{5BFF}\x{5C01}\x{5C03}\x{5C04}\x{5C05}' . + '\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}\x{5C0B}\x{5C0C}\x{5C0D}\x{5C0E}' . + '\x{5C0F}\x{5C10}\x{5C11}\x{5C12}\x{5C13}\x{5C14}\x{5C15}\x{5C16}\x{5C17}' . + '\x{5C18}\x{5C19}\x{5C1A}\x{5C1C}\x{5C1D}\x{5C1E}\x{5C1F}\x{5C20}\x{5C21}' . + '\x{5C22}\x{5C24}\x{5C25}\x{5C27}\x{5C28}\x{5C2A}\x{5C2B}\x{5C2C}\x{5C2D}' . + '\x{5C2E}\x{5C2F}\x{5C30}\x{5C31}\x{5C32}\x{5C33}\x{5C34}\x{5C35}\x{5C37}' . + '\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}' . + '\x{5C41}\x{5C42}\x{5C43}\x{5C44}\x{5C45}\x{5C46}\x{5C47}\x{5C48}\x{5C49}' . + '\x{5C4A}\x{5C4B}\x{5C4C}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C52}' . + '\x{5C53}\x{5C54}\x{5C55}\x{5C56}\x{5C57}\x{5C58}\x{5C59}\x{5C5B}\x{5C5C}' . + '\x{5C5D}\x{5C5E}\x{5C5F}\x{5C60}\x{5C61}\x{5C62}\x{5C63}\x{5C64}\x{5C65}' . + '\x{5C66}\x{5C67}\x{5C68}\x{5C69}\x{5C6A}\x{5C6B}\x{5C6C}\x{5C6D}\x{5C6E}' . + '\x{5C6F}\x{5C70}\x{5C71}\x{5C72}\x{5C73}\x{5C74}\x{5C75}\x{5C76}\x{5C77}' . + '\x{5C78}\x{5C79}\x{5C7A}\x{5C7B}\x{5C7C}\x{5C7D}\x{5C7E}\x{5C7F}\x{5C80}' . + '\x{5C81}\x{5C82}\x{5C83}\x{5C84}\x{5C86}\x{5C87}\x{5C88}\x{5C89}\x{5C8A}' . + '\x{5C8B}\x{5C8C}\x{5C8D}\x{5C8E}\x{5C8F}\x{5C90}\x{5C91}\x{5C92}\x{5C93}' . + '\x{5C94}\x{5C95}\x{5C96}\x{5C97}\x{5C98}\x{5C99}\x{5C9A}\x{5C9B}\x{5C9C}' . + '\x{5C9D}\x{5C9E}\x{5C9F}\x{5CA0}\x{5CA1}\x{5CA2}\x{5CA3}\x{5CA4}\x{5CA5}' . + '\x{5CA6}\x{5CA7}\x{5CA8}\x{5CA9}\x{5CAA}\x{5CAB}\x{5CAC}\x{5CAD}\x{5CAE}' . + '\x{5CAF}\x{5CB0}\x{5CB1}\x{5CB2}\x{5CB3}\x{5CB5}\x{5CB6}\x{5CB7}\x{5CB8}' . + '\x{5CBA}\x{5CBB}\x{5CBC}\x{5CBD}\x{5CBE}\x{5CBF}\x{5CC1}\x{5CC2}\x{5CC3}' . + '\x{5CC4}\x{5CC5}\x{5CC6}\x{5CC7}\x{5CC8}\x{5CC9}\x{5CCA}\x{5CCB}\x{5CCC}' . + '\x{5CCD}\x{5CCE}\x{5CCF}\x{5CD0}\x{5CD1}\x{5CD2}\x{5CD3}\x{5CD4}\x{5CD6}' . + '\x{5CD7}\x{5CD8}\x{5CD9}\x{5CDA}\x{5CDB}\x{5CDC}\x{5CDE}\x{5CDF}\x{5CE0}' . + '\x{5CE1}\x{5CE2}\x{5CE3}\x{5CE4}\x{5CE5}\x{5CE6}\x{5CE7}\x{5CE8}\x{5CE9}' . + '\x{5CEA}\x{5CEB}\x{5CEC}\x{5CED}\x{5CEE}\x{5CEF}\x{5CF0}\x{5CF1}\x{5CF2}' . + '\x{5CF3}\x{5CF4}\x{5CF6}\x{5CF7}\x{5CF8}\x{5CF9}\x{5CFA}\x{5CFB}\x{5CFC}' . + '\x{5CFD}\x{5CFE}\x{5CFF}\x{5D00}\x{5D01}\x{5D02}\x{5D03}\x{5D04}\x{5D05}' . + '\x{5D06}\x{5D07}\x{5D08}\x{5D09}\x{5D0A}\x{5D0B}\x{5D0C}\x{5D0D}\x{5D0E}' . + '\x{5D0F}\x{5D10}\x{5D11}\x{5D12}\x{5D13}\x{5D14}\x{5D15}\x{5D16}\x{5D17}' . + '\x{5D18}\x{5D19}\x{5D1A}\x{5D1B}\x{5D1C}\x{5D1D}\x{5D1E}\x{5D1F}\x{5D20}' . + '\x{5D21}\x{5D22}\x{5D23}\x{5D24}\x{5D25}\x{5D26}\x{5D27}\x{5D28}\x{5D29}' . + '\x{5D2A}\x{5D2C}\x{5D2D}\x{5D2E}\x{5D30}\x{5D31}\x{5D32}\x{5D33}\x{5D34}' . + '\x{5D35}\x{5D36}\x{5D37}\x{5D38}\x{5D39}\x{5D3A}\x{5D3C}\x{5D3D}\x{5D3E}' . + '\x{5D3F}\x{5D40}\x{5D41}\x{5D42}\x{5D43}\x{5D44}\x{5D45}\x{5D46}\x{5D47}' . + '\x{5D48}\x{5D49}\x{5D4A}\x{5D4B}\x{5D4C}\x{5D4D}\x{5D4E}\x{5D4F}\x{5D50}' . + '\x{5D51}\x{5D52}\x{5D54}\x{5D55}\x{5D56}\x{5D58}\x{5D59}\x{5D5A}\x{5D5B}' . + '\x{5D5D}\x{5D5E}\x{5D5F}\x{5D61}\x{5D62}\x{5D63}\x{5D64}\x{5D65}\x{5D66}' . + '\x{5D67}\x{5D68}\x{5D69}\x{5D6A}\x{5D6B}\x{5D6C}\x{5D6D}\x{5D6E}\x{5D6F}' . + '\x{5D70}\x{5D71}\x{5D72}\x{5D73}\x{5D74}\x{5D75}\x{5D76}\x{5D77}\x{5D78}' . + '\x{5D79}\x{5D7A}\x{5D7B}\x{5D7C}\x{5D7D}\x{5D7E}\x{5D7F}\x{5D80}\x{5D81}' . + '\x{5D82}\x{5D84}\x{5D85}\x{5D86}\x{5D87}\x{5D88}\x{5D89}\x{5D8A}\x{5D8B}' . + '\x{5D8C}\x{5D8D}\x{5D8E}\x{5D8F}\x{5D90}\x{5D91}\x{5D92}\x{5D93}\x{5D94}' . + '\x{5D95}\x{5D97}\x{5D98}\x{5D99}\x{5D9A}\x{5D9B}\x{5D9C}\x{5D9D}\x{5D9E}' . + '\x{5D9F}\x{5DA0}\x{5DA1}\x{5DA2}\x{5DA5}\x{5DA6}\x{5DA7}\x{5DA8}\x{5DA9}' . + '\x{5DAA}\x{5DAC}\x{5DAD}\x{5DAE}\x{5DAF}\x{5DB0}\x{5DB1}\x{5DB2}\x{5DB4}' . + '\x{5DB5}\x{5DB6}\x{5DB7}\x{5DB8}\x{5DBA}\x{5DBB}\x{5DBC}\x{5DBD}\x{5DBE}' . + '\x{5DBF}\x{5DC0}\x{5DC1}\x{5DC2}\x{5DC3}\x{5DC5}\x{5DC6}\x{5DC7}\x{5DC8}' . + '\x{5DC9}\x{5DCA}\x{5DCB}\x{5DCC}\x{5DCD}\x{5DCE}\x{5DCF}\x{5DD0}\x{5DD1}' . + '\x{5DD2}\x{5DD3}\x{5DD4}\x{5DD5}\x{5DD6}\x{5DD8}\x{5DD9}\x{5DDB}\x{5DDD}' . + '\x{5DDE}\x{5DDF}\x{5DE0}\x{5DE1}\x{5DE2}\x{5DE3}\x{5DE4}\x{5DE5}\x{5DE6}' . + '\x{5DE7}\x{5DE8}\x{5DE9}\x{5DEA}\x{5DEB}\x{5DEC}\x{5DED}\x{5DEE}\x{5DEF}' . + '\x{5DF0}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DF8}\x{5DF9}' . + '\x{5DFA}\x{5DFB}\x{5DFC}\x{5DFD}\x{5DFE}\x{5DFF}\x{5E00}\x{5E01}\x{5E02}' . + '\x{5E03}\x{5E04}\x{5E05}\x{5E06}\x{5E07}\x{5E08}\x{5E09}\x{5E0A}\x{5E0B}' . + '\x{5E0C}\x{5E0D}\x{5E0E}\x{5E0F}\x{5E10}\x{5E11}\x{5E13}\x{5E14}\x{5E15}' . + '\x{5E16}\x{5E17}\x{5E18}\x{5E19}\x{5E1A}\x{5E1B}\x{5E1C}\x{5E1D}\x{5E1E}' . + '\x{5E1F}\x{5E20}\x{5E21}\x{5E22}\x{5E23}\x{5E24}\x{5E25}\x{5E26}\x{5E27}' . + '\x{5E28}\x{5E29}\x{5E2A}\x{5E2B}\x{5E2C}\x{5E2D}\x{5E2E}\x{5E2F}\x{5E30}' . + '\x{5E31}\x{5E32}\x{5E33}\x{5E34}\x{5E35}\x{5E36}\x{5E37}\x{5E38}\x{5E39}' . + '\x{5E3A}\x{5E3B}\x{5E3C}\x{5E3D}\x{5E3E}\x{5E40}\x{5E41}\x{5E42}\x{5E43}' . + '\x{5E44}\x{5E45}\x{5E46}\x{5E47}\x{5E49}\x{5E4A}\x{5E4B}\x{5E4C}\x{5E4D}' . + '\x{5E4E}\x{5E4F}\x{5E50}\x{5E52}\x{5E53}\x{5E54}\x{5E55}\x{5E56}\x{5E57}' . + '\x{5E58}\x{5E59}\x{5E5A}\x{5E5B}\x{5E5C}\x{5E5D}\x{5E5E}\x{5E5F}\x{5E60}' . + '\x{5E61}\x{5E62}\x{5E63}\x{5E64}\x{5E65}\x{5E66}\x{5E67}\x{5E68}\x{5E69}' . + '\x{5E6A}\x{5E6B}\x{5E6C}\x{5E6D}\x{5E6E}\x{5E6F}\x{5E70}\x{5E71}\x{5E72}' . + '\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E77}\x{5E78}\x{5E79}\x{5E7A}\x{5E7B}' . + '\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E80}\x{5E81}\x{5E82}\x{5E83}\x{5E84}' . + '\x{5E85}\x{5E86}\x{5E87}\x{5E88}\x{5E89}\x{5E8A}\x{5E8B}\x{5E8C}\x{5E8D}' . + '\x{5E8E}\x{5E8F}\x{5E90}\x{5E91}\x{5E93}\x{5E94}\x{5E95}\x{5E96}\x{5E97}' . + '\x{5E98}\x{5E99}\x{5E9A}\x{5E9B}\x{5E9C}\x{5E9D}\x{5E9E}\x{5E9F}\x{5EA0}' . + '\x{5EA1}\x{5EA2}\x{5EA3}\x{5EA4}\x{5EA5}\x{5EA6}\x{5EA7}\x{5EA8}\x{5EA9}' . + '\x{5EAA}\x{5EAB}\x{5EAC}\x{5EAD}\x{5EAE}\x{5EAF}\x{5EB0}\x{5EB1}\x{5EB2}' . + '\x{5EB3}\x{5EB4}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EB9}\x{5EBB}\x{5EBC}' . + '\x{5EBD}\x{5EBE}\x{5EBF}\x{5EC1}\x{5EC2}\x{5EC3}\x{5EC4}\x{5EC5}\x{5EC6}' . + '\x{5EC7}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECB}\x{5ECC}\x{5ECD}\x{5ECE}\x{5ECF}' . + '\x{5ED0}\x{5ED1}\x{5ED2}\x{5ED3}\x{5ED4}\x{5ED5}\x{5ED6}\x{5ED7}\x{5ED8}' . + '\x{5ED9}\x{5EDA}\x{5EDB}\x{5EDC}\x{5EDD}\x{5EDE}\x{5EDF}\x{5EE0}\x{5EE1}' . + '\x{5EE2}\x{5EE3}\x{5EE4}\x{5EE5}\x{5EE6}\x{5EE7}\x{5EE8}\x{5EE9}\x{5EEA}' . + '\x{5EEC}\x{5EED}\x{5EEE}\x{5EEF}\x{5EF0}\x{5EF1}\x{5EF2}\x{5EF3}\x{5EF4}' . + '\x{5EF5}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}\x{5EFB}\x{5EFC}\x{5EFD}\x{5EFE}' . + '\x{5EFF}\x{5F00}\x{5F01}\x{5F02}\x{5F03}\x{5F04}\x{5F05}\x{5F06}\x{5F07}' . + '\x{5F08}\x{5F0A}\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F11}\x{5F12}\x{5F13}' . + '\x{5F14}\x{5F15}\x{5F16}\x{5F17}\x{5F18}\x{5F19}\x{5F1A}\x{5F1B}\x{5F1C}' . + '\x{5F1D}\x{5F1E}\x{5F1F}\x{5F20}\x{5F21}\x{5F22}\x{5F23}\x{5F24}\x{5F25}' . + '\x{5F26}\x{5F27}\x{5F28}\x{5F29}\x{5F2A}\x{5F2B}\x{5F2C}\x{5F2D}\x{5F2E}' . + '\x{5F2F}\x{5F30}\x{5F31}\x{5F32}\x{5F33}\x{5F34}\x{5F35}\x{5F36}\x{5F37}' . + '\x{5F38}\x{5F39}\x{5F3A}\x{5F3C}\x{5F3E}\x{5F3F}\x{5F40}\x{5F41}\x{5F42}' . + '\x{5F43}\x{5F44}\x{5F45}\x{5F46}\x{5F47}\x{5F48}\x{5F49}\x{5F4A}\x{5F4B}' . + '\x{5F4C}\x{5F4D}\x{5F4E}\x{5F4F}\x{5F50}\x{5F51}\x{5F52}\x{5F53}\x{5F54}' . + '\x{5F55}\x{5F56}\x{5F57}\x{5F58}\x{5F59}\x{5F5A}\x{5F5B}\x{5F5C}\x{5F5D}' . + '\x{5F5E}\x{5F5F}\x{5F60}\x{5F61}\x{5F62}\x{5F63}\x{5F64}\x{5F65}\x{5F66}' . + '\x{5F67}\x{5F68}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}\x{5F6D}\x{5F6E}\x{5F6F}' . + '\x{5F70}\x{5F71}\x{5F72}\x{5F73}\x{5F74}\x{5F75}\x{5F76}\x{5F77}\x{5F78}' . + '\x{5F79}\x{5F7A}\x{5F7B}\x{5F7C}\x{5F7D}\x{5F7E}\x{5F7F}\x{5F80}\x{5F81}' . + '\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F86}\x{5F87}\x{5F88}\x{5F89}\x{5F8A}' . + '\x{5F8B}\x{5F8C}\x{5F8D}\x{5F8E}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F94}' . + '\x{5F95}\x{5F96}\x{5F97}\x{5F98}\x{5F99}\x{5F9B}\x{5F9C}\x{5F9D}\x{5F9E}' . + '\x{5F9F}\x{5FA0}\x{5FA1}\x{5FA2}\x{5FA5}\x{5FA6}\x{5FA7}\x{5FA8}\x{5FA9}' . + '\x{5FAA}\x{5FAB}\x{5FAC}\x{5FAD}\x{5FAE}\x{5FAF}\x{5FB1}\x{5FB2}\x{5FB3}' . + '\x{5FB4}\x{5FB5}\x{5FB6}\x{5FB7}\x{5FB8}\x{5FB9}\x{5FBA}\x{5FBB}\x{5FBC}' . + '\x{5FBD}\x{5FBE}\x{5FBF}\x{5FC0}\x{5FC1}\x{5FC3}\x{5FC4}\x{5FC5}\x{5FC6}' . + '\x{5FC7}\x{5FC8}\x{5FC9}\x{5FCA}\x{5FCB}\x{5FCC}\x{5FCD}\x{5FCF}\x{5FD0}' . + '\x{5FD1}\x{5FD2}\x{5FD3}\x{5FD4}\x{5FD5}\x{5FD6}\x{5FD7}\x{5FD8}\x{5FD9}' . + '\x{5FDA}\x{5FDC}\x{5FDD}\x{5FDE}\x{5FE0}\x{5FE1}\x{5FE3}\x{5FE4}\x{5FE5}' . + '\x{5FE6}\x{5FE7}\x{5FE8}\x{5FE9}\x{5FEA}\x{5FEB}\x{5FED}\x{5FEE}\x{5FEF}' . + '\x{5FF0}\x{5FF1}\x{5FF2}\x{5FF3}\x{5FF4}\x{5FF5}\x{5FF6}\x{5FF7}\x{5FF8}' . + '\x{5FF9}\x{5FFA}\x{5FFB}\x{5FFD}\x{5FFE}\x{5FFF}\x{6000}\x{6001}\x{6002}' . + '\x{6003}\x{6004}\x{6005}\x{6006}\x{6007}\x{6008}\x{6009}\x{600A}\x{600B}' . + '\x{600C}\x{600D}\x{600E}\x{600F}\x{6010}\x{6011}\x{6012}\x{6013}\x{6014}' . + '\x{6015}\x{6016}\x{6017}\x{6018}\x{6019}\x{601A}\x{601B}\x{601C}\x{601D}' . + '\x{601E}\x{601F}\x{6020}\x{6021}\x{6022}\x{6024}\x{6025}\x{6026}\x{6027}' . + '\x{6028}\x{6029}\x{602A}\x{602B}\x{602C}\x{602D}\x{602E}\x{602F}\x{6030}' . + '\x{6031}\x{6032}\x{6033}\x{6034}\x{6035}\x{6036}\x{6037}\x{6038}\x{6039}' . + '\x{603A}\x{603B}\x{603C}\x{603D}\x{603E}\x{603F}\x{6040}\x{6041}\x{6042}' . + '\x{6043}\x{6044}\x{6045}\x{6046}\x{6047}\x{6048}\x{6049}\x{604A}\x{604B}' . + '\x{604C}\x{604D}\x{604E}\x{604F}\x{6050}\x{6051}\x{6052}\x{6053}\x{6054}' . + '\x{6055}\x{6057}\x{6058}\x{6059}\x{605A}\x{605B}\x{605C}\x{605D}\x{605E}' . + '\x{605F}\x{6062}\x{6063}\x{6064}\x{6065}\x{6066}\x{6067}\x{6068}\x{6069}' . + '\x{606A}\x{606B}\x{606C}\x{606D}\x{606E}\x{606F}\x{6070}\x{6072}\x{6073}' . + '\x{6075}\x{6076}\x{6077}\x{6078}\x{6079}\x{607A}\x{607B}\x{607C}\x{607D}' . + '\x{607E}\x{607F}\x{6080}\x{6081}\x{6082}\x{6083}\x{6084}\x{6085}\x{6086}' . + '\x{6087}\x{6088}\x{6089}\x{608A}\x{608B}\x{608C}\x{608D}\x{608E}\x{608F}' . + '\x{6090}\x{6092}\x{6094}\x{6095}\x{6096}\x{6097}\x{6098}\x{6099}\x{609A}' . + '\x{609B}\x{609C}\x{609D}\x{609E}\x{609F}\x{60A0}\x{60A1}\x{60A2}\x{60A3}' . + '\x{60A4}\x{60A6}\x{60A7}\x{60A8}\x{60AA}\x{60AB}\x{60AC}\x{60AD}\x{60AE}' . + '\x{60AF}\x{60B0}\x{60B1}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B7}' . + '\x{60B8}\x{60B9}\x{60BA}\x{60BB}\x{60BC}\x{60BD}\x{60BE}\x{60BF}\x{60C0}' . + '\x{60C1}\x{60C2}\x{60C3}\x{60C4}\x{60C5}\x{60C6}\x{60C7}\x{60C8}\x{60C9}' . + '\x{60CA}\x{60CB}\x{60CC}\x{60CD}\x{60CE}\x{60CF}\x{60D0}\x{60D1}\x{60D3}' . + '\x{60D4}\x{60D5}\x{60D7}\x{60D8}\x{60D9}\x{60DA}\x{60DB}\x{60DC}\x{60DD}' . + '\x{60DF}\x{60E0}\x{60E1}\x{60E2}\x{60E4}\x{60E6}\x{60E7}\x{60E8}\x{60E9}' . + '\x{60EA}\x{60EB}\x{60EC}\x{60ED}\x{60EE}\x{60EF}\x{60F0}\x{60F1}\x{60F2}' . + '\x{60F3}\x{60F4}\x{60F5}\x{60F6}\x{60F7}\x{60F8}\x{60F9}\x{60FA}\x{60FB}' . + '\x{60FC}\x{60FE}\x{60FF}\x{6100}\x{6101}\x{6103}\x{6104}\x{6105}\x{6106}' . + '\x{6108}\x{6109}\x{610A}\x{610B}\x{610C}\x{610D}\x{610E}\x{610F}\x{6110}' . + '\x{6112}\x{6113}\x{6114}\x{6115}\x{6116}\x{6117}\x{6118}\x{6119}\x{611A}' . + '\x{611B}\x{611C}\x{611D}\x{611F}\x{6120}\x{6122}\x{6123}\x{6124}\x{6125}' . + '\x{6126}\x{6127}\x{6128}\x{6129}\x{612A}\x{612B}\x{612C}\x{612D}\x{612E}' . + '\x{612F}\x{6130}\x{6132}\x{6134}\x{6136}\x{6137}\x{613A}\x{613B}\x{613C}' . + '\x{613D}\x{613E}\x{613F}\x{6140}\x{6141}\x{6142}\x{6143}\x{6144}\x{6145}' . + '\x{6146}\x{6147}\x{6148}\x{6149}\x{614A}\x{614B}\x{614C}\x{614D}\x{614E}' . + '\x{614F}\x{6150}\x{6151}\x{6152}\x{6153}\x{6154}\x{6155}\x{6156}\x{6157}' . + '\x{6158}\x{6159}\x{615A}\x{615B}\x{615C}\x{615D}\x{615E}\x{615F}\x{6161}' . + '\x{6162}\x{6163}\x{6164}\x{6165}\x{6166}\x{6167}\x{6168}\x{6169}\x{616A}' . + '\x{616B}\x{616C}\x{616D}\x{616E}\x{6170}\x{6171}\x{6172}\x{6173}\x{6174}' . + '\x{6175}\x{6176}\x{6177}\x{6178}\x{6179}\x{617A}\x{617C}\x{617E}\x{6180}' . + '\x{6181}\x{6182}\x{6183}\x{6184}\x{6185}\x{6187}\x{6188}\x{6189}\x{618A}' . + '\x{618B}\x{618C}\x{618D}\x{618E}\x{618F}\x{6190}\x{6191}\x{6192}\x{6193}' . + '\x{6194}\x{6195}\x{6196}\x{6198}\x{6199}\x{619A}\x{619B}\x{619D}\x{619E}' . + '\x{619F}\x{61A0}\x{61A1}\x{61A2}\x{61A3}\x{61A4}\x{61A5}\x{61A6}\x{61A7}' . + '\x{61A8}\x{61A9}\x{61AA}\x{61AB}\x{61AC}\x{61AD}\x{61AE}\x{61AF}\x{61B0}' . + '\x{61B1}\x{61B2}\x{61B3}\x{61B4}\x{61B5}\x{61B6}\x{61B7}\x{61B8}\x{61BA}' . + '\x{61BC}\x{61BD}\x{61BE}\x{61BF}\x{61C0}\x{61C1}\x{61C2}\x{61C3}\x{61C4}' . + '\x{61C5}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' . + '\x{61CE}\x{61CF}\x{61D0}\x{61D1}\x{61D2}\x{61D4}\x{61D6}\x{61D7}\x{61D8}' . + '\x{61D9}\x{61DA}\x{61DB}\x{61DC}\x{61DD}\x{61DE}\x{61DF}\x{61E0}\x{61E1}' . + '\x{61E2}\x{61E3}\x{61E4}\x{61E5}\x{61E6}\x{61E7}\x{61E8}\x{61E9}\x{61EA}' . + '\x{61EB}\x{61ED}\x{61EE}\x{61F0}\x{61F1}\x{61F2}\x{61F3}\x{61F5}\x{61F6}' . + '\x{61F7}\x{61F8}\x{61F9}\x{61FA}\x{61FB}\x{61FC}\x{61FD}\x{61FE}\x{61FF}' . + '\x{6200}\x{6201}\x{6202}\x{6203}\x{6204}\x{6206}\x{6207}\x{6208}\x{6209}' . + '\x{620A}\x{620B}\x{620C}\x{620D}\x{620E}\x{620F}\x{6210}\x{6211}\x{6212}' . + '\x{6213}\x{6214}\x{6215}\x{6216}\x{6217}\x{6218}\x{6219}\x{621A}\x{621B}' . + '\x{621C}\x{621D}\x{621E}\x{621F}\x{6220}\x{6221}\x{6222}\x{6223}\x{6224}' . + '\x{6225}\x{6226}\x{6227}\x{6228}\x{6229}\x{622A}\x{622B}\x{622C}\x{622D}' . + '\x{622E}\x{622F}\x{6230}\x{6231}\x{6232}\x{6233}\x{6234}\x{6236}\x{6237}' . + '\x{6238}\x{623A}\x{623B}\x{623C}\x{623D}\x{623E}\x{623F}\x{6240}\x{6241}' . + '\x{6242}\x{6243}\x{6244}\x{6245}\x{6246}\x{6247}\x{6248}\x{6249}\x{624A}' . + '\x{624B}\x{624C}\x{624D}\x{624E}\x{624F}\x{6250}\x{6251}\x{6252}\x{6253}' . + '\x{6254}\x{6255}\x{6256}\x{6258}\x{6259}\x{625A}\x{625B}\x{625C}\x{625D}' . + '\x{625E}\x{625F}\x{6260}\x{6261}\x{6262}\x{6263}\x{6264}\x{6265}\x{6266}' . + '\x{6267}\x{6268}\x{6269}\x{626A}\x{626B}\x{626C}\x{626D}\x{626E}\x{626F}' . + '\x{6270}\x{6271}\x{6272}\x{6273}\x{6274}\x{6275}\x{6276}\x{6277}\x{6278}' . + '\x{6279}\x{627A}\x{627B}\x{627C}\x{627D}\x{627E}\x{627F}\x{6280}\x{6281}' . + '\x{6283}\x{6284}\x{6285}\x{6286}\x{6287}\x{6288}\x{6289}\x{628A}\x{628B}' . + '\x{628C}\x{628E}\x{628F}\x{6290}\x{6291}\x{6292}\x{6293}\x{6294}\x{6295}' . + '\x{6296}\x{6297}\x{6298}\x{6299}\x{629A}\x{629B}\x{629C}\x{629E}\x{629F}' . + '\x{62A0}\x{62A1}\x{62A2}\x{62A3}\x{62A4}\x{62A5}\x{62A7}\x{62A8}\x{62A9}' . + '\x{62AA}\x{62AB}\x{62AC}\x{62AD}\x{62AE}\x{62AF}\x{62B0}\x{62B1}\x{62B2}' . + '\x{62B3}\x{62B4}\x{62B5}\x{62B6}\x{62B7}\x{62B8}\x{62B9}\x{62BA}\x{62BB}' . + '\x{62BC}\x{62BD}\x{62BE}\x{62BF}\x{62C0}\x{62C1}\x{62C2}\x{62C3}\x{62C4}' . + '\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CB}\x{62CC}\x{62CD}' . + '\x{62CE}\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D5}\x{62D6}' . + '\x{62D7}\x{62D8}\x{62D9}\x{62DA}\x{62DB}\x{62DC}\x{62DD}\x{62DF}\x{62E0}' . + '\x{62E1}\x{62E2}\x{62E3}\x{62E4}\x{62E5}\x{62E6}\x{62E7}\x{62E8}\x{62E9}' . + '\x{62EB}\x{62EC}\x{62ED}\x{62EE}\x{62EF}\x{62F0}\x{62F1}\x{62F2}\x{62F3}' . + '\x{62F4}\x{62F5}\x{62F6}\x{62F7}\x{62F8}\x{62F9}\x{62FA}\x{62FB}\x{62FC}' . + '\x{62FD}\x{62FE}\x{62FF}\x{6300}\x{6301}\x{6302}\x{6303}\x{6304}\x{6305}' . + '\x{6306}\x{6307}\x{6308}\x{6309}\x{630B}\x{630C}\x{630D}\x{630E}\x{630F}' . + '\x{6310}\x{6311}\x{6312}\x{6313}\x{6314}\x{6315}\x{6316}\x{6318}\x{6319}' . + '\x{631A}\x{631B}\x{631C}\x{631D}\x{631E}\x{631F}\x{6320}\x{6321}\x{6322}' . + '\x{6323}\x{6324}\x{6325}\x{6326}\x{6327}\x{6328}\x{6329}\x{632A}\x{632B}' . + '\x{632C}\x{632D}\x{632E}\x{632F}\x{6330}\x{6332}\x{6333}\x{6334}\x{6336}' . + '\x{6338}\x{6339}\x{633A}\x{633B}\x{633C}\x{633D}\x{633E}\x{6340}\x{6341}' . + '\x{6342}\x{6343}\x{6344}\x{6345}\x{6346}\x{6347}\x{6348}\x{6349}\x{634A}' . + '\x{634B}\x{634C}\x{634D}\x{634E}\x{634F}\x{6350}\x{6351}\x{6352}\x{6353}' . + '\x{6354}\x{6355}\x{6356}\x{6357}\x{6358}\x{6359}\x{635A}\x{635C}\x{635D}' . + '\x{635E}\x{635F}\x{6360}\x{6361}\x{6362}\x{6363}\x{6364}\x{6365}\x{6366}' . + '\x{6367}\x{6368}\x{6369}\x{636A}\x{636B}\x{636C}\x{636D}\x{636E}\x{636F}' . + '\x{6370}\x{6371}\x{6372}\x{6373}\x{6374}\x{6375}\x{6376}\x{6377}\x{6378}' . + '\x{6379}\x{637A}\x{637B}\x{637C}\x{637D}\x{637E}\x{6380}\x{6381}\x{6382}' . + '\x{6383}\x{6384}\x{6385}\x{6386}\x{6387}\x{6388}\x{6389}\x{638A}\x{638C}' . + '\x{638D}\x{638E}\x{638F}\x{6390}\x{6391}\x{6392}\x{6394}\x{6395}\x{6396}' . + '\x{6397}\x{6398}\x{6399}\x{639A}\x{639B}\x{639C}\x{639D}\x{639E}\x{639F}' . + '\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A4}\x{63A5}\x{63A6}\x{63A7}\x{63A8}' . + '\x{63A9}\x{63AA}\x{63AB}\x{63AC}\x{63AD}\x{63AE}\x{63AF}\x{63B0}\x{63B1}' . + '\x{63B2}\x{63B3}\x{63B4}\x{63B5}\x{63B6}\x{63B7}\x{63B8}\x{63B9}\x{63BA}' . + '\x{63BC}\x{63BD}\x{63BE}\x{63BF}\x{63C0}\x{63C1}\x{63C2}\x{63C3}\x{63C4}' . + '\x{63C5}\x{63C6}\x{63C7}\x{63C8}\x{63C9}\x{63CA}\x{63CB}\x{63CC}\x{63CD}' . + '\x{63CE}\x{63CF}\x{63D0}\x{63D2}\x{63D3}\x{63D4}\x{63D5}\x{63D6}\x{63D7}' . + '\x{63D8}\x{63D9}\x{63DA}\x{63DB}\x{63DC}\x{63DD}\x{63DE}\x{63DF}\x{63E0}' . + '\x{63E1}\x{63E2}\x{63E3}\x{63E4}\x{63E5}\x{63E6}\x{63E7}\x{63E8}\x{63E9}' . + '\x{63EA}\x{63EB}\x{63EC}\x{63ED}\x{63EE}\x{63EF}\x{63F0}\x{63F1}\x{63F2}' . + '\x{63F3}\x{63F4}\x{63F5}\x{63F6}\x{63F7}\x{63F8}\x{63F9}\x{63FA}\x{63FB}' . + '\x{63FC}\x{63FD}\x{63FE}\x{63FF}\x{6400}\x{6401}\x{6402}\x{6403}\x{6404}' . + '\x{6405}\x{6406}\x{6408}\x{6409}\x{640A}\x{640B}\x{640C}\x{640D}\x{640E}' . + '\x{640F}\x{6410}\x{6411}\x{6412}\x{6413}\x{6414}\x{6415}\x{6416}\x{6417}' . + '\x{6418}\x{6419}\x{641A}\x{641B}\x{641C}\x{641D}\x{641E}\x{641F}\x{6420}' . + '\x{6421}\x{6422}\x{6423}\x{6424}\x{6425}\x{6426}\x{6427}\x{6428}\x{6429}' . + '\x{642A}\x{642B}\x{642C}\x{642D}\x{642E}\x{642F}\x{6430}\x{6431}\x{6432}' . + '\x{6433}\x{6434}\x{6435}\x{6436}\x{6437}\x{6438}\x{6439}\x{643A}\x{643D}' . + '\x{643E}\x{643F}\x{6440}\x{6441}\x{6443}\x{6444}\x{6445}\x{6446}\x{6447}' . + '\x{6448}\x{644A}\x{644B}\x{644C}\x{644D}\x{644E}\x{644F}\x{6450}\x{6451}' . + '\x{6452}\x{6453}\x{6454}\x{6455}\x{6456}\x{6457}\x{6458}\x{6459}\x{645B}' . + '\x{645C}\x{645D}\x{645E}\x{645F}\x{6460}\x{6461}\x{6462}\x{6463}\x{6464}' . + '\x{6465}\x{6466}\x{6467}\x{6468}\x{6469}\x{646A}\x{646B}\x{646C}\x{646D}' . + '\x{646E}\x{646F}\x{6470}\x{6471}\x{6472}\x{6473}\x{6474}\x{6475}\x{6476}' . + '\x{6477}\x{6478}\x{6479}\x{647A}\x{647B}\x{647C}\x{647D}\x{647F}\x{6480}' . + '\x{6481}\x{6482}\x{6483}\x{6484}\x{6485}\x{6487}\x{6488}\x{6489}\x{648A}' . + '\x{648B}\x{648C}\x{648D}\x{648E}\x{648F}\x{6490}\x{6491}\x{6492}\x{6493}' . + '\x{6494}\x{6495}\x{6496}\x{6497}\x{6498}\x{6499}\x{649A}\x{649B}\x{649C}' . + '\x{649D}\x{649E}\x{649F}\x{64A0}\x{64A2}\x{64A3}\x{64A4}\x{64A5}\x{64A6}' . + '\x{64A7}\x{64A8}\x{64A9}\x{64AA}\x{64AB}\x{64AC}\x{64AD}\x{64AE}\x{64B0}' . + '\x{64B1}\x{64B2}\x{64B3}\x{64B4}\x{64B5}\x{64B7}\x{64B8}\x{64B9}\x{64BA}' . + '\x{64BB}\x{64BC}\x{64BD}\x{64BE}\x{64BF}\x{64C0}\x{64C1}\x{64C2}\x{64C3}' . + '\x{64C4}\x{64C5}\x{64C6}\x{64C7}\x{64C9}\x{64CA}\x{64CB}\x{64CC}\x{64CD}' . + '\x{64CE}\x{64CF}\x{64D0}\x{64D1}\x{64D2}\x{64D3}\x{64D4}\x{64D6}\x{64D7}' . + '\x{64D8}\x{64D9}\x{64DA}\x{64DB}\x{64DC}\x{64DD}\x{64DE}\x{64DF}\x{64E0}' . + '\x{64E2}\x{64E3}\x{64E4}\x{64E6}\x{64E7}\x{64E8}\x{64E9}\x{64EA}\x{64EB}' . + '\x{64EC}\x{64ED}\x{64EF}\x{64F0}\x{64F1}\x{64F2}\x{64F3}\x{64F4}\x{64F6}' . + '\x{64F7}\x{64F8}\x{64FA}\x{64FB}\x{64FC}\x{64FD}\x{64FE}\x{64FF}\x{6500}' . + '\x{6501}\x{6503}\x{6504}\x{6505}\x{6506}\x{6507}\x{6508}\x{6509}\x{650B}' . + '\x{650C}\x{650D}\x{650E}\x{650F}\x{6510}\x{6511}\x{6512}\x{6513}\x{6514}' . + '\x{6515}\x{6516}\x{6517}\x{6518}\x{6519}\x{651A}\x{651B}\x{651C}\x{651D}' . + '\x{651E}\x{6520}\x{6521}\x{6522}\x{6523}\x{6524}\x{6525}\x{6526}\x{6527}' . + '\x{6529}\x{652A}\x{652B}\x{652C}\x{652D}\x{652E}\x{652F}\x{6530}\x{6531}' . + '\x{6532}\x{6533}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}\x{653A}' . + '\x{653B}\x{653C}\x{653D}\x{653E}\x{653F}\x{6541}\x{6543}\x{6544}\x{6545}' . + '\x{6546}\x{6547}\x{6548}\x{6549}\x{654A}\x{654B}\x{654C}\x{654D}\x{654E}' . + '\x{654F}\x{6550}\x{6551}\x{6552}\x{6553}\x{6554}\x{6555}\x{6556}\x{6557}' . + '\x{6558}\x{6559}\x{655B}\x{655C}\x{655D}\x{655E}\x{6560}\x{6561}\x{6562}' . + '\x{6563}\x{6564}\x{6565}\x{6566}\x{6567}\x{6568}\x{6569}\x{656A}\x{656B}' . + '\x{656C}\x{656E}\x{656F}\x{6570}\x{6571}\x{6572}\x{6573}\x{6574}\x{6575}' . + '\x{6576}\x{6577}\x{6578}\x{6579}\x{657A}\x{657B}\x{657C}\x{657E}\x{657F}' . + '\x{6580}\x{6581}\x{6582}\x{6583}\x{6584}\x{6585}\x{6586}\x{6587}\x{6588}' . + '\x{6589}\x{658B}\x{658C}\x{658D}\x{658E}\x{658F}\x{6590}\x{6591}\x{6592}' . + '\x{6593}\x{6594}\x{6595}\x{6596}\x{6597}\x{6598}\x{6599}\x{659B}\x{659C}' . + '\x{659D}\x{659E}\x{659F}\x{65A0}\x{65A1}\x{65A2}\x{65A3}\x{65A4}\x{65A5}' . + '\x{65A6}\x{65A7}\x{65A8}\x{65A9}\x{65AA}\x{65AB}\x{65AC}\x{65AD}\x{65AE}' . + '\x{65AF}\x{65B0}\x{65B1}\x{65B2}\x{65B3}\x{65B4}\x{65B6}\x{65B7}\x{65B8}' . + '\x{65B9}\x{65BA}\x{65BB}\x{65BC}\x{65BD}\x{65BF}\x{65C0}\x{65C1}\x{65C2}' . + '\x{65C3}\x{65C4}\x{65C5}\x{65C6}\x{65C7}\x{65CA}\x{65CB}\x{65CC}\x{65CD}' . + '\x{65CE}\x{65CF}\x{65D0}\x{65D2}\x{65D3}\x{65D4}\x{65D5}\x{65D6}\x{65D7}' . + '\x{65DA}\x{65DB}\x{65DD}\x{65DE}\x{65DF}\x{65E0}\x{65E1}\x{65E2}\x{65E3}' . + '\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}\x{65EB}\x{65EC}\x{65ED}\x{65EE}' . + '\x{65EF}\x{65F0}\x{65F1}\x{65F2}\x{65F3}\x{65F4}\x{65F5}\x{65F6}\x{65F7}' . + '\x{65F8}\x{65FA}\x{65FB}\x{65FC}\x{65FD}\x{6600}\x{6601}\x{6602}\x{6603}' . + '\x{6604}\x{6605}\x{6606}\x{6607}\x{6608}\x{6609}\x{660A}\x{660B}\x{660C}' . + '\x{660D}\x{660E}\x{660F}\x{6610}\x{6611}\x{6612}\x{6613}\x{6614}\x{6615}' . + '\x{6616}\x{6618}\x{6619}\x{661A}\x{661B}\x{661C}\x{661D}\x{661F}\x{6620}' . + '\x{6621}\x{6622}\x{6623}\x{6624}\x{6625}\x{6626}\x{6627}\x{6628}\x{6629}' . + '\x{662A}\x{662B}\x{662D}\x{662E}\x{662F}\x{6630}\x{6631}\x{6632}\x{6633}' . + '\x{6634}\x{6635}\x{6636}\x{6639}\x{663A}\x{663C}\x{663D}\x{663E}\x{6640}' . + '\x{6641}\x{6642}\x{6643}\x{6644}\x{6645}\x{6646}\x{6647}\x{6649}\x{664A}' . + '\x{664B}\x{664C}\x{664E}\x{664F}\x{6650}\x{6651}\x{6652}\x{6653}\x{6654}' . + '\x{6655}\x{6656}\x{6657}\x{6658}\x{6659}\x{665A}\x{665B}\x{665C}\x{665D}' . + '\x{665E}\x{665F}\x{6661}\x{6662}\x{6664}\x{6665}\x{6666}\x{6668}\x{6669}' . + '\x{666A}\x{666B}\x{666C}\x{666D}\x{666E}\x{666F}\x{6670}\x{6671}\x{6672}' . + '\x{6673}\x{6674}\x{6675}\x{6676}\x{6677}\x{6678}\x{6679}\x{667A}\x{667B}' . + '\x{667C}\x{667D}\x{667E}\x{667F}\x{6680}\x{6681}\x{6682}\x{6683}\x{6684}' . + '\x{6685}\x{6686}\x{6687}\x{6688}\x{6689}\x{668A}\x{668B}\x{668C}\x{668D}' . + '\x{668E}\x{668F}\x{6690}\x{6691}\x{6693}\x{6694}\x{6695}\x{6696}\x{6697}' . + '\x{6698}\x{6699}\x{669A}\x{669B}\x{669D}\x{669F}\x{66A0}\x{66A1}\x{66A2}' . + '\x{66A3}\x{66A4}\x{66A5}\x{66A6}\x{66A7}\x{66A8}\x{66A9}\x{66AA}\x{66AB}' . + '\x{66AE}\x{66AF}\x{66B0}\x{66B1}\x{66B2}\x{66B3}\x{66B4}\x{66B5}\x{66B6}' . + '\x{66B7}\x{66B8}\x{66B9}\x{66BA}\x{66BB}\x{66BC}\x{66BD}\x{66BE}\x{66BF}' . + '\x{66C0}\x{66C1}\x{66C2}\x{66C3}\x{66C4}\x{66C5}\x{66C6}\x{66C7}\x{66C8}' . + '\x{66C9}\x{66CA}\x{66CB}\x{66CC}\x{66CD}\x{66CE}\x{66CF}\x{66D1}\x{66D2}' . + '\x{66D4}\x{66D5}\x{66D6}\x{66D8}\x{66D9}\x{66DA}\x{66DB}\x{66DC}\x{66DD}' . + '\x{66DE}\x{66E0}\x{66E1}\x{66E2}\x{66E3}\x{66E4}\x{66E5}\x{66E6}\x{66E7}' . + '\x{66E8}\x{66E9}\x{66EA}\x{66EB}\x{66EC}\x{66ED}\x{66EE}\x{66F0}\x{66F1}' . + '\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F6}\x{66F7}\x{66F8}\x{66F9}\x{66FA}' . + '\x{66FB}\x{66FC}\x{66FE}\x{66FF}\x{6700}\x{6701}\x{6703}\x{6704}\x{6705}' . + '\x{6706}\x{6708}\x{6709}\x{670A}\x{670B}\x{670C}\x{670D}\x{670E}\x{670F}' . + '\x{6710}\x{6711}\x{6712}\x{6713}\x{6714}\x{6715}\x{6716}\x{6717}\x{6718}' . + '\x{671A}\x{671B}\x{671C}\x{671D}\x{671E}\x{671F}\x{6720}\x{6721}\x{6722}' . + '\x{6723}\x{6725}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}\x{672D}' . + '\x{672E}\x{672F}\x{6730}\x{6731}\x{6732}\x{6733}\x{6734}\x{6735}\x{6736}' . + '\x{6737}\x{6738}\x{6739}\x{673A}\x{673B}\x{673C}\x{673D}\x{673E}\x{673F}' . + '\x{6740}\x{6741}\x{6742}\x{6743}\x{6744}\x{6745}\x{6746}\x{6747}\x{6748}' . + '\x{6749}\x{674A}\x{674B}\x{674C}\x{674D}\x{674E}\x{674F}\x{6750}\x{6751}' . + '\x{6752}\x{6753}\x{6754}\x{6755}\x{6756}\x{6757}\x{6758}\x{6759}\x{675A}' . + '\x{675B}\x{675C}\x{675D}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' . + '\x{6764}\x{6765}\x{6766}\x{6768}\x{6769}\x{676A}\x{676B}\x{676C}\x{676D}' . + '\x{676E}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}\x{6774}\x{6775}\x{6776}' . + '\x{6777}\x{6778}\x{6779}\x{677A}\x{677B}\x{677C}\x{677D}\x{677E}\x{677F}' . + '\x{6780}\x{6781}\x{6782}\x{6783}\x{6784}\x{6785}\x{6786}\x{6787}\x{6789}' . + '\x{678A}\x{678B}\x{678C}\x{678D}\x{678E}\x{678F}\x{6790}\x{6791}\x{6792}' . + '\x{6793}\x{6794}\x{6795}\x{6797}\x{6798}\x{6799}\x{679A}\x{679B}\x{679C}' . + '\x{679D}\x{679E}\x{679F}\x{67A0}\x{67A1}\x{67A2}\x{67A3}\x{67A4}\x{67A5}' . + '\x{67A6}\x{67A7}\x{67A8}\x{67AA}\x{67AB}\x{67AC}\x{67AD}\x{67AE}\x{67AF}' . + '\x{67B0}\x{67B1}\x{67B2}\x{67B3}\x{67B4}\x{67B5}\x{67B6}\x{67B7}\x{67B8}' . + '\x{67B9}\x{67BA}\x{67BB}\x{67BC}\x{67BE}\x{67C0}\x{67C1}\x{67C2}\x{67C3}' . + '\x{67C4}\x{67C5}\x{67C6}\x{67C7}\x{67C8}\x{67C9}\x{67CA}\x{67CB}\x{67CC}' . + '\x{67CD}\x{67CE}\x{67CF}\x{67D0}\x{67D1}\x{67D2}\x{67D3}\x{67D4}\x{67D6}' . + '\x{67D8}\x{67D9}\x{67DA}\x{67DB}\x{67DC}\x{67DD}\x{67DE}\x{67DF}\x{67E0}' . + '\x{67E1}\x{67E2}\x{67E3}\x{67E4}\x{67E5}\x{67E6}\x{67E7}\x{67E8}\x{67E9}' . + '\x{67EA}\x{67EB}\x{67EC}\x{67ED}\x{67EE}\x{67EF}\x{67F0}\x{67F1}\x{67F2}' . + '\x{67F3}\x{67F4}\x{67F5}\x{67F6}\x{67F7}\x{67F8}\x{67FA}\x{67FB}\x{67FC}' . + '\x{67FD}\x{67FE}\x{67FF}\x{6800}\x{6802}\x{6803}\x{6804}\x{6805}\x{6806}' . + '\x{6807}\x{6808}\x{6809}\x{680A}\x{680B}\x{680C}\x{680D}\x{680E}\x{680F}' . + '\x{6810}\x{6811}\x{6812}\x{6813}\x{6814}\x{6816}\x{6817}\x{6818}\x{6819}' . + '\x{681A}\x{681B}\x{681C}\x{681D}\x{681F}\x{6820}\x{6821}\x{6822}\x{6823}' . + '\x{6824}\x{6825}\x{6826}\x{6828}\x{6829}\x{682A}\x{682B}\x{682C}\x{682D}' . + '\x{682E}\x{682F}\x{6831}\x{6832}\x{6833}\x{6834}\x{6835}\x{6836}\x{6837}' . + '\x{6838}\x{6839}\x{683A}\x{683B}\x{683C}\x{683D}\x{683E}\x{683F}\x{6840}' . + '\x{6841}\x{6842}\x{6843}\x{6844}\x{6845}\x{6846}\x{6847}\x{6848}\x{6849}' . + '\x{684A}\x{684B}\x{684C}\x{684D}\x{684E}\x{684F}\x{6850}\x{6851}\x{6852}' . + '\x{6853}\x{6854}\x{6855}\x{6856}\x{6857}\x{685B}\x{685D}\x{6860}\x{6861}' . + '\x{6862}\x{6863}\x{6864}\x{6865}\x{6866}\x{6867}\x{6868}\x{6869}\x{686A}' . + '\x{686B}\x{686C}\x{686D}\x{686E}\x{686F}\x{6870}\x{6871}\x{6872}\x{6873}' . + '\x{6874}\x{6875}\x{6876}\x{6877}\x{6878}\x{6879}\x{687B}\x{687C}\x{687D}' . + '\x{687E}\x{687F}\x{6880}\x{6881}\x{6882}\x{6883}\x{6884}\x{6885}\x{6886}' . + '\x{6887}\x{6888}\x{6889}\x{688A}\x{688B}\x{688C}\x{688D}\x{688E}\x{688F}' . + '\x{6890}\x{6891}\x{6892}\x{6893}\x{6894}\x{6896}\x{6897}\x{6898}\x{689A}' . + '\x{689B}\x{689C}\x{689D}\x{689E}\x{689F}\x{68A0}\x{68A1}\x{68A2}\x{68A3}' . + '\x{68A4}\x{68A6}\x{68A7}\x{68A8}\x{68A9}\x{68AA}\x{68AB}\x{68AC}\x{68AD}' . + '\x{68AE}\x{68AF}\x{68B0}\x{68B1}\x{68B2}\x{68B3}\x{68B4}\x{68B5}\x{68B6}' . + '\x{68B7}\x{68B9}\x{68BB}\x{68BC}\x{68BD}\x{68BE}\x{68BF}\x{68C0}\x{68C1}' . + '\x{68C2}\x{68C4}\x{68C6}\x{68C7}\x{68C8}\x{68C9}\x{68CA}\x{68CB}\x{68CC}' . + '\x{68CD}\x{68CE}\x{68CF}\x{68D0}\x{68D1}\x{68D2}\x{68D3}\x{68D4}\x{68D5}' . + '\x{68D6}\x{68D7}\x{68D8}\x{68DA}\x{68DB}\x{68DC}\x{68DD}\x{68DE}\x{68DF}' . + '\x{68E0}\x{68E1}\x{68E3}\x{68E4}\x{68E6}\x{68E7}\x{68E8}\x{68E9}\x{68EA}' . + '\x{68EB}\x{68EC}\x{68ED}\x{68EE}\x{68EF}\x{68F0}\x{68F1}\x{68F2}\x{68F3}' . + '\x{68F4}\x{68F5}\x{68F6}\x{68F7}\x{68F8}\x{68F9}\x{68FA}\x{68FB}\x{68FC}' . + '\x{68FD}\x{68FE}\x{68FF}\x{6901}\x{6902}\x{6903}\x{6904}\x{6905}\x{6906}' . + '\x{6907}\x{6908}\x{690A}\x{690B}\x{690C}\x{690D}\x{690E}\x{690F}\x{6910}' . + '\x{6911}\x{6912}\x{6913}\x{6914}\x{6915}\x{6916}\x{6917}\x{6918}\x{6919}' . + '\x{691A}\x{691B}\x{691C}\x{691D}\x{691E}\x{691F}\x{6920}\x{6921}\x{6922}' . + '\x{6923}\x{6924}\x{6925}\x{6926}\x{6927}\x{6928}\x{6929}\x{692A}\x{692B}' . + '\x{692C}\x{692D}\x{692E}\x{692F}\x{6930}\x{6931}\x{6932}\x{6933}\x{6934}' . + '\x{6935}\x{6936}\x{6937}\x{6938}\x{6939}\x{693A}\x{693B}\x{693C}\x{693D}' . + '\x{693F}\x{6940}\x{6941}\x{6942}\x{6943}\x{6944}\x{6945}\x{6946}\x{6947}' . + '\x{6948}\x{6949}\x{694A}\x{694B}\x{694C}\x{694E}\x{694F}\x{6950}\x{6951}' . + '\x{6952}\x{6953}\x{6954}\x{6955}\x{6956}\x{6957}\x{6958}\x{6959}\x{695A}' . + '\x{695B}\x{695C}\x{695D}\x{695E}\x{695F}\x{6960}\x{6961}\x{6962}\x{6963}' . + '\x{6964}\x{6965}\x{6966}\x{6967}\x{6968}\x{6969}\x{696A}\x{696B}\x{696C}' . + '\x{696D}\x{696E}\x{696F}\x{6970}\x{6971}\x{6972}\x{6973}\x{6974}\x{6975}' . + '\x{6976}\x{6977}\x{6978}\x{6979}\x{697A}\x{697B}\x{697C}\x{697D}\x{697E}' . + '\x{697F}\x{6980}\x{6981}\x{6982}\x{6983}\x{6984}\x{6985}\x{6986}\x{6987}' . + '\x{6988}\x{6989}\x{698A}\x{698B}\x{698C}\x{698D}\x{698E}\x{698F}\x{6990}' . + '\x{6991}\x{6992}\x{6993}\x{6994}\x{6995}\x{6996}\x{6997}\x{6998}\x{6999}' . + '\x{699A}\x{699B}\x{699C}\x{699D}\x{699E}\x{69A0}\x{69A1}\x{69A3}\x{69A4}' . + '\x{69A5}\x{69A6}\x{69A7}\x{69A8}\x{69A9}\x{69AA}\x{69AB}\x{69AC}\x{69AD}' . + '\x{69AE}\x{69AF}\x{69B0}\x{69B1}\x{69B2}\x{69B3}\x{69B4}\x{69B5}\x{69B6}' . + '\x{69B7}\x{69B8}\x{69B9}\x{69BA}\x{69BB}\x{69BC}\x{69BD}\x{69BE}\x{69BF}' . + '\x{69C1}\x{69C2}\x{69C3}\x{69C4}\x{69C5}\x{69C6}\x{69C7}\x{69C8}\x{69C9}' . + '\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}\x{69CF}\x{69D0}\x{69D3}\x{69D4}' . + '\x{69D8}\x{69D9}\x{69DA}\x{69DB}\x{69DC}\x{69DD}\x{69DE}\x{69DF}\x{69E0}' . + '\x{69E1}\x{69E2}\x{69E3}\x{69E4}\x{69E5}\x{69E6}\x{69E7}\x{69E8}\x{69E9}' . + '\x{69EA}\x{69EB}\x{69EC}\x{69ED}\x{69EE}\x{69EF}\x{69F0}\x{69F1}\x{69F2}' . + '\x{69F3}\x{69F4}\x{69F5}\x{69F6}\x{69F7}\x{69F8}\x{69FA}\x{69FB}\x{69FC}' . + '\x{69FD}\x{69FE}\x{69FF}\x{6A00}\x{6A01}\x{6A02}\x{6A04}\x{6A05}\x{6A06}' . + '\x{6A07}\x{6A08}\x{6A09}\x{6A0A}\x{6A0B}\x{6A0D}\x{6A0E}\x{6A0F}\x{6A10}' . + '\x{6A11}\x{6A12}\x{6A13}\x{6A14}\x{6A15}\x{6A16}\x{6A17}\x{6A18}\x{6A19}' . + '\x{6A1A}\x{6A1B}\x{6A1D}\x{6A1E}\x{6A1F}\x{6A20}\x{6A21}\x{6A22}\x{6A23}' . + '\x{6A25}\x{6A26}\x{6A27}\x{6A28}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2C}\x{6A2D}' . + '\x{6A2E}\x{6A2F}\x{6A30}\x{6A31}\x{6A32}\x{6A33}\x{6A34}\x{6A35}\x{6A36}' . + '\x{6A38}\x{6A39}\x{6A3A}\x{6A3B}\x{6A3C}\x{6A3D}\x{6A3E}\x{6A3F}\x{6A40}' . + '\x{6A41}\x{6A42}\x{6A43}\x{6A44}\x{6A45}\x{6A46}\x{6A47}\x{6A48}\x{6A49}' . + '\x{6A4B}\x{6A4C}\x{6A4D}\x{6A4E}\x{6A4F}\x{6A50}\x{6A51}\x{6A52}\x{6A54}' . + '\x{6A55}\x{6A56}\x{6A57}\x{6A58}\x{6A59}\x{6A5A}\x{6A5B}\x{6A5D}\x{6A5E}' . + '\x{6A5F}\x{6A60}\x{6A61}\x{6A62}\x{6A63}\x{6A64}\x{6A65}\x{6A66}\x{6A67}' . + '\x{6A68}\x{6A69}\x{6A6A}\x{6A6B}\x{6A6C}\x{6A6D}\x{6A6F}\x{6A71}\x{6A72}' . + '\x{6A73}\x{6A74}\x{6A75}\x{6A76}\x{6A77}\x{6A78}\x{6A79}\x{6A7A}\x{6A7B}' . + '\x{6A7C}\x{6A7D}\x{6A7E}\x{6A7F}\x{6A80}\x{6A81}\x{6A82}\x{6A83}\x{6A84}' . + '\x{6A85}\x{6A87}\x{6A88}\x{6A89}\x{6A8B}\x{6A8C}\x{6A8D}\x{6A8E}\x{6A90}' . + '\x{6A91}\x{6A92}\x{6A93}\x{6A94}\x{6A95}\x{6A96}\x{6A97}\x{6A98}\x{6A9A}' . + '\x{6A9B}\x{6A9C}\x{6A9E}\x{6A9F}\x{6AA0}\x{6AA1}\x{6AA2}\x{6AA3}\x{6AA4}' . + '\x{6AA5}\x{6AA6}\x{6AA7}\x{6AA8}\x{6AA9}\x{6AAB}\x{6AAC}\x{6AAD}\x{6AAE}' . + '\x{6AAF}\x{6AB0}\x{6AB2}\x{6AB3}\x{6AB4}\x{6AB5}\x{6AB6}\x{6AB7}\x{6AB8}' . + '\x{6AB9}\x{6ABA}\x{6ABB}\x{6ABC}\x{6ABD}\x{6ABF}\x{6AC1}\x{6AC2}\x{6AC3}' . + '\x{6AC5}\x{6AC6}\x{6AC7}\x{6ACA}\x{6ACB}\x{6ACC}\x{6ACD}\x{6ACE}\x{6ACF}' . + '\x{6AD0}\x{6AD1}\x{6AD2}\x{6AD3}\x{6AD4}\x{6AD5}\x{6AD6}\x{6AD7}\x{6AD9}' . + '\x{6ADA}\x{6ADB}\x{6ADC}\x{6ADD}\x{6ADE}\x{6ADF}\x{6AE0}\x{6AE1}\x{6AE2}' . + '\x{6AE3}\x{6AE4}\x{6AE5}\x{6AE6}\x{6AE7}\x{6AE8}\x{6AEA}\x{6AEB}\x{6AEC}' . + '\x{6AED}\x{6AEE}\x{6AEF}\x{6AF0}\x{6AF1}\x{6AF2}\x{6AF3}\x{6AF4}\x{6AF5}' . + '\x{6AF6}\x{6AF7}\x{6AF8}\x{6AF9}\x{6AFA}\x{6AFB}\x{6AFC}\x{6AFD}\x{6AFE}' . + '\x{6AFF}\x{6B00}\x{6B01}\x{6B02}\x{6B03}\x{6B04}\x{6B05}\x{6B06}\x{6B07}' . + '\x{6B08}\x{6B09}\x{6B0A}\x{6B0B}\x{6B0C}\x{6B0D}\x{6B0F}\x{6B10}\x{6B11}' . + '\x{6B12}\x{6B13}\x{6B14}\x{6B15}\x{6B16}\x{6B17}\x{6B18}\x{6B19}\x{6B1A}' . + '\x{6B1C}\x{6B1D}\x{6B1E}\x{6B1F}\x{6B20}\x{6B21}\x{6B22}\x{6B23}\x{6B24}' . + '\x{6B25}\x{6B26}\x{6B27}\x{6B28}\x{6B29}\x{6B2A}\x{6B2B}\x{6B2C}\x{6B2D}' . + '\x{6B2F}\x{6B30}\x{6B31}\x{6B32}\x{6B33}\x{6B34}\x{6B36}\x{6B37}\x{6B38}' . + '\x{6B39}\x{6B3A}\x{6B3B}\x{6B3C}\x{6B3D}\x{6B3E}\x{6B3F}\x{6B41}\x{6B42}' . + '\x{6B43}\x{6B44}\x{6B45}\x{6B46}\x{6B47}\x{6B48}\x{6B49}\x{6B4A}\x{6B4B}' . + '\x{6B4C}\x{6B4D}\x{6B4E}\x{6B4F}\x{6B50}\x{6B51}\x{6B52}\x{6B53}\x{6B54}' . + '\x{6B55}\x{6B56}\x{6B59}\x{6B5A}\x{6B5B}\x{6B5C}\x{6B5E}\x{6B5F}\x{6B60}' . + '\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B65}\x{6B66}\x{6B67}\x{6B69}\x{6B6A}' . + '\x{6B6B}\x{6B6D}\x{6B6F}\x{6B70}\x{6B72}\x{6B73}\x{6B74}\x{6B76}\x{6B77}' . + '\x{6B78}\x{6B79}\x{6B7A}\x{6B7B}\x{6B7C}\x{6B7E}\x{6B7F}\x{6B80}\x{6B81}' . + '\x{6B82}\x{6B83}\x{6B84}\x{6B85}\x{6B86}\x{6B87}\x{6B88}\x{6B89}\x{6B8A}' . + '\x{6B8B}\x{6B8C}\x{6B8D}\x{6B8E}\x{6B8F}\x{6B90}\x{6B91}\x{6B92}\x{6B93}' . + '\x{6B94}\x{6B95}\x{6B96}\x{6B97}\x{6B98}\x{6B99}\x{6B9A}\x{6B9B}\x{6B9C}' . + '\x{6B9D}\x{6B9E}\x{6B9F}\x{6BA0}\x{6BA1}\x{6BA2}\x{6BA3}\x{6BA4}\x{6BA5}' . + '\x{6BA6}\x{6BA7}\x{6BA8}\x{6BA9}\x{6BAA}\x{6BAB}\x{6BAC}\x{6BAD}\x{6BAE}' . + '\x{6BAF}\x{6BB0}\x{6BB2}\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB6}\x{6BB7}\x{6BB9}' . + '\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBD}\x{6BBE}\x{6BBF}\x{6BC0}\x{6BC1}\x{6BC2}' . + '\x{6BC3}\x{6BC4}\x{6BC5}\x{6BC6}\x{6BC7}\x{6BC8}\x{6BC9}\x{6BCA}\x{6BCB}' . + '\x{6BCC}\x{6BCD}\x{6BCE}\x{6BCF}\x{6BD0}\x{6BD1}\x{6BD2}\x{6BD3}\x{6BD4}' . + '\x{6BD5}\x{6BD6}\x{6BD7}\x{6BD8}\x{6BD9}\x{6BDA}\x{6BDB}\x{6BDC}\x{6BDD}' . + '\x{6BDE}\x{6BDF}\x{6BE0}\x{6BE1}\x{6BE2}\x{6BE3}\x{6BE4}\x{6BE5}\x{6BE6}' . + '\x{6BE7}\x{6BE8}\x{6BEA}\x{6BEB}\x{6BEC}\x{6BED}\x{6BEE}\x{6BEF}\x{6BF0}' . + '\x{6BF2}\x{6BF3}\x{6BF5}\x{6BF6}\x{6BF7}\x{6BF8}\x{6BF9}\x{6BFB}\x{6BFC}' . + '\x{6BFD}\x{6BFE}\x{6BFF}\x{6C00}\x{6C01}\x{6C02}\x{6C03}\x{6C04}\x{6C05}' . + '\x{6C06}\x{6C07}\x{6C08}\x{6C09}\x{6C0B}\x{6C0C}\x{6C0D}\x{6C0E}\x{6C0F}' . + '\x{6C10}\x{6C11}\x{6C12}\x{6C13}\x{6C14}\x{6C15}\x{6C16}\x{6C18}\x{6C19}' . + '\x{6C1A}\x{6C1B}\x{6C1D}\x{6C1E}\x{6C1F}\x{6C20}\x{6C21}\x{6C22}\x{6C23}' . + '\x{6C24}\x{6C25}\x{6C26}\x{6C27}\x{6C28}\x{6C29}\x{6C2A}\x{6C2B}\x{6C2C}' . + '\x{6C2E}\x{6C2F}\x{6C30}\x{6C31}\x{6C32}\x{6C33}\x{6C34}\x{6C35}\x{6C36}' . + '\x{6C37}\x{6C38}\x{6C3A}\x{6C3B}\x{6C3D}\x{6C3E}\x{6C3F}\x{6C40}\x{6C41}' . + '\x{6C42}\x{6C43}\x{6C44}\x{6C46}\x{6C47}\x{6C48}\x{6C49}\x{6C4A}\x{6C4B}' . + '\x{6C4C}\x{6C4D}\x{6C4E}\x{6C4F}\x{6C50}\x{6C51}\x{6C52}\x{6C53}\x{6C54}' . + '\x{6C55}\x{6C56}\x{6C57}\x{6C58}\x{6C59}\x{6C5A}\x{6C5B}\x{6C5C}\x{6C5D}' . + '\x{6C5E}\x{6C5F}\x{6C60}\x{6C61}\x{6C62}\x{6C63}\x{6C64}\x{6C65}\x{6C66}' . + '\x{6C67}\x{6C68}\x{6C69}\x{6C6A}\x{6C6B}\x{6C6D}\x{6C6F}\x{6C70}\x{6C71}' . + '\x{6C72}\x{6C73}\x{6C74}\x{6C75}\x{6C76}\x{6C77}\x{6C78}\x{6C79}\x{6C7A}' . + '\x{6C7B}\x{6C7C}\x{6C7D}\x{6C7E}\x{6C7F}\x{6C80}\x{6C81}\x{6C82}\x{6C83}' . + '\x{6C84}\x{6C85}\x{6C86}\x{6C87}\x{6C88}\x{6C89}\x{6C8A}\x{6C8B}\x{6C8C}' . + '\x{6C8D}\x{6C8E}\x{6C8F}\x{6C90}\x{6C91}\x{6C92}\x{6C93}\x{6C94}\x{6C95}' . + '\x{6C96}\x{6C97}\x{6C98}\x{6C99}\x{6C9A}\x{6C9B}\x{6C9C}\x{6C9D}\x{6C9E}' . + '\x{6C9F}\x{6CA1}\x{6CA2}\x{6CA3}\x{6CA4}\x{6CA5}\x{6CA6}\x{6CA7}\x{6CA8}' . + '\x{6CA9}\x{6CAA}\x{6CAB}\x{6CAC}\x{6CAD}\x{6CAE}\x{6CAF}\x{6CB0}\x{6CB1}' . + '\x{6CB2}\x{6CB3}\x{6CB4}\x{6CB5}\x{6CB6}\x{6CB7}\x{6CB8}\x{6CB9}\x{6CBA}' . + '\x{6CBB}\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC0}\x{6CC1}\x{6CC2}\x{6CC3}' . + '\x{6CC4}\x{6CC5}\x{6CC6}\x{6CC7}\x{6CC8}\x{6CC9}\x{6CCA}\x{6CCB}\x{6CCC}' . + '\x{6CCD}\x{6CCE}\x{6CCF}\x{6CD0}\x{6CD1}\x{6CD2}\x{6CD3}\x{6CD4}\x{6CD5}' . + '\x{6CD6}\x{6CD7}\x{6CD9}\x{6CDA}\x{6CDB}\x{6CDC}\x{6CDD}\x{6CDE}\x{6CDF}' . + '\x{6CE0}\x{6CE1}\x{6CE2}\x{6CE3}\x{6CE4}\x{6CE5}\x{6CE6}\x{6CE7}\x{6CE8}' . + '\x{6CE9}\x{6CEA}\x{6CEB}\x{6CEC}\x{6CED}\x{6CEE}\x{6CEF}\x{6CF0}\x{6CF1}' . + '\x{6CF2}\x{6CF3}\x{6CF5}\x{6CF6}\x{6CF7}\x{6CF8}\x{6CF9}\x{6CFA}\x{6CFB}' . + '\x{6CFC}\x{6CFD}\x{6CFE}\x{6CFF}\x{6D00}\x{6D01}\x{6D03}\x{6D04}\x{6D05}' . + '\x{6D06}\x{6D07}\x{6D08}\x{6D09}\x{6D0A}\x{6D0B}\x{6D0C}\x{6D0D}\x{6D0E}' . + '\x{6D0F}\x{6D10}\x{6D11}\x{6D12}\x{6D13}\x{6D14}\x{6D15}\x{6D16}\x{6D17}' . + '\x{6D18}\x{6D19}\x{6D1A}\x{6D1B}\x{6D1D}\x{6D1E}\x{6D1F}\x{6D20}\x{6D21}' . + '\x{6D22}\x{6D23}\x{6D25}\x{6D26}\x{6D27}\x{6D28}\x{6D29}\x{6D2A}\x{6D2B}' . + '\x{6D2C}\x{6D2D}\x{6D2E}\x{6D2F}\x{6D30}\x{6D31}\x{6D32}\x{6D33}\x{6D34}' . + '\x{6D35}\x{6D36}\x{6D37}\x{6D38}\x{6D39}\x{6D3A}\x{6D3B}\x{6D3C}\x{6D3D}' . + '\x{6D3E}\x{6D3F}\x{6D40}\x{6D41}\x{6D42}\x{6D43}\x{6D44}\x{6D45}\x{6D46}' . + '\x{6D47}\x{6D48}\x{6D49}\x{6D4A}\x{6D4B}\x{6D4C}\x{6D4D}\x{6D4E}\x{6D4F}' . + '\x{6D50}\x{6D51}\x{6D52}\x{6D53}\x{6D54}\x{6D55}\x{6D56}\x{6D57}\x{6D58}' . + '\x{6D59}\x{6D5A}\x{6D5B}\x{6D5C}\x{6D5D}\x{6D5E}\x{6D5F}\x{6D60}\x{6D61}' . + '\x{6D62}\x{6D63}\x{6D64}\x{6D65}\x{6D66}\x{6D67}\x{6D68}\x{6D69}\x{6D6A}' . + '\x{6D6B}\x{6D6C}\x{6D6D}\x{6D6E}\x{6D6F}\x{6D70}\x{6D72}\x{6D73}\x{6D74}' . + '\x{6D75}\x{6D76}\x{6D77}\x{6D78}\x{6D79}\x{6D7A}\x{6D7B}\x{6D7C}\x{6D7D}' . + '\x{6D7E}\x{6D7F}\x{6D80}\x{6D82}\x{6D83}\x{6D84}\x{6D85}\x{6D86}\x{6D87}' . + '\x{6D88}\x{6D89}\x{6D8A}\x{6D8B}\x{6D8C}\x{6D8D}\x{6D8E}\x{6D8F}\x{6D90}' . + '\x{6D91}\x{6D92}\x{6D93}\x{6D94}\x{6D95}\x{6D97}\x{6D98}\x{6D99}\x{6D9A}' . + '\x{6D9B}\x{6D9D}\x{6D9E}\x{6D9F}\x{6DA0}\x{6DA1}\x{6DA2}\x{6DA3}\x{6DA4}' . + '\x{6DA5}\x{6DA6}\x{6DA7}\x{6DA8}\x{6DA9}\x{6DAA}\x{6DAB}\x{6DAC}\x{6DAD}' . + '\x{6DAE}\x{6DAF}\x{6DB2}\x{6DB3}\x{6DB4}\x{6DB5}\x{6DB7}\x{6DB8}\x{6DB9}' . + '\x{6DBA}\x{6DBB}\x{6DBC}\x{6DBD}\x{6DBE}\x{6DBF}\x{6DC0}\x{6DC1}\x{6DC2}' . + '\x{6DC3}\x{6DC4}\x{6DC5}\x{6DC6}\x{6DC7}\x{6DC8}\x{6DC9}\x{6DCA}\x{6DCB}' . + '\x{6DCC}\x{6DCD}\x{6DCE}\x{6DCF}\x{6DD0}\x{6DD1}\x{6DD2}\x{6DD3}\x{6DD4}' . + '\x{6DD5}\x{6DD6}\x{6DD7}\x{6DD8}\x{6DD9}\x{6DDA}\x{6DDB}\x{6DDC}\x{6DDD}' . + '\x{6DDE}\x{6DDF}\x{6DE0}\x{6DE1}\x{6DE2}\x{6DE3}\x{6DE4}\x{6DE5}\x{6DE6}' . + '\x{6DE7}\x{6DE8}\x{6DE9}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DED}\x{6DEE}\x{6DEF}' . + '\x{6DF0}\x{6DF1}\x{6DF2}\x{6DF3}\x{6DF4}\x{6DF5}\x{6DF6}\x{6DF7}\x{6DF8}' . + '\x{6DF9}\x{6DFA}\x{6DFB}\x{6DFC}\x{6DFD}\x{6E00}\x{6E03}\x{6E04}\x{6E05}' . + '\x{6E07}\x{6E08}\x{6E09}\x{6E0A}\x{6E0B}\x{6E0C}\x{6E0D}\x{6E0E}\x{6E0F}' . + '\x{6E10}\x{6E11}\x{6E14}\x{6E15}\x{6E16}\x{6E17}\x{6E19}\x{6E1A}\x{6E1B}' . + '\x{6E1C}\x{6E1D}\x{6E1E}\x{6E1F}\x{6E20}\x{6E21}\x{6E22}\x{6E23}\x{6E24}' . + '\x{6E25}\x{6E26}\x{6E27}\x{6E28}\x{6E29}\x{6E2B}\x{6E2C}\x{6E2D}\x{6E2E}' . + '\x{6E2F}\x{6E30}\x{6E31}\x{6E32}\x{6E33}\x{6E34}\x{6E35}\x{6E36}\x{6E37}' . + '\x{6E38}\x{6E39}\x{6E3A}\x{6E3B}\x{6E3C}\x{6E3D}\x{6E3E}\x{6E3F}\x{6E40}' . + '\x{6E41}\x{6E42}\x{6E43}\x{6E44}\x{6E45}\x{6E46}\x{6E47}\x{6E48}\x{6E49}' . + '\x{6E4A}\x{6E4B}\x{6E4D}\x{6E4E}\x{6E4F}\x{6E50}\x{6E51}\x{6E52}\x{6E53}' . + '\x{6E54}\x{6E55}\x{6E56}\x{6E57}\x{6E58}\x{6E59}\x{6E5A}\x{6E5B}\x{6E5C}' . + '\x{6E5D}\x{6E5E}\x{6E5F}\x{6E60}\x{6E61}\x{6E62}\x{6E63}\x{6E64}\x{6E65}' . + '\x{6E66}\x{6E67}\x{6E68}\x{6E69}\x{6E6A}\x{6E6B}\x{6E6D}\x{6E6E}\x{6E6F}' . + '\x{6E70}\x{6E71}\x{6E72}\x{6E73}\x{6E74}\x{6E75}\x{6E77}\x{6E78}\x{6E79}' . + '\x{6E7E}\x{6E7F}\x{6E80}\x{6E81}\x{6E82}\x{6E83}\x{6E84}\x{6E85}\x{6E86}' . + '\x{6E87}\x{6E88}\x{6E89}\x{6E8A}\x{6E8D}\x{6E8E}\x{6E8F}\x{6E90}\x{6E91}' . + '\x{6E92}\x{6E93}\x{6E94}\x{6E96}\x{6E97}\x{6E98}\x{6E99}\x{6E9A}\x{6E9B}' . + '\x{6E9C}\x{6E9D}\x{6E9E}\x{6E9F}\x{6EA0}\x{6EA1}\x{6EA2}\x{6EA3}\x{6EA4}' . + '\x{6EA5}\x{6EA6}\x{6EA7}\x{6EA8}\x{6EA9}\x{6EAA}\x{6EAB}\x{6EAC}\x{6EAD}' . + '\x{6EAE}\x{6EAF}\x{6EB0}\x{6EB1}\x{6EB2}\x{6EB3}\x{6EB4}\x{6EB5}\x{6EB6}' . + '\x{6EB7}\x{6EB8}\x{6EB9}\x{6EBA}\x{6EBB}\x{6EBC}\x{6EBD}\x{6EBE}\x{6EBF}' . + '\x{6EC0}\x{6EC1}\x{6EC2}\x{6EC3}\x{6EC4}\x{6EC5}\x{6EC6}\x{6EC7}\x{6EC8}' . + '\x{6EC9}\x{6ECA}\x{6ECB}\x{6ECC}\x{6ECD}\x{6ECE}\x{6ECF}\x{6ED0}\x{6ED1}' . + '\x{6ED2}\x{6ED3}\x{6ED4}\x{6ED5}\x{6ED6}\x{6ED7}\x{6ED8}\x{6ED9}\x{6EDA}' . + '\x{6EDC}\x{6EDE}\x{6EDF}\x{6EE0}\x{6EE1}\x{6EE2}\x{6EE4}\x{6EE5}\x{6EE6}' . + '\x{6EE7}\x{6EE8}\x{6EE9}\x{6EEA}\x{6EEB}\x{6EEC}\x{6EED}\x{6EEE}\x{6EEF}' . + '\x{6EF0}\x{6EF1}\x{6EF2}\x{6EF3}\x{6EF4}\x{6EF5}\x{6EF6}\x{6EF7}\x{6EF8}' . + '\x{6EF9}\x{6EFA}\x{6EFB}\x{6EFC}\x{6EFD}\x{6EFE}\x{6EFF}\x{6F00}\x{6F01}' . + '\x{6F02}\x{6F03}\x{6F05}\x{6F06}\x{6F07}\x{6F08}\x{6F09}\x{6F0A}\x{6F0C}' . + '\x{6F0D}\x{6F0E}\x{6F0F}\x{6F10}\x{6F11}\x{6F12}\x{6F13}\x{6F14}\x{6F15}' . + '\x{6F16}\x{6F17}\x{6F18}\x{6F19}\x{6F1A}\x{6F1B}\x{6F1C}\x{6F1D}\x{6F1E}' . + '\x{6F1F}\x{6F20}\x{6F21}\x{6F22}\x{6F23}\x{6F24}\x{6F25}\x{6F26}\x{6F27}' . + '\x{6F28}\x{6F29}\x{6F2A}\x{6F2B}\x{6F2C}\x{6F2D}\x{6F2E}\x{6F2F}\x{6F30}' . + '\x{6F31}\x{6F32}\x{6F33}\x{6F34}\x{6F35}\x{6F36}\x{6F37}\x{6F38}\x{6F39}' . + '\x{6F3A}\x{6F3B}\x{6F3C}\x{6F3D}\x{6F3E}\x{6F3F}\x{6F40}\x{6F41}\x{6F43}' . + '\x{6F44}\x{6F45}\x{6F46}\x{6F47}\x{6F49}\x{6F4B}\x{6F4C}\x{6F4D}\x{6F4E}' . + '\x{6F4F}\x{6F50}\x{6F51}\x{6F52}\x{6F53}\x{6F54}\x{6F55}\x{6F56}\x{6F57}' . + '\x{6F58}\x{6F59}\x{6F5A}\x{6F5B}\x{6F5C}\x{6F5D}\x{6F5E}\x{6F5F}\x{6F60}' . + '\x{6F61}\x{6F62}\x{6F63}\x{6F64}\x{6F65}\x{6F66}\x{6F67}\x{6F68}\x{6F69}' . + '\x{6F6A}\x{6F6B}\x{6F6C}\x{6F6D}\x{6F6E}\x{6F6F}\x{6F70}\x{6F71}\x{6F72}' . + '\x{6F73}\x{6F74}\x{6F75}\x{6F76}\x{6F77}\x{6F78}\x{6F7A}\x{6F7B}\x{6F7C}' . + '\x{6F7D}\x{6F7E}\x{6F7F}\x{6F80}\x{6F81}\x{6F82}\x{6F83}\x{6F84}\x{6F85}' . + '\x{6F86}\x{6F87}\x{6F88}\x{6F89}\x{6F8A}\x{6F8B}\x{6F8C}\x{6F8D}\x{6F8E}' . + '\x{6F8F}\x{6F90}\x{6F91}\x{6F92}\x{6F93}\x{6F94}\x{6F95}\x{6F96}\x{6F97}' . + '\x{6F99}\x{6F9B}\x{6F9C}\x{6F9D}\x{6F9E}\x{6FA0}\x{6FA1}\x{6FA2}\x{6FA3}' . + '\x{6FA4}\x{6FA5}\x{6FA6}\x{6FA7}\x{6FA8}\x{6FA9}\x{6FAA}\x{6FAB}\x{6FAC}' . + '\x{6FAD}\x{6FAE}\x{6FAF}\x{6FB0}\x{6FB1}\x{6FB2}\x{6FB3}\x{6FB4}\x{6FB5}' . + '\x{6FB6}\x{6FB8}\x{6FB9}\x{6FBA}\x{6FBB}\x{6FBC}\x{6FBD}\x{6FBE}\x{6FBF}' . + '\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC4}\x{6FC6}\x{6FC7}\x{6FC8}\x{6FC9}' . + '\x{6FCA}\x{6FCB}\x{6FCC}\x{6FCD}\x{6FCE}\x{6FCF}\x{6FD1}\x{6FD2}\x{6FD4}' . + '\x{6FD5}\x{6FD6}\x{6FD7}\x{6FD8}\x{6FD9}\x{6FDA}\x{6FDB}\x{6FDC}\x{6FDD}' . + '\x{6FDE}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE2}\x{6FE3}\x{6FE4}\x{6FE5}\x{6FE6}' . + '\x{6FE7}\x{6FE8}\x{6FE9}\x{6FEA}\x{6FEB}\x{6FEC}\x{6FED}\x{6FEE}\x{6FEF}' . + '\x{6FF0}\x{6FF1}\x{6FF2}\x{6FF3}\x{6FF4}\x{6FF6}\x{6FF7}\x{6FF8}\x{6FF9}' . + '\x{6FFA}\x{6FFB}\x{6FFC}\x{6FFE}\x{6FFF}\x{7000}\x{7001}\x{7002}\x{7003}' . + '\x{7004}\x{7005}\x{7006}\x{7007}\x{7008}\x{7009}\x{700A}\x{700B}\x{700C}' . + '\x{700D}\x{700E}\x{700F}\x{7011}\x{7012}\x{7014}\x{7015}\x{7016}\x{7017}' . + '\x{7018}\x{7019}\x{701A}\x{701B}\x{701C}\x{701D}\x{701F}\x{7020}\x{7021}' . + '\x{7022}\x{7023}\x{7024}\x{7025}\x{7026}\x{7027}\x{7028}\x{7029}\x{702A}' . + '\x{702B}\x{702C}\x{702D}\x{702E}\x{702F}\x{7030}\x{7031}\x{7032}\x{7033}' . + '\x{7034}\x{7035}\x{7036}\x{7037}\x{7038}\x{7039}\x{703A}\x{703B}\x{703C}' . + '\x{703D}\x{703E}\x{703F}\x{7040}\x{7041}\x{7042}\x{7043}\x{7044}\x{7045}' . + '\x{7046}\x{7048}\x{7049}\x{704A}\x{704C}\x{704D}\x{704F}\x{7050}\x{7051}' . + '\x{7052}\x{7053}\x{7054}\x{7055}\x{7056}\x{7057}\x{7058}\x{7059}\x{705A}' . + '\x{705B}\x{705C}\x{705D}\x{705E}\x{705F}\x{7060}\x{7061}\x{7062}\x{7063}' . + '\x{7064}\x{7065}\x{7066}\x{7067}\x{7068}\x{7069}\x{706A}\x{706B}\x{706C}' . + '\x{706D}\x{706E}\x{706F}\x{7070}\x{7071}\x{7074}\x{7075}\x{7076}\x{7077}' . + '\x{7078}\x{7079}\x{707A}\x{707C}\x{707D}\x{707E}\x{707F}\x{7080}\x{7082}' . + '\x{7083}\x{7084}\x{7085}\x{7086}\x{7087}\x{7088}\x{7089}\x{708A}\x{708B}' . + '\x{708C}\x{708E}\x{708F}\x{7090}\x{7091}\x{7092}\x{7093}\x{7094}\x{7095}' . + '\x{7096}\x{7098}\x{7099}\x{709A}\x{709C}\x{709D}\x{709E}\x{709F}\x{70A0}' . + '\x{70A1}\x{70A2}\x{70A3}\x{70A4}\x{70A5}\x{70A6}\x{70A7}\x{70A8}\x{70A9}' . + '\x{70AB}\x{70AC}\x{70AD}\x{70AE}\x{70AF}\x{70B0}\x{70B1}\x{70B3}\x{70B4}' . + '\x{70B5}\x{70B7}\x{70B8}\x{70B9}\x{70BA}\x{70BB}\x{70BC}\x{70BD}\x{70BE}' . + '\x{70BF}\x{70C0}\x{70C1}\x{70C2}\x{70C3}\x{70C4}\x{70C5}\x{70C6}\x{70C7}' . + '\x{70C8}\x{70C9}\x{70CA}\x{70CB}\x{70CC}\x{70CD}\x{70CE}\x{70CF}\x{70D0}' . + '\x{70D1}\x{70D2}\x{70D3}\x{70D4}\x{70D6}\x{70D7}\x{70D8}\x{70D9}\x{70DA}' . + '\x{70DB}\x{70DC}\x{70DD}\x{70DE}\x{70DF}\x{70E0}\x{70E1}\x{70E2}\x{70E3}' . + '\x{70E4}\x{70E5}\x{70E6}\x{70E7}\x{70E8}\x{70E9}\x{70EA}\x{70EB}\x{70EC}' . + '\x{70ED}\x{70EE}\x{70EF}\x{70F0}\x{70F1}\x{70F2}\x{70F3}\x{70F4}\x{70F5}' . + '\x{70F6}\x{70F7}\x{70F8}\x{70F9}\x{70FA}\x{70FB}\x{70FC}\x{70FD}\x{70FF}' . + '\x{7100}\x{7101}\x{7102}\x{7103}\x{7104}\x{7105}\x{7106}\x{7107}\x{7109}' . + '\x{710A}\x{710B}\x{710C}\x{710D}\x{710E}\x{710F}\x{7110}\x{7111}\x{7112}' . + '\x{7113}\x{7115}\x{7116}\x{7117}\x{7118}\x{7119}\x{711A}\x{711B}\x{711C}' . + '\x{711D}\x{711E}\x{711F}\x{7120}\x{7121}\x{7122}\x{7123}\x{7125}\x{7126}' . + '\x{7127}\x{7128}\x{7129}\x{712A}\x{712B}\x{712C}\x{712D}\x{712E}\x{712F}' . + '\x{7130}\x{7131}\x{7132}\x{7135}\x{7136}\x{7137}\x{7138}\x{7139}\x{713A}' . + '\x{713B}\x{713D}\x{713E}\x{713F}\x{7140}\x{7141}\x{7142}\x{7143}\x{7144}' . + '\x{7145}\x{7146}\x{7147}\x{7148}\x{7149}\x{714A}\x{714B}\x{714C}\x{714D}' . + '\x{714E}\x{714F}\x{7150}\x{7151}\x{7152}\x{7153}\x{7154}\x{7156}\x{7158}' . + '\x{7159}\x{715A}\x{715B}\x{715C}\x{715D}\x{715E}\x{715F}\x{7160}\x{7161}' . + '\x{7162}\x{7163}\x{7164}\x{7165}\x{7166}\x{7167}\x{7168}\x{7169}\x{716A}' . + '\x{716C}\x{716E}\x{716F}\x{7170}\x{7171}\x{7172}\x{7173}\x{7174}\x{7175}' . + '\x{7176}\x{7177}\x{7178}\x{7179}\x{717A}\x{717B}\x{717C}\x{717D}\x{717E}' . + '\x{717F}\x{7180}\x{7181}\x{7182}\x{7183}\x{7184}\x{7185}\x{7186}\x{7187}' . + '\x{7188}\x{7189}\x{718A}\x{718B}\x{718C}\x{718E}\x{718F}\x{7190}\x{7191}' . + '\x{7192}\x{7193}\x{7194}\x{7195}\x{7197}\x{7198}\x{7199}\x{719A}\x{719B}' . + '\x{719C}\x{719D}\x{719E}\x{719F}\x{71A0}\x{71A1}\x{71A2}\x{71A3}\x{71A4}' . + '\x{71A5}\x{71A7}\x{71A8}\x{71A9}\x{71AA}\x{71AC}\x{71AD}\x{71AE}\x{71AF}' . + '\x{71B0}\x{71B1}\x{71B2}\x{71B3}\x{71B4}\x{71B5}\x{71B7}\x{71B8}\x{71B9}' . + '\x{71BA}\x{71BB}\x{71BC}\x{71BD}\x{71BE}\x{71BF}\x{71C0}\x{71C1}\x{71C2}' . + '\x{71C3}\x{71C4}\x{71C5}\x{71C6}\x{71C7}\x{71C8}\x{71C9}\x{71CA}\x{71CB}' . + '\x{71CD}\x{71CE}\x{71CF}\x{71D0}\x{71D1}\x{71D2}\x{71D4}\x{71D5}\x{71D6}' . + '\x{71D7}\x{71D8}\x{71D9}\x{71DA}\x{71DB}\x{71DC}\x{71DD}\x{71DE}\x{71DF}' . + '\x{71E0}\x{71E1}\x{71E2}\x{71E3}\x{71E4}\x{71E5}\x{71E6}\x{71E7}\x{71E8}' . + '\x{71E9}\x{71EA}\x{71EB}\x{71EC}\x{71ED}\x{71EE}\x{71EF}\x{71F0}\x{71F1}' . + '\x{71F2}\x{71F4}\x{71F5}\x{71F6}\x{71F7}\x{71F8}\x{71F9}\x{71FB}\x{71FC}' . + '\x{71FD}\x{71FE}\x{71FF}\x{7201}\x{7202}\x{7203}\x{7204}\x{7205}\x{7206}' . + '\x{7207}\x{7208}\x{7209}\x{720A}\x{720C}\x{720D}\x{720E}\x{720F}\x{7210}' . + '\x{7212}\x{7213}\x{7214}\x{7216}\x{7218}\x{7219}\x{721A}\x{721B}\x{721C}' . + '\x{721D}\x{721E}\x{721F}\x{7221}\x{7222}\x{7223}\x{7226}\x{7227}\x{7228}' . + '\x{7229}\x{722A}\x{722B}\x{722C}\x{722D}\x{722E}\x{7230}\x{7231}\x{7232}' . + '\x{7233}\x{7235}\x{7236}\x{7237}\x{7238}\x{7239}\x{723A}\x{723B}\x{723C}' . + '\x{723D}\x{723E}\x{723F}\x{7240}\x{7241}\x{7242}\x{7243}\x{7244}\x{7246}' . + '\x{7247}\x{7248}\x{7249}\x{724A}\x{724B}\x{724C}\x{724D}\x{724F}\x{7251}' . + '\x{7252}\x{7253}\x{7254}\x{7256}\x{7257}\x{7258}\x{7259}\x{725A}\x{725B}' . + '\x{725C}\x{725D}\x{725E}\x{725F}\x{7260}\x{7261}\x{7262}\x{7263}\x{7264}' . + '\x{7265}\x{7266}\x{7267}\x{7268}\x{7269}\x{726A}\x{726B}\x{726C}\x{726D}' . + '\x{726E}\x{726F}\x{7270}\x{7271}\x{7272}\x{7273}\x{7274}\x{7275}\x{7276}' . + '\x{7277}\x{7278}\x{7279}\x{727A}\x{727B}\x{727C}\x{727D}\x{727E}\x{727F}' . + '\x{7280}\x{7281}\x{7282}\x{7283}\x{7284}\x{7285}\x{7286}\x{7287}\x{7288}' . + '\x{7289}\x{728A}\x{728B}\x{728C}\x{728D}\x{728E}\x{728F}\x{7290}\x{7291}' . + '\x{7292}\x{7293}\x{7294}\x{7295}\x{7296}\x{7297}\x{7298}\x{7299}\x{729A}' . + '\x{729B}\x{729C}\x{729D}\x{729E}\x{729F}\x{72A1}\x{72A2}\x{72A3}\x{72A4}' . + '\x{72A5}\x{72A6}\x{72A7}\x{72A8}\x{72A9}\x{72AA}\x{72AC}\x{72AD}\x{72AE}' . + '\x{72AF}\x{72B0}\x{72B1}\x{72B2}\x{72B3}\x{72B4}\x{72B5}\x{72B6}\x{72B7}' . + '\x{72B8}\x{72B9}\x{72BA}\x{72BB}\x{72BC}\x{72BD}\x{72BF}\x{72C0}\x{72C1}' . + '\x{72C2}\x{72C3}\x{72C4}\x{72C5}\x{72C6}\x{72C7}\x{72C8}\x{72C9}\x{72CA}' . + '\x{72CB}\x{72CC}\x{72CD}\x{72CE}\x{72CF}\x{72D0}\x{72D1}\x{72D2}\x{72D3}' . + '\x{72D4}\x{72D5}\x{72D6}\x{72D7}\x{72D8}\x{72D9}\x{72DA}\x{72DB}\x{72DC}' . + '\x{72DD}\x{72DE}\x{72DF}\x{72E0}\x{72E1}\x{72E2}\x{72E3}\x{72E4}\x{72E5}' . + '\x{72E6}\x{72E7}\x{72E8}\x{72E9}\x{72EA}\x{72EB}\x{72EC}\x{72ED}\x{72EE}' . + '\x{72EF}\x{72F0}\x{72F1}\x{72F2}\x{72F3}\x{72F4}\x{72F5}\x{72F6}\x{72F7}' . + '\x{72F8}\x{72F9}\x{72FA}\x{72FB}\x{72FC}\x{72FD}\x{72FE}\x{72FF}\x{7300}' . + '\x{7301}\x{7303}\x{7304}\x{7305}\x{7306}\x{7307}\x{7308}\x{7309}\x{730A}' . + '\x{730B}\x{730C}\x{730D}\x{730E}\x{730F}\x{7311}\x{7312}\x{7313}\x{7314}' . + '\x{7315}\x{7316}\x{7317}\x{7318}\x{7319}\x{731A}\x{731B}\x{731C}\x{731D}' . + '\x{731E}\x{7320}\x{7321}\x{7322}\x{7323}\x{7324}\x{7325}\x{7326}\x{7327}' . + '\x{7329}\x{732A}\x{732B}\x{732C}\x{732D}\x{732E}\x{7330}\x{7331}\x{7332}' . + '\x{7333}\x{7334}\x{7335}\x{7336}\x{7337}\x{7338}\x{7339}\x{733A}\x{733B}' . + '\x{733C}\x{733D}\x{733E}\x{733F}\x{7340}\x{7341}\x{7342}\x{7343}\x{7344}' . + '\x{7345}\x{7346}\x{7347}\x{7348}\x{7349}\x{734A}\x{734B}\x{734C}\x{734D}' . + '\x{734E}\x{7350}\x{7351}\x{7352}\x{7354}\x{7355}\x{7356}\x{7357}\x{7358}' . + '\x{7359}\x{735A}\x{735B}\x{735C}\x{735D}\x{735E}\x{735F}\x{7360}\x{7361}' . + '\x{7362}\x{7364}\x{7365}\x{7366}\x{7367}\x{7368}\x{7369}\x{736A}\x{736B}' . + '\x{736C}\x{736D}\x{736E}\x{736F}\x{7370}\x{7371}\x{7372}\x{7373}\x{7374}' . + '\x{7375}\x{7376}\x{7377}\x{7378}\x{7379}\x{737A}\x{737B}\x{737C}\x{737D}' . + '\x{737E}\x{737F}\x{7380}\x{7381}\x{7382}\x{7383}\x{7384}\x{7385}\x{7386}' . + '\x{7387}\x{7388}\x{7389}\x{738A}\x{738B}\x{738C}\x{738D}\x{738E}\x{738F}' . + '\x{7390}\x{7391}\x{7392}\x{7393}\x{7394}\x{7395}\x{7396}\x{7397}\x{7398}' . + '\x{7399}\x{739A}\x{739B}\x{739D}\x{739E}\x{739F}\x{73A0}\x{73A1}\x{73A2}' . + '\x{73A3}\x{73A4}\x{73A5}\x{73A6}\x{73A7}\x{73A8}\x{73A9}\x{73AA}\x{73AB}' . + '\x{73AC}\x{73AD}\x{73AE}\x{73AF}\x{73B0}\x{73B1}\x{73B2}\x{73B3}\x{73B4}' . + '\x{73B5}\x{73B6}\x{73B7}\x{73B8}\x{73B9}\x{73BA}\x{73BB}\x{73BC}\x{73BD}' . + '\x{73BE}\x{73BF}\x{73C0}\x{73C2}\x{73C3}\x{73C4}\x{73C5}\x{73C6}\x{73C7}' . + '\x{73C8}\x{73C9}\x{73CA}\x{73CB}\x{73CC}\x{73CD}\x{73CE}\x{73CF}\x{73D0}' . + '\x{73D1}\x{73D2}\x{73D3}\x{73D4}\x{73D5}\x{73D6}\x{73D7}\x{73D8}\x{73D9}' . + '\x{73DA}\x{73DB}\x{73DC}\x{73DD}\x{73DE}\x{73DF}\x{73E0}\x{73E2}\x{73E3}' . + '\x{73E5}\x{73E6}\x{73E7}\x{73E8}\x{73E9}\x{73EA}\x{73EB}\x{73EC}\x{73ED}' . + '\x{73EE}\x{73EF}\x{73F0}\x{73F1}\x{73F2}\x{73F4}\x{73F5}\x{73F6}\x{73F7}' . + '\x{73F8}\x{73F9}\x{73FA}\x{73FC}\x{73FD}\x{73FE}\x{73FF}\x{7400}\x{7401}' . + '\x{7402}\x{7403}\x{7404}\x{7405}\x{7406}\x{7407}\x{7408}\x{7409}\x{740A}' . + '\x{740B}\x{740C}\x{740D}\x{740E}\x{740F}\x{7410}\x{7411}\x{7412}\x{7413}' . + '\x{7414}\x{7415}\x{7416}\x{7417}\x{7419}\x{741A}\x{741B}\x{741C}\x{741D}' . + '\x{741E}\x{741F}\x{7420}\x{7421}\x{7422}\x{7423}\x{7424}\x{7425}\x{7426}' . + '\x{7427}\x{7428}\x{7429}\x{742A}\x{742B}\x{742C}\x{742D}\x{742E}\x{742F}' . + '\x{7430}\x{7431}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{7437}\x{7438}' . + '\x{743A}\x{743B}\x{743C}\x{743D}\x{743F}\x{7440}\x{7441}\x{7442}\x{7443}' . + '\x{7444}\x{7445}\x{7446}\x{7448}\x{744A}\x{744B}\x{744C}\x{744D}\x{744E}' . + '\x{744F}\x{7450}\x{7451}\x{7452}\x{7453}\x{7454}\x{7455}\x{7456}\x{7457}' . + '\x{7459}\x{745A}\x{745B}\x{745C}\x{745D}\x{745E}\x{745F}\x{7461}\x{7462}' . + '\x{7463}\x{7464}\x{7465}\x{7466}\x{7467}\x{7468}\x{7469}\x{746A}\x{746B}' . + '\x{746C}\x{746D}\x{746E}\x{746F}\x{7470}\x{7471}\x{7472}\x{7473}\x{7474}' . + '\x{7475}\x{7476}\x{7477}\x{7478}\x{7479}\x{747A}\x{747C}\x{747D}\x{747E}' . + '\x{747F}\x{7480}\x{7481}\x{7482}\x{7483}\x{7485}\x{7486}\x{7487}\x{7488}' . + '\x{7489}\x{748A}\x{748B}\x{748C}\x{748D}\x{748E}\x{748F}\x{7490}\x{7491}' . + '\x{7492}\x{7493}\x{7494}\x{7495}\x{7497}\x{7498}\x{7499}\x{749A}\x{749B}' . + '\x{749C}\x{749E}\x{749F}\x{74A0}\x{74A1}\x{74A3}\x{74A4}\x{74A5}\x{74A6}' . + '\x{74A7}\x{74A8}\x{74A9}\x{74AA}\x{74AB}\x{74AC}\x{74AD}\x{74AE}\x{74AF}' . + '\x{74B0}\x{74B1}\x{74B2}\x{74B3}\x{74B4}\x{74B5}\x{74B6}\x{74B7}\x{74B8}' . + '\x{74B9}\x{74BA}\x{74BB}\x{74BC}\x{74BD}\x{74BE}\x{74BF}\x{74C0}\x{74C1}' . + '\x{74C2}\x{74C3}\x{74C4}\x{74C5}\x{74C6}\x{74CA}\x{74CB}\x{74CD}\x{74CE}' . + '\x{74CF}\x{74D0}\x{74D1}\x{74D2}\x{74D3}\x{74D4}\x{74D5}\x{74D6}\x{74D7}' . + '\x{74D8}\x{74D9}\x{74DA}\x{74DB}\x{74DC}\x{74DD}\x{74DE}\x{74DF}\x{74E0}' . + '\x{74E1}\x{74E2}\x{74E3}\x{74E4}\x{74E5}\x{74E6}\x{74E7}\x{74E8}\x{74E9}' . + '\x{74EA}\x{74EC}\x{74ED}\x{74EE}\x{74EF}\x{74F0}\x{74F1}\x{74F2}\x{74F3}' . + '\x{74F4}\x{74F5}\x{74F6}\x{74F7}\x{74F8}\x{74F9}\x{74FA}\x{74FB}\x{74FC}' . + '\x{74FD}\x{74FE}\x{74FF}\x{7500}\x{7501}\x{7502}\x{7503}\x{7504}\x{7505}' . + '\x{7506}\x{7507}\x{7508}\x{7509}\x{750A}\x{750B}\x{750C}\x{750D}\x{750F}' . + '\x{7510}\x{7511}\x{7512}\x{7513}\x{7514}\x{7515}\x{7516}\x{7517}\x{7518}' . + '\x{7519}\x{751A}\x{751B}\x{751C}\x{751D}\x{751E}\x{751F}\x{7521}\x{7522}' . + '\x{7523}\x{7524}\x{7525}\x{7526}\x{7527}\x{7528}\x{7529}\x{752A}\x{752B}' . + '\x{752C}\x{752D}\x{752E}\x{752F}\x{7530}\x{7531}\x{7532}\x{7533}\x{7535}' . + '\x{7536}\x{7537}\x{7538}\x{7539}\x{753A}\x{753B}\x{753C}\x{753D}\x{753E}' . + '\x{753F}\x{7540}\x{7542}\x{7543}\x{7544}\x{7545}\x{7546}\x{7547}\x{7548}' . + '\x{7549}\x{754B}\x{754C}\x{754D}\x{754E}\x{754F}\x{7550}\x{7551}\x{7553}' . + '\x{7554}\x{7556}\x{7557}\x{7558}\x{7559}\x{755A}\x{755B}\x{755C}\x{755D}' . + '\x{755F}\x{7560}\x{7562}\x{7563}\x{7564}\x{7565}\x{7566}\x{7567}\x{7568}' . + '\x{7569}\x{756A}\x{756B}\x{756C}\x{756D}\x{756E}\x{756F}\x{7570}\x{7572}' . + '\x{7574}\x{7575}\x{7576}\x{7577}\x{7578}\x{7579}\x{757C}\x{757D}\x{757E}' . + '\x{757F}\x{7580}\x{7581}\x{7582}\x{7583}\x{7584}\x{7586}\x{7587}\x{7588}' . + '\x{7589}\x{758A}\x{758B}\x{758C}\x{758D}\x{758F}\x{7590}\x{7591}\x{7592}' . + '\x{7593}\x{7594}\x{7595}\x{7596}\x{7597}\x{7598}\x{7599}\x{759A}\x{759B}' . + '\x{759C}\x{759D}\x{759E}\x{759F}\x{75A0}\x{75A1}\x{75A2}\x{75A3}\x{75A4}' . + '\x{75A5}\x{75A6}\x{75A7}\x{75A8}\x{75AA}\x{75AB}\x{75AC}\x{75AD}\x{75AE}' . + '\x{75AF}\x{75B0}\x{75B1}\x{75B2}\x{75B3}\x{75B4}\x{75B5}\x{75B6}\x{75B8}' . + '\x{75B9}\x{75BA}\x{75BB}\x{75BC}\x{75BD}\x{75BE}\x{75BF}\x{75C0}\x{75C1}' . + '\x{75C2}\x{75C3}\x{75C4}\x{75C5}\x{75C6}\x{75C7}\x{75C8}\x{75C9}\x{75CA}' . + '\x{75CB}\x{75CC}\x{75CD}\x{75CE}\x{75CF}\x{75D0}\x{75D1}\x{75D2}\x{75D3}' . + '\x{75D4}\x{75D5}\x{75D6}\x{75D7}\x{75D8}\x{75D9}\x{75DA}\x{75DB}\x{75DD}' . + '\x{75DE}\x{75DF}\x{75E0}\x{75E1}\x{75E2}\x{75E3}\x{75E4}\x{75E5}\x{75E6}' . + '\x{75E7}\x{75E8}\x{75EA}\x{75EB}\x{75EC}\x{75ED}\x{75EF}\x{75F0}\x{75F1}' . + '\x{75F2}\x{75F3}\x{75F4}\x{75F5}\x{75F6}\x{75F7}\x{75F8}\x{75F9}\x{75FA}' . + '\x{75FB}\x{75FC}\x{75FD}\x{75FE}\x{75FF}\x{7600}\x{7601}\x{7602}\x{7603}' . + '\x{7604}\x{7605}\x{7606}\x{7607}\x{7608}\x{7609}\x{760A}\x{760B}\x{760C}' . + '\x{760D}\x{760E}\x{760F}\x{7610}\x{7611}\x{7612}\x{7613}\x{7614}\x{7615}' . + '\x{7616}\x{7617}\x{7618}\x{7619}\x{761A}\x{761B}\x{761C}\x{761D}\x{761E}' . + '\x{761F}\x{7620}\x{7621}\x{7622}\x{7623}\x{7624}\x{7625}\x{7626}\x{7627}' . + '\x{7628}\x{7629}\x{762A}\x{762B}\x{762D}\x{762E}\x{762F}\x{7630}\x{7631}' . + '\x{7632}\x{7633}\x{7634}\x{7635}\x{7636}\x{7637}\x{7638}\x{7639}\x{763A}' . + '\x{763B}\x{763C}\x{763D}\x{763E}\x{763F}\x{7640}\x{7641}\x{7642}\x{7643}' . + '\x{7646}\x{7647}\x{7648}\x{7649}\x{764A}\x{764B}\x{764C}\x{764D}\x{764F}' . + '\x{7650}\x{7652}\x{7653}\x{7654}\x{7656}\x{7657}\x{7658}\x{7659}\x{765A}' . + '\x{765B}\x{765C}\x{765D}\x{765E}\x{765F}\x{7660}\x{7661}\x{7662}\x{7663}' . + '\x{7664}\x{7665}\x{7666}\x{7667}\x{7668}\x{7669}\x{766A}\x{766B}\x{766C}' . + '\x{766D}\x{766E}\x{766F}\x{7670}\x{7671}\x{7672}\x{7674}\x{7675}\x{7676}' . + '\x{7677}\x{7678}\x{7679}\x{767B}\x{767C}\x{767D}\x{767E}\x{767F}\x{7680}' . + '\x{7681}\x{7682}\x{7683}\x{7684}\x{7685}\x{7686}\x{7687}\x{7688}\x{7689}' . + '\x{768A}\x{768B}\x{768C}\x{768E}\x{768F}\x{7690}\x{7691}\x{7692}\x{7693}' . + '\x{7694}\x{7695}\x{7696}\x{7697}\x{7698}\x{7699}\x{769A}\x{769B}\x{769C}' . + '\x{769D}\x{769E}\x{769F}\x{76A0}\x{76A3}\x{76A4}\x{76A6}\x{76A7}\x{76A9}' . + '\x{76AA}\x{76AB}\x{76AC}\x{76AD}\x{76AE}\x{76AF}\x{76B0}\x{76B1}\x{76B2}' . + '\x{76B4}\x{76B5}\x{76B7}\x{76B8}\x{76BA}\x{76BB}\x{76BC}\x{76BD}\x{76BE}' . + '\x{76BF}\x{76C0}\x{76C2}\x{76C3}\x{76C4}\x{76C5}\x{76C6}\x{76C7}\x{76C8}' . + '\x{76C9}\x{76CA}\x{76CD}\x{76CE}\x{76CF}\x{76D0}\x{76D1}\x{76D2}\x{76D3}' . + '\x{76D4}\x{76D5}\x{76D6}\x{76D7}\x{76D8}\x{76DA}\x{76DB}\x{76DC}\x{76DD}' . + '\x{76DE}\x{76DF}\x{76E0}\x{76E1}\x{76E2}\x{76E3}\x{76E4}\x{76E5}\x{76E6}' . + '\x{76E7}\x{76E8}\x{76E9}\x{76EA}\x{76EC}\x{76ED}\x{76EE}\x{76EF}\x{76F0}' . + '\x{76F1}\x{76F2}\x{76F3}\x{76F4}\x{76F5}\x{76F6}\x{76F7}\x{76F8}\x{76F9}' . + '\x{76FA}\x{76FB}\x{76FC}\x{76FD}\x{76FE}\x{76FF}\x{7701}\x{7703}\x{7704}' . + '\x{7705}\x{7706}\x{7707}\x{7708}\x{7709}\x{770A}\x{770B}\x{770C}\x{770D}' . + '\x{770F}\x{7710}\x{7711}\x{7712}\x{7713}\x{7714}\x{7715}\x{7716}\x{7717}' . + '\x{7718}\x{7719}\x{771A}\x{771B}\x{771C}\x{771D}\x{771E}\x{771F}\x{7720}' . + '\x{7722}\x{7723}\x{7725}\x{7726}\x{7727}\x{7728}\x{7729}\x{772A}\x{772C}' . + '\x{772D}\x{772E}\x{772F}\x{7730}\x{7731}\x{7732}\x{7733}\x{7734}\x{7735}' . + '\x{7736}\x{7737}\x{7738}\x{7739}\x{773A}\x{773B}\x{773C}\x{773D}\x{773E}' . + '\x{7740}\x{7741}\x{7743}\x{7744}\x{7745}\x{7746}\x{7747}\x{7748}\x{7749}' . + '\x{774A}\x{774B}\x{774C}\x{774D}\x{774E}\x{774F}\x{7750}\x{7751}\x{7752}' . + '\x{7753}\x{7754}\x{7755}\x{7756}\x{7757}\x{7758}\x{7759}\x{775A}\x{775B}' . + '\x{775C}\x{775D}\x{775E}\x{775F}\x{7760}\x{7761}\x{7762}\x{7763}\x{7765}' . + '\x{7766}\x{7767}\x{7768}\x{7769}\x{776A}\x{776B}\x{776C}\x{776D}\x{776E}' . + '\x{776F}\x{7770}\x{7771}\x{7772}\x{7773}\x{7774}\x{7775}\x{7776}\x{7777}' . + '\x{7778}\x{7779}\x{777A}\x{777B}\x{777C}\x{777D}\x{777E}\x{777F}\x{7780}' . + '\x{7781}\x{7782}\x{7783}\x{7784}\x{7785}\x{7786}\x{7787}\x{7788}\x{7789}' . + '\x{778A}\x{778B}\x{778C}\x{778D}\x{778E}\x{778F}\x{7790}\x{7791}\x{7792}' . + '\x{7793}\x{7794}\x{7795}\x{7797}\x{7798}\x{7799}\x{779A}\x{779B}\x{779C}' . + '\x{779D}\x{779E}\x{779F}\x{77A0}\x{77A1}\x{77A2}\x{77A3}\x{77A5}\x{77A6}' . + '\x{77A7}\x{77A8}\x{77A9}\x{77AA}\x{77AB}\x{77AC}\x{77AD}\x{77AE}\x{77AF}' . + '\x{77B0}\x{77B1}\x{77B2}\x{77B3}\x{77B4}\x{77B5}\x{77B6}\x{77B7}\x{77B8}' . + '\x{77B9}\x{77BA}\x{77BB}\x{77BC}\x{77BD}\x{77BF}\x{77C0}\x{77C2}\x{77C3}' . + '\x{77C4}\x{77C5}\x{77C6}\x{77C7}\x{77C8}\x{77C9}\x{77CA}\x{77CB}\x{77CC}' . + '\x{77CD}\x{77CE}\x{77CF}\x{77D0}\x{77D1}\x{77D3}\x{77D4}\x{77D5}\x{77D6}' . + '\x{77D7}\x{77D8}\x{77D9}\x{77DA}\x{77DB}\x{77DC}\x{77DE}\x{77DF}\x{77E0}' . + '\x{77E1}\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E8}\x{77E9}\x{77EA}\x{77EB}' . + '\x{77EC}\x{77ED}\x{77EE}\x{77EF}\x{77F0}\x{77F1}\x{77F2}\x{77F3}\x{77F6}' . + '\x{77F7}\x{77F8}\x{77F9}\x{77FA}\x{77FB}\x{77FC}\x{77FD}\x{77FE}\x{77FF}' . + '\x{7800}\x{7801}\x{7802}\x{7803}\x{7804}\x{7805}\x{7806}\x{7808}\x{7809}' . + '\x{780A}\x{780B}\x{780C}\x{780D}\x{780E}\x{780F}\x{7810}\x{7811}\x{7812}' . + '\x{7813}\x{7814}\x{7815}\x{7816}\x{7817}\x{7818}\x{7819}\x{781A}\x{781B}' . + '\x{781C}\x{781D}\x{781E}\x{781F}\x{7820}\x{7821}\x{7822}\x{7823}\x{7825}' . + '\x{7826}\x{7827}\x{7828}\x{7829}\x{782A}\x{782B}\x{782C}\x{782D}\x{782E}' . + '\x{782F}\x{7830}\x{7831}\x{7832}\x{7833}\x{7834}\x{7835}\x{7837}\x{7838}' . + '\x{7839}\x{783A}\x{783B}\x{783C}\x{783D}\x{783E}\x{7840}\x{7841}\x{7843}' . + '\x{7844}\x{7845}\x{7847}\x{7848}\x{7849}\x{784A}\x{784C}\x{784D}\x{784E}' . + '\x{7850}\x{7851}\x{7852}\x{7853}\x{7854}\x{7855}\x{7856}\x{7857}\x{7858}' . + '\x{7859}\x{785A}\x{785B}\x{785C}\x{785D}\x{785E}\x{785F}\x{7860}\x{7861}' . + '\x{7862}\x{7863}\x{7864}\x{7865}\x{7866}\x{7867}\x{7868}\x{7869}\x{786A}' . + '\x{786B}\x{786C}\x{786D}\x{786E}\x{786F}\x{7870}\x{7871}\x{7872}\x{7873}' . + '\x{7874}\x{7875}\x{7877}\x{7878}\x{7879}\x{787A}\x{787B}\x{787C}\x{787D}' . + '\x{787E}\x{787F}\x{7880}\x{7881}\x{7882}\x{7883}\x{7884}\x{7885}\x{7886}' . + '\x{7887}\x{7889}\x{788A}\x{788B}\x{788C}\x{788D}\x{788E}\x{788F}\x{7890}' . + '\x{7891}\x{7892}\x{7893}\x{7894}\x{7895}\x{7896}\x{7897}\x{7898}\x{7899}' . + '\x{789A}\x{789B}\x{789C}\x{789D}\x{789E}\x{789F}\x{78A0}\x{78A1}\x{78A2}' . + '\x{78A3}\x{78A4}\x{78A5}\x{78A6}\x{78A7}\x{78A8}\x{78A9}\x{78AA}\x{78AB}' . + '\x{78AC}\x{78AD}\x{78AE}\x{78AF}\x{78B0}\x{78B1}\x{78B2}\x{78B3}\x{78B4}' . + '\x{78B5}\x{78B6}\x{78B7}\x{78B8}\x{78B9}\x{78BA}\x{78BB}\x{78BC}\x{78BD}' . + '\x{78BE}\x{78BF}\x{78C0}\x{78C1}\x{78C3}\x{78C4}\x{78C5}\x{78C6}\x{78C8}' . + '\x{78C9}\x{78CA}\x{78CB}\x{78CC}\x{78CD}\x{78CE}\x{78CF}\x{78D0}\x{78D1}' . + '\x{78D3}\x{78D4}\x{78D5}\x{78D6}\x{78D7}\x{78D8}\x{78D9}\x{78DA}\x{78DB}' . + '\x{78DC}\x{78DD}\x{78DE}\x{78DF}\x{78E0}\x{78E1}\x{78E2}\x{78E3}\x{78E4}' . + '\x{78E5}\x{78E6}\x{78E7}\x{78E8}\x{78E9}\x{78EA}\x{78EB}\x{78EC}\x{78ED}' . + '\x{78EE}\x{78EF}\x{78F1}\x{78F2}\x{78F3}\x{78F4}\x{78F5}\x{78F6}\x{78F7}' . + '\x{78F9}\x{78FA}\x{78FB}\x{78FC}\x{78FD}\x{78FE}\x{78FF}\x{7901}\x{7902}' . + '\x{7903}\x{7904}\x{7905}\x{7906}\x{7907}\x{7909}\x{790A}\x{790B}\x{790C}' . + '\x{790E}\x{790F}\x{7910}\x{7911}\x{7912}\x{7913}\x{7914}\x{7916}\x{7917}' . + '\x{7918}\x{7919}\x{791A}\x{791B}\x{791C}\x{791D}\x{791E}\x{7921}\x{7922}' . + '\x{7923}\x{7924}\x{7925}\x{7926}\x{7927}\x{7928}\x{7929}\x{792A}\x{792B}' . + '\x{792C}\x{792D}\x{792E}\x{792F}\x{7930}\x{7931}\x{7933}\x{7934}\x{7935}' . + '\x{7937}\x{7938}\x{7939}\x{793A}\x{793B}\x{793C}\x{793D}\x{793E}\x{793F}' . + '\x{7940}\x{7941}\x{7942}\x{7943}\x{7944}\x{7945}\x{7946}\x{7947}\x{7948}' . + '\x{7949}\x{794A}\x{794B}\x{794C}\x{794D}\x{794E}\x{794F}\x{7950}\x{7951}' . + '\x{7952}\x{7953}\x{7954}\x{7955}\x{7956}\x{7957}\x{7958}\x{795A}\x{795B}' . + '\x{795C}\x{795D}\x{795E}\x{795F}\x{7960}\x{7961}\x{7962}\x{7963}\x{7964}' . + '\x{7965}\x{7966}\x{7967}\x{7968}\x{7969}\x{796A}\x{796B}\x{796D}\x{796F}' . + '\x{7970}\x{7971}\x{7972}\x{7973}\x{7974}\x{7977}\x{7978}\x{7979}\x{797A}' . + '\x{797B}\x{797C}\x{797D}\x{797E}\x{797F}\x{7980}\x{7981}\x{7982}\x{7983}' . + '\x{7984}\x{7985}\x{7988}\x{7989}\x{798A}\x{798B}\x{798C}\x{798D}\x{798E}' . + '\x{798F}\x{7990}\x{7991}\x{7992}\x{7993}\x{7994}\x{7995}\x{7996}\x{7997}' . + '\x{7998}\x{7999}\x{799A}\x{799B}\x{799C}\x{799F}\x{79A0}\x{79A1}\x{79A2}' . + '\x{79A3}\x{79A4}\x{79A5}\x{79A6}\x{79A7}\x{79A8}\x{79AA}\x{79AB}\x{79AC}' . + '\x{79AD}\x{79AE}\x{79AF}\x{79B0}\x{79B1}\x{79B2}\x{79B3}\x{79B4}\x{79B5}' . + '\x{79B6}\x{79B7}\x{79B8}\x{79B9}\x{79BA}\x{79BB}\x{79BD}\x{79BE}\x{79BF}' . + '\x{79C0}\x{79C1}\x{79C2}\x{79C3}\x{79C5}\x{79C6}\x{79C8}\x{79C9}\x{79CA}' . + '\x{79CB}\x{79CD}\x{79CE}\x{79CF}\x{79D0}\x{79D1}\x{79D2}\x{79D3}\x{79D5}' . + '\x{79D6}\x{79D8}\x{79D9}\x{79DA}\x{79DB}\x{79DC}\x{79DD}\x{79DE}\x{79DF}' . + '\x{79E0}\x{79E1}\x{79E2}\x{79E3}\x{79E4}\x{79E5}\x{79E6}\x{79E7}\x{79E8}' . + '\x{79E9}\x{79EA}\x{79EB}\x{79EC}\x{79ED}\x{79EE}\x{79EF}\x{79F0}\x{79F1}' . + '\x{79F2}\x{79F3}\x{79F4}\x{79F5}\x{79F6}\x{79F7}\x{79F8}\x{79F9}\x{79FA}' . + '\x{79FB}\x{79FC}\x{79FD}\x{79FE}\x{79FF}\x{7A00}\x{7A02}\x{7A03}\x{7A04}' . + '\x{7A05}\x{7A06}\x{7A08}\x{7A0A}\x{7A0B}\x{7A0C}\x{7A0D}\x{7A0E}\x{7A0F}' . + '\x{7A10}\x{7A11}\x{7A12}\x{7A13}\x{7A14}\x{7A15}\x{7A16}\x{7A17}\x{7A18}' . + '\x{7A19}\x{7A1A}\x{7A1B}\x{7A1C}\x{7A1D}\x{7A1E}\x{7A1F}\x{7A20}\x{7A21}' . + '\x{7A22}\x{7A23}\x{7A24}\x{7A25}\x{7A26}\x{7A27}\x{7A28}\x{7A29}\x{7A2A}' . + '\x{7A2B}\x{7A2D}\x{7A2E}\x{7A2F}\x{7A30}\x{7A31}\x{7A32}\x{7A33}\x{7A34}' . + '\x{7A35}\x{7A37}\x{7A39}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' . + '\x{7A41}\x{7A42}\x{7A43}\x{7A44}\x{7A45}\x{7A46}\x{7A47}\x{7A48}\x{7A49}' . + '\x{7A4A}\x{7A4B}\x{7A4C}\x{7A4D}\x{7A4E}\x{7A50}\x{7A51}\x{7A52}\x{7A53}' . + '\x{7A54}\x{7A55}\x{7A56}\x{7A57}\x{7A58}\x{7A59}\x{7A5A}\x{7A5B}\x{7A5C}' . + '\x{7A5D}\x{7A5E}\x{7A5F}\x{7A60}\x{7A61}\x{7A62}\x{7A65}\x{7A66}\x{7A67}' . + '\x{7A68}\x{7A69}\x{7A6B}\x{7A6C}\x{7A6D}\x{7A6E}\x{7A70}\x{7A71}\x{7A72}' . + '\x{7A73}\x{7A74}\x{7A75}\x{7A76}\x{7A77}\x{7A78}\x{7A79}\x{7A7A}\x{7A7B}' . + '\x{7A7C}\x{7A7D}\x{7A7E}\x{7A7F}\x{7A80}\x{7A81}\x{7A83}\x{7A84}\x{7A85}' . + '\x{7A86}\x{7A87}\x{7A88}\x{7A89}\x{7A8A}\x{7A8B}\x{7A8C}\x{7A8D}\x{7A8E}' . + '\x{7A8F}\x{7A90}\x{7A91}\x{7A92}\x{7A93}\x{7A94}\x{7A95}\x{7A96}\x{7A97}' . + '\x{7A98}\x{7A99}\x{7A9C}\x{7A9D}\x{7A9E}\x{7A9F}\x{7AA0}\x{7AA1}\x{7AA2}' . + '\x{7AA3}\x{7AA4}\x{7AA5}\x{7AA6}\x{7AA7}\x{7AA8}\x{7AA9}\x{7AAA}\x{7AAB}' . + '\x{7AAC}\x{7AAD}\x{7AAE}\x{7AAF}\x{7AB0}\x{7AB1}\x{7AB2}\x{7AB3}\x{7AB4}' . + '\x{7AB5}\x{7AB6}\x{7AB7}\x{7AB8}\x{7ABA}\x{7ABE}\x{7ABF}\x{7AC0}\x{7AC1}' . + '\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}\x{7AC9}\x{7ACA}\x{7ACB}\x{7ACC}\x{7ACD}' . + '\x{7ACE}\x{7ACF}\x{7AD0}\x{7AD1}\x{7AD2}\x{7AD3}\x{7AD4}\x{7AD5}\x{7AD6}' . + '\x{7AD8}\x{7AD9}\x{7ADB}\x{7ADC}\x{7ADD}\x{7ADE}\x{7ADF}\x{7AE0}\x{7AE1}' . + '\x{7AE2}\x{7AE3}\x{7AE4}\x{7AE5}\x{7AE6}\x{7AE7}\x{7AE8}\x{7AEA}\x{7AEB}' . + '\x{7AEC}\x{7AED}\x{7AEE}\x{7AEF}\x{7AF0}\x{7AF1}\x{7AF2}\x{7AF3}\x{7AF4}' . + '\x{7AF6}\x{7AF7}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFB}\x{7AFD}\x{7AFE}\x{7AFF}' . + '\x{7B00}\x{7B01}\x{7B02}\x{7B03}\x{7B04}\x{7B05}\x{7B06}\x{7B08}\x{7B09}' . + '\x{7B0A}\x{7B0B}\x{7B0C}\x{7B0D}\x{7B0E}\x{7B0F}\x{7B10}\x{7B11}\x{7B12}' . + '\x{7B13}\x{7B14}\x{7B15}\x{7B16}\x{7B17}\x{7B18}\x{7B19}\x{7B1A}\x{7B1B}' . + '\x{7B1C}\x{7B1D}\x{7B1E}\x{7B20}\x{7B21}\x{7B22}\x{7B23}\x{7B24}\x{7B25}' . + '\x{7B26}\x{7B28}\x{7B2A}\x{7B2B}\x{7B2C}\x{7B2D}\x{7B2E}\x{7B2F}\x{7B30}' . + '\x{7B31}\x{7B32}\x{7B33}\x{7B34}\x{7B35}\x{7B36}\x{7B37}\x{7B38}\x{7B39}' . + '\x{7B3A}\x{7B3B}\x{7B3C}\x{7B3D}\x{7B3E}\x{7B3F}\x{7B40}\x{7B41}\x{7B43}' . + '\x{7B44}\x{7B45}\x{7B46}\x{7B47}\x{7B48}\x{7B49}\x{7B4A}\x{7B4B}\x{7B4C}' . + '\x{7B4D}\x{7B4E}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B55}\x{7B56}' . + '\x{7B57}\x{7B58}\x{7B59}\x{7B5A}\x{7B5B}\x{7B5C}\x{7B5D}\x{7B5E}\x{7B5F}' . + '\x{7B60}\x{7B61}\x{7B62}\x{7B63}\x{7B64}\x{7B65}\x{7B66}\x{7B67}\x{7B68}' . + '\x{7B69}\x{7B6A}\x{7B6B}\x{7B6C}\x{7B6D}\x{7B6E}\x{7B70}\x{7B71}\x{7B72}' . + '\x{7B73}\x{7B74}\x{7B75}\x{7B76}\x{7B77}\x{7B78}\x{7B79}\x{7B7B}\x{7B7C}' . + '\x{7B7D}\x{7B7E}\x{7B7F}\x{7B80}\x{7B81}\x{7B82}\x{7B83}\x{7B84}\x{7B85}' . + '\x{7B87}\x{7B88}\x{7B89}\x{7B8A}\x{7B8B}\x{7B8C}\x{7B8D}\x{7B8E}\x{7B8F}' . + '\x{7B90}\x{7B91}\x{7B93}\x{7B94}\x{7B95}\x{7B96}\x{7B97}\x{7B98}\x{7B99}' . + '\x{7B9A}\x{7B9B}\x{7B9C}\x{7B9D}\x{7B9E}\x{7B9F}\x{7BA0}\x{7BA1}\x{7BA2}' . + '\x{7BA4}\x{7BA6}\x{7BA7}\x{7BA8}\x{7BA9}\x{7BAA}\x{7BAB}\x{7BAC}\x{7BAD}' . + '\x{7BAE}\x{7BAF}\x{7BB1}\x{7BB3}\x{7BB4}\x{7BB5}\x{7BB6}\x{7BB7}\x{7BB8}' . + '\x{7BB9}\x{7BBA}\x{7BBB}\x{7BBC}\x{7BBD}\x{7BBE}\x{7BBF}\x{7BC0}\x{7BC1}' . + '\x{7BC2}\x{7BC3}\x{7BC4}\x{7BC5}\x{7BC6}\x{7BC7}\x{7BC8}\x{7BC9}\x{7BCA}' . + '\x{7BCB}\x{7BCC}\x{7BCD}\x{7BCE}\x{7BD0}\x{7BD1}\x{7BD2}\x{7BD3}\x{7BD4}' . + '\x{7BD5}\x{7BD6}\x{7BD7}\x{7BD8}\x{7BD9}\x{7BDA}\x{7BDB}\x{7BDC}\x{7BDD}' . + '\x{7BDE}\x{7BDF}\x{7BE0}\x{7BE1}\x{7BE2}\x{7BE3}\x{7BE4}\x{7BE5}\x{7BE6}' . + '\x{7BE7}\x{7BE8}\x{7BE9}\x{7BEA}\x{7BEB}\x{7BEC}\x{7BED}\x{7BEE}\x{7BEF}' . + '\x{7BF0}\x{7BF1}\x{7BF2}\x{7BF3}\x{7BF4}\x{7BF5}\x{7BF6}\x{7BF7}\x{7BF8}' . + '\x{7BF9}\x{7BFB}\x{7BFC}\x{7BFD}\x{7BFE}\x{7BFF}\x{7C00}\x{7C01}\x{7C02}' . + '\x{7C03}\x{7C04}\x{7C05}\x{7C06}\x{7C07}\x{7C08}\x{7C09}\x{7C0A}\x{7C0B}' . + '\x{7C0C}\x{7C0D}\x{7C0E}\x{7C0F}\x{7C10}\x{7C11}\x{7C12}\x{7C13}\x{7C15}' . + '\x{7C16}\x{7C17}\x{7C18}\x{7C19}\x{7C1A}\x{7C1C}\x{7C1D}\x{7C1E}\x{7C1F}' . + '\x{7C20}\x{7C21}\x{7C22}\x{7C23}\x{7C24}\x{7C25}\x{7C26}\x{7C27}\x{7C28}' . + '\x{7C29}\x{7C2A}\x{7C2B}\x{7C2C}\x{7C2D}\x{7C30}\x{7C31}\x{7C32}\x{7C33}' . + '\x{7C34}\x{7C35}\x{7C36}\x{7C37}\x{7C38}\x{7C39}\x{7C3A}\x{7C3B}\x{7C3C}' . + '\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C41}\x{7C42}\x{7C43}\x{7C44}\x{7C45}' . + '\x{7C46}\x{7C47}\x{7C48}\x{7C49}\x{7C4A}\x{7C4B}\x{7C4C}\x{7C4D}\x{7C4E}' . + '\x{7C50}\x{7C51}\x{7C53}\x{7C54}\x{7C56}\x{7C57}\x{7C58}\x{7C59}\x{7C5A}' . + '\x{7C5B}\x{7C5C}\x{7C5E}\x{7C5F}\x{7C60}\x{7C61}\x{7C62}\x{7C63}\x{7C64}' . + '\x{7C65}\x{7C66}\x{7C67}\x{7C68}\x{7C69}\x{7C6A}\x{7C6B}\x{7C6C}\x{7C6D}' . + '\x{7C6E}\x{7C6F}\x{7C70}\x{7C71}\x{7C72}\x{7C73}\x{7C74}\x{7C75}\x{7C77}' . + '\x{7C78}\x{7C79}\x{7C7A}\x{7C7B}\x{7C7C}\x{7C7D}\x{7C7E}\x{7C7F}\x{7C80}' . + '\x{7C81}\x{7C82}\x{7C84}\x{7C85}\x{7C86}\x{7C88}\x{7C89}\x{7C8A}\x{7C8B}' . + '\x{7C8C}\x{7C8D}\x{7C8E}\x{7C8F}\x{7C90}\x{7C91}\x{7C92}\x{7C94}\x{7C95}' . + '\x{7C96}\x{7C97}\x{7C98}\x{7C99}\x{7C9B}\x{7C9C}\x{7C9D}\x{7C9E}\x{7C9F}' . + '\x{7CA0}\x{7CA1}\x{7CA2}\x{7CA3}\x{7CA4}\x{7CA5}\x{7CA6}\x{7CA7}\x{7CA8}' . + '\x{7CA9}\x{7CAA}\x{7CAD}\x{7CAE}\x{7CAF}\x{7CB0}\x{7CB1}\x{7CB2}\x{7CB3}' . + '\x{7CB4}\x{7CB5}\x{7CB6}\x{7CB7}\x{7CB8}\x{7CB9}\x{7CBA}\x{7CBB}\x{7CBC}' . + '\x{7CBD}\x{7CBE}\x{7CBF}\x{7CC0}\x{7CC1}\x{7CC2}\x{7CC3}\x{7CC4}\x{7CC5}' . + '\x{7CC6}\x{7CC7}\x{7CC8}\x{7CC9}\x{7CCA}\x{7CCB}\x{7CCC}\x{7CCD}\x{7CCE}' . + '\x{7CCF}\x{7CD0}\x{7CD1}\x{7CD2}\x{7CD4}\x{7CD5}\x{7CD6}\x{7CD7}\x{7CD8}' . + '\x{7CD9}\x{7CDC}\x{7CDD}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE4}\x{7CE7}' . + '\x{7CE8}\x{7CE9}\x{7CEA}\x{7CEB}\x{7CEC}\x{7CED}\x{7CEE}\x{7CEF}\x{7CF0}' . + '\x{7CF1}\x{7CF2}\x{7CF3}\x{7CF4}\x{7CF5}\x{7CF6}\x{7CF7}\x{7CF8}\x{7CF9}' . + '\x{7CFA}\x{7CFB}\x{7CFD}\x{7CFE}\x{7D00}\x{7D01}\x{7D02}\x{7D03}\x{7D04}' . + '\x{7D05}\x{7D06}\x{7D07}\x{7D08}\x{7D09}\x{7D0A}\x{7D0B}\x{7D0C}\x{7D0D}' . + '\x{7D0E}\x{7D0F}\x{7D10}\x{7D11}\x{7D12}\x{7D13}\x{7D14}\x{7D15}\x{7D16}' . + '\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D1D}\x{7D1E}\x{7D1F}' . + '\x{7D20}\x{7D21}\x{7D22}\x{7D24}\x{7D25}\x{7D26}\x{7D27}\x{7D28}\x{7D29}' . + '\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D31}\x{7D32}\x{7D33}\x{7D34}' . + '\x{7D35}\x{7D36}\x{7D37}\x{7D38}\x{7D39}\x{7D3A}\x{7D3B}\x{7D3C}\x{7D3D}' . + '\x{7D3E}\x{7D3F}\x{7D40}\x{7D41}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}' . + '\x{7D47}\x{7D49}\x{7D4A}\x{7D4B}\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D51}' . + '\x{7D52}\x{7D53}\x{7D54}\x{7D55}\x{7D56}\x{7D57}\x{7D58}\x{7D59}\x{7D5B}' . + '\x{7D5C}\x{7D5D}\x{7D5E}\x{7D5F}\x{7D60}\x{7D61}\x{7D62}\x{7D63}\x{7D65}' . + '\x{7D66}\x{7D67}\x{7D68}\x{7D69}\x{7D6A}\x{7D6B}\x{7D6C}\x{7D6D}\x{7D6E}' . + '\x{7D6F}\x{7D70}\x{7D71}\x{7D72}\x{7D73}\x{7D74}\x{7D75}\x{7D76}\x{7D77}' . + '\x{7D79}\x{7D7A}\x{7D7B}\x{7D7C}\x{7D7D}\x{7D7E}\x{7D7F}\x{7D80}\x{7D81}' . + '\x{7D83}\x{7D84}\x{7D85}\x{7D86}\x{7D87}\x{7D88}\x{7D89}\x{7D8A}\x{7D8B}' . + '\x{7D8C}\x{7D8D}\x{7D8E}\x{7D8F}\x{7D90}\x{7D91}\x{7D92}\x{7D93}\x{7D94}' . + '\x{7D96}\x{7D97}\x{7D99}\x{7D9B}\x{7D9C}\x{7D9D}\x{7D9E}\x{7D9F}\x{7DA0}' . + '\x{7DA1}\x{7DA2}\x{7DA3}\x{7DA5}\x{7DA6}\x{7DA7}\x{7DA9}\x{7DAA}\x{7DAB}' . + '\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}\x{7DB1}\x{7DB2}\x{7DB3}\x{7DB4}' . + '\x{7DB5}\x{7DB6}\x{7DB7}\x{7DB8}\x{7DB9}\x{7DBA}\x{7DBB}\x{7DBC}\x{7DBD}' . + '\x{7DBE}\x{7DBF}\x{7DC0}\x{7DC1}\x{7DC2}\x{7DC3}\x{7DC4}\x{7DC5}\x{7DC6}' . + '\x{7DC7}\x{7DC8}\x{7DC9}\x{7DCA}\x{7DCB}\x{7DCC}\x{7DCE}\x{7DCF}\x{7DD0}' . + '\x{7DD1}\x{7DD2}\x{7DD4}\x{7DD5}\x{7DD6}\x{7DD7}\x{7DD8}\x{7DD9}\x{7DDA}' . + '\x{7DDB}\x{7DDD}\x{7DDE}\x{7DDF}\x{7DE0}\x{7DE1}\x{7DE2}\x{7DE3}\x{7DE6}' . + '\x{7DE7}\x{7DE8}\x{7DE9}\x{7DEA}\x{7DEC}\x{7DED}\x{7DEE}\x{7DEF}\x{7DF0}' . + '\x{7DF1}\x{7DF2}\x{7DF3}\x{7DF4}\x{7DF5}\x{7DF6}\x{7DF7}\x{7DF8}\x{7DF9}' . + '\x{7DFA}\x{7DFB}\x{7DFC}\x{7E00}\x{7E01}\x{7E02}\x{7E03}\x{7E04}\x{7E05}' . + '\x{7E06}\x{7E07}\x{7E08}\x{7E09}\x{7E0A}\x{7E0B}\x{7E0C}\x{7E0D}\x{7E0E}' . + '\x{7E0F}\x{7E10}\x{7E11}\x{7E12}\x{7E13}\x{7E14}\x{7E15}\x{7E16}\x{7E17}' . + '\x{7E19}\x{7E1A}\x{7E1B}\x{7E1C}\x{7E1D}\x{7E1E}\x{7E1F}\x{7E20}\x{7E21}' . + '\x{7E22}\x{7E23}\x{7E24}\x{7E25}\x{7E26}\x{7E27}\x{7E28}\x{7E29}\x{7E2A}' . + '\x{7E2B}\x{7E2C}\x{7E2D}\x{7E2E}\x{7E2F}\x{7E30}\x{7E31}\x{7E32}\x{7E33}' . + '\x{7E34}\x{7E35}\x{7E36}\x{7E37}\x{7E38}\x{7E39}\x{7E3A}\x{7E3B}\x{7E3C}' . + '\x{7E3D}\x{7E3E}\x{7E3F}\x{7E40}\x{7E41}\x{7E42}\x{7E43}\x{7E44}\x{7E45}' . + '\x{7E46}\x{7E47}\x{7E48}\x{7E49}\x{7E4C}\x{7E4D}\x{7E4E}\x{7E4F}\x{7E50}' . + '\x{7E51}\x{7E52}\x{7E53}\x{7E54}\x{7E55}\x{7E56}\x{7E57}\x{7E58}\x{7E59}' . + '\x{7E5A}\x{7E5C}\x{7E5D}\x{7E5E}\x{7E5F}\x{7E60}\x{7E61}\x{7E62}\x{7E63}' . + '\x{7E65}\x{7E66}\x{7E67}\x{7E68}\x{7E69}\x{7E6A}\x{7E6B}\x{7E6C}\x{7E6D}' . + '\x{7E6E}\x{7E6F}\x{7E70}\x{7E71}\x{7E72}\x{7E73}\x{7E74}\x{7E75}\x{7E76}' . + '\x{7E77}\x{7E78}\x{7E79}\x{7E7A}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7E}\x{7E7F}' . + '\x{7E80}\x{7E81}\x{7E82}\x{7E83}\x{7E84}\x{7E85}\x{7E86}\x{7E87}\x{7E88}' . + '\x{7E89}\x{7E8A}\x{7E8B}\x{7E8C}\x{7E8D}\x{7E8E}\x{7E8F}\x{7E90}\x{7E91}' . + '\x{7E92}\x{7E93}\x{7E94}\x{7E95}\x{7E96}\x{7E97}\x{7E98}\x{7E99}\x{7E9A}' . + '\x{7E9B}\x{7E9C}\x{7E9E}\x{7E9F}\x{7EA0}\x{7EA1}\x{7EA2}\x{7EA3}\x{7EA4}' . + '\x{7EA5}\x{7EA6}\x{7EA7}\x{7EA8}\x{7EA9}\x{7EAA}\x{7EAB}\x{7EAC}\x{7EAD}' . + '\x{7EAE}\x{7EAF}\x{7EB0}\x{7EB1}\x{7EB2}\x{7EB3}\x{7EB4}\x{7EB5}\x{7EB6}' . + '\x{7EB7}\x{7EB8}\x{7EB9}\x{7EBA}\x{7EBB}\x{7EBC}\x{7EBD}\x{7EBE}\x{7EBF}' . + '\x{7EC0}\x{7EC1}\x{7EC2}\x{7EC3}\x{7EC4}\x{7EC5}\x{7EC6}\x{7EC7}\x{7EC8}' . + '\x{7EC9}\x{7ECA}\x{7ECB}\x{7ECC}\x{7ECD}\x{7ECE}\x{7ECF}\x{7ED0}\x{7ED1}' . + '\x{7ED2}\x{7ED3}\x{7ED4}\x{7ED5}\x{7ED6}\x{7ED7}\x{7ED8}\x{7ED9}\x{7EDA}' . + '\x{7EDB}\x{7EDC}\x{7EDD}\x{7EDE}\x{7EDF}\x{7EE0}\x{7EE1}\x{7EE2}\x{7EE3}' . + '\x{7EE4}\x{7EE5}\x{7EE6}\x{7EE7}\x{7EE8}\x{7EE9}\x{7EEA}\x{7EEB}\x{7EEC}' . + '\x{7EED}\x{7EEE}\x{7EEF}\x{7EF0}\x{7EF1}\x{7EF2}\x{7EF3}\x{7EF4}\x{7EF5}' . + '\x{7EF6}\x{7EF7}\x{7EF8}\x{7EF9}\x{7EFA}\x{7EFB}\x{7EFC}\x{7EFD}\x{7EFE}' . + '\x{7EFF}\x{7F00}\x{7F01}\x{7F02}\x{7F03}\x{7F04}\x{7F05}\x{7F06}\x{7F07}' . + '\x{7F08}\x{7F09}\x{7F0A}\x{7F0B}\x{7F0C}\x{7F0D}\x{7F0E}\x{7F0F}\x{7F10}' . + '\x{7F11}\x{7F12}\x{7F13}\x{7F14}\x{7F15}\x{7F16}\x{7F17}\x{7F18}\x{7F19}' . + '\x{7F1A}\x{7F1B}\x{7F1C}\x{7F1D}\x{7F1E}\x{7F1F}\x{7F20}\x{7F21}\x{7F22}' . + '\x{7F23}\x{7F24}\x{7F25}\x{7F26}\x{7F27}\x{7F28}\x{7F29}\x{7F2A}\x{7F2B}' . + '\x{7F2C}\x{7F2D}\x{7F2E}\x{7F2F}\x{7F30}\x{7F31}\x{7F32}\x{7F33}\x{7F34}' . + '\x{7F35}\x{7F36}\x{7F37}\x{7F38}\x{7F39}\x{7F3A}\x{7F3D}\x{7F3E}\x{7F3F}' . + '\x{7F40}\x{7F42}\x{7F43}\x{7F44}\x{7F45}\x{7F47}\x{7F48}\x{7F49}\x{7F4A}' . + '\x{7F4B}\x{7F4C}\x{7F4D}\x{7F4E}\x{7F4F}\x{7F50}\x{7F51}\x{7F52}\x{7F53}' . + '\x{7F54}\x{7F55}\x{7F56}\x{7F57}\x{7F58}\x{7F5A}\x{7F5B}\x{7F5C}\x{7F5D}' . + '\x{7F5E}\x{7F5F}\x{7F60}\x{7F61}\x{7F62}\x{7F63}\x{7F64}\x{7F65}\x{7F66}' . + '\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6C}\x{7F6D}\x{7F6E}\x{7F6F}' . + '\x{7F70}\x{7F71}\x{7F72}\x{7F73}\x{7F74}\x{7F75}\x{7F76}\x{7F77}\x{7F78}' . + '\x{7F79}\x{7F7A}\x{7F7B}\x{7F7C}\x{7F7D}\x{7F7E}\x{7F7F}\x{7F80}\x{7F81}' . + '\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}\x{7F88}\x{7F89}\x{7F8A}\x{7F8B}' . + '\x{7F8C}\x{7F8D}\x{7F8E}\x{7F8F}\x{7F91}\x{7F92}\x{7F93}\x{7F94}\x{7F95}' . + '\x{7F96}\x{7F98}\x{7F9A}\x{7F9B}\x{7F9C}\x{7F9D}\x{7F9E}\x{7F9F}\x{7FA0}' . + '\x{7FA1}\x{7FA2}\x{7FA3}\x{7FA4}\x{7FA5}\x{7FA6}\x{7FA7}\x{7FA8}\x{7FA9}' . + '\x{7FAA}\x{7FAB}\x{7FAC}\x{7FAD}\x{7FAE}\x{7FAF}\x{7FB0}\x{7FB1}\x{7FB2}' . + '\x{7FB3}\x{7FB5}\x{7FB6}\x{7FB7}\x{7FB8}\x{7FB9}\x{7FBA}\x{7FBB}\x{7FBC}' . + '\x{7FBD}\x{7FBE}\x{7FBF}\x{7FC0}\x{7FC1}\x{7FC2}\x{7FC3}\x{7FC4}\x{7FC5}' . + '\x{7FC6}\x{7FC7}\x{7FC8}\x{7FC9}\x{7FCA}\x{7FCB}\x{7FCC}\x{7FCD}\x{7FCE}' . + '\x{7FCF}\x{7FD0}\x{7FD1}\x{7FD2}\x{7FD3}\x{7FD4}\x{7FD5}\x{7FD7}\x{7FD8}' . + '\x{7FD9}\x{7FDA}\x{7FDB}\x{7FDC}\x{7FDE}\x{7FDF}\x{7FE0}\x{7FE1}\x{7FE2}' . + '\x{7FE3}\x{7FE5}\x{7FE6}\x{7FE7}\x{7FE8}\x{7FE9}\x{7FEA}\x{7FEB}\x{7FEC}' . + '\x{7FED}\x{7FEE}\x{7FEF}\x{7FF0}\x{7FF1}\x{7FF2}\x{7FF3}\x{7FF4}\x{7FF5}' . + '\x{7FF6}\x{7FF7}\x{7FF8}\x{7FF9}\x{7FFA}\x{7FFB}\x{7FFC}\x{7FFD}\x{7FFE}' . + '\x{7FFF}\x{8000}\x{8001}\x{8002}\x{8003}\x{8004}\x{8005}\x{8006}\x{8007}' . + '\x{8008}\x{8009}\x{800B}\x{800C}\x{800D}\x{800E}\x{800F}\x{8010}\x{8011}' . + '\x{8012}\x{8013}\x{8014}\x{8015}\x{8016}\x{8017}\x{8018}\x{8019}\x{801A}' . + '\x{801B}\x{801C}\x{801D}\x{801E}\x{801F}\x{8020}\x{8021}\x{8022}\x{8023}' . + '\x{8024}\x{8025}\x{8026}\x{8027}\x{8028}\x{8029}\x{802A}\x{802B}\x{802C}' . + '\x{802D}\x{802E}\x{8030}\x{8031}\x{8032}\x{8033}\x{8034}\x{8035}\x{8036}' . + '\x{8037}\x{8038}\x{8039}\x{803A}\x{803B}\x{803D}\x{803E}\x{803F}\x{8041}' . + '\x{8042}\x{8043}\x{8044}\x{8045}\x{8046}\x{8047}\x{8048}\x{8049}\x{804A}' . + '\x{804B}\x{804C}\x{804D}\x{804E}\x{804F}\x{8050}\x{8051}\x{8052}\x{8053}' . + '\x{8054}\x{8055}\x{8056}\x{8057}\x{8058}\x{8059}\x{805A}\x{805B}\x{805C}' . + '\x{805D}\x{805E}\x{805F}\x{8060}\x{8061}\x{8062}\x{8063}\x{8064}\x{8065}' . + '\x{8067}\x{8068}\x{8069}\x{806A}\x{806B}\x{806C}\x{806D}\x{806E}\x{806F}' . + '\x{8070}\x{8071}\x{8072}\x{8073}\x{8074}\x{8075}\x{8076}\x{8077}\x{8078}' . + '\x{8079}\x{807A}\x{807B}\x{807C}\x{807D}\x{807E}\x{807F}\x{8080}\x{8081}' . + '\x{8082}\x{8083}\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808A}\x{808B}' . + '\x{808C}\x{808D}\x{808F}\x{8090}\x{8091}\x{8092}\x{8093}\x{8095}\x{8096}' . + '\x{8097}\x{8098}\x{8099}\x{809A}\x{809B}\x{809C}\x{809D}\x{809E}\x{809F}' . + '\x{80A0}\x{80A1}\x{80A2}\x{80A3}\x{80A4}\x{80A5}\x{80A9}\x{80AA}\x{80AB}' . + '\x{80AD}\x{80AE}\x{80AF}\x{80B0}\x{80B1}\x{80B2}\x{80B4}\x{80B5}\x{80B6}' . + '\x{80B7}\x{80B8}\x{80BA}\x{80BB}\x{80BC}\x{80BD}\x{80BE}\x{80BF}\x{80C0}' . + '\x{80C1}\x{80C2}\x{80C3}\x{80C4}\x{80C5}\x{80C6}\x{80C7}\x{80C8}\x{80C9}' . + '\x{80CA}\x{80CB}\x{80CC}\x{80CD}\x{80CE}\x{80CF}\x{80D0}\x{80D1}\x{80D2}' . + '\x{80D3}\x{80D4}\x{80D5}\x{80D6}\x{80D7}\x{80D8}\x{80D9}\x{80DA}\x{80DB}' . + '\x{80DC}\x{80DD}\x{80DE}\x{80E0}\x{80E1}\x{80E2}\x{80E3}\x{80E4}\x{80E5}' . + '\x{80E6}\x{80E7}\x{80E8}\x{80E9}\x{80EA}\x{80EB}\x{80EC}\x{80ED}\x{80EE}' . + '\x{80EF}\x{80F0}\x{80F1}\x{80F2}\x{80F3}\x{80F4}\x{80F5}\x{80F6}\x{80F7}' . + '\x{80F8}\x{80F9}\x{80FA}\x{80FB}\x{80FC}\x{80FD}\x{80FE}\x{80FF}\x{8100}' . + '\x{8101}\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{810B}' . + '\x{810C}\x{810D}\x{810E}\x{810F}\x{8110}\x{8111}\x{8112}\x{8113}\x{8114}' . + '\x{8115}\x{8116}\x{8118}\x{8119}\x{811A}\x{811B}\x{811C}\x{811D}\x{811E}' . + '\x{811F}\x{8120}\x{8121}\x{8122}\x{8123}\x{8124}\x{8125}\x{8126}\x{8127}' . + '\x{8128}\x{8129}\x{812A}\x{812B}\x{812C}\x{812D}\x{812E}\x{812F}\x{8130}' . + '\x{8131}\x{8132}\x{8136}\x{8137}\x{8138}\x{8139}\x{813A}\x{813B}\x{813C}' . + '\x{813D}\x{813E}\x{813F}\x{8140}\x{8141}\x{8142}\x{8143}\x{8144}\x{8145}' . + '\x{8146}\x{8147}\x{8148}\x{8149}\x{814A}\x{814B}\x{814C}\x{814D}\x{814E}' . + '\x{814F}\x{8150}\x{8151}\x{8152}\x{8153}\x{8154}\x{8155}\x{8156}\x{8157}' . + '\x{8158}\x{8159}\x{815A}\x{815B}\x{815C}\x{815D}\x{815E}\x{8160}\x{8161}' . + '\x{8162}\x{8163}\x{8164}\x{8165}\x{8166}\x{8167}\x{8168}\x{8169}\x{816A}' . + '\x{816B}\x{816C}\x{816D}\x{816E}\x{816F}\x{8170}\x{8171}\x{8172}\x{8173}' . + '\x{8174}\x{8175}\x{8176}\x{8177}\x{8178}\x{8179}\x{817A}\x{817B}\x{817C}' . + '\x{817D}\x{817E}\x{817F}\x{8180}\x{8181}\x{8182}\x{8183}\x{8185}\x{8186}' . + '\x{8187}\x{8188}\x{8189}\x{818A}\x{818B}\x{818C}\x{818D}\x{818E}\x{818F}' . + '\x{8191}\x{8192}\x{8193}\x{8194}\x{8195}\x{8197}\x{8198}\x{8199}\x{819A}' . + '\x{819B}\x{819C}\x{819D}\x{819E}\x{819F}\x{81A0}\x{81A1}\x{81A2}\x{81A3}' . + '\x{81A4}\x{81A5}\x{81A6}\x{81A7}\x{81A8}\x{81A9}\x{81AA}\x{81AB}\x{81AC}' . + '\x{81AD}\x{81AE}\x{81AF}\x{81B0}\x{81B1}\x{81B2}\x{81B3}\x{81B4}\x{81B5}' . + '\x{81B6}\x{81B7}\x{81B8}\x{81B9}\x{81BA}\x{81BB}\x{81BC}\x{81BD}\x{81BE}' . + '\x{81BF}\x{81C0}\x{81C1}\x{81C2}\x{81C3}\x{81C4}\x{81C5}\x{81C6}\x{81C7}' . + '\x{81C8}\x{81C9}\x{81CA}\x{81CC}\x{81CD}\x{81CE}\x{81CF}\x{81D0}\x{81D1}' . + '\x{81D2}\x{81D4}\x{81D5}\x{81D6}\x{81D7}\x{81D8}\x{81D9}\x{81DA}\x{81DB}' . + '\x{81DC}\x{81DD}\x{81DE}\x{81DF}\x{81E0}\x{81E1}\x{81E2}\x{81E3}\x{81E5}' . + '\x{81E6}\x{81E7}\x{81E8}\x{81E9}\x{81EA}\x{81EB}\x{81EC}\x{81ED}\x{81EE}' . + '\x{81F1}\x{81F2}\x{81F3}\x{81F4}\x{81F5}\x{81F6}\x{81F7}\x{81F8}\x{81F9}' . + '\x{81FA}\x{81FB}\x{81FC}\x{81FD}\x{81FE}\x{81FF}\x{8200}\x{8201}\x{8202}' . + '\x{8203}\x{8204}\x{8205}\x{8206}\x{8207}\x{8208}\x{8209}\x{820A}\x{820B}' . + '\x{820C}\x{820D}\x{820E}\x{820F}\x{8210}\x{8211}\x{8212}\x{8214}\x{8215}' . + '\x{8216}\x{8218}\x{8219}\x{821A}\x{821B}\x{821C}\x{821D}\x{821E}\x{821F}' . + '\x{8220}\x{8221}\x{8222}\x{8223}\x{8225}\x{8226}\x{8227}\x{8228}\x{8229}' . + '\x{822A}\x{822B}\x{822C}\x{822D}\x{822F}\x{8230}\x{8231}\x{8232}\x{8233}' . + '\x{8234}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{823A}\x{823B}\x{823C}' . + '\x{823D}\x{823E}\x{823F}\x{8240}\x{8242}\x{8243}\x{8244}\x{8245}\x{8246}' . + '\x{8247}\x{8248}\x{8249}\x{824A}\x{824B}\x{824C}\x{824D}\x{824E}\x{824F}' . + '\x{8250}\x{8251}\x{8252}\x{8253}\x{8254}\x{8255}\x{8256}\x{8257}\x{8258}' . + '\x{8259}\x{825A}\x{825B}\x{825C}\x{825D}\x{825E}\x{825F}\x{8260}\x{8261}' . + '\x{8263}\x{8264}\x{8266}\x{8267}\x{8268}\x{8269}\x{826A}\x{826B}\x{826C}' . + '\x{826D}\x{826E}\x{826F}\x{8270}\x{8271}\x{8272}\x{8273}\x{8274}\x{8275}' . + '\x{8276}\x{8277}\x{8278}\x{8279}\x{827A}\x{827B}\x{827C}\x{827D}\x{827E}' . + '\x{827F}\x{8280}\x{8281}\x{8282}\x{8283}\x{8284}\x{8285}\x{8286}\x{8287}' . + '\x{8288}\x{8289}\x{828A}\x{828B}\x{828D}\x{828E}\x{828F}\x{8290}\x{8291}' . + '\x{8292}\x{8293}\x{8294}\x{8295}\x{8296}\x{8297}\x{8298}\x{8299}\x{829A}' . + '\x{829B}\x{829C}\x{829D}\x{829E}\x{829F}\x{82A0}\x{82A1}\x{82A2}\x{82A3}' . + '\x{82A4}\x{82A5}\x{82A6}\x{82A7}\x{82A8}\x{82A9}\x{82AA}\x{82AB}\x{82AC}' . + '\x{82AD}\x{82AE}\x{82AF}\x{82B0}\x{82B1}\x{82B3}\x{82B4}\x{82B5}\x{82B6}' . + '\x{82B7}\x{82B8}\x{82B9}\x{82BA}\x{82BB}\x{82BC}\x{82BD}\x{82BE}\x{82BF}' . + '\x{82C0}\x{82C1}\x{82C2}\x{82C3}\x{82C4}\x{82C5}\x{82C6}\x{82C7}\x{82C8}' . + '\x{82C9}\x{82CA}\x{82CB}\x{82CC}\x{82CD}\x{82CE}\x{82CF}\x{82D0}\x{82D1}' . + '\x{82D2}\x{82D3}\x{82D4}\x{82D5}\x{82D6}\x{82D7}\x{82D8}\x{82D9}\x{82DA}' . + '\x{82DB}\x{82DC}\x{82DD}\x{82DE}\x{82DF}\x{82E0}\x{82E1}\x{82E3}\x{82E4}' . + '\x{82E5}\x{82E6}\x{82E7}\x{82E8}\x{82E9}\x{82EA}\x{82EB}\x{82EC}\x{82ED}' . + '\x{82EE}\x{82EF}\x{82F0}\x{82F1}\x{82F2}\x{82F3}\x{82F4}\x{82F5}\x{82F6}' . + '\x{82F7}\x{82F8}\x{82F9}\x{82FA}\x{82FB}\x{82FD}\x{82FE}\x{82FF}\x{8300}' . + '\x{8301}\x{8302}\x{8303}\x{8304}\x{8305}\x{8306}\x{8307}\x{8308}\x{8309}' . + '\x{830B}\x{830C}\x{830D}\x{830E}\x{830F}\x{8311}\x{8312}\x{8313}\x{8314}' . + '\x{8315}\x{8316}\x{8317}\x{8318}\x{8319}\x{831A}\x{831B}\x{831C}\x{831D}' . + '\x{831E}\x{831F}\x{8320}\x{8321}\x{8322}\x{8323}\x{8324}\x{8325}\x{8326}' . + '\x{8327}\x{8328}\x{8329}\x{832A}\x{832B}\x{832C}\x{832D}\x{832E}\x{832F}' . + '\x{8331}\x{8332}\x{8333}\x{8334}\x{8335}\x{8336}\x{8337}\x{8338}\x{8339}' . + '\x{833A}\x{833B}\x{833C}\x{833D}\x{833E}\x{833F}\x{8340}\x{8341}\x{8342}' . + '\x{8343}\x{8344}\x{8345}\x{8346}\x{8347}\x{8348}\x{8349}\x{834A}\x{834B}' . + '\x{834C}\x{834D}\x{834E}\x{834F}\x{8350}\x{8351}\x{8352}\x{8353}\x{8354}' . + '\x{8356}\x{8357}\x{8358}\x{8359}\x{835A}\x{835B}\x{835C}\x{835D}\x{835E}' . + '\x{835F}\x{8360}\x{8361}\x{8362}\x{8363}\x{8364}\x{8365}\x{8366}\x{8367}' . + '\x{8368}\x{8369}\x{836A}\x{836B}\x{836C}\x{836D}\x{836E}\x{836F}\x{8370}' . + '\x{8371}\x{8372}\x{8373}\x{8374}\x{8375}\x{8376}\x{8377}\x{8378}\x{8379}' . + '\x{837A}\x{837B}\x{837C}\x{837D}\x{837E}\x{837F}\x{8380}\x{8381}\x{8382}' . + '\x{8383}\x{8384}\x{8385}\x{8386}\x{8387}\x{8388}\x{8389}\x{838A}\x{838B}' . + '\x{838C}\x{838D}\x{838E}\x{838F}\x{8390}\x{8391}\x{8392}\x{8393}\x{8394}' . + '\x{8395}\x{8396}\x{8397}\x{8398}\x{8399}\x{839A}\x{839B}\x{839C}\x{839D}' . + '\x{839E}\x{83A0}\x{83A1}\x{83A2}\x{83A3}\x{83A4}\x{83A5}\x{83A6}\x{83A7}' . + '\x{83A8}\x{83A9}\x{83AA}\x{83AB}\x{83AC}\x{83AD}\x{83AE}\x{83AF}\x{83B0}' . + '\x{83B1}\x{83B2}\x{83B3}\x{83B4}\x{83B6}\x{83B7}\x{83B8}\x{83B9}\x{83BA}' . + '\x{83BB}\x{83BC}\x{83BD}\x{83BF}\x{83C0}\x{83C1}\x{83C2}\x{83C3}\x{83C4}' . + '\x{83C5}\x{83C6}\x{83C7}\x{83C8}\x{83C9}\x{83CA}\x{83CB}\x{83CC}\x{83CD}' . + '\x{83CE}\x{83CF}\x{83D0}\x{83D1}\x{83D2}\x{83D3}\x{83D4}\x{83D5}\x{83D6}' . + '\x{83D7}\x{83D8}\x{83D9}\x{83DA}\x{83DB}\x{83DC}\x{83DD}\x{83DE}\x{83DF}' . + '\x{83E0}\x{83E1}\x{83E2}\x{83E3}\x{83E4}\x{83E5}\x{83E7}\x{83E8}\x{83E9}' . + '\x{83EA}\x{83EB}\x{83EC}\x{83EE}\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F3}' . + '\x{83F4}\x{83F5}\x{83F6}\x{83F7}\x{83F8}\x{83F9}\x{83FA}\x{83FB}\x{83FC}' . + '\x{83FD}\x{83FE}\x{83FF}\x{8400}\x{8401}\x{8402}\x{8403}\x{8404}\x{8405}' . + '\x{8406}\x{8407}\x{8408}\x{8409}\x{840A}\x{840B}\x{840C}\x{840D}\x{840E}' . + '\x{840F}\x{8410}\x{8411}\x{8412}\x{8413}\x{8415}\x{8418}\x{8419}\x{841A}' . + '\x{841B}\x{841C}\x{841D}\x{841E}\x{8421}\x{8422}\x{8423}\x{8424}\x{8425}' . + '\x{8426}\x{8427}\x{8428}\x{8429}\x{842A}\x{842B}\x{842C}\x{842D}\x{842E}' . + '\x{842F}\x{8430}\x{8431}\x{8432}\x{8433}\x{8434}\x{8435}\x{8436}\x{8437}' . + '\x{8438}\x{8439}\x{843A}\x{843B}\x{843C}\x{843D}\x{843E}\x{843F}\x{8440}' . + '\x{8441}\x{8442}\x{8443}\x{8444}\x{8445}\x{8446}\x{8447}\x{8448}\x{8449}' . + '\x{844A}\x{844B}\x{844C}\x{844D}\x{844E}\x{844F}\x{8450}\x{8451}\x{8452}' . + '\x{8453}\x{8454}\x{8455}\x{8456}\x{8457}\x{8459}\x{845A}\x{845B}\x{845C}' . + '\x{845D}\x{845E}\x{845F}\x{8460}\x{8461}\x{8462}\x{8463}\x{8464}\x{8465}' . + '\x{8466}\x{8467}\x{8468}\x{8469}\x{846A}\x{846B}\x{846C}\x{846D}\x{846E}' . + '\x{846F}\x{8470}\x{8471}\x{8472}\x{8473}\x{8474}\x{8475}\x{8476}\x{8477}' . + '\x{8478}\x{8479}\x{847A}\x{847B}\x{847C}\x{847D}\x{847E}\x{847F}\x{8480}' . + '\x{8481}\x{8482}\x{8484}\x{8485}\x{8486}\x{8487}\x{8488}\x{8489}\x{848A}' . + '\x{848B}\x{848C}\x{848D}\x{848E}\x{848F}\x{8490}\x{8491}\x{8492}\x{8493}' . + '\x{8494}\x{8496}\x{8497}\x{8498}\x{8499}\x{849A}\x{849B}\x{849C}\x{849D}' . + '\x{849E}\x{849F}\x{84A0}\x{84A1}\x{84A2}\x{84A3}\x{84A4}\x{84A5}\x{84A6}' . + '\x{84A7}\x{84A8}\x{84A9}\x{84AA}\x{84AB}\x{84AC}\x{84AE}\x{84AF}\x{84B0}' . + '\x{84B1}\x{84B2}\x{84B3}\x{84B4}\x{84B5}\x{84B6}\x{84B8}\x{84B9}\x{84BA}' . + '\x{84BB}\x{84BC}\x{84BD}\x{84BE}\x{84BF}\x{84C0}\x{84C1}\x{84C2}\x{84C4}' . + '\x{84C5}\x{84C6}\x{84C7}\x{84C8}\x{84C9}\x{84CA}\x{84CB}\x{84CC}\x{84CD}' . + '\x{84CE}\x{84CF}\x{84D0}\x{84D1}\x{84D2}\x{84D3}\x{84D4}\x{84D5}\x{84D6}' . + '\x{84D7}\x{84D8}\x{84D9}\x{84DB}\x{84DC}\x{84DD}\x{84DE}\x{84DF}\x{84E0}' . + '\x{84E1}\x{84E2}\x{84E3}\x{84E4}\x{84E5}\x{84E6}\x{84E7}\x{84E8}\x{84E9}' . + '\x{84EA}\x{84EB}\x{84EC}\x{84EE}\x{84EF}\x{84F0}\x{84F1}\x{84F2}\x{84F3}' . + '\x{84F4}\x{84F5}\x{84F6}\x{84F7}\x{84F8}\x{84F9}\x{84FA}\x{84FB}\x{84FC}' . + '\x{84FD}\x{84FE}\x{84FF}\x{8500}\x{8501}\x{8502}\x{8503}\x{8504}\x{8506}' . + '\x{8507}\x{8508}\x{8509}\x{850A}\x{850B}\x{850C}\x{850D}\x{850E}\x{850F}' . + '\x{8511}\x{8512}\x{8513}\x{8514}\x{8515}\x{8516}\x{8517}\x{8518}\x{8519}' . + '\x{851A}\x{851B}\x{851C}\x{851D}\x{851E}\x{851F}\x{8520}\x{8521}\x{8522}' . + '\x{8523}\x{8524}\x{8525}\x{8526}\x{8527}\x{8528}\x{8529}\x{852A}\x{852B}' . + '\x{852C}\x{852D}\x{852E}\x{852F}\x{8530}\x{8531}\x{8534}\x{8535}\x{8536}' . + '\x{8537}\x{8538}\x{8539}\x{853A}\x{853B}\x{853C}\x{853D}\x{853E}\x{853F}' . + '\x{8540}\x{8541}\x{8542}\x{8543}\x{8544}\x{8545}\x{8546}\x{8547}\x{8548}' . + '\x{8549}\x{854A}\x{854B}\x{854D}\x{854E}\x{854F}\x{8551}\x{8552}\x{8553}' . + '\x{8554}\x{8555}\x{8556}\x{8557}\x{8558}\x{8559}\x{855A}\x{855B}\x{855C}' . + '\x{855D}\x{855E}\x{855F}\x{8560}\x{8561}\x{8562}\x{8563}\x{8564}\x{8565}' . + '\x{8566}\x{8567}\x{8568}\x{8569}\x{856A}\x{856B}\x{856C}\x{856D}\x{856E}' . + '\x{856F}\x{8570}\x{8571}\x{8572}\x{8573}\x{8574}\x{8575}\x{8576}\x{8577}' . + '\x{8578}\x{8579}\x{857A}\x{857B}\x{857C}\x{857D}\x{857E}\x{8580}\x{8581}' . + '\x{8582}\x{8583}\x{8584}\x{8585}\x{8586}\x{8587}\x{8588}\x{8589}\x{858A}' . + '\x{858B}\x{858C}\x{858D}\x{858E}\x{858F}\x{8590}\x{8591}\x{8592}\x{8594}' . + '\x{8595}\x{8596}\x{8598}\x{8599}\x{859A}\x{859B}\x{859C}\x{859D}\x{859E}' . + '\x{859F}\x{85A0}\x{85A1}\x{85A2}\x{85A3}\x{85A4}\x{85A5}\x{85A6}\x{85A7}' . + '\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AD}\x{85AE}\x{85AF}\x{85B0}' . + '\x{85B1}\x{85B3}\x{85B4}\x{85B5}\x{85B6}\x{85B7}\x{85B8}\x{85B9}\x{85BA}' . + '\x{85BC}\x{85BD}\x{85BE}\x{85BF}\x{85C0}\x{85C1}\x{85C2}\x{85C3}\x{85C4}' . + '\x{85C5}\x{85C6}\x{85C7}\x{85C8}\x{85C9}\x{85CA}\x{85CB}\x{85CD}\x{85CE}' . + '\x{85CF}\x{85D0}\x{85D1}\x{85D2}\x{85D3}\x{85D4}\x{85D5}\x{85D6}\x{85D7}' . + '\x{85D8}\x{85D9}\x{85DA}\x{85DB}\x{85DC}\x{85DD}\x{85DE}\x{85DF}\x{85E0}' . + '\x{85E1}\x{85E2}\x{85E3}\x{85E4}\x{85E5}\x{85E6}\x{85E7}\x{85E8}\x{85E9}' . + '\x{85EA}\x{85EB}\x{85EC}\x{85ED}\x{85EF}\x{85F0}\x{85F1}\x{85F2}\x{85F4}' . + '\x{85F5}\x{85F6}\x{85F7}\x{85F8}\x{85F9}\x{85FA}\x{85FB}\x{85FD}\x{85FE}' . + '\x{85FF}\x{8600}\x{8601}\x{8602}\x{8604}\x{8605}\x{8606}\x{8607}\x{8608}' . + '\x{8609}\x{860A}\x{860B}\x{860C}\x{860F}\x{8611}\x{8612}\x{8613}\x{8614}' . + '\x{8616}\x{8617}\x{8618}\x{8619}\x{861A}\x{861B}\x{861C}\x{861E}\x{861F}' . + '\x{8620}\x{8621}\x{8622}\x{8623}\x{8624}\x{8625}\x{8626}\x{8627}\x{8628}' . + '\x{8629}\x{862A}\x{862B}\x{862C}\x{862D}\x{862E}\x{862F}\x{8630}\x{8631}' . + '\x{8632}\x{8633}\x{8634}\x{8635}\x{8636}\x{8638}\x{8639}\x{863A}\x{863B}' . + '\x{863C}\x{863D}\x{863E}\x{863F}\x{8640}\x{8641}\x{8642}\x{8643}\x{8644}' . + '\x{8645}\x{8646}\x{8647}\x{8648}\x{8649}\x{864A}\x{864B}\x{864C}\x{864D}' . + '\x{864E}\x{864F}\x{8650}\x{8651}\x{8652}\x{8653}\x{8654}\x{8655}\x{8656}' . + '\x{8658}\x{8659}\x{865A}\x{865B}\x{865C}\x{865D}\x{865E}\x{865F}\x{8660}' . + '\x{8661}\x{8662}\x{8663}\x{8664}\x{8665}\x{8666}\x{8667}\x{8668}\x{8669}' . + '\x{866A}\x{866B}\x{866C}\x{866D}\x{866E}\x{866F}\x{8670}\x{8671}\x{8672}' . + '\x{8673}\x{8674}\x{8676}\x{8677}\x{8678}\x{8679}\x{867A}\x{867B}\x{867C}' . + '\x{867D}\x{867E}\x{867F}\x{8680}\x{8681}\x{8682}\x{8683}\x{8684}\x{8685}' . + '\x{8686}\x{8687}\x{8688}\x{868A}\x{868B}\x{868C}\x{868D}\x{868E}\x{868F}' . + '\x{8690}\x{8691}\x{8693}\x{8694}\x{8695}\x{8696}\x{8697}\x{8698}\x{8699}' . + '\x{869A}\x{869B}\x{869C}\x{869D}\x{869E}\x{869F}\x{86A1}\x{86A2}\x{86A3}' . + '\x{86A4}\x{86A5}\x{86A7}\x{86A8}\x{86A9}\x{86AA}\x{86AB}\x{86AC}\x{86AD}' . + '\x{86AE}\x{86AF}\x{86B0}\x{86B1}\x{86B2}\x{86B3}\x{86B4}\x{86B5}\x{86B6}' . + '\x{86B7}\x{86B8}\x{86B9}\x{86BA}\x{86BB}\x{86BC}\x{86BD}\x{86BE}\x{86BF}' . + '\x{86C0}\x{86C1}\x{86C2}\x{86C3}\x{86C4}\x{86C5}\x{86C6}\x{86C7}\x{86C8}' . + '\x{86C9}\x{86CA}\x{86CB}\x{86CC}\x{86CE}\x{86CF}\x{86D0}\x{86D1}\x{86D2}' . + '\x{86D3}\x{86D4}\x{86D6}\x{86D7}\x{86D8}\x{86D9}\x{86DA}\x{86DB}\x{86DC}' . + '\x{86DD}\x{86DE}\x{86DF}\x{86E1}\x{86E2}\x{86E3}\x{86E4}\x{86E5}\x{86E6}' . + '\x{86E8}\x{86E9}\x{86EA}\x{86EB}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F0}' . + '\x{86F1}\x{86F2}\x{86F3}\x{86F4}\x{86F5}\x{86F6}\x{86F7}\x{86F8}\x{86F9}' . + '\x{86FA}\x{86FB}\x{86FC}\x{86FE}\x{86FF}\x{8700}\x{8701}\x{8702}\x{8703}' . + '\x{8704}\x{8705}\x{8706}\x{8707}\x{8708}\x{8709}\x{870A}\x{870B}\x{870C}' . + '\x{870D}\x{870E}\x{870F}\x{8710}\x{8711}\x{8712}\x{8713}\x{8714}\x{8715}' . + '\x{8716}\x{8717}\x{8718}\x{8719}\x{871A}\x{871B}\x{871C}\x{871E}\x{871F}' . + '\x{8720}\x{8721}\x{8722}\x{8723}\x{8724}\x{8725}\x{8726}\x{8727}\x{8728}' . + '\x{8729}\x{872A}\x{872B}\x{872C}\x{872D}\x{872E}\x{8730}\x{8731}\x{8732}' . + '\x{8733}\x{8734}\x{8735}\x{8736}\x{8737}\x{8738}\x{8739}\x{873A}\x{873B}' . + '\x{873C}\x{873E}\x{873F}\x{8740}\x{8741}\x{8742}\x{8743}\x{8744}\x{8746}' . + '\x{8747}\x{8748}\x{8749}\x{874A}\x{874C}\x{874D}\x{874E}\x{874F}\x{8750}' . + '\x{8751}\x{8752}\x{8753}\x{8754}\x{8755}\x{8756}\x{8757}\x{8758}\x{8759}' . + '\x{875A}\x{875B}\x{875C}\x{875D}\x{875E}\x{875F}\x{8760}\x{8761}\x{8762}' . + '\x{8763}\x{8764}\x{8765}\x{8766}\x{8767}\x{8768}\x{8769}\x{876A}\x{876B}' . + '\x{876C}\x{876D}\x{876E}\x{876F}\x{8770}\x{8772}\x{8773}\x{8774}\x{8775}' . + '\x{8776}\x{8777}\x{8778}\x{8779}\x{877A}\x{877B}\x{877C}\x{877D}\x{877E}' . + '\x{8780}\x{8781}\x{8782}\x{8783}\x{8784}\x{8785}\x{8786}\x{8787}\x{8788}' . + '\x{8789}\x{878A}\x{878B}\x{878C}\x{878D}\x{878F}\x{8790}\x{8791}\x{8792}' . + '\x{8793}\x{8794}\x{8795}\x{8796}\x{8797}\x{8798}\x{879A}\x{879B}\x{879C}' . + '\x{879D}\x{879E}\x{879F}\x{87A0}\x{87A1}\x{87A2}\x{87A3}\x{87A4}\x{87A5}' . + '\x{87A6}\x{87A7}\x{87A8}\x{87A9}\x{87AA}\x{87AB}\x{87AC}\x{87AD}\x{87AE}' . + '\x{87AF}\x{87B0}\x{87B1}\x{87B2}\x{87B3}\x{87B4}\x{87B5}\x{87B6}\x{87B7}' . + '\x{87B8}\x{87B9}\x{87BA}\x{87BB}\x{87BC}\x{87BD}\x{87BE}\x{87BF}\x{87C0}' . + '\x{87C1}\x{87C2}\x{87C3}\x{87C4}\x{87C5}\x{87C6}\x{87C7}\x{87C8}\x{87C9}' . + '\x{87CA}\x{87CB}\x{87CC}\x{87CD}\x{87CE}\x{87CF}\x{87D0}\x{87D1}\x{87D2}' . + '\x{87D3}\x{87D4}\x{87D5}\x{87D6}\x{87D7}\x{87D8}\x{87D9}\x{87DB}\x{87DC}' . + '\x{87DD}\x{87DE}\x{87DF}\x{87E0}\x{87E1}\x{87E2}\x{87E3}\x{87E4}\x{87E5}' . + '\x{87E6}\x{87E7}\x{87E8}\x{87E9}\x{87EA}\x{87EB}\x{87EC}\x{87ED}\x{87EE}' . + '\x{87EF}\x{87F1}\x{87F2}\x{87F3}\x{87F4}\x{87F5}\x{87F6}\x{87F7}\x{87F8}' . + '\x{87F9}\x{87FA}\x{87FB}\x{87FC}\x{87FD}\x{87FE}\x{87FF}\x{8800}\x{8801}' . + '\x{8802}\x{8803}\x{8804}\x{8805}\x{8806}\x{8808}\x{8809}\x{880A}\x{880B}' . + '\x{880C}\x{880D}\x{880E}\x{880F}\x{8810}\x{8811}\x{8813}\x{8814}\x{8815}' . + '\x{8816}\x{8817}\x{8818}\x{8819}\x{881A}\x{881B}\x{881C}\x{881D}\x{881E}' . + '\x{881F}\x{8820}\x{8821}\x{8822}\x{8823}\x{8824}\x{8825}\x{8826}\x{8827}' . + '\x{8828}\x{8829}\x{882A}\x{882B}\x{882C}\x{882E}\x{882F}\x{8830}\x{8831}' . + '\x{8832}\x{8833}\x{8834}\x{8835}\x{8836}\x{8837}\x{8838}\x{8839}\x{883B}' . + '\x{883C}\x{883D}\x{883E}\x{883F}\x{8840}\x{8841}\x{8842}\x{8843}\x{8844}' . + '\x{8845}\x{8846}\x{8848}\x{8849}\x{884A}\x{884B}\x{884C}\x{884D}\x{884E}' . + '\x{884F}\x{8850}\x{8851}\x{8852}\x{8853}\x{8854}\x{8855}\x{8856}\x{8857}' . + '\x{8859}\x{885A}\x{885B}\x{885D}\x{885E}\x{8860}\x{8861}\x{8862}\x{8863}' . + '\x{8864}\x{8865}\x{8866}\x{8867}\x{8868}\x{8869}\x{886A}\x{886B}\x{886C}' . + '\x{886D}\x{886E}\x{886F}\x{8870}\x{8871}\x{8872}\x{8873}\x{8874}\x{8875}' . + '\x{8876}\x{8877}\x{8878}\x{8879}\x{887B}\x{887C}\x{887D}\x{887E}\x{887F}' . + '\x{8880}\x{8881}\x{8882}\x{8883}\x{8884}\x{8885}\x{8886}\x{8887}\x{8888}' . + '\x{8889}\x{888A}\x{888B}\x{888C}\x{888D}\x{888E}\x{888F}\x{8890}\x{8891}' . + '\x{8892}\x{8893}\x{8894}\x{8895}\x{8896}\x{8897}\x{8898}\x{8899}\x{889A}' . + '\x{889B}\x{889C}\x{889D}\x{889E}\x{889F}\x{88A0}\x{88A1}\x{88A2}\x{88A3}' . + '\x{88A4}\x{88A5}\x{88A6}\x{88A7}\x{88A8}\x{88A9}\x{88AA}\x{88AB}\x{88AC}' . + '\x{88AD}\x{88AE}\x{88AF}\x{88B0}\x{88B1}\x{88B2}\x{88B3}\x{88B4}\x{88B6}' . + '\x{88B7}\x{88B8}\x{88B9}\x{88BA}\x{88BB}\x{88BC}\x{88BD}\x{88BE}\x{88BF}' . + '\x{88C0}\x{88C1}\x{88C2}\x{88C3}\x{88C4}\x{88C5}\x{88C6}\x{88C7}\x{88C8}' . + '\x{88C9}\x{88CA}\x{88CB}\x{88CC}\x{88CD}\x{88CE}\x{88CF}\x{88D0}\x{88D1}' . + '\x{88D2}\x{88D3}\x{88D4}\x{88D5}\x{88D6}\x{88D7}\x{88D8}\x{88D9}\x{88DA}' . + '\x{88DB}\x{88DC}\x{88DD}\x{88DE}\x{88DF}\x{88E0}\x{88E1}\x{88E2}\x{88E3}' . + '\x{88E4}\x{88E5}\x{88E7}\x{88E8}\x{88EA}\x{88EB}\x{88EC}\x{88EE}\x{88EF}' . + '\x{88F0}\x{88F1}\x{88F2}\x{88F3}\x{88F4}\x{88F5}\x{88F6}\x{88F7}\x{88F8}' . + '\x{88F9}\x{88FA}\x{88FB}\x{88FC}\x{88FD}\x{88FE}\x{88FF}\x{8900}\x{8901}' . + '\x{8902}\x{8904}\x{8905}\x{8906}\x{8907}\x{8908}\x{8909}\x{890A}\x{890B}' . + '\x{890C}\x{890D}\x{890E}\x{8910}\x{8911}\x{8912}\x{8913}\x{8914}\x{8915}' . + '\x{8916}\x{8917}\x{8918}\x{8919}\x{891A}\x{891B}\x{891C}\x{891D}\x{891E}' . + '\x{891F}\x{8920}\x{8921}\x{8922}\x{8923}\x{8925}\x{8926}\x{8927}\x{8928}' . + '\x{8929}\x{892A}\x{892B}\x{892C}\x{892D}\x{892E}\x{892F}\x{8930}\x{8931}' . + '\x{8932}\x{8933}\x{8934}\x{8935}\x{8936}\x{8937}\x{8938}\x{8939}\x{893A}' . + '\x{893B}\x{893C}\x{893D}\x{893E}\x{893F}\x{8940}\x{8941}\x{8942}\x{8943}' . + '\x{8944}\x{8945}\x{8946}\x{8947}\x{8948}\x{8949}\x{894A}\x{894B}\x{894C}' . + '\x{894E}\x{894F}\x{8950}\x{8951}\x{8952}\x{8953}\x{8954}\x{8955}\x{8956}' . + '\x{8957}\x{8958}\x{8959}\x{895A}\x{895B}\x{895C}\x{895D}\x{895E}\x{895F}' . + '\x{8960}\x{8961}\x{8962}\x{8963}\x{8964}\x{8966}\x{8967}\x{8968}\x{8969}' . + '\x{896A}\x{896B}\x{896C}\x{896D}\x{896E}\x{896F}\x{8970}\x{8971}\x{8972}' . + '\x{8973}\x{8974}\x{8976}\x{8977}\x{8978}\x{8979}\x{897A}\x{897B}\x{897C}' . + '\x{897E}\x{897F}\x{8980}\x{8981}\x{8982}\x{8983}\x{8984}\x{8985}\x{8986}' . + '\x{8987}\x{8988}\x{8989}\x{898A}\x{898B}\x{898C}\x{898E}\x{898F}\x{8991}' . + '\x{8992}\x{8993}\x{8995}\x{8996}\x{8997}\x{8998}\x{899A}\x{899B}\x{899C}' . + '\x{899D}\x{899E}\x{899F}\x{89A0}\x{89A1}\x{89A2}\x{89A3}\x{89A4}\x{89A5}' . + '\x{89A6}\x{89A7}\x{89A8}\x{89AA}\x{89AB}\x{89AC}\x{89AD}\x{89AE}\x{89AF}' . + '\x{89B1}\x{89B2}\x{89B3}\x{89B5}\x{89B6}\x{89B7}\x{89B8}\x{89B9}\x{89BA}' . + '\x{89BD}\x{89BE}\x{89BF}\x{89C0}\x{89C1}\x{89C2}\x{89C3}\x{89C4}\x{89C5}' . + '\x{89C6}\x{89C7}\x{89C8}\x{89C9}\x{89CA}\x{89CB}\x{89CC}\x{89CD}\x{89CE}' . + '\x{89CF}\x{89D0}\x{89D1}\x{89D2}\x{89D3}\x{89D4}\x{89D5}\x{89D6}\x{89D7}' . + '\x{89D8}\x{89D9}\x{89DA}\x{89DB}\x{89DC}\x{89DD}\x{89DE}\x{89DF}\x{89E0}' . + '\x{89E1}\x{89E2}\x{89E3}\x{89E4}\x{89E5}\x{89E6}\x{89E7}\x{89E8}\x{89E9}' . + '\x{89EA}\x{89EB}\x{89EC}\x{89ED}\x{89EF}\x{89F0}\x{89F1}\x{89F2}\x{89F3}' . + '\x{89F4}\x{89F6}\x{89F7}\x{89F8}\x{89FA}\x{89FB}\x{89FC}\x{89FE}\x{89FF}' . + '\x{8A00}\x{8A01}\x{8A02}\x{8A03}\x{8A04}\x{8A07}\x{8A08}\x{8A09}\x{8A0A}' . + '\x{8A0B}\x{8A0C}\x{8A0D}\x{8A0E}\x{8A0F}\x{8A10}\x{8A11}\x{8A12}\x{8A13}' . + '\x{8A15}\x{8A16}\x{8A17}\x{8A18}\x{8A1A}\x{8A1B}\x{8A1C}\x{8A1D}\x{8A1E}' . + '\x{8A1F}\x{8A22}\x{8A23}\x{8A24}\x{8A25}\x{8A26}\x{8A27}\x{8A28}\x{8A29}' . + '\x{8A2A}\x{8A2C}\x{8A2D}\x{8A2E}\x{8A2F}\x{8A30}\x{8A31}\x{8A32}\x{8A34}' . + '\x{8A35}\x{8A36}\x{8A37}\x{8A38}\x{8A39}\x{8A3A}\x{8A3B}\x{8A3C}\x{8A3E}' . + '\x{8A3F}\x{8A40}\x{8A41}\x{8A42}\x{8A43}\x{8A44}\x{8A45}\x{8A46}\x{8A47}' . + '\x{8A48}\x{8A49}\x{8A4A}\x{8A4C}\x{8A4D}\x{8A4E}\x{8A4F}\x{8A50}\x{8A51}' . + '\x{8A52}\x{8A53}\x{8A54}\x{8A55}\x{8A56}\x{8A57}\x{8A58}\x{8A59}\x{8A5A}' . + '\x{8A5B}\x{8A5C}\x{8A5D}\x{8A5E}\x{8A5F}\x{8A60}\x{8A61}\x{8A62}\x{8A63}' . + '\x{8A65}\x{8A66}\x{8A67}\x{8A68}\x{8A69}\x{8A6A}\x{8A6B}\x{8A6C}\x{8A6D}' . + '\x{8A6E}\x{8A6F}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A74}\x{8A75}\x{8A76}' . + '\x{8A77}\x{8A79}\x{8A7A}\x{8A7B}\x{8A7C}\x{8A7E}\x{8A7F}\x{8A80}\x{8A81}' . + '\x{8A82}\x{8A83}\x{8A84}\x{8A85}\x{8A86}\x{8A87}\x{8A89}\x{8A8A}\x{8A8B}' . + '\x{8A8C}\x{8A8D}\x{8A8E}\x{8A8F}\x{8A90}\x{8A91}\x{8A92}\x{8A93}\x{8A94}' . + '\x{8A95}\x{8A96}\x{8A97}\x{8A98}\x{8A99}\x{8A9A}\x{8A9B}\x{8A9C}\x{8A9D}' . + '\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA2}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA7}' . + '\x{8AA8}\x{8AA9}\x{8AAA}\x{8AAB}\x{8AAC}\x{8AAE}\x{8AB0}\x{8AB1}\x{8AB2}' . + '\x{8AB3}\x{8AB4}\x{8AB5}\x{8AB6}\x{8AB8}\x{8AB9}\x{8ABA}\x{8ABB}\x{8ABC}' . + '\x{8ABD}\x{8ABE}\x{8ABF}\x{8AC0}\x{8AC1}\x{8AC2}\x{8AC3}\x{8AC4}\x{8AC5}' . + '\x{8AC6}\x{8AC7}\x{8AC8}\x{8AC9}\x{8ACA}\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACE}' . + '\x{8ACF}\x{8AD1}\x{8AD2}\x{8AD3}\x{8AD4}\x{8AD5}\x{8AD6}\x{8AD7}\x{8AD8}' . + '\x{8AD9}\x{8ADA}\x{8ADB}\x{8ADC}\x{8ADD}\x{8ADE}\x{8ADF}\x{8AE0}\x{8AE1}' . + '\x{8AE2}\x{8AE3}\x{8AE4}\x{8AE5}\x{8AE6}\x{8AE7}\x{8AE8}\x{8AE9}\x{8AEA}' . + '\x{8AEB}\x{8AED}\x{8AEE}\x{8AEF}\x{8AF0}\x{8AF1}\x{8AF2}\x{8AF3}\x{8AF4}' . + '\x{8AF5}\x{8AF6}\x{8AF7}\x{8AF8}\x{8AF9}\x{8AFA}\x{8AFB}\x{8AFC}\x{8AFD}' . + '\x{8AFE}\x{8AFF}\x{8B00}\x{8B01}\x{8B02}\x{8B03}\x{8B04}\x{8B05}\x{8B06}' . + '\x{8B07}\x{8B08}\x{8B09}\x{8B0A}\x{8B0B}\x{8B0D}\x{8B0E}\x{8B0F}\x{8B10}' . + '\x{8B11}\x{8B12}\x{8B13}\x{8B14}\x{8B15}\x{8B16}\x{8B17}\x{8B18}\x{8B19}' . + '\x{8B1A}\x{8B1B}\x{8B1C}\x{8B1D}\x{8B1E}\x{8B1F}\x{8B20}\x{8B21}\x{8B22}' . + '\x{8B23}\x{8B24}\x{8B25}\x{8B26}\x{8B27}\x{8B28}\x{8B2A}\x{8B2B}\x{8B2C}' . + '\x{8B2D}\x{8B2E}\x{8B2F}\x{8B30}\x{8B31}\x{8B33}\x{8B34}\x{8B35}\x{8B36}' . + '\x{8B37}\x{8B39}\x{8B3A}\x{8B3B}\x{8B3C}\x{8B3D}\x{8B3E}\x{8B40}\x{8B41}' . + '\x{8B42}\x{8B43}\x{8B44}\x{8B45}\x{8B46}\x{8B47}\x{8B48}\x{8B49}\x{8B4A}' . + '\x{8B4B}\x{8B4C}\x{8B4D}\x{8B4E}\x{8B4F}\x{8B50}\x{8B51}\x{8B52}\x{8B53}' . + '\x{8B54}\x{8B55}\x{8B56}\x{8B57}\x{8B58}\x{8B59}\x{8B5A}\x{8B5B}\x{8B5C}' . + '\x{8B5D}\x{8B5E}\x{8B5F}\x{8B60}\x{8B63}\x{8B64}\x{8B65}\x{8B66}\x{8B67}' . + '\x{8B68}\x{8B6A}\x{8B6B}\x{8B6C}\x{8B6D}\x{8B6E}\x{8B6F}\x{8B70}\x{8B71}' . + '\x{8B73}\x{8B74}\x{8B76}\x{8B77}\x{8B78}\x{8B79}\x{8B7A}\x{8B7B}\x{8B7D}' . + '\x{8B7E}\x{8B7F}\x{8B80}\x{8B82}\x{8B83}\x{8B84}\x{8B85}\x{8B86}\x{8B88}' . + '\x{8B89}\x{8B8A}\x{8B8B}\x{8B8C}\x{8B8E}\x{8B90}\x{8B91}\x{8B92}\x{8B93}' . + '\x{8B94}\x{8B95}\x{8B96}\x{8B97}\x{8B98}\x{8B99}\x{8B9A}\x{8B9C}\x{8B9D}' . + '\x{8B9E}\x{8B9F}\x{8BA0}\x{8BA1}\x{8BA2}\x{8BA3}\x{8BA4}\x{8BA5}\x{8BA6}' . + '\x{8BA7}\x{8BA8}\x{8BA9}\x{8BAA}\x{8BAB}\x{8BAC}\x{8BAD}\x{8BAE}\x{8BAF}' . + '\x{8BB0}\x{8BB1}\x{8BB2}\x{8BB3}\x{8BB4}\x{8BB5}\x{8BB6}\x{8BB7}\x{8BB8}' . + '\x{8BB9}\x{8BBA}\x{8BBB}\x{8BBC}\x{8BBD}\x{8BBE}\x{8BBF}\x{8BC0}\x{8BC1}' . + '\x{8BC2}\x{8BC3}\x{8BC4}\x{8BC5}\x{8BC6}\x{8BC7}\x{8BC8}\x{8BC9}\x{8BCA}' . + '\x{8BCB}\x{8BCC}\x{8BCD}\x{8BCE}\x{8BCF}\x{8BD0}\x{8BD1}\x{8BD2}\x{8BD3}' . + '\x{8BD4}\x{8BD5}\x{8BD6}\x{8BD7}\x{8BD8}\x{8BD9}\x{8BDA}\x{8BDB}\x{8BDC}' . + '\x{8BDD}\x{8BDE}\x{8BDF}\x{8BE0}\x{8BE1}\x{8BE2}\x{8BE3}\x{8BE4}\x{8BE5}' . + '\x{8BE6}\x{8BE7}\x{8BE8}\x{8BE9}\x{8BEA}\x{8BEB}\x{8BEC}\x{8BED}\x{8BEE}' . + '\x{8BEF}\x{8BF0}\x{8BF1}\x{8BF2}\x{8BF3}\x{8BF4}\x{8BF5}\x{8BF6}\x{8BF7}' . + '\x{8BF8}\x{8BF9}\x{8BFA}\x{8BFB}\x{8BFC}\x{8BFD}\x{8BFE}\x{8BFF}\x{8C00}' . + '\x{8C01}\x{8C02}\x{8C03}\x{8C04}\x{8C05}\x{8C06}\x{8C07}\x{8C08}\x{8C09}' . + '\x{8C0A}\x{8C0B}\x{8C0C}\x{8C0D}\x{8C0E}\x{8C0F}\x{8C10}\x{8C11}\x{8C12}' . + '\x{8C13}\x{8C14}\x{8C15}\x{8C16}\x{8C17}\x{8C18}\x{8C19}\x{8C1A}\x{8C1B}' . + '\x{8C1C}\x{8C1D}\x{8C1E}\x{8C1F}\x{8C20}\x{8C21}\x{8C22}\x{8C23}\x{8C24}' . + '\x{8C25}\x{8C26}\x{8C27}\x{8C28}\x{8C29}\x{8C2A}\x{8C2B}\x{8C2C}\x{8C2D}' . + '\x{8C2E}\x{8C2F}\x{8C30}\x{8C31}\x{8C32}\x{8C33}\x{8C34}\x{8C35}\x{8C36}' . + '\x{8C37}\x{8C39}\x{8C3A}\x{8C3B}\x{8C3C}\x{8C3D}\x{8C3E}\x{8C3F}\x{8C41}' . + '\x{8C42}\x{8C43}\x{8C45}\x{8C46}\x{8C47}\x{8C48}\x{8C49}\x{8C4A}\x{8C4B}' . + '\x{8C4C}\x{8C4D}\x{8C4E}\x{8C4F}\x{8C50}\x{8C54}\x{8C55}\x{8C56}\x{8C57}' . + '\x{8C59}\x{8C5A}\x{8C5B}\x{8C5C}\x{8C5D}\x{8C5E}\x{8C5F}\x{8C60}\x{8C61}' . + '\x{8C62}\x{8C63}\x{8C64}\x{8C65}\x{8C66}\x{8C67}\x{8C68}\x{8C69}\x{8C6A}' . + '\x{8C6B}\x{8C6C}\x{8C6D}\x{8C6E}\x{8C6F}\x{8C70}\x{8C71}\x{8C72}\x{8C73}' . + '\x{8C75}\x{8C76}\x{8C77}\x{8C78}\x{8C79}\x{8C7A}\x{8C7B}\x{8C7D}\x{8C7E}' . + '\x{8C80}\x{8C81}\x{8C82}\x{8C84}\x{8C85}\x{8C86}\x{8C88}\x{8C89}\x{8C8A}' . + '\x{8C8C}\x{8C8D}\x{8C8F}\x{8C90}\x{8C91}\x{8C92}\x{8C93}\x{8C94}\x{8C95}' . + '\x{8C96}\x{8C97}\x{8C98}\x{8C99}\x{8C9A}\x{8C9C}\x{8C9D}\x{8C9E}\x{8C9F}' . + '\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA3}\x{8CA4}\x{8CA5}\x{8CA7}\x{8CA8}\x{8CA9}' . + '\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}\x{8CB1}\x{8CB2}' . + '\x{8CB3}\x{8CB4}\x{8CB5}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CB9}\x{8CBA}\x{8CBB}' . + '\x{8CBC}\x{8CBD}\x{8CBE}\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}' . + '\x{8CC5}\x{8CC6}\x{8CC7}\x{8CC8}\x{8CC9}\x{8CCA}\x{8CCC}\x{8CCE}\x{8CCF}' . + '\x{8CD0}\x{8CD1}\x{8CD2}\x{8CD3}\x{8CD4}\x{8CD5}\x{8CD7}\x{8CD9}\x{8CDA}' . + '\x{8CDB}\x{8CDC}\x{8CDD}\x{8CDE}\x{8CDF}\x{8CE0}\x{8CE1}\x{8CE2}\x{8CE3}' . + '\x{8CE4}\x{8CE5}\x{8CE6}\x{8CE7}\x{8CE8}\x{8CEA}\x{8CEB}\x{8CEC}\x{8CED}' . + '\x{8CEE}\x{8CEF}\x{8CF0}\x{8CF1}\x{8CF2}\x{8CF3}\x{8CF4}\x{8CF5}\x{8CF6}' . + '\x{8CF8}\x{8CF9}\x{8CFA}\x{8CFB}\x{8CFC}\x{8CFD}\x{8CFE}\x{8CFF}\x{8D00}' . + '\x{8D02}\x{8D03}\x{8D04}\x{8D05}\x{8D06}\x{8D07}\x{8D08}\x{8D09}\x{8D0A}' . + '\x{8D0B}\x{8D0C}\x{8D0D}\x{8D0E}\x{8D0F}\x{8D10}\x{8D13}\x{8D14}\x{8D15}' . + '\x{8D16}\x{8D17}\x{8D18}\x{8D19}\x{8D1A}\x{8D1B}\x{8D1C}\x{8D1D}\x{8D1E}' . + '\x{8D1F}\x{8D20}\x{8D21}\x{8D22}\x{8D23}\x{8D24}\x{8D25}\x{8D26}\x{8D27}' . + '\x{8D28}\x{8D29}\x{8D2A}\x{8D2B}\x{8D2C}\x{8D2D}\x{8D2E}\x{8D2F}\x{8D30}' . + '\x{8D31}\x{8D32}\x{8D33}\x{8D34}\x{8D35}\x{8D36}\x{8D37}\x{8D38}\x{8D39}' . + '\x{8D3A}\x{8D3B}\x{8D3C}\x{8D3D}\x{8D3E}\x{8D3F}\x{8D40}\x{8D41}\x{8D42}' . + '\x{8D43}\x{8D44}\x{8D45}\x{8D46}\x{8D47}\x{8D48}\x{8D49}\x{8D4A}\x{8D4B}' . + '\x{8D4C}\x{8D4D}\x{8D4E}\x{8D4F}\x{8D50}\x{8D51}\x{8D52}\x{8D53}\x{8D54}' . + '\x{8D55}\x{8D56}\x{8D57}\x{8D58}\x{8D59}\x{8D5A}\x{8D5B}\x{8D5C}\x{8D5D}' . + '\x{8D5E}\x{8D5F}\x{8D60}\x{8D61}\x{8D62}\x{8D63}\x{8D64}\x{8D65}\x{8D66}' . + '\x{8D67}\x{8D68}\x{8D69}\x{8D6A}\x{8D6B}\x{8D6C}\x{8D6D}\x{8D6E}\x{8D6F}' . + '\x{8D70}\x{8D71}\x{8D72}\x{8D73}\x{8D74}\x{8D75}\x{8D76}\x{8D77}\x{8D78}' . + '\x{8D79}\x{8D7A}\x{8D7B}\x{8D7D}\x{8D7E}\x{8D7F}\x{8D80}\x{8D81}\x{8D82}' . + '\x{8D83}\x{8D84}\x{8D85}\x{8D86}\x{8D87}\x{8D88}\x{8D89}\x{8D8A}\x{8D8B}' . + '\x{8D8C}\x{8D8D}\x{8D8E}\x{8D8F}\x{8D90}\x{8D91}\x{8D92}\x{8D93}\x{8D94}' . + '\x{8D95}\x{8D96}\x{8D97}\x{8D98}\x{8D99}\x{8D9A}\x{8D9B}\x{8D9C}\x{8D9D}' . + '\x{8D9E}\x{8D9F}\x{8DA0}\x{8DA1}\x{8DA2}\x{8DA3}\x{8DA4}\x{8DA5}\x{8DA7}' . + '\x{8DA8}\x{8DA9}\x{8DAA}\x{8DAB}\x{8DAC}\x{8DAD}\x{8DAE}\x{8DAF}\x{8DB0}' . + '\x{8DB1}\x{8DB2}\x{8DB3}\x{8DB4}\x{8DB5}\x{8DB6}\x{8DB7}\x{8DB8}\x{8DB9}' . + '\x{8DBA}\x{8DBB}\x{8DBC}\x{8DBD}\x{8DBE}\x{8DBF}\x{8DC1}\x{8DC2}\x{8DC3}' . + '\x{8DC4}\x{8DC5}\x{8DC6}\x{8DC7}\x{8DC8}\x{8DC9}\x{8DCA}\x{8DCB}\x{8DCC}' . + '\x{8DCD}\x{8DCE}\x{8DCF}\x{8DD0}\x{8DD1}\x{8DD2}\x{8DD3}\x{8DD4}\x{8DD5}' . + '\x{8DD6}\x{8DD7}\x{8DD8}\x{8DD9}\x{8DDA}\x{8DDB}\x{8DDC}\x{8DDD}\x{8DDE}' . + '\x{8DDF}\x{8DE0}\x{8DE1}\x{8DE2}\x{8DE3}\x{8DE4}\x{8DE6}\x{8DE7}\x{8DE8}' . + '\x{8DE9}\x{8DEA}\x{8DEB}\x{8DEC}\x{8DED}\x{8DEE}\x{8DEF}\x{8DF0}\x{8DF1}' . + '\x{8DF2}\x{8DF3}\x{8DF4}\x{8DF5}\x{8DF6}\x{8DF7}\x{8DF8}\x{8DF9}\x{8DFA}' . + '\x{8DFB}\x{8DFC}\x{8DFD}\x{8DFE}\x{8DFF}\x{8E00}\x{8E02}\x{8E03}\x{8E04}' . + '\x{8E05}\x{8E06}\x{8E07}\x{8E08}\x{8E09}\x{8E0A}\x{8E0C}\x{8E0D}\x{8E0E}' . + '\x{8E0F}\x{8E10}\x{8E11}\x{8E12}\x{8E13}\x{8E14}\x{8E15}\x{8E16}\x{8E17}' . + '\x{8E18}\x{8E19}\x{8E1A}\x{8E1B}\x{8E1C}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E20}' . + '\x{8E21}\x{8E22}\x{8E23}\x{8E24}\x{8E25}\x{8E26}\x{8E27}\x{8E28}\x{8E29}' . + '\x{8E2A}\x{8E2B}\x{8E2C}\x{8E2D}\x{8E2E}\x{8E2F}\x{8E30}\x{8E31}\x{8E33}' . + '\x{8E34}\x{8E35}\x{8E36}\x{8E37}\x{8E38}\x{8E39}\x{8E3A}\x{8E3B}\x{8E3C}' . + '\x{8E3D}\x{8E3E}\x{8E3F}\x{8E40}\x{8E41}\x{8E42}\x{8E43}\x{8E44}\x{8E45}' . + '\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4B}\x{8E4C}\x{8E4D}\x{8E4E}\x{8E50}' . + '\x{8E51}\x{8E52}\x{8E53}\x{8E54}\x{8E55}\x{8E56}\x{8E57}\x{8E58}\x{8E59}' . + '\x{8E5A}\x{8E5B}\x{8E5C}\x{8E5D}\x{8E5E}\x{8E5F}\x{8E60}\x{8E61}\x{8E62}' . + '\x{8E63}\x{8E64}\x{8E65}\x{8E66}\x{8E67}\x{8E68}\x{8E69}\x{8E6A}\x{8E6B}' . + '\x{8E6C}\x{8E6D}\x{8E6F}\x{8E70}\x{8E71}\x{8E72}\x{8E73}\x{8E74}\x{8E76}' . + '\x{8E78}\x{8E7A}\x{8E7B}\x{8E7C}\x{8E7D}\x{8E7E}\x{8E7F}\x{8E80}\x{8E81}' . + '\x{8E82}\x{8E83}\x{8E84}\x{8E85}\x{8E86}\x{8E87}\x{8E88}\x{8E89}\x{8E8A}' . + '\x{8E8B}\x{8E8C}\x{8E8D}\x{8E8E}\x{8E8F}\x{8E90}\x{8E91}\x{8E92}\x{8E93}' . + '\x{8E94}\x{8E95}\x{8E96}\x{8E97}\x{8E98}\x{8E9A}\x{8E9C}\x{8E9D}\x{8E9E}' . + '\x{8E9F}\x{8EA0}\x{8EA1}\x{8EA3}\x{8EA4}\x{8EA5}\x{8EA6}\x{8EA7}\x{8EA8}' . + '\x{8EA9}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAD}\x{8EAE}\x{8EAF}\x{8EB0}\x{8EB1}' . + '\x{8EB2}\x{8EB4}\x{8EB5}\x{8EB8}\x{8EB9}\x{8EBA}\x{8EBB}\x{8EBC}\x{8EBD}' . + '\x{8EBE}\x{8EBF}\x{8EC0}\x{8EC2}\x{8EC3}\x{8EC5}\x{8EC6}\x{8EC7}\x{8EC8}' . + '\x{8EC9}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ECE}\x{8ECF}\x{8ED0}\x{8ED1}' . + '\x{8ED2}\x{8ED3}\x{8ED4}\x{8ED5}\x{8ED6}\x{8ED7}\x{8ED8}\x{8EDA}\x{8EDB}' . + '\x{8EDC}\x{8EDD}\x{8EDE}\x{8EDF}\x{8EE0}\x{8EE1}\x{8EE4}\x{8EE5}\x{8EE6}' . + '\x{8EE7}\x{8EE8}\x{8EE9}\x{8EEA}\x{8EEB}\x{8EEC}\x{8EED}\x{8EEE}\x{8EEF}' . + '\x{8EF1}\x{8EF2}\x{8EF3}\x{8EF4}\x{8EF5}\x{8EF6}\x{8EF7}\x{8EF8}\x{8EF9}' . + '\x{8EFA}\x{8EFB}\x{8EFC}\x{8EFD}\x{8EFE}\x{8EFF}\x{8F00}\x{8F01}\x{8F02}' . + '\x{8F03}\x{8F04}\x{8F05}\x{8F06}\x{8F07}\x{8F08}\x{8F09}\x{8F0A}\x{8F0B}' . + '\x{8F0D}\x{8F0E}\x{8F10}\x{8F11}\x{8F12}\x{8F13}\x{8F14}\x{8F15}\x{8F16}' . + '\x{8F17}\x{8F18}\x{8F1A}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1E}\x{8F1F}\x{8F20}' . + '\x{8F21}\x{8F22}\x{8F23}\x{8F24}\x{8F25}\x{8F26}\x{8F27}\x{8F28}\x{8F29}' . + '\x{8F2A}\x{8F2B}\x{8F2C}\x{8F2E}\x{8F2F}\x{8F30}\x{8F31}\x{8F32}\x{8F33}' . + '\x{8F34}\x{8F35}\x{8F36}\x{8F37}\x{8F38}\x{8F39}\x{8F3B}\x{8F3C}\x{8F3D}' . + '\x{8F3E}\x{8F3F}\x{8F40}\x{8F42}\x{8F43}\x{8F44}\x{8F45}\x{8F46}\x{8F47}' . + '\x{8F48}\x{8F49}\x{8F4A}\x{8F4B}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F4F}\x{8F50}' . + '\x{8F51}\x{8F52}\x{8F53}\x{8F54}\x{8F55}\x{8F56}\x{8F57}\x{8F58}\x{8F59}' . + '\x{8F5A}\x{8F5B}\x{8F5D}\x{8F5E}\x{8F5F}\x{8F60}\x{8F61}\x{8F62}\x{8F63}' . + '\x{8F64}\x{8F65}\x{8F66}\x{8F67}\x{8F68}\x{8F69}\x{8F6A}\x{8F6B}\x{8F6C}' . + '\x{8F6D}\x{8F6E}\x{8F6F}\x{8F70}\x{8F71}\x{8F72}\x{8F73}\x{8F74}\x{8F75}' . + '\x{8F76}\x{8F77}\x{8F78}\x{8F79}\x{8F7A}\x{8F7B}\x{8F7C}\x{8F7D}\x{8F7E}' . + '\x{8F7F}\x{8F80}\x{8F81}\x{8F82}\x{8F83}\x{8F84}\x{8F85}\x{8F86}\x{8F87}' . + '\x{8F88}\x{8F89}\x{8F8A}\x{8F8B}\x{8F8C}\x{8F8D}\x{8F8E}\x{8F8F}\x{8F90}' . + '\x{8F91}\x{8F92}\x{8F93}\x{8F94}\x{8F95}\x{8F96}\x{8F97}\x{8F98}\x{8F99}' . + '\x{8F9A}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA0}\x{8FA1}\x{8FA2}\x{8FA3}' . + '\x{8FA5}\x{8FA6}\x{8FA7}\x{8FA8}\x{8FA9}\x{8FAA}\x{8FAB}\x{8FAC}\x{8FAD}' . + '\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB4}\x{8FB5}\x{8FB6}\x{8FB7}' . + '\x{8FB8}\x{8FB9}\x{8FBB}\x{8FBC}\x{8FBD}\x{8FBE}\x{8FBF}\x{8FC0}\x{8FC1}' . + '\x{8FC2}\x{8FC4}\x{8FC5}\x{8FC6}\x{8FC7}\x{8FC8}\x{8FC9}\x{8FCB}\x{8FCC}' . + '\x{8FCD}\x{8FCE}\x{8FCF}\x{8FD0}\x{8FD1}\x{8FD2}\x{8FD3}\x{8FD4}\x{8FD5}' . + '\x{8FD6}\x{8FD7}\x{8FD8}\x{8FD9}\x{8FDA}\x{8FDB}\x{8FDC}\x{8FDD}\x{8FDE}' . + '\x{8FDF}\x{8FE0}\x{8FE1}\x{8FE2}\x{8FE3}\x{8FE4}\x{8FE5}\x{8FE6}\x{8FE8}' . + '\x{8FE9}\x{8FEA}\x{8FEB}\x{8FEC}\x{8FED}\x{8FEE}\x{8FEF}\x{8FF0}\x{8FF1}' . + '\x{8FF2}\x{8FF3}\x{8FF4}\x{8FF5}\x{8FF6}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}' . + '\x{8FFB}\x{8FFC}\x{8FFD}\x{8FFE}\x{8FFF}\x{9000}\x{9001}\x{9002}\x{9003}' . + '\x{9004}\x{9005}\x{9006}\x{9007}\x{9008}\x{9009}\x{900A}\x{900B}\x{900C}' . + '\x{900D}\x{900F}\x{9010}\x{9011}\x{9012}\x{9013}\x{9014}\x{9015}\x{9016}' . + '\x{9017}\x{9018}\x{9019}\x{901A}\x{901B}\x{901C}\x{901D}\x{901E}\x{901F}' . + '\x{9020}\x{9021}\x{9022}\x{9023}\x{9024}\x{9025}\x{9026}\x{9027}\x{9028}' . + '\x{9029}\x{902B}\x{902D}\x{902E}\x{902F}\x{9030}\x{9031}\x{9032}\x{9033}' . + '\x{9034}\x{9035}\x{9036}\x{9038}\x{903A}\x{903B}\x{903C}\x{903D}\x{903E}' . + '\x{903F}\x{9041}\x{9042}\x{9043}\x{9044}\x{9045}\x{9047}\x{9048}\x{9049}' . + '\x{904A}\x{904B}\x{904C}\x{904D}\x{904E}\x{904F}\x{9050}\x{9051}\x{9052}' . + '\x{9053}\x{9054}\x{9055}\x{9056}\x{9057}\x{9058}\x{9059}\x{905A}\x{905B}' . + '\x{905C}\x{905D}\x{905E}\x{905F}\x{9060}\x{9061}\x{9062}\x{9063}\x{9064}' . + '\x{9065}\x{9066}\x{9067}\x{9068}\x{9069}\x{906A}\x{906B}\x{906C}\x{906D}' . + '\x{906E}\x{906F}\x{9070}\x{9071}\x{9072}\x{9073}\x{9074}\x{9075}\x{9076}' . + '\x{9077}\x{9078}\x{9079}\x{907A}\x{907B}\x{907C}\x{907D}\x{907E}\x{907F}' . + '\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9085}\x{9086}\x{9087}\x{9088}' . + '\x{9089}\x{908A}\x{908B}\x{908C}\x{908D}\x{908E}\x{908F}\x{9090}\x{9091}' . + '\x{9092}\x{9093}\x{9094}\x{9095}\x{9096}\x{9097}\x{9098}\x{9099}\x{909A}' . + '\x{909B}\x{909C}\x{909D}\x{909E}\x{909F}\x{90A0}\x{90A1}\x{90A2}\x{90A3}' . + '\x{90A4}\x{90A5}\x{90A6}\x{90A7}\x{90A8}\x{90A9}\x{90AA}\x{90AC}\x{90AD}' . + '\x{90AE}\x{90AF}\x{90B0}\x{90B1}\x{90B2}\x{90B3}\x{90B4}\x{90B5}\x{90B6}' . + '\x{90B7}\x{90B8}\x{90B9}\x{90BA}\x{90BB}\x{90BC}\x{90BD}\x{90BE}\x{90BF}' . + '\x{90C0}\x{90C1}\x{90C2}\x{90C3}\x{90C4}\x{90C5}\x{90C6}\x{90C7}\x{90C8}' . + '\x{90C9}\x{90CA}\x{90CB}\x{90CE}\x{90CF}\x{90D0}\x{90D1}\x{90D3}\x{90D4}' . + '\x{90D5}\x{90D6}\x{90D7}\x{90D8}\x{90D9}\x{90DA}\x{90DB}\x{90DC}\x{90DD}' . + '\x{90DE}\x{90DF}\x{90E0}\x{90E1}\x{90E2}\x{90E3}\x{90E4}\x{90E5}\x{90E6}' . + '\x{90E7}\x{90E8}\x{90E9}\x{90EA}\x{90EB}\x{90EC}\x{90ED}\x{90EE}\x{90EF}' . + '\x{90F0}\x{90F1}\x{90F2}\x{90F3}\x{90F4}\x{90F5}\x{90F7}\x{90F8}\x{90F9}' . + '\x{90FA}\x{90FB}\x{90FC}\x{90FD}\x{90FE}\x{90FF}\x{9100}\x{9101}\x{9102}' . + '\x{9103}\x{9104}\x{9105}\x{9106}\x{9107}\x{9108}\x{9109}\x{910B}\x{910C}' . + '\x{910D}\x{910E}\x{910F}\x{9110}\x{9111}\x{9112}\x{9113}\x{9114}\x{9115}' . + '\x{9116}\x{9117}\x{9118}\x{9119}\x{911A}\x{911B}\x{911C}\x{911D}\x{911E}' . + '\x{911F}\x{9120}\x{9121}\x{9122}\x{9123}\x{9124}\x{9125}\x{9126}\x{9127}' . + '\x{9128}\x{9129}\x{912A}\x{912B}\x{912C}\x{912D}\x{912E}\x{912F}\x{9130}' . + '\x{9131}\x{9132}\x{9133}\x{9134}\x{9135}\x{9136}\x{9137}\x{9138}\x{9139}' . + '\x{913A}\x{913B}\x{913E}\x{913F}\x{9140}\x{9141}\x{9142}\x{9143}\x{9144}' . + '\x{9145}\x{9146}\x{9147}\x{9148}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}' . + '\x{914E}\x{914F}\x{9150}\x{9151}\x{9152}\x{9153}\x{9154}\x{9155}\x{9156}' . + '\x{9157}\x{9158}\x{915A}\x{915B}\x{915C}\x{915D}\x{915E}\x{915F}\x{9160}' . + '\x{9161}\x{9162}\x{9163}\x{9164}\x{9165}\x{9166}\x{9167}\x{9168}\x{9169}' . + '\x{916A}\x{916B}\x{916C}\x{916D}\x{916E}\x{916F}\x{9170}\x{9171}\x{9172}' . + '\x{9173}\x{9174}\x{9175}\x{9176}\x{9177}\x{9178}\x{9179}\x{917A}\x{917C}' . + '\x{917D}\x{917E}\x{917F}\x{9180}\x{9181}\x{9182}\x{9183}\x{9184}\x{9185}' . + '\x{9186}\x{9187}\x{9188}\x{9189}\x{918A}\x{918B}\x{918C}\x{918D}\x{918E}' . + '\x{918F}\x{9190}\x{9191}\x{9192}\x{9193}\x{9194}\x{9196}\x{9199}\x{919A}' . + '\x{919B}\x{919C}\x{919D}\x{919E}\x{919F}\x{91A0}\x{91A1}\x{91A2}\x{91A3}' . + '\x{91A5}\x{91A6}\x{91A7}\x{91A8}\x{91AA}\x{91AB}\x{91AC}\x{91AD}\x{91AE}' . + '\x{91AF}\x{91B0}\x{91B1}\x{91B2}\x{91B3}\x{91B4}\x{91B5}\x{91B6}\x{91B7}' . + '\x{91B9}\x{91BA}\x{91BB}\x{91BC}\x{91BD}\x{91BE}\x{91C0}\x{91C1}\x{91C2}' . + '\x{91C3}\x{91C5}\x{91C6}\x{91C7}\x{91C9}\x{91CA}\x{91CB}\x{91CC}\x{91CD}' . + '\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D2}\x{91D3}\x{91D4}\x{91D5}\x{91D7}' . + '\x{91D8}\x{91D9}\x{91DA}\x{91DB}\x{91DC}\x{91DD}\x{91DE}\x{91DF}\x{91E2}' . + '\x{91E3}\x{91E4}\x{91E5}\x{91E6}\x{91E7}\x{91E8}\x{91E9}\x{91EA}\x{91EB}' . + '\x{91EC}\x{91ED}\x{91EE}\x{91F0}\x{91F1}\x{91F2}\x{91F3}\x{91F4}\x{91F5}' . + '\x{91F7}\x{91F8}\x{91F9}\x{91FA}\x{91FB}\x{91FD}\x{91FE}\x{91FF}\x{9200}' . + '\x{9201}\x{9202}\x{9203}\x{9204}\x{9205}\x{9206}\x{9207}\x{9208}\x{9209}' . + '\x{920A}\x{920B}\x{920C}\x{920D}\x{920E}\x{920F}\x{9210}\x{9211}\x{9212}' . + '\x{9214}\x{9215}\x{9216}\x{9217}\x{9218}\x{9219}\x{921A}\x{921B}\x{921C}' . + '\x{921D}\x{921E}\x{9220}\x{9221}\x{9223}\x{9224}\x{9225}\x{9226}\x{9227}' . + '\x{9228}\x{9229}\x{922A}\x{922B}\x{922D}\x{922E}\x{922F}\x{9230}\x{9231}' . + '\x{9232}\x{9233}\x{9234}\x{9235}\x{9236}\x{9237}\x{9238}\x{9239}\x{923A}' . + '\x{923B}\x{923C}\x{923D}\x{923E}\x{923F}\x{9240}\x{9241}\x{9242}\x{9245}' . + '\x{9246}\x{9247}\x{9248}\x{9249}\x{924A}\x{924B}\x{924C}\x{924D}\x{924E}' . + '\x{924F}\x{9250}\x{9251}\x{9252}\x{9253}\x{9254}\x{9255}\x{9256}\x{9257}' . + '\x{9258}\x{9259}\x{925A}\x{925B}\x{925C}\x{925D}\x{925E}\x{925F}\x{9260}' . + '\x{9261}\x{9262}\x{9263}\x{9264}\x{9265}\x{9266}\x{9267}\x{9268}\x{926B}' . + '\x{926C}\x{926D}\x{926E}\x{926F}\x{9270}\x{9272}\x{9273}\x{9274}\x{9275}' . + '\x{9276}\x{9277}\x{9278}\x{9279}\x{927A}\x{927B}\x{927C}\x{927D}\x{927E}' . + '\x{927F}\x{9280}\x{9282}\x{9283}\x{9285}\x{9286}\x{9287}\x{9288}\x{9289}' . + '\x{928A}\x{928B}\x{928C}\x{928D}\x{928E}\x{928F}\x{9290}\x{9291}\x{9292}' . + '\x{9293}\x{9294}\x{9295}\x{9296}\x{9297}\x{9298}\x{9299}\x{929A}\x{929B}' . + '\x{929C}\x{929D}\x{929F}\x{92A0}\x{92A1}\x{92A2}\x{92A3}\x{92A4}\x{92A5}' . + '\x{92A6}\x{92A7}\x{92A8}\x{92A9}\x{92AA}\x{92AB}\x{92AC}\x{92AD}\x{92AE}' . + '\x{92AF}\x{92B0}\x{92B1}\x{92B2}\x{92B3}\x{92B4}\x{92B5}\x{92B6}\x{92B7}' . + '\x{92B8}\x{92B9}\x{92BA}\x{92BB}\x{92BC}\x{92BE}\x{92BF}\x{92C0}\x{92C1}' . + '\x{92C2}\x{92C3}\x{92C4}\x{92C5}\x{92C6}\x{92C7}\x{92C8}\x{92C9}\x{92CA}' . + '\x{92CB}\x{92CC}\x{92CD}\x{92CE}\x{92CF}\x{92D0}\x{92D1}\x{92D2}\x{92D3}' . + '\x{92D5}\x{92D6}\x{92D7}\x{92D8}\x{92D9}\x{92DA}\x{92DC}\x{92DD}\x{92DE}' . + '\x{92DF}\x{92E0}\x{92E1}\x{92E3}\x{92E4}\x{92E5}\x{92E6}\x{92E7}\x{92E8}' . + '\x{92E9}\x{92EA}\x{92EB}\x{92EC}\x{92ED}\x{92EE}\x{92EF}\x{92F0}\x{92F1}' . + '\x{92F2}\x{92F3}\x{92F4}\x{92F5}\x{92F6}\x{92F7}\x{92F8}\x{92F9}\x{92FA}' . + '\x{92FB}\x{92FC}\x{92FD}\x{92FE}\x{92FF}\x{9300}\x{9301}\x{9302}\x{9303}' . + '\x{9304}\x{9305}\x{9306}\x{9307}\x{9308}\x{9309}\x{930A}\x{930B}\x{930C}' . + '\x{930D}\x{930E}\x{930F}\x{9310}\x{9311}\x{9312}\x{9313}\x{9314}\x{9315}' . + '\x{9316}\x{9317}\x{9318}\x{9319}\x{931A}\x{931B}\x{931D}\x{931E}\x{931F}' . + '\x{9320}\x{9321}\x{9322}\x{9323}\x{9324}\x{9325}\x{9326}\x{9327}\x{9328}' . + '\x{9329}\x{932A}\x{932B}\x{932D}\x{932E}\x{932F}\x{9332}\x{9333}\x{9334}' . + '\x{9335}\x{9336}\x{9337}\x{9338}\x{9339}\x{933A}\x{933B}\x{933C}\x{933D}' . + '\x{933E}\x{933F}\x{9340}\x{9341}\x{9342}\x{9343}\x{9344}\x{9345}\x{9346}' . + '\x{9347}\x{9348}\x{9349}\x{934A}\x{934B}\x{934C}\x{934D}\x{934E}\x{934F}' . + '\x{9350}\x{9351}\x{9352}\x{9353}\x{9354}\x{9355}\x{9356}\x{9357}\x{9358}' . + '\x{9359}\x{935A}\x{935B}\x{935C}\x{935D}\x{935E}\x{935F}\x{9360}\x{9361}' . + '\x{9363}\x{9364}\x{9365}\x{9366}\x{9367}\x{9369}\x{936A}\x{936C}\x{936D}' . + '\x{936E}\x{9370}\x{9371}\x{9372}\x{9374}\x{9375}\x{9376}\x{9377}\x{9379}' . + '\x{937A}\x{937B}\x{937C}\x{937D}\x{937E}\x{9380}\x{9382}\x{9383}\x{9384}' . + '\x{9385}\x{9386}\x{9387}\x{9388}\x{9389}\x{938A}\x{938C}\x{938D}\x{938E}' . + '\x{938F}\x{9390}\x{9391}\x{9392}\x{9393}\x{9394}\x{9395}\x{9396}\x{9397}' . + '\x{9398}\x{9399}\x{939A}\x{939B}\x{939D}\x{939E}\x{939F}\x{93A1}\x{93A2}' . + '\x{93A3}\x{93A4}\x{93A5}\x{93A6}\x{93A7}\x{93A8}\x{93A9}\x{93AA}\x{93AC}' . + '\x{93AD}\x{93AE}\x{93AF}\x{93B0}\x{93B1}\x{93B2}\x{93B3}\x{93B4}\x{93B5}' . + '\x{93B6}\x{93B7}\x{93B8}\x{93B9}\x{93BA}\x{93BC}\x{93BD}\x{93BE}\x{93BF}' . + '\x{93C0}\x{93C1}\x{93C2}\x{93C3}\x{93C4}\x{93C5}\x{93C6}\x{93C7}\x{93C8}' . + '\x{93C9}\x{93CA}\x{93CB}\x{93CC}\x{93CD}\x{93CE}\x{93CF}\x{93D0}\x{93D1}' . + '\x{93D2}\x{93D3}\x{93D4}\x{93D5}\x{93D6}\x{93D7}\x{93D8}\x{93D9}\x{93DA}' . + '\x{93DB}\x{93DC}\x{93DD}\x{93DE}\x{93DF}\x{93E1}\x{93E2}\x{93E3}\x{93E4}' . + '\x{93E6}\x{93E7}\x{93E8}\x{93E9}\x{93EA}\x{93EB}\x{93EC}\x{93ED}\x{93EE}' . + '\x{93EF}\x{93F0}\x{93F1}\x{93F2}\x{93F4}\x{93F5}\x{93F6}\x{93F7}\x{93F8}' . + '\x{93F9}\x{93FA}\x{93FB}\x{93FC}\x{93FD}\x{93FE}\x{93FF}\x{9400}\x{9401}' . + '\x{9403}\x{9404}\x{9405}\x{9406}\x{9407}\x{9408}\x{9409}\x{940A}\x{940B}' . + '\x{940C}\x{940D}\x{940E}\x{940F}\x{9410}\x{9411}\x{9412}\x{9413}\x{9414}' . + '\x{9415}\x{9416}\x{9418}\x{9419}\x{941B}\x{941D}\x{9420}\x{9422}\x{9423}' . + '\x{9425}\x{9426}\x{9427}\x{9428}\x{9429}\x{942A}\x{942B}\x{942C}\x{942D}' . + '\x{942E}\x{942F}\x{9430}\x{9431}\x{9432}\x{9433}\x{9434}\x{9435}\x{9436}' . + '\x{9437}\x{9438}\x{9439}\x{943A}\x{943B}\x{943C}\x{943D}\x{943E}\x{943F}' . + '\x{9440}\x{9441}\x{9442}\x{9444}\x{9445}\x{9446}\x{9447}\x{9448}\x{9449}' . + '\x{944A}\x{944B}\x{944C}\x{944D}\x{944F}\x{9450}\x{9451}\x{9452}\x{9453}' . + '\x{9454}\x{9455}\x{9456}\x{9457}\x{9458}\x{9459}\x{945B}\x{945C}\x{945D}' . + '\x{945E}\x{945F}\x{9460}\x{9461}\x{9462}\x{9463}\x{9464}\x{9465}\x{9466}' . + '\x{9467}\x{9468}\x{9469}\x{946A}\x{946B}\x{946D}\x{946E}\x{946F}\x{9470}' . + '\x{9471}\x{9472}\x{9473}\x{9474}\x{9475}\x{9476}\x{9477}\x{9478}\x{9479}' . + '\x{947A}\x{947C}\x{947D}\x{947E}\x{947F}\x{9480}\x{9481}\x{9482}\x{9483}' . + '\x{9484}\x{9485}\x{9486}\x{9487}\x{9488}\x{9489}\x{948A}\x{948B}\x{948C}' . + '\x{948D}\x{948E}\x{948F}\x{9490}\x{9491}\x{9492}\x{9493}\x{9494}\x{9495}' . + '\x{9496}\x{9497}\x{9498}\x{9499}\x{949A}\x{949B}\x{949C}\x{949D}\x{949E}' . + '\x{949F}\x{94A0}\x{94A1}\x{94A2}\x{94A3}\x{94A4}\x{94A5}\x{94A6}\x{94A7}' . + '\x{94A8}\x{94A9}\x{94AA}\x{94AB}\x{94AC}\x{94AD}\x{94AE}\x{94AF}\x{94B0}' . + '\x{94B1}\x{94B2}\x{94B3}\x{94B4}\x{94B5}\x{94B6}\x{94B7}\x{94B8}\x{94B9}' . + '\x{94BA}\x{94BB}\x{94BC}\x{94BD}\x{94BE}\x{94BF}\x{94C0}\x{94C1}\x{94C2}' . + '\x{94C3}\x{94C4}\x{94C5}\x{94C6}\x{94C7}\x{94C8}\x{94C9}\x{94CA}\x{94CB}' . + '\x{94CC}\x{94CD}\x{94CE}\x{94CF}\x{94D0}\x{94D1}\x{94D2}\x{94D3}\x{94D4}' . + '\x{94D5}\x{94D6}\x{94D7}\x{94D8}\x{94D9}\x{94DA}\x{94DB}\x{94DC}\x{94DD}' . + '\x{94DE}\x{94DF}\x{94E0}\x{94E1}\x{94E2}\x{94E3}\x{94E4}\x{94E5}\x{94E6}' . + '\x{94E7}\x{94E8}\x{94E9}\x{94EA}\x{94EB}\x{94EC}\x{94ED}\x{94EE}\x{94EF}' . + '\x{94F0}\x{94F1}\x{94F2}\x{94F3}\x{94F4}\x{94F5}\x{94F6}\x{94F7}\x{94F8}' . + '\x{94F9}\x{94FA}\x{94FB}\x{94FC}\x{94FD}\x{94FE}\x{94FF}\x{9500}\x{9501}' . + '\x{9502}\x{9503}\x{9504}\x{9505}\x{9506}\x{9507}\x{9508}\x{9509}\x{950A}' . + '\x{950B}\x{950C}\x{950D}\x{950E}\x{950F}\x{9510}\x{9511}\x{9512}\x{9513}' . + '\x{9514}\x{9515}\x{9516}\x{9517}\x{9518}\x{9519}\x{951A}\x{951B}\x{951C}' . + '\x{951D}\x{951E}\x{951F}\x{9520}\x{9521}\x{9522}\x{9523}\x{9524}\x{9525}' . + '\x{9526}\x{9527}\x{9528}\x{9529}\x{952A}\x{952B}\x{952C}\x{952D}\x{952E}' . + '\x{952F}\x{9530}\x{9531}\x{9532}\x{9533}\x{9534}\x{9535}\x{9536}\x{9537}' . + '\x{9538}\x{9539}\x{953A}\x{953B}\x{953C}\x{953D}\x{953E}\x{953F}\x{9540}' . + '\x{9541}\x{9542}\x{9543}\x{9544}\x{9545}\x{9546}\x{9547}\x{9548}\x{9549}' . + '\x{954A}\x{954B}\x{954C}\x{954D}\x{954E}\x{954F}\x{9550}\x{9551}\x{9552}' . + '\x{9553}\x{9554}\x{9555}\x{9556}\x{9557}\x{9558}\x{9559}\x{955A}\x{955B}' . + '\x{955C}\x{955D}\x{955E}\x{955F}\x{9560}\x{9561}\x{9562}\x{9563}\x{9564}' . + '\x{9565}\x{9566}\x{9567}\x{9568}\x{9569}\x{956A}\x{956B}\x{956C}\x{956D}' . + '\x{956E}\x{956F}\x{9570}\x{9571}\x{9572}\x{9573}\x{9574}\x{9575}\x{9576}' . + '\x{9577}\x{957A}\x{957B}\x{957C}\x{957D}\x{957F}\x{9580}\x{9581}\x{9582}' . + '\x{9583}\x{9584}\x{9586}\x{9587}\x{9588}\x{9589}\x{958A}\x{958B}\x{958C}' . + '\x{958D}\x{958E}\x{958F}\x{9590}\x{9591}\x{9592}\x{9593}\x{9594}\x{9595}' . + '\x{9596}\x{9598}\x{9599}\x{959A}\x{959B}\x{959C}\x{959D}\x{959E}\x{959F}' . + '\x{95A1}\x{95A2}\x{95A3}\x{95A4}\x{95A5}\x{95A6}\x{95A7}\x{95A8}\x{95A9}' . + '\x{95AA}\x{95AB}\x{95AC}\x{95AD}\x{95AE}\x{95AF}\x{95B0}\x{95B1}\x{95B2}' . + '\x{95B5}\x{95B6}\x{95B7}\x{95B9}\x{95BA}\x{95BB}\x{95BC}\x{95BD}\x{95BE}' . + '\x{95BF}\x{95C0}\x{95C2}\x{95C3}\x{95C4}\x{95C5}\x{95C6}\x{95C7}\x{95C8}' . + '\x{95C9}\x{95CA}\x{95CB}\x{95CC}\x{95CD}\x{95CE}\x{95CF}\x{95D0}\x{95D1}' . + '\x{95D2}\x{95D3}\x{95D4}\x{95D5}\x{95D6}\x{95D7}\x{95D8}\x{95DA}\x{95DB}' . + '\x{95DC}\x{95DE}\x{95DF}\x{95E0}\x{95E1}\x{95E2}\x{95E3}\x{95E4}\x{95E5}' . + '\x{95E6}\x{95E7}\x{95E8}\x{95E9}\x{95EA}\x{95EB}\x{95EC}\x{95ED}\x{95EE}' . + '\x{95EF}\x{95F0}\x{95F1}\x{95F2}\x{95F3}\x{95F4}\x{95F5}\x{95F6}\x{95F7}' . + '\x{95F8}\x{95F9}\x{95FA}\x{95FB}\x{95FC}\x{95FD}\x{95FE}\x{95FF}\x{9600}' . + '\x{9601}\x{9602}\x{9603}\x{9604}\x{9605}\x{9606}\x{9607}\x{9608}\x{9609}' . + '\x{960A}\x{960B}\x{960C}\x{960D}\x{960E}\x{960F}\x{9610}\x{9611}\x{9612}' . + '\x{9613}\x{9614}\x{9615}\x{9616}\x{9617}\x{9618}\x{9619}\x{961A}\x{961B}' . + '\x{961C}\x{961D}\x{961E}\x{961F}\x{9620}\x{9621}\x{9622}\x{9623}\x{9624}' . + '\x{9627}\x{9628}\x{962A}\x{962B}\x{962C}\x{962D}\x{962E}\x{962F}\x{9630}' . + '\x{9631}\x{9632}\x{9633}\x{9634}\x{9635}\x{9636}\x{9637}\x{9638}\x{9639}' . + '\x{963A}\x{963B}\x{963C}\x{963D}\x{963F}\x{9640}\x{9641}\x{9642}\x{9643}' . + '\x{9644}\x{9645}\x{9646}\x{9647}\x{9648}\x{9649}\x{964A}\x{964B}\x{964C}' . + '\x{964D}\x{964E}\x{964F}\x{9650}\x{9651}\x{9652}\x{9653}\x{9654}\x{9655}' . + '\x{9658}\x{9659}\x{965A}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9660}' . + '\x{9661}\x{9662}\x{9663}\x{9664}\x{9666}\x{9667}\x{9668}\x{9669}\x{966A}' . + '\x{966B}\x{966C}\x{966D}\x{966E}\x{966F}\x{9670}\x{9671}\x{9672}\x{9673}' . + '\x{9674}\x{9675}\x{9676}\x{9677}\x{9678}\x{967C}\x{967D}\x{967E}\x{9680}' . + '\x{9683}\x{9684}\x{9685}\x{9686}\x{9687}\x{9688}\x{9689}\x{968A}\x{968B}' . + '\x{968D}\x{968E}\x{968F}\x{9690}\x{9691}\x{9692}\x{9693}\x{9694}\x{9695}' . + '\x{9697}\x{9698}\x{9699}\x{969B}\x{969C}\x{969E}\x{96A0}\x{96A1}\x{96A2}' . + '\x{96A3}\x{96A4}\x{96A5}\x{96A6}\x{96A7}\x{96A8}\x{96A9}\x{96AA}\x{96AC}' . + '\x{96AD}\x{96AE}\x{96B0}\x{96B1}\x{96B3}\x{96B4}\x{96B6}\x{96B7}\x{96B8}' . + '\x{96B9}\x{96BA}\x{96BB}\x{96BC}\x{96BD}\x{96BE}\x{96BF}\x{96C0}\x{96C1}' . + '\x{96C2}\x{96C3}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C8}\x{96C9}\x{96CA}' . + '\x{96CB}\x{96CC}\x{96CD}\x{96CE}\x{96CF}\x{96D0}\x{96D1}\x{96D2}\x{96D3}' . + '\x{96D4}\x{96D5}\x{96D6}\x{96D7}\x{96D8}\x{96D9}\x{96DA}\x{96DB}\x{96DC}' . + '\x{96DD}\x{96DE}\x{96DF}\x{96E0}\x{96E1}\x{96E2}\x{96E3}\x{96E5}\x{96E8}' . + '\x{96E9}\x{96EA}\x{96EB}\x{96EC}\x{96ED}\x{96EE}\x{96EF}\x{96F0}\x{96F1}' . + '\x{96F2}\x{96F3}\x{96F4}\x{96F5}\x{96F6}\x{96F7}\x{96F8}\x{96F9}\x{96FA}' . + '\x{96FB}\x{96FD}\x{96FE}\x{96FF}\x{9700}\x{9701}\x{9702}\x{9703}\x{9704}' . + '\x{9705}\x{9706}\x{9707}\x{9708}\x{9709}\x{970A}\x{970B}\x{970C}\x{970D}' . + '\x{970E}\x{970F}\x{9710}\x{9711}\x{9712}\x{9713}\x{9715}\x{9716}\x{9718}' . + '\x{9719}\x{971C}\x{971D}\x{971E}\x{971F}\x{9720}\x{9721}\x{9722}\x{9723}' . + '\x{9724}\x{9725}\x{9726}\x{9727}\x{9728}\x{9729}\x{972A}\x{972B}\x{972C}' . + '\x{972D}\x{972E}\x{972F}\x{9730}\x{9731}\x{9732}\x{9735}\x{9736}\x{9738}' . + '\x{9739}\x{973A}\x{973B}\x{973C}\x{973D}\x{973E}\x{973F}\x{9742}\x{9743}' . + '\x{9744}\x{9745}\x{9746}\x{9747}\x{9748}\x{9749}\x{974A}\x{974B}\x{974C}' . + '\x{974E}\x{974F}\x{9750}\x{9751}\x{9752}\x{9753}\x{9754}\x{9755}\x{9756}' . + '\x{9758}\x{9759}\x{975A}\x{975B}\x{975C}\x{975D}\x{975E}\x{975F}\x{9760}' . + '\x{9761}\x{9762}\x{9765}\x{9766}\x{9767}\x{9768}\x{9769}\x{976A}\x{976B}' . + '\x{976C}\x{976D}\x{976E}\x{976F}\x{9770}\x{9772}\x{9773}\x{9774}\x{9776}' . + '\x{9777}\x{9778}\x{9779}\x{977A}\x{977B}\x{977C}\x{977D}\x{977E}\x{977F}' . + '\x{9780}\x{9781}\x{9782}\x{9783}\x{9784}\x{9785}\x{9786}\x{9788}\x{978A}' . + '\x{978B}\x{978C}\x{978D}\x{978E}\x{978F}\x{9790}\x{9791}\x{9792}\x{9793}' . + '\x{9794}\x{9795}\x{9796}\x{9797}\x{9798}\x{9799}\x{979A}\x{979C}\x{979D}' . + '\x{979E}\x{979F}\x{97A0}\x{97A1}\x{97A2}\x{97A3}\x{97A4}\x{97A5}\x{97A6}' . + '\x{97A7}\x{97A8}\x{97AA}\x{97AB}\x{97AC}\x{97AD}\x{97AE}\x{97AF}\x{97B2}' . + '\x{97B3}\x{97B4}\x{97B6}\x{97B7}\x{97B8}\x{97B9}\x{97BA}\x{97BB}\x{97BC}' . + '\x{97BD}\x{97BF}\x{97C1}\x{97C2}\x{97C3}\x{97C4}\x{97C5}\x{97C6}\x{97C7}' . + '\x{97C8}\x{97C9}\x{97CA}\x{97CB}\x{97CC}\x{97CD}\x{97CE}\x{97CF}\x{97D0}' . + '\x{97D1}\x{97D3}\x{97D4}\x{97D5}\x{97D6}\x{97D7}\x{97D8}\x{97D9}\x{97DA}' . + '\x{97DB}\x{97DC}\x{97DD}\x{97DE}\x{97DF}\x{97E0}\x{97E1}\x{97E2}\x{97E3}' . + '\x{97E4}\x{97E5}\x{97E6}\x{97E7}\x{97E8}\x{97E9}\x{97EA}\x{97EB}\x{97EC}' . + '\x{97ED}\x{97EE}\x{97EF}\x{97F0}\x{97F1}\x{97F2}\x{97F3}\x{97F4}\x{97F5}' . + '\x{97F6}\x{97F7}\x{97F8}\x{97F9}\x{97FA}\x{97FB}\x{97FD}\x{97FE}\x{97FF}' . + '\x{9800}\x{9801}\x{9802}\x{9803}\x{9804}\x{9805}\x{9806}\x{9807}\x{9808}' . + '\x{9809}\x{980A}\x{980B}\x{980C}\x{980D}\x{980E}\x{980F}\x{9810}\x{9811}' . + '\x{9812}\x{9813}\x{9814}\x{9815}\x{9816}\x{9817}\x{9818}\x{9819}\x{981A}' . + '\x{981B}\x{981C}\x{981D}\x{981E}\x{9820}\x{9821}\x{9822}\x{9823}\x{9824}' . + '\x{9826}\x{9827}\x{9828}\x{9829}\x{982B}\x{982D}\x{982E}\x{982F}\x{9830}' . + '\x{9831}\x{9832}\x{9834}\x{9835}\x{9836}\x{9837}\x{9838}\x{9839}\x{983B}' . + '\x{983C}\x{983D}\x{983F}\x{9840}\x{9841}\x{9843}\x{9844}\x{9845}\x{9846}' . + '\x{9848}\x{9849}\x{984A}\x{984C}\x{984D}\x{984E}\x{984F}\x{9850}\x{9851}' . + '\x{9852}\x{9853}\x{9854}\x{9855}\x{9857}\x{9858}\x{9859}\x{985A}\x{985B}' . + '\x{985C}\x{985D}\x{985E}\x{985F}\x{9860}\x{9861}\x{9862}\x{9863}\x{9864}' . + '\x{9865}\x{9867}\x{9869}\x{986A}\x{986B}\x{986C}\x{986D}\x{986E}\x{986F}' . + '\x{9870}\x{9871}\x{9872}\x{9873}\x{9874}\x{9875}\x{9876}\x{9877}\x{9878}' . + '\x{9879}\x{987A}\x{987B}\x{987C}\x{987D}\x{987E}\x{987F}\x{9880}\x{9881}' . + '\x{9882}\x{9883}\x{9884}\x{9885}\x{9886}\x{9887}\x{9888}\x{9889}\x{988A}' . + '\x{988B}\x{988C}\x{988D}\x{988E}\x{988F}\x{9890}\x{9891}\x{9892}\x{9893}' . + '\x{9894}\x{9895}\x{9896}\x{9897}\x{9898}\x{9899}\x{989A}\x{989B}\x{989C}' . + '\x{989D}\x{989E}\x{989F}\x{98A0}\x{98A1}\x{98A2}\x{98A3}\x{98A4}\x{98A5}' . + '\x{98A6}\x{98A7}\x{98A8}\x{98A9}\x{98AA}\x{98AB}\x{98AC}\x{98AD}\x{98AE}' . + '\x{98AF}\x{98B0}\x{98B1}\x{98B2}\x{98B3}\x{98B4}\x{98B5}\x{98B6}\x{98B8}' . + '\x{98B9}\x{98BA}\x{98BB}\x{98BC}\x{98BD}\x{98BE}\x{98BF}\x{98C0}\x{98C1}' . + '\x{98C2}\x{98C3}\x{98C4}\x{98C5}\x{98C6}\x{98C8}\x{98C9}\x{98CB}\x{98CC}' . + '\x{98CD}\x{98CE}\x{98CF}\x{98D0}\x{98D1}\x{98D2}\x{98D3}\x{98D4}\x{98D5}' . + '\x{98D6}\x{98D7}\x{98D8}\x{98D9}\x{98DA}\x{98DB}\x{98DC}\x{98DD}\x{98DE}' . + '\x{98DF}\x{98E0}\x{98E2}\x{98E3}\x{98E5}\x{98E6}\x{98E7}\x{98E8}\x{98E9}' . + '\x{98EA}\x{98EB}\x{98ED}\x{98EF}\x{98F0}\x{98F2}\x{98F3}\x{98F4}\x{98F5}' . + '\x{98F6}\x{98F7}\x{98F9}\x{98FA}\x{98FC}\x{98FD}\x{98FE}\x{98FF}\x{9900}' . + '\x{9901}\x{9902}\x{9903}\x{9904}\x{9905}\x{9906}\x{9907}\x{9908}\x{9909}' . + '\x{990A}\x{990B}\x{990C}\x{990D}\x{990E}\x{990F}\x{9910}\x{9911}\x{9912}' . + '\x{9913}\x{9914}\x{9915}\x{9916}\x{9917}\x{9918}\x{991A}\x{991B}\x{991C}' . + '\x{991D}\x{991E}\x{991F}\x{9920}\x{9921}\x{9922}\x{9923}\x{9924}\x{9925}' . + '\x{9926}\x{9927}\x{9928}\x{9929}\x{992A}\x{992B}\x{992C}\x{992D}\x{992E}' . + '\x{992F}\x{9930}\x{9931}\x{9932}\x{9933}\x{9934}\x{9935}\x{9936}\x{9937}' . + '\x{9938}\x{9939}\x{993A}\x{993C}\x{993D}\x{993E}\x{993F}\x{9940}\x{9941}' . + '\x{9942}\x{9943}\x{9945}\x{9946}\x{9947}\x{9948}\x{9949}\x{994A}\x{994B}' . + '\x{994C}\x{994E}\x{994F}\x{9950}\x{9951}\x{9952}\x{9953}\x{9954}\x{9955}' . + '\x{9956}\x{9957}\x{9958}\x{9959}\x{995B}\x{995C}\x{995E}\x{995F}\x{9960}' . + '\x{9961}\x{9962}\x{9963}\x{9964}\x{9965}\x{9966}\x{9967}\x{9968}\x{9969}' . + '\x{996A}\x{996B}\x{996C}\x{996D}\x{996E}\x{996F}\x{9970}\x{9971}\x{9972}' . + '\x{9973}\x{9974}\x{9975}\x{9976}\x{9977}\x{9978}\x{9979}\x{997A}\x{997B}' . + '\x{997C}\x{997D}\x{997E}\x{997F}\x{9980}\x{9981}\x{9982}\x{9983}\x{9984}' . + '\x{9985}\x{9986}\x{9987}\x{9988}\x{9989}\x{998A}\x{998B}\x{998C}\x{998D}' . + '\x{998E}\x{998F}\x{9990}\x{9991}\x{9992}\x{9993}\x{9994}\x{9995}\x{9996}' . + '\x{9997}\x{9998}\x{9999}\x{999A}\x{999B}\x{999C}\x{999D}\x{999E}\x{999F}' . + '\x{99A0}\x{99A1}\x{99A2}\x{99A3}\x{99A4}\x{99A5}\x{99A6}\x{99A7}\x{99A8}' . + '\x{99A9}\x{99AA}\x{99AB}\x{99AC}\x{99AD}\x{99AE}\x{99AF}\x{99B0}\x{99B1}' . + '\x{99B2}\x{99B3}\x{99B4}\x{99B5}\x{99B6}\x{99B7}\x{99B8}\x{99B9}\x{99BA}' . + '\x{99BB}\x{99BC}\x{99BD}\x{99BE}\x{99C0}\x{99C1}\x{99C2}\x{99C3}\x{99C4}' . + '\x{99C6}\x{99C7}\x{99C8}\x{99C9}\x{99CA}\x{99CB}\x{99CC}\x{99CD}\x{99CE}' . + '\x{99CF}\x{99D0}\x{99D1}\x{99D2}\x{99D3}\x{99D4}\x{99D5}\x{99D6}\x{99D7}' . + '\x{99D8}\x{99D9}\x{99DA}\x{99DB}\x{99DC}\x{99DD}\x{99DE}\x{99DF}\x{99E1}' . + '\x{99E2}\x{99E3}\x{99E4}\x{99E5}\x{99E7}\x{99E8}\x{99E9}\x{99EA}\x{99EC}' . + '\x{99ED}\x{99EE}\x{99EF}\x{99F0}\x{99F1}\x{99F2}\x{99F3}\x{99F4}\x{99F6}' . + '\x{99F7}\x{99F8}\x{99F9}\x{99FA}\x{99FB}\x{99FC}\x{99FD}\x{99FE}\x{99FF}' . + '\x{9A00}\x{9A01}\x{9A02}\x{9A03}\x{9A04}\x{9A05}\x{9A06}\x{9A07}\x{9A08}' . + '\x{9A09}\x{9A0A}\x{9A0B}\x{9A0C}\x{9A0D}\x{9A0E}\x{9A0F}\x{9A11}\x{9A14}' . + '\x{9A15}\x{9A16}\x{9A19}\x{9A1A}\x{9A1B}\x{9A1C}\x{9A1D}\x{9A1E}\x{9A1F}' . + '\x{9A20}\x{9A21}\x{9A22}\x{9A23}\x{9A24}\x{9A25}\x{9A26}\x{9A27}\x{9A29}' . + '\x{9A2A}\x{9A2B}\x{9A2C}\x{9A2D}\x{9A2E}\x{9A2F}\x{9A30}\x{9A31}\x{9A32}' . + '\x{9A33}\x{9A34}\x{9A35}\x{9A36}\x{9A37}\x{9A38}\x{9A39}\x{9A3A}\x{9A3C}' . + '\x{9A3D}\x{9A3E}\x{9A3F}\x{9A40}\x{9A41}\x{9A42}\x{9A43}\x{9A44}\x{9A45}' . + '\x{9A46}\x{9A47}\x{9A48}\x{9A49}\x{9A4A}\x{9A4B}\x{9A4C}\x{9A4D}\x{9A4E}' . + '\x{9A4F}\x{9A50}\x{9A52}\x{9A53}\x{9A54}\x{9A55}\x{9A56}\x{9A57}\x{9A59}' . + '\x{9A5A}\x{9A5B}\x{9A5C}\x{9A5E}\x{9A5F}\x{9A60}\x{9A61}\x{9A62}\x{9A64}' . + '\x{9A65}\x{9A66}\x{9A67}\x{9A68}\x{9A69}\x{9A6A}\x{9A6B}\x{9A6C}\x{9A6D}' . + '\x{9A6E}\x{9A6F}\x{9A70}\x{9A71}\x{9A72}\x{9A73}\x{9A74}\x{9A75}\x{9A76}' . + '\x{9A77}\x{9A78}\x{9A79}\x{9A7A}\x{9A7B}\x{9A7C}\x{9A7D}\x{9A7E}\x{9A7F}' . + '\x{9A80}\x{9A81}\x{9A82}\x{9A83}\x{9A84}\x{9A85}\x{9A86}\x{9A87}\x{9A88}' . + '\x{9A89}\x{9A8A}\x{9A8B}\x{9A8C}\x{9A8D}\x{9A8E}\x{9A8F}\x{9A90}\x{9A91}' . + '\x{9A92}\x{9A93}\x{9A94}\x{9A95}\x{9A96}\x{9A97}\x{9A98}\x{9A99}\x{9A9A}' . + '\x{9A9B}\x{9A9C}\x{9A9D}\x{9A9E}\x{9A9F}\x{9AA0}\x{9AA1}\x{9AA2}\x{9AA3}' . + '\x{9AA4}\x{9AA5}\x{9AA6}\x{9AA7}\x{9AA8}\x{9AAA}\x{9AAB}\x{9AAC}\x{9AAD}' . + '\x{9AAE}\x{9AAF}\x{9AB0}\x{9AB1}\x{9AB2}\x{9AB3}\x{9AB4}\x{9AB5}\x{9AB6}' . + '\x{9AB7}\x{9AB8}\x{9AB9}\x{9ABA}\x{9ABB}\x{9ABC}\x{9ABE}\x{9ABF}\x{9AC0}' . + '\x{9AC1}\x{9AC2}\x{9AC3}\x{9AC4}\x{9AC5}\x{9AC6}\x{9AC7}\x{9AC9}\x{9ACA}' . + '\x{9ACB}\x{9ACC}\x{9ACD}\x{9ACE}\x{9ACF}\x{9AD0}\x{9AD1}\x{9AD2}\x{9AD3}' . + '\x{9AD4}\x{9AD5}\x{9AD6}\x{9AD8}\x{9AD9}\x{9ADA}\x{9ADB}\x{9ADC}\x{9ADD}' . + '\x{9ADE}\x{9ADF}\x{9AE1}\x{9AE2}\x{9AE3}\x{9AE5}\x{9AE6}\x{9AE7}\x{9AEA}' . + '\x{9AEB}\x{9AEC}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF2}\x{9AF3}\x{9AF4}' . + '\x{9AF5}\x{9AF6}\x{9AF7}\x{9AF8}\x{9AF9}\x{9AFA}\x{9AFB}\x{9AFC}\x{9AFD}' . + '\x{9AFE}\x{9AFF}\x{9B01}\x{9B03}\x{9B04}\x{9B05}\x{9B06}\x{9B07}\x{9B08}' . + '\x{9B0A}\x{9B0B}\x{9B0C}\x{9B0D}\x{9B0E}\x{9B0F}\x{9B10}\x{9B11}\x{9B12}' . + '\x{9B13}\x{9B15}\x{9B16}\x{9B17}\x{9B18}\x{9B19}\x{9B1A}\x{9B1C}\x{9B1D}' . + '\x{9B1E}\x{9B1F}\x{9B20}\x{9B21}\x{9B22}\x{9B23}\x{9B24}\x{9B25}\x{9B26}' . + '\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2B}\x{9B2C}\x{9B2D}\x{9B2E}\x{9B2F}' . + '\x{9B30}\x{9B31}\x{9B32}\x{9B33}\x{9B35}\x{9B36}\x{9B37}\x{9B38}\x{9B39}' . + '\x{9B3A}\x{9B3B}\x{9B3C}\x{9B3E}\x{9B3F}\x{9B41}\x{9B42}\x{9B43}\x{9B44}' . + '\x{9B45}\x{9B46}\x{9B47}\x{9B48}\x{9B49}\x{9B4A}\x{9B4B}\x{9B4C}\x{9B4D}' . + '\x{9B4E}\x{9B4F}\x{9B51}\x{9B52}\x{9B53}\x{9B54}\x{9B55}\x{9B56}\x{9B58}' . + '\x{9B59}\x{9B5A}\x{9B5B}\x{9B5C}\x{9B5D}\x{9B5E}\x{9B5F}\x{9B60}\x{9B61}' . + '\x{9B63}\x{9B64}\x{9B65}\x{9B66}\x{9B67}\x{9B68}\x{9B69}\x{9B6A}\x{9B6B}' . + '\x{9B6C}\x{9B6D}\x{9B6E}\x{9B6F}\x{9B70}\x{9B71}\x{9B73}\x{9B74}\x{9B75}' . + '\x{9B76}\x{9B77}\x{9B78}\x{9B79}\x{9B7A}\x{9B7B}\x{9B7C}\x{9B7D}\x{9B7E}' . + '\x{9B7F}\x{9B80}\x{9B81}\x{9B82}\x{9B83}\x{9B84}\x{9B85}\x{9B86}\x{9B87}' . + '\x{9B88}\x{9B8A}\x{9B8B}\x{9B8D}\x{9B8E}\x{9B8F}\x{9B90}\x{9B91}\x{9B92}' . + '\x{9B93}\x{9B94}\x{9B95}\x{9B96}\x{9B97}\x{9B98}\x{9B9A}\x{9B9B}\x{9B9C}' . + '\x{9B9D}\x{9B9E}\x{9B9F}\x{9BA0}\x{9BA1}\x{9BA2}\x{9BA3}\x{9BA4}\x{9BA5}' . + '\x{9BA6}\x{9BA7}\x{9BA8}\x{9BA9}\x{9BAA}\x{9BAB}\x{9BAC}\x{9BAD}\x{9BAE}' . + '\x{9BAF}\x{9BB0}\x{9BB1}\x{9BB2}\x{9BB3}\x{9BB4}\x{9BB5}\x{9BB6}\x{9BB7}' . + '\x{9BB8}\x{9BB9}\x{9BBA}\x{9BBB}\x{9BBC}\x{9BBD}\x{9BBE}\x{9BBF}\x{9BC0}' . + '\x{9BC1}\x{9BC3}\x{9BC4}\x{9BC5}\x{9BC6}\x{9BC7}\x{9BC8}\x{9BC9}\x{9BCA}' . + '\x{9BCB}\x{9BCC}\x{9BCD}\x{9BCE}\x{9BCF}\x{9BD0}\x{9BD1}\x{9BD2}\x{9BD3}' . + '\x{9BD4}\x{9BD5}\x{9BD6}\x{9BD7}\x{9BD8}\x{9BD9}\x{9BDA}\x{9BDB}\x{9BDC}' . + '\x{9BDD}\x{9BDE}\x{9BDF}\x{9BE0}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}\x{9BE5}' . + '\x{9BE6}\x{9BE7}\x{9BE8}\x{9BE9}\x{9BEA}\x{9BEB}\x{9BEC}\x{9BED}\x{9BEE}' . + '\x{9BEF}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF3}\x{9BF4}\x{9BF5}\x{9BF7}\x{9BF8}' . + '\x{9BF9}\x{9BFA}\x{9BFB}\x{9BFC}\x{9BFD}\x{9BFE}\x{9BFF}\x{9C02}\x{9C05}' . + '\x{9C06}\x{9C07}\x{9C08}\x{9C09}\x{9C0A}\x{9C0B}\x{9C0C}\x{9C0D}\x{9C0E}' . + '\x{9C0F}\x{9C10}\x{9C11}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C16}\x{9C17}' . + '\x{9C18}\x{9C19}\x{9C1A}\x{9C1B}\x{9C1C}\x{9C1D}\x{9C1E}\x{9C1F}\x{9C20}' . + '\x{9C21}\x{9C22}\x{9C23}\x{9C24}\x{9C25}\x{9C26}\x{9C27}\x{9C28}\x{9C29}' . + '\x{9C2A}\x{9C2B}\x{9C2C}\x{9C2D}\x{9C2F}\x{9C30}\x{9C31}\x{9C32}\x{9C33}' . + '\x{9C34}\x{9C35}\x{9C36}\x{9C37}\x{9C38}\x{9C39}\x{9C3A}\x{9C3B}\x{9C3C}' . + '\x{9C3D}\x{9C3E}\x{9C3F}\x{9C40}\x{9C41}\x{9C43}\x{9C44}\x{9C45}\x{9C46}' . + '\x{9C47}\x{9C48}\x{9C49}\x{9C4A}\x{9C4B}\x{9C4C}\x{9C4D}\x{9C4E}\x{9C50}' . + '\x{9C52}\x{9C53}\x{9C54}\x{9C55}\x{9C56}\x{9C57}\x{9C58}\x{9C59}\x{9C5A}' . + '\x{9C5B}\x{9C5C}\x{9C5D}\x{9C5E}\x{9C5F}\x{9C60}\x{9C62}\x{9C63}\x{9C65}' . + '\x{9C66}\x{9C67}\x{9C68}\x{9C69}\x{9C6A}\x{9C6B}\x{9C6C}\x{9C6D}\x{9C6E}' . + '\x{9C6F}\x{9C70}\x{9C71}\x{9C72}\x{9C73}\x{9C74}\x{9C75}\x{9C77}\x{9C78}' . + '\x{9C79}\x{9C7A}\x{9C7C}\x{9C7D}\x{9C7E}\x{9C7F}\x{9C80}\x{9C81}\x{9C82}' . + '\x{9C83}\x{9C84}\x{9C85}\x{9C86}\x{9C87}\x{9C88}\x{9C89}\x{9C8A}\x{9C8B}' . + '\x{9C8C}\x{9C8D}\x{9C8E}\x{9C8F}\x{9C90}\x{9C91}\x{9C92}\x{9C93}\x{9C94}' . + '\x{9C95}\x{9C96}\x{9C97}\x{9C98}\x{9C99}\x{9C9A}\x{9C9B}\x{9C9C}\x{9C9D}' . + '\x{9C9E}\x{9C9F}\x{9CA0}\x{9CA1}\x{9CA2}\x{9CA3}\x{9CA4}\x{9CA5}\x{9CA6}' . + '\x{9CA7}\x{9CA8}\x{9CA9}\x{9CAA}\x{9CAB}\x{9CAC}\x{9CAD}\x{9CAE}\x{9CAF}' . + '\x{9CB0}\x{9CB1}\x{9CB2}\x{9CB3}\x{9CB4}\x{9CB5}\x{9CB6}\x{9CB7}\x{9CB8}' . + '\x{9CB9}\x{9CBA}\x{9CBB}\x{9CBC}\x{9CBD}\x{9CBE}\x{9CBF}\x{9CC0}\x{9CC1}' . + '\x{9CC2}\x{9CC3}\x{9CC4}\x{9CC5}\x{9CC6}\x{9CC7}\x{9CC8}\x{9CC9}\x{9CCA}' . + '\x{9CCB}\x{9CCC}\x{9CCD}\x{9CCE}\x{9CCF}\x{9CD0}\x{9CD1}\x{9CD2}\x{9CD3}' . + '\x{9CD4}\x{9CD5}\x{9CD6}\x{9CD7}\x{9CD8}\x{9CD9}\x{9CDA}\x{9CDB}\x{9CDC}' . + '\x{9CDD}\x{9CDE}\x{9CDF}\x{9CE0}\x{9CE1}\x{9CE2}\x{9CE3}\x{9CE4}\x{9CE5}' . + '\x{9CE6}\x{9CE7}\x{9CE8}\x{9CE9}\x{9CEA}\x{9CEB}\x{9CEC}\x{9CED}\x{9CEE}' . + '\x{9CEF}\x{9CF0}\x{9CF1}\x{9CF2}\x{9CF3}\x{9CF4}\x{9CF5}\x{9CF6}\x{9CF7}' . + '\x{9CF8}\x{9CF9}\x{9CFA}\x{9CFB}\x{9CFC}\x{9CFD}\x{9CFE}\x{9CFF}\x{9D00}' . + '\x{9D01}\x{9D02}\x{9D03}\x{9D04}\x{9D05}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' . + '\x{9D0A}\x{9D0B}\x{9D0F}\x{9D10}\x{9D12}\x{9D13}\x{9D14}\x{9D15}\x{9D16}' . + '\x{9D17}\x{9D18}\x{9D19}\x{9D1A}\x{9D1B}\x{9D1C}\x{9D1D}\x{9D1E}\x{9D1F}' . + '\x{9D20}\x{9D21}\x{9D22}\x{9D23}\x{9D24}\x{9D25}\x{9D26}\x{9D28}\x{9D29}' . + '\x{9D2B}\x{9D2D}\x{9D2E}\x{9D2F}\x{9D30}\x{9D31}\x{9D32}\x{9D33}\x{9D34}' . + '\x{9D36}\x{9D37}\x{9D38}\x{9D39}\x{9D3A}\x{9D3B}\x{9D3D}\x{9D3E}\x{9D3F}' . + '\x{9D40}\x{9D41}\x{9D42}\x{9D43}\x{9D45}\x{9D46}\x{9D47}\x{9D48}\x{9D49}' . + '\x{9D4A}\x{9D4B}\x{9D4C}\x{9D4D}\x{9D4E}\x{9D4F}\x{9D50}\x{9D51}\x{9D52}' . + '\x{9D53}\x{9D54}\x{9D55}\x{9D56}\x{9D57}\x{9D58}\x{9D59}\x{9D5A}\x{9D5B}' . + '\x{9D5C}\x{9D5D}\x{9D5E}\x{9D5F}\x{9D60}\x{9D61}\x{9D62}\x{9D63}\x{9D64}' . + '\x{9D65}\x{9D66}\x{9D67}\x{9D68}\x{9D69}\x{9D6A}\x{9D6B}\x{9D6C}\x{9D6E}' . + '\x{9D6F}\x{9D70}\x{9D71}\x{9D72}\x{9D73}\x{9D74}\x{9D75}\x{9D76}\x{9D77}' . + '\x{9D78}\x{9D79}\x{9D7A}\x{9D7B}\x{9D7C}\x{9D7D}\x{9D7E}\x{9D7F}\x{9D80}' . + '\x{9D81}\x{9D82}\x{9D83}\x{9D84}\x{9D85}\x{9D86}\x{9D87}\x{9D88}\x{9D89}' . + '\x{9D8A}\x{9D8B}\x{9D8C}\x{9D8D}\x{9D8E}\x{9D90}\x{9D91}\x{9D92}\x{9D93}' . + '\x{9D94}\x{9D96}\x{9D97}\x{9D98}\x{9D99}\x{9D9A}\x{9D9B}\x{9D9C}\x{9D9D}' . + '\x{9D9E}\x{9D9F}\x{9DA0}\x{9DA1}\x{9DA2}\x{9DA3}\x{9DA4}\x{9DA5}\x{9DA6}' . + '\x{9DA7}\x{9DA8}\x{9DA9}\x{9DAA}\x{9DAB}\x{9DAC}\x{9DAD}\x{9DAF}\x{9DB0}' . + '\x{9DB1}\x{9DB2}\x{9DB3}\x{9DB4}\x{9DB5}\x{9DB6}\x{9DB7}\x{9DB8}\x{9DB9}' . + '\x{9DBA}\x{9DBB}\x{9DBC}\x{9DBE}\x{9DBF}\x{9DC1}\x{9DC2}\x{9DC3}\x{9DC4}' . + '\x{9DC5}\x{9DC7}\x{9DC8}\x{9DC9}\x{9DCA}\x{9DCB}\x{9DCC}\x{9DCD}\x{9DCE}' . + '\x{9DCF}\x{9DD0}\x{9DD1}\x{9DD2}\x{9DD3}\x{9DD4}\x{9DD5}\x{9DD6}\x{9DD7}' . + '\x{9DD8}\x{9DD9}\x{9DDA}\x{9DDB}\x{9DDC}\x{9DDD}\x{9DDE}\x{9DDF}\x{9DE0}' . + '\x{9DE1}\x{9DE2}\x{9DE3}\x{9DE4}\x{9DE5}\x{9DE6}\x{9DE7}\x{9DE8}\x{9DE9}' . + '\x{9DEB}\x{9DEC}\x{9DED}\x{9DEE}\x{9DEF}\x{9DF0}\x{9DF1}\x{9DF2}\x{9DF3}' . + '\x{9DF4}\x{9DF5}\x{9DF6}\x{9DF7}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFB}\x{9DFD}' . + '\x{9DFE}\x{9DFF}\x{9E00}\x{9E01}\x{9E02}\x{9E03}\x{9E04}\x{9E05}\x{9E06}' . + '\x{9E07}\x{9E08}\x{9E09}\x{9E0A}\x{9E0B}\x{9E0C}\x{9E0D}\x{9E0F}\x{9E10}' . + '\x{9E11}\x{9E12}\x{9E13}\x{9E14}\x{9E15}\x{9E17}\x{9E18}\x{9E19}\x{9E1A}' . + '\x{9E1B}\x{9E1D}\x{9E1E}\x{9E1F}\x{9E20}\x{9E21}\x{9E22}\x{9E23}\x{9E24}' . + '\x{9E25}\x{9E26}\x{9E27}\x{9E28}\x{9E29}\x{9E2A}\x{9E2B}\x{9E2C}\x{9E2D}' . + '\x{9E2E}\x{9E2F}\x{9E30}\x{9E31}\x{9E32}\x{9E33}\x{9E34}\x{9E35}\x{9E36}' . + '\x{9E37}\x{9E38}\x{9E39}\x{9E3A}\x{9E3B}\x{9E3C}\x{9E3D}\x{9E3E}\x{9E3F}' . + '\x{9E40}\x{9E41}\x{9E42}\x{9E43}\x{9E44}\x{9E45}\x{9E46}\x{9E47}\x{9E48}' . + '\x{9E49}\x{9E4A}\x{9E4B}\x{9E4C}\x{9E4D}\x{9E4E}\x{9E4F}\x{9E50}\x{9E51}' . + '\x{9E52}\x{9E53}\x{9E54}\x{9E55}\x{9E56}\x{9E57}\x{9E58}\x{9E59}\x{9E5A}' . + '\x{9E5B}\x{9E5C}\x{9E5D}\x{9E5E}\x{9E5F}\x{9E60}\x{9E61}\x{9E62}\x{9E63}' . + '\x{9E64}\x{9E65}\x{9E66}\x{9E67}\x{9E68}\x{9E69}\x{9E6A}\x{9E6B}\x{9E6C}' . + '\x{9E6D}\x{9E6E}\x{9E6F}\x{9E70}\x{9E71}\x{9E72}\x{9E73}\x{9E74}\x{9E75}' . + '\x{9E76}\x{9E77}\x{9E79}\x{9E7A}\x{9E7C}\x{9E7D}\x{9E7E}\x{9E7F}\x{9E80}' . + '\x{9E81}\x{9E82}\x{9E83}\x{9E84}\x{9E85}\x{9E86}\x{9E87}\x{9E88}\x{9E89}' . + '\x{9E8A}\x{9E8B}\x{9E8C}\x{9E8D}\x{9E8E}\x{9E91}\x{9E92}\x{9E93}\x{9E94}' . + '\x{9E96}\x{9E97}\x{9E99}\x{9E9A}\x{9E9B}\x{9E9C}\x{9E9D}\x{9E9F}\x{9EA0}' . + '\x{9EA1}\x{9EA3}\x{9EA4}\x{9EA5}\x{9EA6}\x{9EA7}\x{9EA8}\x{9EA9}\x{9EAA}' . + '\x{9EAD}\x{9EAE}\x{9EAF}\x{9EB0}\x{9EB2}\x{9EB3}\x{9EB4}\x{9EB5}\x{9EB6}' . + '\x{9EB7}\x{9EB8}\x{9EBB}\x{9EBC}\x{9EBD}\x{9EBE}\x{9EBF}\x{9EC0}\x{9EC1}' . + '\x{9EC2}\x{9EC3}\x{9EC4}\x{9EC5}\x{9EC6}\x{9EC7}\x{9EC8}\x{9EC9}\x{9ECA}' . + '\x{9ECB}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED1}\x{9ED2}\x{9ED3}' . + '\x{9ED4}\x{9ED5}\x{9ED6}\x{9ED7}\x{9ED8}\x{9ED9}\x{9EDA}\x{9EDB}\x{9EDC}' . + '\x{9EDD}\x{9EDE}\x{9EDF}\x{9EE0}\x{9EE1}\x{9EE2}\x{9EE3}\x{9EE4}\x{9EE5}' . + '\x{9EE6}\x{9EE7}\x{9EE8}\x{9EE9}\x{9EEA}\x{9EEB}\x{9EED}\x{9EEE}\x{9EEF}' . + '\x{9EF0}\x{9EF2}\x{9EF3}\x{9EF4}\x{9EF5}\x{9EF6}\x{9EF7}\x{9EF8}\x{9EF9}' . + '\x{9EFA}\x{9EFB}\x{9EFC}\x{9EFD}\x{9EFE}\x{9EFF}\x{9F00}\x{9F01}\x{9F02}' . + '\x{9F04}\x{9F05}\x{9F06}\x{9F07}\x{9F08}\x{9F09}\x{9F0A}\x{9F0B}\x{9F0C}' . + '\x{9F0D}\x{9F0E}\x{9F0F}\x{9F10}\x{9F12}\x{9F13}\x{9F15}\x{9F16}\x{9F17}' . + '\x{9F18}\x{9F19}\x{9F1A}\x{9F1B}\x{9F1C}\x{9F1D}\x{9F1E}\x{9F1F}\x{9F20}' . + '\x{9F22}\x{9F23}\x{9F24}\x{9F25}\x{9F27}\x{9F28}\x{9F29}\x{9F2A}\x{9F2B}' . + '\x{9F2C}\x{9F2D}\x{9F2E}\x{9F2F}\x{9F30}\x{9F31}\x{9F32}\x{9F33}\x{9F34}' . + '\x{9F35}\x{9F36}\x{9F37}\x{9F38}\x{9F39}\x{9F3A}\x{9F3B}\x{9F3C}\x{9F3D}' . + '\x{9F3E}\x{9F3F}\x{9F40}\x{9F41}\x{9F42}\x{9F43}\x{9F44}\x{9F46}\x{9F47}' . + '\x{9F48}\x{9F49}\x{9F4A}\x{9F4B}\x{9F4C}\x{9F4D}\x{9F4E}\x{9F4F}\x{9F50}' . + '\x{9F51}\x{9F52}\x{9F54}\x{9F55}\x{9F56}\x{9F57}\x{9F58}\x{9F59}\x{9F5A}' . + '\x{9F5B}\x{9F5C}\x{9F5D}\x{9F5E}\x{9F5F}\x{9F60}\x{9F61}\x{9F63}\x{9F64}' . + '\x{9F65}\x{9F66}\x{9F67}\x{9F68}\x{9F69}\x{9F6A}\x{9F6B}\x{9F6C}\x{9F6E}' . + '\x{9F6F}\x{9F70}\x{9F71}\x{9F72}\x{9F73}\x{9F74}\x{9F75}\x{9F76}\x{9F77}' . + '\x{9F78}\x{9F79}\x{9F7A}\x{9F7B}\x{9F7C}\x{9F7D}\x{9F7E}\x{9F7F}\x{9F80}' . + '\x{9F81}\x{9F82}\x{9F83}\x{9F84}\x{9F85}\x{9F86}\x{9F87}\x{9F88}\x{9F89}' . + '\x{9F8A}\x{9F8B}\x{9F8C}\x{9F8D}\x{9F8E}\x{9F8F}\x{9F90}\x{9F91}\x{9F92}' . + '\x{9F93}\x{9F94}\x{9F95}\x{9F96}\x{9F97}\x{9F98}\x{9F99}\x{9F9A}\x{9F9B}' . + '\x{9F9C}\x{9F9D}\x{9F9E}\x{9F9F}\x{9FA0}\x{9FA2}\x{9FA4}\x{9FA5}]{1,20}$/iu', +]; diff --git a/lib/laminas/laminas-validator/src/Hostname/Com.php b/lib/laminas/laminas-validator/src/Hostname/Com.php new file mode 100644 index 000000000..06f071d90 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname/Com.php @@ -0,0 +1,181 @@ + '/^[\x{002d}0-9\x{0400}-\x{052f}]{1,63}$/iu', + 2 => '/^[\x{002d}0-9\x{0370}-\x{03ff}]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-z\x{ac00}-\x{d7a3}]{1,17}$/iu', + // @codingStandardsIgnoreStart + 4 => '/^[\x{002d}0-9a-z·à-öø-ÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĸĺļľłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżž]{1,63}$/iu', + // @codingStandardsIgnoreEnd + 5 => '/^[\x{002d}0-9A-Za-z\x{3400}-\x{3401}\x{3404}-\x{3406}\x{340C}\x{3416}\x{341C}' . +'\x{3421}\x{3424}\x{3428}-\x{3429}\x{342B}-\x{342E}\x{3430}-\x{3434}\x{3436}' . +'\x{3438}-\x{343C}\x{343E}\x{3441}-\x{3445}\x{3447}\x{3449}-\x{3451}\x{3453}' . +'\x{3457}-\x{345F}\x{3463}-\x{3467}\x{346E}-\x{3471}\x{3473}-\x{3477}\x{3479}-\x{348E}\x{3491}-\x{3497}' . +'\x{3499}-\x{34A1}\x{34A4}-\x{34AD}\x{34AF}-\x{34B0}\x{34B2}-\x{34BF}\x{34C2}-\x{34C5}\x{34C7}-\x{34CC}' . +'\x{34CE}-\x{34D1}\x{34D3}-\x{34D8}\x{34DA}-\x{34E4}\x{34E7}-\x{34E9}\x{34EC}-\x{34EF}\x{34F1}-\x{34FE}' . +'\x{3500}-\x{3507}\x{350A}-\x{3513}\x{3515}\x{3517}-\x{351A}\x{351C}-\x{351E}\x{3520}-\x{352A}' . +'\x{352C}-\x{3552}\x{3554}-\x{355C}\x{355E}-\x{3567}\x{3569}-\x{3573}\x{3575}-\x{357C}\x{3580}-\x{3588}' . +'\x{358F}-\x{3598}\x{359E}-\x{35AB}\x{35B4}-\x{35CD}\x{35D0}\x{35D3}-\x{35DC}\x{35E2}-\x{35ED}' . +'\x{35F0}-\x{35F6}\x{35FB}-\x{3602}\x{3605}-\x{360E}\x{3610}-\x{3611}\x{3613}-\x{3616}\x{3619}-\x{362D}' . +'\x{362F}-\x{3634}\x{3636}-\x{363B}\x{363F}-\x{3645}\x{3647}-\x{364B}\x{364D}-\x{3653}\x{3655}' . +'\x{3659}-\x{365E}\x{3660}-\x{3665}\x{3667}-\x{367C}\x{367E}\x{3680}-\x{3685}\x{3687}' . +'\x{3689}-\x{3690}\x{3692}-\x{3698}\x{369A}\x{369C}-\x{36AE}\x{36B0}-\x{36BF}\x{36C1}-\x{36C5}' . +'\x{36C9}-\x{36CA}\x{36CD}-\x{36DE}\x{36E1}-\x{36E2}\x{36E5}-\x{36FE}\x{3701}-\x{3713}\x{3715}-\x{371E}' . +'\x{3720}-\x{372C}\x{372E}-\x{3745}\x{3747}-\x{3748}\x{374A}\x{374C}-\x{3759}\x{375B}-\x{3760}' . +'\x{3762}-\x{3767}\x{3769}-\x{3772}\x{3774}-\x{378C}\x{378F}-\x{379C}\x{379F}\x{37A1}-\x{37AD}' . +'\x{37AF}-\x{37B7}\x{37B9}-\x{37C1}\x{37C3}-\x{37C5}\x{37C7}-\x{37D4}\x{37D6}-\x{37E0}\x{37E2}' . +'\x{37E5}-\x{37ED}\x{37EF}-\x{37F6}\x{37F8}-\x{3802}\x{3804}-\x{381D}\x{3820}-\x{3822}\x{3825}-\x{382A}' . +'\x{382D}-\x{382F}\x{3831}-\x{3832}\x{3834}-\x{384C}\x{384E}-\x{3860}\x{3862}-\x{3863}\x{3865}-\x{386B}' . +'\x{386D}-\x{3886}\x{3888}-\x{38A1}\x{38A3}\x{38A5}-\x{38AA}\x{38AC}\x{38AE}-\x{38B0}' . +'\x{38B2}-\x{38B6}\x{38B8}\x{38BA}-\x{38BE}\x{38C0}-\x{38C9}\x{38CB}-\x{38D4}\x{38D8}-\x{38E0}' . +'\x{38E2}-\x{38E6}\x{38EB}-\x{38ED}\x{38EF}-\x{38F2}\x{38F5}-\x{38F7}\x{38FA}-\x{38FF}\x{3901}-\x{392A}' . +'\x{392C}\x{392E}-\x{393B}\x{393E}-\x{3956}\x{395A}-\x{3969}\x{396B}-\x{397A}\x{397C}-\x{3987}' . +'\x{3989}-\x{3998}\x{399A}-\x{39B0}\x{39B2}\x{39B4}-\x{39D0}\x{39D2}-\x{39DA}\x{39DE}-\x{39DF}' . +'\x{39E1}-\x{39EF}\x{39F1}-\x{3A17}\x{3A19}-\x{3A2A}\x{3A2D}-\x{3A40}\x{3A43}-\x{3A4E}\x{3A50}' . +'\x{3A52}-\x{3A5E}\x{3A60}-\x{3A6D}\x{3A6F}-\x{3A77}\x{3A79}-\x{3A82}\x{3A84}-\x{3A85}\x{3A87}-\x{3A89}' . +'\x{3A8B}-\x{3A8F}\x{3A91}-\x{3A93}\x{3A95}-\x{3A96}\x{3A9A}\x{3A9C}-\x{3AA6}\x{3AA8}-\x{3AA9}' . +'\x{3AAB}-\x{3AB1}\x{3AB4}-\x{3ABC}\x{3ABE}-\x{3AC5}\x{3ACA}-\x{3ACB}\x{3ACD}-\x{3AD5}\x{3AD7}-\x{3AE1}' . +'\x{3AE4}-\x{3AE7}\x{3AE9}-\x{3AEC}\x{3AEE}-\x{3AFD}\x{3B01}-\x{3B10}\x{3B12}-\x{3B15}\x{3B17}-\x{3B1E}' . +'\x{3B20}-\x{3B23}\x{3B25}-\x{3B27}\x{3B29}-\x{3B36}\x{3B38}-\x{3B39}\x{3B3B}-\x{3B3C}\x{3B3F}' . +'\x{3B41}-\x{3B44}\x{3B47}-\x{3B4C}\x{3B4E}\x{3B51}-\x{3B55}\x{3B58}-\x{3B62}\x{3B68}-\x{3B72}' . +'\x{3B78}-\x{3B88}\x{3B8B}-\x{3B9F}\x{3BA1}\x{3BA3}-\x{3BBA}\x{3BBC}\x{3BBF}-\x{3BD0}' . +'\x{3BD3}-\x{3BE6}\x{3BEA}-\x{3BFB}\x{3BFE}-\x{3C12}\x{3C14}-\x{3C1B}\x{3C1D}-\x{3C37}\x{3C39}-\x{3C4F}' . +'\x{3C52}\x{3C54}-\x{3C5C}\x{3C5E}-\x{3C68}\x{3C6A}-\x{3C76}\x{3C78}-\x{3C8F}\x{3C91}-\x{3CA8}' . +'\x{3CAA}-\x{3CAD}\x{3CAF}-\x{3CBE}\x{3CC0}-\x{3CC8}\x{3CCA}-\x{3CD3}\x{3CD6}-\x{3CE0}\x{3CE4}-\x{3CEE}' . +'\x{3CF3}-\x{3D0A}\x{3D0E}-\x{3D1E}\x{3D20}-\x{3D21}\x{3D25}-\x{3D38}\x{3D3B}-\x{3D46}\x{3D4A}-\x{3D59}' . +'\x{3D5D}-\x{3D7B}\x{3D7D}-\x{3D81}\x{3D84}-\x{3D88}\x{3D8C}-\x{3D8F}\x{3D91}-\x{3D98}\x{3D9A}-\x{3D9C}' . +'\x{3D9E}-\x{3DA1}\x{3DA3}-\x{3DB0}\x{3DB2}-\x{3DB5}\x{3DB9}-\x{3DBC}\x{3DBE}-\x{3DCB}\x{3DCD}-\x{3DDB}' . +'\x{3DDF}-\x{3DE8}\x{3DEB}-\x{3DF0}\x{3DF3}-\x{3DF9}\x{3DFB}-\x{3DFC}\x{3DFE}-\x{3E05}\x{3E08}-\x{3E33}' . +'\x{3E35}-\x{3E3E}\x{3E40}-\x{3E47}\x{3E49}-\x{3E67}\x{3E6B}-\x{3E6F}\x{3E71}-\x{3E85}\x{3E87}-\x{3E8C}' . +'\x{3E8E}-\x{3E98}\x{3E9A}-\x{3EA1}\x{3EA3}-\x{3EAE}\x{3EB0}-\x{3EB5}\x{3EB7}-\x{3EBA}\x{3EBD}' . +'\x{3EBF}-\x{3EC4}\x{3EC7}-\x{3ECE}\x{3ED1}-\x{3ED7}\x{3ED9}-\x{3EDA}\x{3EDD}-\x{3EE3}\x{3EE7}-\x{3EE8}' . +'\x{3EEB}-\x{3EF2}\x{3EF5}-\x{3EFF}\x{3F01}-\x{3F02}\x{3F04}-\x{3F07}\x{3F09}-\x{3F44}\x{3F46}-\x{3F4E}' . +'\x{3F50}-\x{3F53}\x{3F55}-\x{3F72}\x{3F74}-\x{3F75}\x{3F77}-\x{3F7B}\x{3F7D}-\x{3FB0}\x{3FB6}-\x{3FBF}' . +'\x{3FC1}-\x{3FCF}\x{3FD1}-\x{3FD3}\x{3FD5}-\x{3FDF}\x{3FE1}-\x{400B}\x{400D}-\x{401C}\x{401E}-\x{4024}' . +'\x{4027}-\x{403F}\x{4041}-\x{4060}\x{4062}-\x{4069}\x{406B}-\x{408A}\x{408C}-\x{40A7}\x{40A9}-\x{40B4}' . +'\x{40B6}-\x{40C2}\x{40C7}-\x{40CF}\x{40D1}-\x{40DE}\x{40E0}-\x{40E7}\x{40E9}-\x{40EE}\x{40F0}-\x{40FB}' . +'\x{40FD}-\x{4109}\x{410B}-\x{4115}\x{4118}-\x{411D}\x{411F}-\x{4122}\x{4124}-\x{4133}\x{4136}-\x{4138}' . +'\x{413A}-\x{4148}\x{414A}-\x{4169}\x{416C}-\x{4185}\x{4188}-\x{418B}\x{418D}-\x{41AD}\x{41AF}-\x{41B3}' . +'\x{41B5}-\x{41C3}\x{41C5}-\x{41C9}\x{41CB}-\x{41F2}\x{41F5}-\x{41FE}\x{4200}-\x{4227}\x{422A}-\x{4246}' . +'\x{4248}-\x{4263}\x{4265}-\x{428B}\x{428D}-\x{42A1}\x{42A3}-\x{42C4}\x{42C8}-\x{42DC}\x{42DE}-\x{430A}' . +'\x{430C}-\x{4335}\x{4337}\x{4342}-\x{435F}\x{4361}-\x{439A}\x{439C}-\x{439D}\x{439F}-\x{43A4}' . +'\x{43A6}-\x{43EC}\x{43EF}-\x{4405}\x{4407}-\x{4429}\x{442B}-\x{4455}\x{4457}-\x{4468}\x{446A}-\x{446D}' . +'\x{446F}-\x{4476}\x{4479}-\x{447D}\x{447F}-\x{4486}\x{4488}-\x{4490}\x{4492}-\x{4498}\x{449A}-\x{44AD}' . +'\x{44B0}-\x{44BD}\x{44C1}-\x{44D3}\x{44D6}-\x{44E7}\x{44EA}\x{44EC}-\x{44FA}\x{44FC}-\x{4541}' . +'\x{4543}-\x{454F}\x{4551}-\x{4562}\x{4564}-\x{4575}\x{4577}-\x{45AB}\x{45AD}-\x{45BD}\x{45BF}-\x{45D5}' . +'\x{45D7}-\x{45EC}\x{45EE}-\x{45F2}\x{45F4}-\x{45FA}\x{45FC}-\x{461A}\x{461C}-\x{461D}\x{461F}-\x{4631}' . +'\x{4633}-\x{4649}\x{464C}\x{464E}-\x{4652}\x{4654}-\x{466A}\x{466C}-\x{4675}\x{4677}-\x{467A}' . +'\x{467C}-\x{4694}\x{4696}-\x{46A3}\x{46A5}-\x{46AB}\x{46AD}-\x{46D2}\x{46D4}-\x{4723}\x{4729}-\x{4732}' . +'\x{4734}-\x{4758}\x{475A}\x{475C}-\x{478B}\x{478D}\x{4791}-\x{47B1}\x{47B3}-\x{47F1}' . +'\x{47F3}-\x{480B}\x{480D}-\x{4815}\x{4817}-\x{4839}\x{483B}-\x{4870}\x{4872}-\x{487A}\x{487C}-\x{487F}' . +'\x{4883}-\x{488E}\x{4890}-\x{4896}\x{4899}-\x{48A2}\x{48A4}-\x{48B9}\x{48BB}-\x{48C8}\x{48CA}-\x{48D1}' . +'\x{48D3}-\x{48E5}\x{48E7}-\x{48F2}\x{48F4}-\x{48FF}\x{4901}-\x{4922}\x{4924}-\x{4928}\x{492A}-\x{4931}' . +'\x{4933}-\x{495B}\x{495D}-\x{4978}\x{497A}\x{497D}\x{4982}-\x{4983}\x{4985}-\x{49A8}' . +'\x{49AA}-\x{49AF}\x{49B1}-\x{49B7}\x{49B9}-\x{49BD}\x{49C1}-\x{49C7}\x{49C9}-\x{49CE}\x{49D0}-\x{49E8}' . +'\x{49EA}\x{49EC}\x{49EE}-\x{4A19}\x{4A1B}-\x{4A43}\x{4A45}-\x{4A4D}\x{4A4F}-\x{4A9E}' . +'\x{4AA0}-\x{4AA9}\x{4AAB}-\x{4B4E}\x{4B50}-\x{4B5B}\x{4B5D}-\x{4B69}\x{4B6B}-\x{4BC2}\x{4BC6}-\x{4BE8}' . +'\x{4BEA}-\x{4BFA}\x{4BFC}-\x{4C06}\x{4C08}-\x{4C2D}\x{4C2F}-\x{4C32}\x{4C34}-\x{4C35}\x{4C37}-\x{4C69}' . +'\x{4C6B}-\x{4C73}\x{4C75}-\x{4C86}\x{4C88}-\x{4C97}\x{4C99}-\x{4C9C}\x{4C9F}-\x{4CA3}\x{4CA5}-\x{4CB5}' . +'\x{4CB7}-\x{4CF8}\x{4CFA}-\x{4D27}\x{4D29}-\x{4DAC}\x{4DAE}-\x{4DB1}\x{4DB3}-\x{4DB5}\x{4E00}-\x{4E54}' . +'\x{4E56}-\x{4E89}\x{4E8B}-\x{4EEC}\x{4EEE}-\x{4FAC}\x{4FAE}-\x{503C}\x{503E}-\x{51E5}\x{51E7}-\x{5270}' . +'\x{5272}-\x{56A1}\x{56A3}-\x{5840}\x{5842}-\x{58B5}\x{58B7}-\x{58CB}\x{58CD}-\x{5BC8}\x{5BCA}-\x{5C01}' . +'\x{5C03}-\x{5C25}\x{5C27}-\x{5D5B}\x{5D5D}-\x{5F08}\x{5F0A}-\x{61F3}\x{61F5}-\x{63BA}\x{63BC}-\x{6441}' . +'\x{6443}-\x{657C}\x{657E}-\x{663E}\x{6640}-\x{66FC}\x{66FE}-\x{6728}\x{672A}-\x{6766}\x{6768}-\x{67A8}' . +'\x{67AA}-\x{685B}\x{685D}-\x{685E}\x{6860}-\x{68B9}\x{68BB}-\x{6AC8}\x{6ACA}-\x{6BB0}\x{6BB2}-\x{6C16}' . +'\x{6C18}-\x{6D9B}\x{6D9D}-\x{6E12}\x{6E14}-\x{6E8B}\x{6E8D}-\x{704D}\x{704F}-\x{7113}\x{7115}-\x{713B}' . +'\x{713D}-\x{7154}\x{7156}-\x{729F}\x{72A1}-\x{731E}\x{7320}-\x{7362}\x{7364}-\x{7533}\x{7535}-\x{7551}' . +'\x{7553}-\x{7572}\x{7574}-\x{75E8}\x{75EA}-\x{7679}\x{767B}-\x{783E}\x{7840}-\x{7A62}\x{7A64}-\x{7AC2}' . +'\x{7AC4}-\x{7B06}\x{7B08}-\x{7B79}\x{7B7B}-\x{7BCE}\x{7BD0}-\x{7D99}\x{7D9B}-\x{7E49}\x{7E4C}-\x{8132}' . +'\x{8134}\x{8136}-\x{81D2}\x{81D4}-\x{8216}\x{8218}-\x{822D}\x{822F}-\x{83B4}\x{83B6}-\x{841F}' . +'\x{8421}-\x{86CC}\x{86CE}-\x{874A}\x{874C}-\x{877E}\x{8780}-\x{8A32}\x{8A34}-\x{8B71}\x{8B73}-\x{8B8E}' . +'\x{8B90}-\x{8DE4}\x{8DE6}-\x{8E9A}\x{8E9C}-\x{8EE1}\x{8EE4}-\x{8F0B}\x{8F0D}-\x{8FB9}\x{8FBB}-\x{9038}' . +'\x{903A}-\x{9196}\x{9198}-\x{91A3}\x{91A5}-\x{91B7}\x{91B9}-\x{91C7}\x{91C9}-\x{91E0}\x{91E2}-\x{91FB}' . +'\x{91FD}-\x{922B}\x{922D}-\x{9270}\x{9272}-\x{9420}\x{9422}-\x{9664}\x{9666}-\x{9679}\x{967B}-\x{9770}' . +'\x{9772}-\x{982B}\x{982D}-\x{98ED}\x{98EF}-\x{99C4}\x{99C6}-\x{9A11}\x{9A14}-\x{9A27}\x{9A29}-\x{9D0D}' . +'\x{9D0F}-\x{9D2B}\x{9D2D}-\x{9D8E}\x{9D90}-\x{9DC5}\x{9DC7}-\x{9E77}\x{9E79}-\x{9EB8}\x{9EBB}-\x{9F20}' . +'\x{9F22}-\x{9F61}\x{9F63}-\x{9FA5}\x{FA28}]{1,20}$/iu', + 6 => '/^[\x{002d}0-9A-Za-z]{1,63}$/iu', + 7 => '/^[\x{00A1}-\x{00FF}]{1,63}$/iu', + 8 => '/^[\x{0100}-\x{017f}]{1,63}$/iu', + 9 => '/^[\x{0180}-\x{024f}]{1,63}$/iu', + 10 => '/^[\x{0250}-\x{02af}]{1,63}$/iu', + 11 => '/^[\x{02b0}-\x{02ff}]{1,63}$/iu', + 12 => '/^[\x{0300}-\x{036f}]{1,63}$/iu', + 13 => '/^[\x{0370}-\x{03ff}]{1,63}$/iu', + 14 => '/^[\x{0400}-\x{04ff}]{1,63}$/iu', + 15 => '/^[\x{0500}-\x{052f}]{1,63}$/iu', + 16 => '/^[\x{0530}-\x{058F}]{1,63}$/iu', + 17 => '/^[\x{0590}-\x{05FF}]{1,63}$/iu', + 18 => '/^[\x{0600}-\x{06FF}]{1,63}$/iu', + 19 => '/^[\x{0700}-\x{074F}]{1,63}$/iu', + 20 => '/^[\x{0780}-\x{07BF}]{1,63}$/iu', + 21 => '/^[\x{0900}-\x{097F}]{1,63}$/iu', + 22 => '/^[\x{0980}-\x{09FF}]{1,63}$/iu', + 23 => '/^[\x{0A00}-\x{0A7F}]{1,63}$/iu', + 24 => '/^[\x{0A80}-\x{0AFF}]{1,63}$/iu', + 25 => '/^[\x{0B00}-\x{0B7F}]{1,63}$/iu', + 26 => '/^[\x{0B80}-\x{0BFF}]{1,63}$/iu', + 27 => '/^[\x{0C00}-\x{0C7F}]{1,63}$/iu', + 28 => '/^[\x{0C80}-\x{0CFF}]{1,63}$/iu', + 29 => '/^[\x{0D00}-\x{0D7F}]{1,63}$/iu', + 30 => '/^[\x{0D80}-\x{0DFF}]{1,63}$/iu', + 31 => '/^[\x{0E00}-\x{0E7F}]{1,63}$/iu', + 32 => '/^[\x{0E80}-\x{0EFF}]{1,63}$/iu', + 33 => '/^[\x{0F00}-\x{0FFF}]{1,63}$/iu', + 34 => '/^[\x{1000}-\x{109F}]{1,63}$/iu', + 35 => '/^[\x{10A0}-\x{10FF}]{1,63}$/iu', + 36 => '/^[\x{1100}-\x{11FF}]{1,63}$/iu', + 37 => '/^[\x{1200}-\x{137F}]{1,63}$/iu', + 38 => '/^[\x{13A0}-\x{13FF}]{1,63}$/iu', + 39 => '/^[\x{1400}-\x{167F}]{1,63}$/iu', + 40 => '/^[\x{1680}-\x{169F}]{1,63}$/iu', + 41 => '/^[\x{16A0}-\x{16FF}]{1,63}$/iu', + 42 => '/^[\x{1700}-\x{171F}]{1,63}$/iu', + 43 => '/^[\x{1720}-\x{173F}]{1,63}$/iu', + 44 => '/^[\x{1740}-\x{175F}]{1,63}$/iu', + 45 => '/^[\x{1760}-\x{177F}]{1,63}$/iu', + 46 => '/^[\x{1780}-\x{17FF}]{1,63}$/iu', + 47 => '/^[\x{1800}-\x{18AF}]{1,63}$/iu', + 48 => '/^[\x{1E00}-\x{1EFF}]{1,63}$/iu', + 49 => '/^[\x{1F00}-\x{1FFF}]{1,63}$/iu', + 50 => '/^[\x{2070}-\x{209F}]{1,63}$/iu', + 51 => '/^[\x{2100}-\x{214F}]{1,63}$/iu', + 52 => '/^[\x{2150}-\x{218F}]{1,63}$/iu', + 53 => '/^[\x{2460}-\x{24FF}]{1,63}$/iu', + 54 => '/^[\x{2E80}-\x{2EFF}]{1,63}$/iu', + 55 => '/^[\x{2F00}-\x{2FDF}]{1,63}$/iu', + 56 => '/^[\x{2FF0}-\x{2FFF}]{1,63}$/iu', + 57 => '/^[\x{3040}-\x{309F}]{1,63}$/iu', + 58 => '/^[\x{30A0}-\x{30FF}]{1,63}$/iu', + 59 => '/^[\x{3100}-\x{312F}]{1,63}$/iu', + 60 => '/^[\x{3130}-\x{318F}]{1,63}$/iu', + 61 => '/^[\x{3190}-\x{319F}]{1,63}$/iu', + 62 => '/^[\x{31A0}-\x{31BF}]{1,63}$/iu', + 63 => '/^[\x{31F0}-\x{31FF}]{1,63}$/iu', + 64 => '/^[\x{3200}-\x{32FF}]{1,63}$/iu', + 65 => '/^[\x{3300}-\x{33FF}]{1,63}$/iu', + 66 => '/^[\x{3400}-\x{4DBF}]{1,63}$/iu', + 67 => '/^[\x{4E00}-\x{9FFF}]{1,63}$/iu', + 68 => '/^[\x{A000}-\x{A48F}]{1,63}$/iu', + 69 => '/^[\x{A490}-\x{A4CF}]{1,63}$/iu', + 70 => '/^[\x{AC00}-\x{D7AF}]{1,63}$/iu', + 73 => '/^[\x{F900}-\x{FAFF}]{1,63}$/iu', + 74 => '/^[\x{FB00}-\x{FB4F}]{1,63}$/iu', + 75 => '/^[\x{FB50}-\x{FDFF}]{1,63}$/iu', + 76 => '/^[\x{FE20}-\x{FE2F}]{1,63}$/iu', + 77 => '/^[\x{FE70}-\x{FEFF}]{1,63}$/iu', + 78 => '/^[\x{FF00}-\x{FFEF}]{1,63}$/iu', + 79 => '/^[\x{20000}-\x{2A6DF}]{1,63}$/iu', + 80 => '/^[\x{2F800}-\x{2FA1F}]{1,63}$/iu', +]; diff --git a/lib/laminas/laminas-validator/src/Hostname/Jp.php b/lib/laminas/laminas-validator/src/Hostname/Jp.php new file mode 100644 index 000000000..160349c16 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname/Jp.php @@ -0,0 +1,724 @@ + '/^[\x{002d}0-9a-z\x{3005}-\x{3007}\x{3041}-\x{3093}\x{309D}\x{309E}' . + '\x{30A1}-\x{30F6}\x{30FC}' . + '\x{30FD}\x{30FE}\x{4E00}\x{4E01}\x{4E03}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' . + '\x{4E0B}\x{4E0D}\x{4E0E}\x{4E10}\x{4E11}\x{4E14}\x{4E15}\x{4E16}\x{4E17}' . + '\x{4E18}\x{4E19}\x{4E1E}\x{4E21}\x{4E26}\x{4E2A}\x{4E2D}\x{4E31}\x{4E32}' . + '\x{4E36}\x{4E38}\x{4E39}\x{4E3B}\x{4E3C}\x{4E3F}\x{4E42}\x{4E43}\x{4E45}' . + '\x{4E4B}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E55}\x{4E56}\x{4E57}\x{4E58}\x{4E59}' . + '\x{4E5D}\x{4E5E}\x{4E5F}\x{4E62}\x{4E71}\x{4E73}\x{4E7E}\x{4E80}\x{4E82}' . + '\x{4E85}\x{4E86}\x{4E88}\x{4E89}\x{4E8A}\x{4E8B}\x{4E8C}\x{4E8E}\x{4E91}' . + '\x{4E92}\x{4E94}\x{4E95}\x{4E98}\x{4E99}\x{4E9B}\x{4E9C}\x{4E9E}\x{4E9F}' . + '\x{4EA0}\x{4EA1}\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA8}\x{4EAB}\x{4EAC}' . + '\x{4EAD}\x{4EAE}\x{4EB0}\x{4EB3}\x{4EB6}\x{4EBA}\x{4EC0}\x{4EC1}\x{4EC2}' . + '\x{4EC4}\x{4EC6}\x{4EC7}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED4}' . + '\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE3}' . + '\x{4EE4}\x{4EE5}\x{4EED}\x{4EEE}\x{4EF0}\x{4EF2}\x{4EF6}\x{4EF7}\x{4EFB}' . + '\x{4F01}\x{4F09}\x{4F0A}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}\x{4F11}\x{4F1A}' . + '\x{4F1C}\x{4F1D}\x{4F2F}\x{4F30}\x{4F34}\x{4F36}\x{4F38}\x{4F3A}\x{4F3C}' . + '\x{4F3D}\x{4F43}\x{4F46}\x{4F47}\x{4F4D}\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}' . + '\x{4F53}\x{4F55}\x{4F57}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}' . + '\x{4F69}\x{4F6F}\x{4F70}\x{4F73}\x{4F75}\x{4F76}\x{4F7B}\x{4F7C}\x{4F7F}' . + '\x{4F83}\x{4F86}\x{4F88}\x{4F8B}\x{4F8D}\x{4F8F}\x{4F91}\x{4F96}\x{4F98}' . + '\x{4F9B}\x{4F9D}\x{4FA0}\x{4FA1}\x{4FAB}\x{4FAD}\x{4FAE}\x{4FAF}\x{4FB5}' . + '\x{4FB6}\x{4FBF}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FCA}\x{4FCE}\x{4FD0}\x{4FD1}' . + '\x{4FD4}\x{4FD7}\x{4FD8}\x{4FDA}\x{4FDB}\x{4FDD}\x{4FDF}\x{4FE1}\x{4FE3}' . + '\x{4FE4}\x{4FE5}\x{4FEE}\x{4FEF}\x{4FF3}\x{4FF5}\x{4FF6}\x{4FF8}\x{4FFA}' . + '\x{4FFE}\x{5005}\x{5006}\x{5009}\x{500B}\x{500D}\x{500F}\x{5011}\x{5012}' . + '\x{5014}\x{5016}\x{5019}\x{501A}\x{501F}\x{5021}\x{5023}\x{5024}\x{5025}' . + '\x{5026}\x{5028}\x{5029}\x{502A}\x{502B}\x{502C}\x{502D}\x{5036}\x{5039}' . + '\x{5043}\x{5047}\x{5048}\x{5049}\x{504F}\x{5050}\x{5055}\x{5056}\x{505A}' . + '\x{505C}\x{5065}\x{506C}\x{5072}\x{5074}\x{5075}\x{5076}\x{5078}\x{507D}' . + '\x{5080}\x{5085}\x{508D}\x{5091}\x{5098}\x{5099}\x{509A}\x{50AC}\x{50AD}' . + '\x{50B2}\x{50B3}\x{50B4}\x{50B5}\x{50B7}\x{50BE}\x{50C2}\x{50C5}\x{50C9}' . + '\x{50CA}\x{50CD}\x{50CF}\x{50D1}\x{50D5}\x{50D6}\x{50DA}\x{50DE}\x{50E3}' . + '\x{50E5}\x{50E7}\x{50ED}\x{50EE}\x{50F5}\x{50F9}\x{50FB}\x{5100}\x{5101}' . + '\x{5102}\x{5104}\x{5109}\x{5112}\x{5114}\x{5115}\x{5116}\x{5118}\x{511A}' . + '\x{511F}\x{5121}\x{512A}\x{5132}\x{5137}\x{513A}\x{513B}\x{513C}\x{513F}' . + '\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' . + '\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5152}\x{5154}\x{515A}\x{515C}' . + '\x{5162}\x{5165}\x{5168}\x{5169}\x{516A}\x{516B}\x{516C}\x{516D}\x{516E}' . + '\x{5171}\x{5175}\x{5176}\x{5177}\x{5178}\x{517C}\x{5180}\x{5182}\x{5185}' . + '\x{5186}\x{5189}\x{518A}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}' . + '\x{5193}\x{5195}\x{5196}\x{5197}\x{5199}\x{51A0}\x{51A2}\x{51A4}\x{51A5}' . + '\x{51A6}\x{51A8}\x{51A9}\x{51AA}\x{51AB}\x{51AC}\x{51B0}\x{51B1}\x{51B2}' . + '\x{51B3}\x{51B4}\x{51B5}\x{51B6}\x{51B7}\x{51BD}\x{51C4}\x{51C5}\x{51C6}' . + '\x{51C9}\x{51CB}\x{51CC}\x{51CD}\x{51D6}\x{51DB}\x{51DC}\x{51DD}\x{51E0}' . + '\x{51E1}\x{51E6}\x{51E7}\x{51E9}\x{51EA}\x{51ED}\x{51F0}\x{51F1}\x{51F5}' . + '\x{51F6}\x{51F8}\x{51F9}\x{51FA}\x{51FD}\x{51FE}\x{5200}\x{5203}\x{5204}' . + '\x{5206}\x{5207}\x{5208}\x{520A}\x{520B}\x{520E}\x{5211}\x{5214}\x{5217}' . + '\x{521D}\x{5224}\x{5225}\x{5227}\x{5229}\x{522A}\x{522E}\x{5230}\x{5233}' . + '\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{5243}\x{5244}\x{5247}' . + '\x{524A}\x{524B}\x{524C}\x{524D}\x{524F}\x{5254}\x{5256}\x{525B}\x{525E}' . + '\x{5263}\x{5264}\x{5265}\x{5269}\x{526A}\x{526F}\x{5270}\x{5271}\x{5272}' . + '\x{5273}\x{5274}\x{5275}\x{527D}\x{527F}\x{5283}\x{5287}\x{5288}\x{5289}' . + '\x{528D}\x{5291}\x{5292}\x{5294}\x{529B}\x{529F}\x{52A0}\x{52A3}\x{52A9}' . + '\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52B1}\x{52B4}\x{52B5}\x{52B9}\x{52BC}' . + '\x{52BE}\x{52C1}\x{52C3}\x{52C5}\x{52C7}\x{52C9}\x{52CD}\x{52D2}\x{52D5}' . + '\x{52D7}\x{52D8}\x{52D9}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}' . + '\x{52E4}\x{52E6}\x{52E7}\x{52F2}\x{52F3}\x{52F5}\x{52F8}\x{52F9}\x{52FA}' . + '\x{52FE}\x{52FF}\x{5301}\x{5302}\x{5305}\x{5306}\x{5308}\x{530D}\x{530F}' . + '\x{5310}\x{5315}\x{5316}\x{5317}\x{5319}\x{531A}\x{531D}\x{5320}\x{5321}' . + '\x{5323}\x{532A}\x{532F}\x{5331}\x{5333}\x{5338}\x{5339}\x{533A}\x{533B}' . + '\x{533F}\x{5340}\x{5341}\x{5343}\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}' . + '\x{534A}\x{534D}\x{5351}\x{5352}\x{5353}\x{5354}\x{5357}\x{5358}\x{535A}' . + '\x{535C}\x{535E}\x{5360}\x{5366}\x{5369}\x{536E}\x{536F}\x{5370}\x{5371}' . + '\x{5373}\x{5374}\x{5375}\x{5377}\x{5378}\x{537B}\x{537F}\x{5382}\x{5384}' . + '\x{5396}\x{5398}\x{539A}\x{539F}\x{53A0}\x{53A5}\x{53A6}\x{53A8}\x{53A9}' . + '\x{53AD}\x{53AE}\x{53B0}\x{53B3}\x{53B6}\x{53BB}\x{53C2}\x{53C3}\x{53C8}' . + '\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}\x{53D4}\x{53D6}\x{53D7}' . + '\x{53D9}\x{53DB}\x{53DF}\x{53E1}\x{53E2}\x{53E3}\x{53E4}\x{53E5}\x{53E8}' . + '\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}\x{53EF}\x{53F0}\x{53F1}' . + '\x{53F2}\x{53F3}\x{53F6}\x{53F7}\x{53F8}\x{53FA}\x{5401}\x{5403}\x{5404}' . + '\x{5408}\x{5409}\x{540A}\x{540B}\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}' . + '\x{5411}\x{541B}\x{541D}\x{541F}\x{5420}\x{5426}\x{5429}\x{542B}\x{542C}' . + '\x{542D}\x{542E}\x{5436}\x{5438}\x{5439}\x{543B}\x{543C}\x{543D}\x{543E}' . + '\x{5440}\x{5442}\x{5446}\x{5448}\x{5449}\x{544A}\x{544E}\x{5451}\x{545F}' . + '\x{5468}\x{546A}\x{5470}\x{5471}\x{5473}\x{5475}\x{5476}\x{5477}\x{547B}' . + '\x{547C}\x{547D}\x{5480}\x{5484}\x{5486}\x{548B}\x{548C}\x{548E}\x{548F}' . + '\x{5490}\x{5492}\x{54A2}\x{54A4}\x{54A5}\x{54A8}\x{54AB}\x{54AC}\x{54AF}' . + '\x{54B2}\x{54B3}\x{54B8}\x{54BC}\x{54BD}\x{54BE}\x{54C0}\x{54C1}\x{54C2}' . + '\x{54C4}\x{54C7}\x{54C8}\x{54C9}\x{54D8}\x{54E1}\x{54E2}\x{54E5}\x{54E6}' . + '\x{54E8}\x{54E9}\x{54ED}\x{54EE}\x{54F2}\x{54FA}\x{54FD}\x{5504}\x{5506}' . + '\x{5507}\x{550F}\x{5510}\x{5514}\x{5516}\x{552E}\x{552F}\x{5531}\x{5533}' . + '\x{5538}\x{5539}\x{553E}\x{5540}\x{5544}\x{5545}\x{5546}\x{554C}\x{554F}' . + '\x{5553}\x{5556}\x{5557}\x{555C}\x{555D}\x{5563}\x{557B}\x{557C}\x{557E}' . + '\x{5580}\x{5583}\x{5584}\x{5587}\x{5589}\x{558A}\x{558B}\x{5598}\x{5599}' . + '\x{559A}\x{559C}\x{559D}\x{559E}\x{559F}\x{55A7}\x{55A8}\x{55A9}\x{55AA}' . + '\x{55AB}\x{55AC}\x{55AE}\x{55B0}\x{55B6}\x{55C4}\x{55C5}\x{55C7}\x{55D4}' . + '\x{55DA}\x{55DC}\x{55DF}\x{55E3}\x{55E4}\x{55F7}\x{55F9}\x{55FD}\x{55FE}' . + '\x{5606}\x{5609}\x{5614}\x{5616}\x{5617}\x{5618}\x{561B}\x{5629}\x{562F}' . + '\x{5631}\x{5632}\x{5634}\x{5636}\x{5638}\x{5642}\x{564C}\x{564E}\x{5650}' . + '\x{565B}\x{5664}\x{5668}\x{566A}\x{566B}\x{566C}\x{5674}\x{5678}\x{567A}' . + '\x{5680}\x{5686}\x{5687}\x{568A}\x{568F}\x{5694}\x{56A0}\x{56A2}\x{56A5}' . + '\x{56AE}\x{56B4}\x{56B6}\x{56BC}\x{56C0}\x{56C1}\x{56C2}\x{56C3}\x{56C8}' . + '\x{56CE}\x{56D1}\x{56D3}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DE}\x{56E0}' . + '\x{56E3}\x{56EE}\x{56F0}\x{56F2}\x{56F3}\x{56F9}\x{56FA}\x{56FD}\x{56FF}' . + '\x{5700}\x{5703}\x{5704}\x{5708}\x{5709}\x{570B}\x{570D}\x{570F}\x{5712}' . + '\x{5713}\x{5716}\x{5718}\x{571C}\x{571F}\x{5726}\x{5727}\x{5728}\x{572D}' . + '\x{5730}\x{5737}\x{5738}\x{573B}\x{5740}\x{5742}\x{5747}\x{574A}\x{574E}' . + '\x{574F}\x{5750}\x{5751}\x{5761}\x{5764}\x{5766}\x{5769}\x{576A}\x{577F}' . + '\x{5782}\x{5788}\x{5789}\x{578B}\x{5793}\x{57A0}\x{57A2}\x{57A3}\x{57A4}' . + '\x{57AA}\x{57B0}\x{57B3}\x{57C0}\x{57C3}\x{57C6}\x{57CB}\x{57CE}\x{57D2}' . + '\x{57D3}\x{57D4}\x{57D6}\x{57DC}\x{57DF}\x{57E0}\x{57E3}\x{57F4}\x{57F7}' . + '\x{57F9}\x{57FA}\x{57FC}\x{5800}\x{5802}\x{5805}\x{5806}\x{580A}\x{580B}' . + '\x{5815}\x{5819}\x{581D}\x{5821}\x{5824}\x{582A}\x{582F}\x{5830}\x{5831}' . + '\x{5834}\x{5835}\x{583A}\x{583D}\x{5840}\x{5841}\x{584A}\x{584B}\x{5851}' . + '\x{5852}\x{5854}\x{5857}\x{5858}\x{5859}\x{585A}\x{585E}\x{5862}\x{5869}' . + '\x{586B}\x{5870}\x{5872}\x{5875}\x{5879}\x{587E}\x{5883}\x{5885}\x{5893}' . + '\x{5897}\x{589C}\x{589F}\x{58A8}\x{58AB}\x{58AE}\x{58B3}\x{58B8}\x{58B9}' . + '\x{58BA}\x{58BB}\x{58BE}\x{58C1}\x{58C5}\x{58C7}\x{58CA}\x{58CC}\x{58D1}' . + '\x{58D3}\x{58D5}\x{58D7}\x{58D8}\x{58D9}\x{58DC}\x{58DE}\x{58DF}\x{58E4}' . + '\x{58E5}\x{58EB}\x{58EC}\x{58EE}\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F7}' . + '\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{5902}\x{5909}\x{590A}\x{590F}' . + '\x{5910}\x{5915}\x{5916}\x{5918}\x{5919}\x{591A}\x{591B}\x{591C}\x{5922}' . + '\x{5925}\x{5927}\x{5929}\x{592A}\x{592B}\x{592C}\x{592D}\x{592E}\x{5931}' . + '\x{5932}\x{5937}\x{5938}\x{593E}\x{5944}\x{5947}\x{5948}\x{5949}\x{594E}' . + '\x{594F}\x{5950}\x{5951}\x{5954}\x{5955}\x{5957}\x{5958}\x{595A}\x{5960}' . + '\x{5962}\x{5965}\x{5967}\x{5968}\x{5969}\x{596A}\x{596C}\x{596E}\x{5973}' . + '\x{5974}\x{5978}\x{597D}\x{5981}\x{5982}\x{5983}\x{5984}\x{598A}\x{598D}' . + '\x{5993}\x{5996}\x{5999}\x{599B}\x{599D}\x{59A3}\x{59A5}\x{59A8}\x{59AC}' . + '\x{59B2}\x{59B9}\x{59BB}\x{59BE}\x{59C6}\x{59C9}\x{59CB}\x{59D0}\x{59D1}' . + '\x{59D3}\x{59D4}\x{59D9}\x{59DA}\x{59DC}\x{59E5}\x{59E6}\x{59E8}\x{59EA}' . + '\x{59EB}\x{59F6}\x{59FB}\x{59FF}\x{5A01}\x{5A03}\x{5A09}\x{5A11}\x{5A18}' . + '\x{5A1A}\x{5A1C}\x{5A1F}\x{5A20}\x{5A25}\x{5A29}\x{5A2F}\x{5A35}\x{5A36}' . + '\x{5A3C}\x{5A40}\x{5A41}\x{5A46}\x{5A49}\x{5A5A}\x{5A62}\x{5A66}\x{5A6A}' . + '\x{5A6C}\x{5A7F}\x{5A92}\x{5A9A}\x{5A9B}\x{5ABC}\x{5ABD}\x{5ABE}\x{5AC1}' . + '\x{5AC2}\x{5AC9}\x{5ACB}\x{5ACC}\x{5AD0}\x{5AD6}\x{5AD7}\x{5AE1}\x{5AE3}' . + '\x{5AE6}\x{5AE9}\x{5AFA}\x{5AFB}\x{5B09}\x{5B0B}\x{5B0C}\x{5B16}\x{5B22}' . + '\x{5B2A}\x{5B2C}\x{5B30}\x{5B32}\x{5B36}\x{5B3E}\x{5B40}\x{5B43}\x{5B45}' . + '\x{5B50}\x{5B51}\x{5B54}\x{5B55}\x{5B57}\x{5B58}\x{5B5A}\x{5B5B}\x{5B5C}' . + '\x{5B5D}\x{5B5F}\x{5B63}\x{5B64}\x{5B65}\x{5B66}\x{5B69}\x{5B6B}\x{5B70}' . + '\x{5B71}\x{5B73}\x{5B75}\x{5B78}\x{5B7A}\x{5B80}\x{5B83}\x{5B85}\x{5B87}' . + '\x{5B88}\x{5B89}\x{5B8B}\x{5B8C}\x{5B8D}\x{5B8F}\x{5B95}\x{5B97}\x{5B98}' . + '\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9F}\x{5BA2}\x{5BA3}\x{5BA4}' . + '\x{5BA5}\x{5BA6}\x{5BAE}\x{5BB0}\x{5BB3}\x{5BB4}\x{5BB5}\x{5BB6}\x{5BB8}' . + '\x{5BB9}\x{5BBF}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BC9}' . + '\x{5BCC}\x{5BD0}\x{5BD2}\x{5BD3}\x{5BD4}\x{5BDB}\x{5BDD}\x{5BDE}\x{5BDF}' . + '\x{5BE1}\x{5BE2}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}\x{5BE8}\x{5BE9}\x{5BEB}' . + '\x{5BEE}\x{5BF0}\x{5BF3}\x{5BF5}\x{5BF6}\x{5BF8}\x{5BFA}\x{5BFE}\x{5BFF}' . + '\x{5C01}\x{5C02}\x{5C04}\x{5C05}\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}' . + '\x{5C0B}\x{5C0D}\x{5C0E}\x{5C0F}\x{5C11}\x{5C13}\x{5C16}\x{5C1A}\x{5C20}' . + '\x{5C22}\x{5C24}\x{5C28}\x{5C2D}\x{5C31}\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}' . + '\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}\x{5C41}\x{5C45}\x{5C46}\x{5C48}' . + '\x{5C4A}\x{5C4B}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C53}\x{5C55}' . + '\x{5C5E}\x{5C60}\x{5C61}\x{5C64}\x{5C65}\x{5C6C}\x{5C6E}\x{5C6F}\x{5C71}' . + '\x{5C76}\x{5C79}\x{5C8C}\x{5C90}\x{5C91}\x{5C94}\x{5CA1}\x{5CA8}\x{5CA9}' . + '\x{5CAB}\x{5CAC}\x{5CB1}\x{5CB3}\x{5CB6}\x{5CB7}\x{5CB8}\x{5CBB}\x{5CBC}' . + '\x{5CBE}\x{5CC5}\x{5CC7}\x{5CD9}\x{5CE0}\x{5CE1}\x{5CE8}\x{5CE9}\x{5CEA}' . + '\x{5CED}\x{5CEF}\x{5CF0}\x{5CF6}\x{5CFA}\x{5CFB}\x{5CFD}\x{5D07}\x{5D0B}' . + '\x{5D0E}\x{5D11}\x{5D14}\x{5D15}\x{5D16}\x{5D17}\x{5D18}\x{5D19}\x{5D1A}' . + '\x{5D1B}\x{5D1F}\x{5D22}\x{5D29}\x{5D4B}\x{5D4C}\x{5D4E}\x{5D50}\x{5D52}' . + '\x{5D5C}\x{5D69}\x{5D6C}\x{5D6F}\x{5D73}\x{5D76}\x{5D82}\x{5D84}\x{5D87}' . + '\x{5D8B}\x{5D8C}\x{5D90}\x{5D9D}\x{5DA2}\x{5DAC}\x{5DAE}\x{5DB7}\x{5DBA}' . + '\x{5DBC}\x{5DBD}\x{5DC9}\x{5DCC}\x{5DCD}\x{5DD2}\x{5DD3}\x{5DD6}\x{5DDB}' . + '\x{5DDD}\x{5DDE}\x{5DE1}\x{5DE3}\x{5DE5}\x{5DE6}\x{5DE7}\x{5DE8}\x{5DEB}' . + '\x{5DEE}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DFB}\x{5DFD}' . + '\x{5DFE}\x{5E02}\x{5E03}\x{5E06}\x{5E0B}\x{5E0C}\x{5E11}\x{5E16}\x{5E19}' . + '\x{5E1A}\x{5E1B}\x{5E1D}\x{5E25}\x{5E2B}\x{5E2D}\x{5E2F}\x{5E30}\x{5E33}' . + '\x{5E36}\x{5E37}\x{5E38}\x{5E3D}\x{5E40}\x{5E43}\x{5E44}\x{5E45}\x{5E47}' . + '\x{5E4C}\x{5E4E}\x{5E54}\x{5E55}\x{5E57}\x{5E5F}\x{5E61}\x{5E62}\x{5E63}' . + '\x{5E64}\x{5E72}\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E78}\x{5E79}\x{5E7A}' . + '\x{5E7B}\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E81}\x{5E83}\x{5E84}\x{5E87}' . + '\x{5E8A}\x{5E8F}\x{5E95}\x{5E96}\x{5E97}\x{5E9A}\x{5E9C}\x{5EA0}\x{5EA6}' . + '\x{5EA7}\x{5EAB}\x{5EAD}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EC1}\x{5EC2}' . + '\x{5EC3}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECF}\x{5ED0}\x{5ED3}\x{5ED6}\x{5EDA}' . + '\x{5EDB}\x{5EDD}\x{5EDF}\x{5EE0}\x{5EE1}\x{5EE2}\x{5EE3}\x{5EE8}\x{5EE9}' . + '\x{5EEC}\x{5EF0}\x{5EF1}\x{5EF3}\x{5EF4}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}' . + '\x{5EFB}\x{5EFC}\x{5EFE}\x{5EFF}\x{5F01}\x{5F03}\x{5F04}\x{5F09}\x{5F0A}' . + '\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F10}\x{5F11}\x{5F13}\x{5F14}\x{5F15}' . + '\x{5F16}\x{5F17}\x{5F18}\x{5F1B}\x{5F1F}\x{5F25}\x{5F26}\x{5F27}\x{5F29}' . + '\x{5F2D}\x{5F2F}\x{5F31}\x{5F35}\x{5F37}\x{5F38}\x{5F3C}\x{5F3E}\x{5F41}' . + '\x{5F48}\x{5F4A}\x{5F4C}\x{5F4E}\x{5F51}\x{5F53}\x{5F56}\x{5F57}\x{5F59}' . + '\x{5F5C}\x{5F5D}\x{5F61}\x{5F62}\x{5F66}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}' . + '\x{5F6D}\x{5F70}\x{5F71}\x{5F73}\x{5F77}\x{5F79}\x{5F7C}\x{5F7F}\x{5F80}' . + '\x{5F81}\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F87}\x{5F88}\x{5F8A}\x{5F8B}' . + '\x{5F8C}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F97}\x{5F98}\x{5F99}\x{5F9E}' . + '\x{5FA0}\x{5FA1}\x{5FA8}\x{5FA9}\x{5FAA}\x{5FAD}\x{5FAE}\x{5FB3}\x{5FB4}' . + '\x{5FB9}\x{5FBC}\x{5FBD}\x{5FC3}\x{5FC5}\x{5FCC}\x{5FCD}\x{5FD6}\x{5FD7}' . + '\x{5FD8}\x{5FD9}\x{5FDC}\x{5FDD}\x{5FE0}\x{5FE4}\x{5FEB}\x{5FF0}\x{5FF1}' . + '\x{5FF5}\x{5FF8}\x{5FFB}\x{5FFD}\x{5FFF}\x{600E}\x{600F}\x{6010}\x{6012}' . + '\x{6015}\x{6016}\x{6019}\x{601B}\x{601C}\x{601D}\x{6020}\x{6021}\x{6025}' . + '\x{6026}\x{6027}\x{6028}\x{6029}\x{602A}\x{602B}\x{602F}\x{6031}\x{603A}' . + '\x{6041}\x{6042}\x{6043}\x{6046}\x{604A}\x{604B}\x{604D}\x{6050}\x{6052}' . + '\x{6055}\x{6059}\x{605A}\x{605F}\x{6060}\x{6062}\x{6063}\x{6064}\x{6065}' . + '\x{6068}\x{6069}\x{606A}\x{606B}\x{606C}\x{606D}\x{606F}\x{6070}\x{6075}' . + '\x{6077}\x{6081}\x{6083}\x{6084}\x{6089}\x{608B}\x{608C}\x{608D}\x{6092}' . + '\x{6094}\x{6096}\x{6097}\x{609A}\x{609B}\x{609F}\x{60A0}\x{60A3}\x{60A6}' . + '\x{60A7}\x{60A9}\x{60AA}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B8}' . + '\x{60BC}\x{60BD}\x{60C5}\x{60C6}\x{60C7}\x{60D1}\x{60D3}\x{60D8}\x{60DA}' . + '\x{60DC}\x{60DF}\x{60E0}\x{60E1}\x{60E3}\x{60E7}\x{60E8}\x{60F0}\x{60F1}' . + '\x{60F3}\x{60F4}\x{60F6}\x{60F7}\x{60F9}\x{60FA}\x{60FB}\x{6100}\x{6101}' . + '\x{6103}\x{6106}\x{6108}\x{6109}\x{610D}\x{610E}\x{610F}\x{6115}\x{611A}' . + '\x{611B}\x{611F}\x{6121}\x{6127}\x{6128}\x{612C}\x{6134}\x{613C}\x{613D}' . + '\x{613E}\x{613F}\x{6142}\x{6144}\x{6147}\x{6148}\x{614A}\x{614B}\x{614C}' . + '\x{614D}\x{614E}\x{6153}\x{6155}\x{6158}\x{6159}\x{615A}\x{615D}\x{615F}' . + '\x{6162}\x{6163}\x{6165}\x{6167}\x{6168}\x{616B}\x{616E}\x{616F}\x{6170}' . + '\x{6171}\x{6173}\x{6174}\x{6175}\x{6176}\x{6177}\x{617E}\x{6182}\x{6187}' . + '\x{618A}\x{618E}\x{6190}\x{6191}\x{6194}\x{6196}\x{6199}\x{619A}\x{61A4}' . + '\x{61A7}\x{61A9}\x{61AB}\x{61AC}\x{61AE}\x{61B2}\x{61B6}\x{61BA}\x{61BE}' . + '\x{61C3}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' . + '\x{61D0}\x{61E3}\x{61E6}\x{61F2}\x{61F4}\x{61F6}\x{61F7}\x{61F8}\x{61FA}' . + '\x{61FC}\x{61FD}\x{61FE}\x{61FF}\x{6200}\x{6208}\x{6209}\x{620A}\x{620C}' . + '\x{620D}\x{620E}\x{6210}\x{6211}\x{6212}\x{6214}\x{6216}\x{621A}\x{621B}' . + '\x{621D}\x{621E}\x{621F}\x{6221}\x{6226}\x{622A}\x{622E}\x{622F}\x{6230}' . + '\x{6232}\x{6233}\x{6234}\x{6238}\x{623B}\x{623F}\x{6240}\x{6241}\x{6247}' . + '\x{6248}\x{6249}\x{624B}\x{624D}\x{624E}\x{6253}\x{6255}\x{6258}\x{625B}' . + '\x{625E}\x{6260}\x{6263}\x{6268}\x{626E}\x{6271}\x{6276}\x{6279}\x{627C}' . + '\x{627E}\x{627F}\x{6280}\x{6282}\x{6283}\x{6284}\x{6289}\x{628A}\x{6291}' . + '\x{6292}\x{6293}\x{6294}\x{6295}\x{6296}\x{6297}\x{6298}\x{629B}\x{629C}' . + '\x{629E}\x{62AB}\x{62AC}\x{62B1}\x{62B5}\x{62B9}\x{62BB}\x{62BC}\x{62BD}' . + '\x{62C2}\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CC}\x{62CD}' . + '\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D7}\x{62D8}\x{62D9}' . + '\x{62DB}\x{62DC}\x{62DD}\x{62E0}\x{62E1}\x{62EC}\x{62ED}\x{62EE}\x{62EF}' . + '\x{62F1}\x{62F3}\x{62F5}\x{62F6}\x{62F7}\x{62FE}\x{62FF}\x{6301}\x{6302}' . + '\x{6307}\x{6308}\x{6309}\x{630C}\x{6311}\x{6319}\x{631F}\x{6327}\x{6328}' . + '\x{632B}\x{632F}\x{633A}\x{633D}\x{633E}\x{633F}\x{6349}\x{634C}\x{634D}' . + '\x{634F}\x{6350}\x{6355}\x{6357}\x{635C}\x{6367}\x{6368}\x{6369}\x{636B}' . + '\x{636E}\x{6372}\x{6376}\x{6377}\x{637A}\x{637B}\x{6380}\x{6383}\x{6388}' . + '\x{6389}\x{638C}\x{638E}\x{638F}\x{6392}\x{6396}\x{6398}\x{639B}\x{639F}' . + '\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A5}\x{63A7}\x{63A8}\x{63A9}\x{63AA}' . + '\x{63AB}\x{63AC}\x{63B2}\x{63B4}\x{63B5}\x{63BB}\x{63BE}\x{63C0}\x{63C3}' . + '\x{63C4}\x{63C6}\x{63C9}\x{63CF}\x{63D0}\x{63D2}\x{63D6}\x{63DA}\x{63DB}' . + '\x{63E1}\x{63E3}\x{63E9}\x{63EE}\x{63F4}\x{63F6}\x{63FA}\x{6406}\x{640D}' . + '\x{640F}\x{6413}\x{6416}\x{6417}\x{641C}\x{6426}\x{6428}\x{642C}\x{642D}' . + '\x{6434}\x{6436}\x{643A}\x{643E}\x{6442}\x{644E}\x{6458}\x{6467}\x{6469}' . + '\x{646F}\x{6476}\x{6478}\x{647A}\x{6483}\x{6488}\x{6492}\x{6493}\x{6495}' . + '\x{649A}\x{649E}\x{64A4}\x{64A5}\x{64A9}\x{64AB}\x{64AD}\x{64AE}\x{64B0}' . + '\x{64B2}\x{64B9}\x{64BB}\x{64BC}\x{64C1}\x{64C2}\x{64C5}\x{64C7}\x{64CD}' . + '\x{64D2}\x{64D4}\x{64D8}\x{64DA}\x{64E0}\x{64E1}\x{64E2}\x{64E3}\x{64E6}' . + '\x{64E7}\x{64EC}\x{64EF}\x{64F1}\x{64F2}\x{64F4}\x{64F6}\x{64FA}\x{64FD}' . + '\x{64FE}\x{6500}\x{6505}\x{6518}\x{651C}\x{651D}\x{6523}\x{6524}\x{652A}' . + '\x{652B}\x{652C}\x{652F}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}' . + '\x{653B}\x{653E}\x{653F}\x{6545}\x{6548}\x{654D}\x{654F}\x{6551}\x{6555}' . + '\x{6556}\x{6557}\x{6558}\x{6559}\x{655D}\x{655E}\x{6562}\x{6563}\x{6566}' . + '\x{656C}\x{6570}\x{6572}\x{6574}\x{6575}\x{6577}\x{6578}\x{6582}\x{6583}' . + '\x{6587}\x{6588}\x{6589}\x{658C}\x{658E}\x{6590}\x{6591}\x{6597}\x{6599}' . + '\x{659B}\x{659C}\x{659F}\x{65A1}\x{65A4}\x{65A5}\x{65A7}\x{65AB}\x{65AC}' . + '\x{65AD}\x{65AF}\x{65B0}\x{65B7}\x{65B9}\x{65BC}\x{65BD}\x{65C1}\x{65C3}' . + '\x{65C4}\x{65C5}\x{65C6}\x{65CB}\x{65CC}\x{65CF}\x{65D2}\x{65D7}\x{65D9}' . + '\x{65DB}\x{65E0}\x{65E1}\x{65E2}\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}' . + '\x{65EC}\x{65ED}\x{65F1}\x{65FA}\x{65FB}\x{6602}\x{6603}\x{6606}\x{6607}' . + '\x{660A}\x{660C}\x{660E}\x{660F}\x{6613}\x{6614}\x{661C}\x{661F}\x{6620}' . + '\x{6625}\x{6627}\x{6628}\x{662D}\x{662F}\x{6634}\x{6635}\x{6636}\x{663C}' . + '\x{663F}\x{6641}\x{6642}\x{6643}\x{6644}\x{6649}\x{664B}\x{664F}\x{6652}' . + '\x{665D}\x{665E}\x{665F}\x{6662}\x{6664}\x{6666}\x{6667}\x{6668}\x{6669}' . + '\x{666E}\x{666F}\x{6670}\x{6674}\x{6676}\x{667A}\x{6681}\x{6683}\x{6684}' . + '\x{6687}\x{6688}\x{6689}\x{668E}\x{6691}\x{6696}\x{6697}\x{6698}\x{669D}' . + '\x{66A2}\x{66A6}\x{66AB}\x{66AE}\x{66B4}\x{66B8}\x{66B9}\x{66BC}\x{66BE}' . + '\x{66C1}\x{66C4}\x{66C7}\x{66C9}\x{66D6}\x{66D9}\x{66DA}\x{66DC}\x{66DD}' . + '\x{66E0}\x{66E6}\x{66E9}\x{66F0}\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F7}' . + '\x{66F8}\x{66F9}\x{66FC}\x{66FD}\x{66FE}\x{66FF}\x{6700}\x{6703}\x{6708}' . + '\x{6709}\x{670B}\x{670D}\x{670F}\x{6714}\x{6715}\x{6716}\x{6717}\x{671B}' . + '\x{671D}\x{671E}\x{671F}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}' . + '\x{672D}\x{672E}\x{6731}\x{6734}\x{6736}\x{6737}\x{6738}\x{673A}\x{673D}' . + '\x{673F}\x{6741}\x{6746}\x{6749}\x{674E}\x{674F}\x{6750}\x{6751}\x{6753}' . + '\x{6756}\x{6759}\x{675C}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' . + '\x{6764}\x{6765}\x{676A}\x{676D}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}' . + '\x{6775}\x{6777}\x{677C}\x{677E}\x{677F}\x{6785}\x{6787}\x{6789}\x{678B}' . + '\x{678C}\x{6790}\x{6795}\x{6797}\x{679A}\x{679C}\x{679D}\x{67A0}\x{67A1}' . + '\x{67A2}\x{67A6}\x{67A9}\x{67AF}\x{67B3}\x{67B4}\x{67B6}\x{67B7}\x{67B8}' . + '\x{67B9}\x{67C1}\x{67C4}\x{67C6}\x{67CA}\x{67CE}\x{67CF}\x{67D0}\x{67D1}' . + '\x{67D3}\x{67D4}\x{67D8}\x{67DA}\x{67DD}\x{67DE}\x{67E2}\x{67E4}\x{67E7}' . + '\x{67E9}\x{67EC}\x{67EE}\x{67EF}\x{67F1}\x{67F3}\x{67F4}\x{67F5}\x{67FB}' . + '\x{67FE}\x{67FF}\x{6802}\x{6803}\x{6804}\x{6813}\x{6816}\x{6817}\x{681E}' . + '\x{6821}\x{6822}\x{6829}\x{682A}\x{682B}\x{6832}\x{6834}\x{6838}\x{6839}' . + '\x{683C}\x{683D}\x{6840}\x{6841}\x{6842}\x{6843}\x{6846}\x{6848}\x{684D}' . + '\x{684E}\x{6850}\x{6851}\x{6853}\x{6854}\x{6859}\x{685C}\x{685D}\x{685F}' . + '\x{6863}\x{6867}\x{6874}\x{6876}\x{6877}\x{687E}\x{687F}\x{6881}\x{6883}' . + '\x{6885}\x{688D}\x{688F}\x{6893}\x{6894}\x{6897}\x{689B}\x{689D}\x{689F}' . + '\x{68A0}\x{68A2}\x{68A6}\x{68A7}\x{68A8}\x{68AD}\x{68AF}\x{68B0}\x{68B1}' . + '\x{68B3}\x{68B5}\x{68B6}\x{68B9}\x{68BA}\x{68BC}\x{68C4}\x{68C6}\x{68C9}' . + '\x{68CA}\x{68CB}\x{68CD}\x{68D2}\x{68D4}\x{68D5}\x{68D7}\x{68D8}\x{68DA}' . + '\x{68DF}\x{68E0}\x{68E1}\x{68E3}\x{68E7}\x{68EE}\x{68EF}\x{68F2}\x{68F9}' . + '\x{68FA}\x{6900}\x{6901}\x{6904}\x{6905}\x{6908}\x{690B}\x{690C}\x{690D}' . + '\x{690E}\x{690F}\x{6912}\x{6919}\x{691A}\x{691B}\x{691C}\x{6921}\x{6922}' . + '\x{6923}\x{6925}\x{6926}\x{6928}\x{692A}\x{6930}\x{6934}\x{6936}\x{6939}' . + '\x{693D}\x{693F}\x{694A}\x{6953}\x{6954}\x{6955}\x{6959}\x{695A}\x{695C}' . + '\x{695D}\x{695E}\x{6960}\x{6961}\x{6962}\x{696A}\x{696B}\x{696D}\x{696E}' . + '\x{696F}\x{6973}\x{6974}\x{6975}\x{6977}\x{6978}\x{6979}\x{697C}\x{697D}' . + '\x{697E}\x{6981}\x{6982}\x{698A}\x{698E}\x{6991}\x{6994}\x{6995}\x{699B}' . + '\x{699C}\x{69A0}\x{69A7}\x{69AE}\x{69B1}\x{69B2}\x{69B4}\x{69BB}\x{69BE}' . + '\x{69BF}\x{69C1}\x{69C3}\x{69C7}\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}' . + '\x{69D0}\x{69D3}\x{69D8}\x{69D9}\x{69DD}\x{69DE}\x{69E7}\x{69E8}\x{69EB}' . + '\x{69ED}\x{69F2}\x{69F9}\x{69FB}\x{69FD}\x{69FF}\x{6A02}\x{6A05}\x{6A0A}' . + '\x{6A0B}\x{6A0C}\x{6A12}\x{6A13}\x{6A14}\x{6A17}\x{6A19}\x{6A1B}\x{6A1E}' . + '\x{6A1F}\x{6A21}\x{6A22}\x{6A23}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2E}\x{6A35}' . + '\x{6A36}\x{6A38}\x{6A39}\x{6A3A}\x{6A3D}\x{6A44}\x{6A47}\x{6A48}\x{6A4B}' . + '\x{6A58}\x{6A59}\x{6A5F}\x{6A61}\x{6A62}\x{6A66}\x{6A72}\x{6A78}\x{6A7F}' . + '\x{6A80}\x{6A84}\x{6A8D}\x{6A8E}\x{6A90}\x{6A97}\x{6A9C}\x{6AA0}\x{6AA2}' . + '\x{6AA3}\x{6AAA}\x{6AAC}\x{6AAE}\x{6AB3}\x{6AB8}\x{6ABB}\x{6AC1}\x{6AC2}' . + '\x{6AC3}\x{6AD1}\x{6AD3}\x{6ADA}\x{6ADB}\x{6ADE}\x{6ADF}\x{6AE8}\x{6AEA}' . + '\x{6AFA}\x{6AFB}\x{6B04}\x{6B05}\x{6B0A}\x{6B12}\x{6B16}\x{6B1D}\x{6B1F}' . + '\x{6B20}\x{6B21}\x{6B23}\x{6B27}\x{6B32}\x{6B37}\x{6B38}\x{6B39}\x{6B3A}' . + '\x{6B3D}\x{6B3E}\x{6B43}\x{6B47}\x{6B49}\x{6B4C}\x{6B4E}\x{6B50}\x{6B53}' . + '\x{6B54}\x{6B59}\x{6B5B}\x{6B5F}\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B66}' . + '\x{6B69}\x{6B6A}\x{6B6F}\x{6B73}\x{6B74}\x{6B78}\x{6B79}\x{6B7B}\x{6B7F}' . + '\x{6B80}\x{6B83}\x{6B84}\x{6B86}\x{6B89}\x{6B8A}\x{6B8B}\x{6B8D}\x{6B95}' . + '\x{6B96}\x{6B98}\x{6B9E}\x{6BA4}\x{6BAA}\x{6BAB}\x{6BAF}\x{6BB1}\x{6BB2}' . + '\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB7}\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBF}\x{6BC0}' . + '\x{6BC5}\x{6BC6}\x{6BCB}\x{6BCD}\x{6BCE}\x{6BD2}\x{6BD3}\x{6BD4}\x{6BD8}' . + '\x{6BDB}\x{6BDF}\x{6BEB}\x{6BEC}\x{6BEF}\x{6BF3}\x{6C08}\x{6C0F}\x{6C11}' . + '\x{6C13}\x{6C14}\x{6C17}\x{6C1B}\x{6C23}\x{6C24}\x{6C34}\x{6C37}\x{6C38}' . + '\x{6C3E}\x{6C40}\x{6C41}\x{6C42}\x{6C4E}\x{6C50}\x{6C55}\x{6C57}\x{6C5A}' . + '\x{6C5D}\x{6C5E}\x{6C5F}\x{6C60}\x{6C62}\x{6C68}\x{6C6A}\x{6C70}\x{6C72}' . + '\x{6C73}\x{6C7A}\x{6C7D}\x{6C7E}\x{6C81}\x{6C82}\x{6C83}\x{6C88}\x{6C8C}' . + '\x{6C8D}\x{6C90}\x{6C92}\x{6C93}\x{6C96}\x{6C99}\x{6C9A}\x{6C9B}\x{6CA1}' . + '\x{6CA2}\x{6CAB}\x{6CAE}\x{6CB1}\x{6CB3}\x{6CB8}\x{6CB9}\x{6CBA}\x{6CBB}' . + '\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC1}\x{6CC4}\x{6CC5}\x{6CC9}\x{6CCA}' . + '\x{6CCC}\x{6CD3}\x{6CD5}\x{6CD7}\x{6CD9}\x{6CDB}\x{6CDD}\x{6CE1}\x{6CE2}' . + '\x{6CE3}\x{6CE5}\x{6CE8}\x{6CEA}\x{6CEF}\x{6CF0}\x{6CF1}\x{6CF3}\x{6D0B}' . + '\x{6D0C}\x{6D12}\x{6D17}\x{6D19}\x{6D1B}\x{6D1E}\x{6D1F}\x{6D25}\x{6D29}' . + '\x{6D2A}\x{6D2B}\x{6D32}\x{6D33}\x{6D35}\x{6D36}\x{6D38}\x{6D3B}\x{6D3D}' . + '\x{6D3E}\x{6D41}\x{6D44}\x{6D45}\x{6D59}\x{6D5A}\x{6D5C}\x{6D63}\x{6D64}' . + '\x{6D66}\x{6D69}\x{6D6A}\x{6D6C}\x{6D6E}\x{6D74}\x{6D77}\x{6D78}\x{6D79}' . + '\x{6D85}\x{6D88}\x{6D8C}\x{6D8E}\x{6D93}\x{6D95}\x{6D99}\x{6D9B}\x{6D9C}' . + '\x{6DAF}\x{6DB2}\x{6DB5}\x{6DB8}\x{6DBC}\x{6DC0}\x{6DC5}\x{6DC6}\x{6DC7}' . + '\x{6DCB}\x{6DCC}\x{6DD1}\x{6DD2}\x{6DD5}\x{6DD8}\x{6DD9}\x{6DDE}\x{6DE1}' . + '\x{6DE4}\x{6DE6}\x{6DE8}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DEE}\x{6DF1}\x{6DF3}' . + '\x{6DF5}\x{6DF7}\x{6DF9}\x{6DFA}\x{6DFB}\x{6E05}\x{6E07}\x{6E08}\x{6E09}' . + '\x{6E0A}\x{6E0B}\x{6E13}\x{6E15}\x{6E19}\x{6E1A}\x{6E1B}\x{6E1D}\x{6E1F}' . + '\x{6E20}\x{6E21}\x{6E23}\x{6E24}\x{6E25}\x{6E26}\x{6E29}\x{6E2B}\x{6E2C}' . + '\x{6E2D}\x{6E2E}\x{6E2F}\x{6E38}\x{6E3A}\x{6E3E}\x{6E43}\x{6E4A}\x{6E4D}' . + '\x{6E4E}\x{6E56}\x{6E58}\x{6E5B}\x{6E5F}\x{6E67}\x{6E6B}\x{6E6E}\x{6E6F}' . + '\x{6E72}\x{6E76}\x{6E7E}\x{6E7F}\x{6E80}\x{6E82}\x{6E8C}\x{6E8F}\x{6E90}' . + '\x{6E96}\x{6E98}\x{6E9C}\x{6E9D}\x{6E9F}\x{6EA2}\x{6EA5}\x{6EAA}\x{6EAF}' . + '\x{6EB2}\x{6EB6}\x{6EB7}\x{6EBA}\x{6EBD}\x{6EC2}\x{6EC4}\x{6EC5}\x{6EC9}' . + '\x{6ECB}\x{6ECC}\x{6ED1}\x{6ED3}\x{6ED4}\x{6ED5}\x{6EDD}\x{6EDE}\x{6EEC}' . + '\x{6EEF}\x{6EF2}\x{6EF4}\x{6EF7}\x{6EF8}\x{6EFE}\x{6EFF}\x{6F01}\x{6F02}' . + '\x{6F06}\x{6F09}\x{6F0F}\x{6F11}\x{6F13}\x{6F14}\x{6F15}\x{6F20}\x{6F22}' . + '\x{6F23}\x{6F2B}\x{6F2C}\x{6F31}\x{6F32}\x{6F38}\x{6F3E}\x{6F3F}\x{6F41}' . + '\x{6F45}\x{6F54}\x{6F58}\x{6F5B}\x{6F5C}\x{6F5F}\x{6F64}\x{6F66}\x{6F6D}' . + '\x{6F6E}\x{6F6F}\x{6F70}\x{6F74}\x{6F78}\x{6F7A}\x{6F7C}\x{6F80}\x{6F81}' . + '\x{6F82}\x{6F84}\x{6F86}\x{6F8E}\x{6F91}\x{6F97}\x{6FA1}\x{6FA3}\x{6FA4}' . + '\x{6FAA}\x{6FB1}\x{6FB3}\x{6FB9}\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC6}' . + '\x{6FD4}\x{6FD5}\x{6FD8}\x{6FDB}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE4}\x{6FEB}' . + '\x{6FEC}\x{6FEE}\x{6FEF}\x{6FF1}\x{6FF3}\x{6FF6}\x{6FFA}\x{6FFE}\x{7001}' . + '\x{7009}\x{700B}\x{700F}\x{7011}\x{7015}\x{7018}\x{701A}\x{701B}\x{701D}' . + '\x{701E}\x{701F}\x{7026}\x{7027}\x{702C}\x{7030}\x{7032}\x{703E}\x{704C}' . + '\x{7051}\x{7058}\x{7063}\x{706B}\x{706F}\x{7070}\x{7078}\x{707C}\x{707D}' . + '\x{7089}\x{708A}\x{708E}\x{7092}\x{7099}\x{70AC}\x{70AD}\x{70AE}\x{70AF}' . + '\x{70B3}\x{70B8}\x{70B9}\x{70BA}\x{70C8}\x{70CB}\x{70CF}\x{70D9}\x{70DD}' . + '\x{70DF}\x{70F1}\x{70F9}\x{70FD}\x{7109}\x{7114}\x{7119}\x{711A}\x{711C}' . + '\x{7121}\x{7126}\x{7136}\x{713C}\x{7149}\x{714C}\x{714E}\x{7155}\x{7156}' . + '\x{7159}\x{7162}\x{7164}\x{7165}\x{7166}\x{7167}\x{7169}\x{716C}\x{716E}' . + '\x{717D}\x{7184}\x{7188}\x{718A}\x{718F}\x{7194}\x{7195}\x{7199}\x{719F}' . + '\x{71A8}\x{71AC}\x{71B1}\x{71B9}\x{71BE}\x{71C3}\x{71C8}\x{71C9}\x{71CE}' . + '\x{71D0}\x{71D2}\x{71D4}\x{71D5}\x{71D7}\x{71DF}\x{71E0}\x{71E5}\x{71E6}' . + '\x{71E7}\x{71EC}\x{71ED}\x{71EE}\x{71F5}\x{71F9}\x{71FB}\x{71FC}\x{71FF}' . + '\x{7206}\x{720D}\x{7210}\x{721B}\x{7228}\x{722A}\x{722C}\x{722D}\x{7230}' . + '\x{7232}\x{7235}\x{7236}\x{723A}\x{723B}\x{723C}\x{723D}\x{723E}\x{723F}' . + '\x{7240}\x{7246}\x{7247}\x{7248}\x{724B}\x{724C}\x{7252}\x{7258}\x{7259}' . + '\x{725B}\x{725D}\x{725F}\x{7261}\x{7262}\x{7267}\x{7269}\x{7272}\x{7274}' . + '\x{7279}\x{727D}\x{727E}\x{7280}\x{7281}\x{7282}\x{7287}\x{7292}\x{7296}' . + '\x{72A0}\x{72A2}\x{72A7}\x{72AC}\x{72AF}\x{72B2}\x{72B6}\x{72B9}\x{72C2}' . + '\x{72C3}\x{72C4}\x{72C6}\x{72CE}\x{72D0}\x{72D2}\x{72D7}\x{72D9}\x{72DB}' . + '\x{72E0}\x{72E1}\x{72E2}\x{72E9}\x{72EC}\x{72ED}\x{72F7}\x{72F8}\x{72F9}' . + '\x{72FC}\x{72FD}\x{730A}\x{7316}\x{7317}\x{731B}\x{731C}\x{731D}\x{731F}' . + '\x{7325}\x{7329}\x{732A}\x{732B}\x{732E}\x{732F}\x{7334}\x{7336}\x{7337}' . + '\x{733E}\x{733F}\x{7344}\x{7345}\x{734E}\x{734F}\x{7357}\x{7363}\x{7368}' . + '\x{736A}\x{7370}\x{7372}\x{7375}\x{7378}\x{737A}\x{737B}\x{7384}\x{7387}' . + '\x{7389}\x{738B}\x{7396}\x{73A9}\x{73B2}\x{73B3}\x{73BB}\x{73C0}\x{73C2}' . + '\x{73C8}\x{73CA}\x{73CD}\x{73CE}\x{73DE}\x{73E0}\x{73E5}\x{73EA}\x{73ED}' . + '\x{73EE}\x{73F1}\x{73F8}\x{73FE}\x{7403}\x{7405}\x{7406}\x{7409}\x{7422}' . + '\x{7425}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{743A}\x{743F}\x{7441}' . + '\x{7455}\x{7459}\x{745A}\x{745B}\x{745C}\x{745E}\x{745F}\x{7460}\x{7463}' . + '\x{7464}\x{7469}\x{746A}\x{746F}\x{7470}\x{7473}\x{7476}\x{747E}\x{7483}' . + '\x{748B}\x{749E}\x{74A2}\x{74A7}\x{74B0}\x{74BD}\x{74CA}\x{74CF}\x{74D4}' . + '\x{74DC}\x{74E0}\x{74E2}\x{74E3}\x{74E6}\x{74E7}\x{74E9}\x{74EE}\x{74F0}' . + '\x{74F1}\x{74F2}\x{74F6}\x{74F7}\x{74F8}\x{7503}\x{7504}\x{7505}\x{750C}' . + '\x{750D}\x{750E}\x{7511}\x{7513}\x{7515}\x{7518}\x{751A}\x{751C}\x{751E}' . + '\x{751F}\x{7523}\x{7525}\x{7526}\x{7528}\x{752B}\x{752C}\x{7530}\x{7531}' . + '\x{7532}\x{7533}\x{7537}\x{7538}\x{753A}\x{753B}\x{753C}\x{7544}\x{7546}' . + '\x{7549}\x{754A}\x{754B}\x{754C}\x{754D}\x{754F}\x{7551}\x{7554}\x{7559}' . + '\x{755A}\x{755B}\x{755C}\x{755D}\x{7560}\x{7562}\x{7564}\x{7565}\x{7566}' . + '\x{7567}\x{7569}\x{756A}\x{756B}\x{756D}\x{7570}\x{7573}\x{7574}\x{7576}' . + '\x{7577}\x{7578}\x{757F}\x{7582}\x{7586}\x{7587}\x{7589}\x{758A}\x{758B}' . + '\x{758E}\x{758F}\x{7591}\x{7594}\x{759A}\x{759D}\x{75A3}\x{75A5}\x{75AB}' . + '\x{75B1}\x{75B2}\x{75B3}\x{75B5}\x{75B8}\x{75B9}\x{75BC}\x{75BD}\x{75BE}' . + '\x{75C2}\x{75C3}\x{75C5}\x{75C7}\x{75CA}\x{75CD}\x{75D2}\x{75D4}\x{75D5}' . + '\x{75D8}\x{75D9}\x{75DB}\x{75DE}\x{75E2}\x{75E3}\x{75E9}\x{75F0}\x{75F2}' . + '\x{75F3}\x{75F4}\x{75FA}\x{75FC}\x{75FE}\x{75FF}\x{7601}\x{7609}\x{760B}' . + '\x{760D}\x{761F}\x{7620}\x{7621}\x{7622}\x{7624}\x{7627}\x{7630}\x{7634}' . + '\x{763B}\x{7642}\x{7646}\x{7647}\x{7648}\x{764C}\x{7652}\x{7656}\x{7658}' . + '\x{765C}\x{7661}\x{7662}\x{7667}\x{7668}\x{7669}\x{766A}\x{766C}\x{7670}' . + '\x{7672}\x{7676}\x{7678}\x{767A}\x{767B}\x{767C}\x{767D}\x{767E}\x{7680}' . + '\x{7683}\x{7684}\x{7686}\x{7687}\x{7688}\x{768B}\x{768E}\x{7690}\x{7693}' . + '\x{7696}\x{7699}\x{769A}\x{76AE}\x{76B0}\x{76B4}\x{76B7}\x{76B8}\x{76B9}' . + '\x{76BA}\x{76BF}\x{76C2}\x{76C3}\x{76C6}\x{76C8}\x{76CA}\x{76CD}\x{76D2}' . + '\x{76D6}\x{76D7}\x{76DB}\x{76DC}\x{76DE}\x{76DF}\x{76E1}\x{76E3}\x{76E4}' . + '\x{76E5}\x{76E7}\x{76EA}\x{76EE}\x{76F2}\x{76F4}\x{76F8}\x{76FB}\x{76FE}' . + '\x{7701}\x{7704}\x{7707}\x{7708}\x{7709}\x{770B}\x{770C}\x{771B}\x{771E}' . + '\x{771F}\x{7720}\x{7724}\x{7725}\x{7726}\x{7729}\x{7737}\x{7738}\x{773A}' . + '\x{773C}\x{7740}\x{7747}\x{775A}\x{775B}\x{7761}\x{7763}\x{7765}\x{7766}' . + '\x{7768}\x{776B}\x{7779}\x{777E}\x{777F}\x{778B}\x{778E}\x{7791}\x{779E}' . + '\x{77A0}\x{77A5}\x{77AC}\x{77AD}\x{77B0}\x{77B3}\x{77B6}\x{77B9}\x{77BB}' . + '\x{77BC}\x{77BD}\x{77BF}\x{77C7}\x{77CD}\x{77D7}\x{77DA}\x{77DB}\x{77DC}' . + '\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E9}\x{77ED}\x{77EE}\x{77EF}\x{77F3}' . + '\x{77FC}\x{7802}\x{780C}\x{7812}\x{7814}\x{7815}\x{7820}\x{7825}\x{7826}' . + '\x{7827}\x{7832}\x{7834}\x{783A}\x{783F}\x{7845}\x{785D}\x{786B}\x{786C}' . + '\x{786F}\x{7872}\x{7874}\x{787C}\x{7881}\x{7886}\x{7887}\x{788C}\x{788D}' . + '\x{788E}\x{7891}\x{7893}\x{7895}\x{7897}\x{789A}\x{78A3}\x{78A7}\x{78A9}' . + '\x{78AA}\x{78AF}\x{78B5}\x{78BA}\x{78BC}\x{78BE}\x{78C1}\x{78C5}\x{78C6}' . + '\x{78CA}\x{78CB}\x{78D0}\x{78D1}\x{78D4}\x{78DA}\x{78E7}\x{78E8}\x{78EC}' . + '\x{78EF}\x{78F4}\x{78FD}\x{7901}\x{7907}\x{790E}\x{7911}\x{7912}\x{7919}' . + '\x{7926}\x{792A}\x{792B}\x{792C}\x{793A}\x{793C}\x{793E}\x{7940}\x{7941}' . + '\x{7947}\x{7948}\x{7949}\x{7950}\x{7953}\x{7955}\x{7956}\x{7957}\x{795A}' . + '\x{795D}\x{795E}\x{795F}\x{7960}\x{7962}\x{7965}\x{7968}\x{796D}\x{7977}' . + '\x{797A}\x{797F}\x{7980}\x{7981}\x{7984}\x{7985}\x{798A}\x{798D}\x{798E}' . + '\x{798F}\x{799D}\x{79A6}\x{79A7}\x{79AA}\x{79AE}\x{79B0}\x{79B3}\x{79B9}' . + '\x{79BA}\x{79BD}\x{79BE}\x{79BF}\x{79C0}\x{79C1}\x{79C9}\x{79CB}\x{79D1}' . + '\x{79D2}\x{79D5}\x{79D8}\x{79DF}\x{79E1}\x{79E3}\x{79E4}\x{79E6}\x{79E7}' . + '\x{79E9}\x{79EC}\x{79F0}\x{79FB}\x{7A00}\x{7A08}\x{7A0B}\x{7A0D}\x{7A0E}' . + '\x{7A14}\x{7A17}\x{7A18}\x{7A19}\x{7A1A}\x{7A1C}\x{7A1F}\x{7A20}\x{7A2E}' . + '\x{7A31}\x{7A32}\x{7A37}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' . + '\x{7A42}\x{7A43}\x{7A46}\x{7A49}\x{7A4D}\x{7A4E}\x{7A4F}\x{7A50}\x{7A57}' . + '\x{7A61}\x{7A62}\x{7A63}\x{7A69}\x{7A6B}\x{7A70}\x{7A74}\x{7A76}\x{7A79}' . + '\x{7A7A}\x{7A7D}\x{7A7F}\x{7A81}\x{7A83}\x{7A84}\x{7A88}\x{7A92}\x{7A93}' . + '\x{7A95}\x{7A96}\x{7A97}\x{7A98}\x{7A9F}\x{7AA9}\x{7AAA}\x{7AAE}\x{7AAF}' . + '\x{7AB0}\x{7AB6}\x{7ABA}\x{7ABF}\x{7AC3}\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}' . + '\x{7ACA}\x{7ACB}\x{7ACD}\x{7ACF}\x{7AD2}\x{7AD3}\x{7AD5}\x{7AD9}\x{7ADA}' . + '\x{7ADC}\x{7ADD}\x{7ADF}\x{7AE0}\x{7AE1}\x{7AE2}\x{7AE3}\x{7AE5}\x{7AE6}' . + '\x{7AEA}\x{7AED}\x{7AEF}\x{7AF0}\x{7AF6}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFF}' . + '\x{7B02}\x{7B04}\x{7B06}\x{7B08}\x{7B0A}\x{7B0B}\x{7B0F}\x{7B11}\x{7B18}' . + '\x{7B19}\x{7B1B}\x{7B1E}\x{7B20}\x{7B25}\x{7B26}\x{7B28}\x{7B2C}\x{7B33}' . + '\x{7B35}\x{7B36}\x{7B39}\x{7B45}\x{7B46}\x{7B48}\x{7B49}\x{7B4B}\x{7B4C}' . + '\x{7B4D}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B56}\x{7B5D}\x{7B65}' . + '\x{7B67}\x{7B6C}\x{7B6E}\x{7B70}\x{7B71}\x{7B74}\x{7B75}\x{7B7A}\x{7B86}' . + '\x{7B87}\x{7B8B}\x{7B8D}\x{7B8F}\x{7B92}\x{7B94}\x{7B95}\x{7B97}\x{7B98}' . + '\x{7B99}\x{7B9A}\x{7B9C}\x{7B9D}\x{7B9F}\x{7BA1}\x{7BAA}\x{7BAD}\x{7BB1}' . + '\x{7BB4}\x{7BB8}\x{7BC0}\x{7BC1}\x{7BC4}\x{7BC6}\x{7BC7}\x{7BC9}\x{7BCB}' . + '\x{7BCC}\x{7BCF}\x{7BDD}\x{7BE0}\x{7BE4}\x{7BE5}\x{7BE6}\x{7BE9}\x{7BED}' . + '\x{7BF3}\x{7BF6}\x{7BF7}\x{7C00}\x{7C07}\x{7C0D}\x{7C11}\x{7C12}\x{7C13}' . + '\x{7C14}\x{7C17}\x{7C1F}\x{7C21}\x{7C23}\x{7C27}\x{7C2A}\x{7C2B}\x{7C37}' . + '\x{7C38}\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C43}\x{7C4C}\x{7C4D}\x{7C4F}' . + '\x{7C50}\x{7C54}\x{7C56}\x{7C58}\x{7C5F}\x{7C60}\x{7C64}\x{7C65}\x{7C6C}' . + '\x{7C73}\x{7C75}\x{7C7E}\x{7C81}\x{7C82}\x{7C83}\x{7C89}\x{7C8B}\x{7C8D}' . + '\x{7C90}\x{7C92}\x{7C95}\x{7C97}\x{7C98}\x{7C9B}\x{7C9F}\x{7CA1}\x{7CA2}' . + '\x{7CA4}\x{7CA5}\x{7CA7}\x{7CA8}\x{7CAB}\x{7CAD}\x{7CAE}\x{7CB1}\x{7CB2}' . + '\x{7CB3}\x{7CB9}\x{7CBD}\x{7CBE}\x{7CC0}\x{7CC2}\x{7CC5}\x{7CCA}\x{7CCE}' . + '\x{7CD2}\x{7CD6}\x{7CD8}\x{7CDC}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE7}' . + '\x{7CEF}\x{7CF2}\x{7CF4}\x{7CF6}\x{7CF8}\x{7CFA}\x{7CFB}\x{7CFE}\x{7D00}' . + '\x{7D02}\x{7D04}\x{7D05}\x{7D06}\x{7D0A}\x{7D0B}\x{7D0D}\x{7D10}\x{7D14}' . + '\x{7D15}\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D20}\x{7D21}' . + '\x{7D22}\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D32}\x{7D33}\x{7D35}' . + '\x{7D39}\x{7D3A}\x{7D3F}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}\x{7D4B}' . + '\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D56}\x{7D5B}\x{7D5E}\x{7D61}\x{7D62}' . + '\x{7D63}\x{7D66}\x{7D68}\x{7D6E}\x{7D71}\x{7D72}\x{7D73}\x{7D75}\x{7D76}' . + '\x{7D79}\x{7D7D}\x{7D89}\x{7D8F}\x{7D93}\x{7D99}\x{7D9A}\x{7D9B}\x{7D9C}' . + '\x{7D9F}\x{7DA2}\x{7DA3}\x{7DAB}\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}' . + '\x{7DB1}\x{7DB2}\x{7DB4}\x{7DB5}\x{7DB8}\x{7DBA}\x{7DBB}\x{7DBD}\x{7DBE}' . + '\x{7DBF}\x{7DC7}\x{7DCA}\x{7DCB}\x{7DCF}\x{7DD1}\x{7DD2}\x{7DD5}\x{7DD8}' . + '\x{7DDA}\x{7DDC}\x{7DDD}\x{7DDE}\x{7DE0}\x{7DE1}\x{7DE4}\x{7DE8}\x{7DE9}' . + '\x{7DEC}\x{7DEF}\x{7DF2}\x{7DF4}\x{7DFB}\x{7E01}\x{7E04}\x{7E05}\x{7E09}' . + '\x{7E0A}\x{7E0B}\x{7E12}\x{7E1B}\x{7E1E}\x{7E1F}\x{7E21}\x{7E22}\x{7E23}' . + '\x{7E26}\x{7E2B}\x{7E2E}\x{7E31}\x{7E32}\x{7E35}\x{7E37}\x{7E39}\x{7E3A}' . + '\x{7E3B}\x{7E3D}\x{7E3E}\x{7E41}\x{7E43}\x{7E46}\x{7E4A}\x{7E4B}\x{7E4D}' . + '\x{7E54}\x{7E55}\x{7E56}\x{7E59}\x{7E5A}\x{7E5D}\x{7E5E}\x{7E66}\x{7E67}' . + '\x{7E69}\x{7E6A}\x{7E6D}\x{7E70}\x{7E79}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7F}' . + '\x{7E82}\x{7E83}\x{7E88}\x{7E89}\x{7E8C}\x{7E8E}\x{7E8F}\x{7E90}\x{7E92}' . + '\x{7E93}\x{7E94}\x{7E96}\x{7E9B}\x{7E9C}\x{7F36}\x{7F38}\x{7F3A}\x{7F45}' . + '\x{7F4C}\x{7F4D}\x{7F4E}\x{7F50}\x{7F51}\x{7F54}\x{7F55}\x{7F58}\x{7F5F}' . + '\x{7F60}\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6E}\x{7F70}\x{7F72}' . + '\x{7F75}\x{7F77}\x{7F78}\x{7F79}\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}' . + '\x{7F88}\x{7F8A}\x{7F8C}\x{7F8E}\x{7F94}\x{7F9A}\x{7F9D}\x{7F9E}\x{7FA3}' . + '\x{7FA4}\x{7FA8}\x{7FA9}\x{7FAE}\x{7FAF}\x{7FB2}\x{7FB6}\x{7FB8}\x{7FB9}' . + '\x{7FBD}\x{7FC1}\x{7FC5}\x{7FC6}\x{7FCA}\x{7FCC}\x{7FD2}\x{7FD4}\x{7FD5}' . + '\x{7FE0}\x{7FE1}\x{7FE6}\x{7FE9}\x{7FEB}\x{7FF0}\x{7FF3}\x{7FF9}\x{7FFB}' . + '\x{7FFC}\x{8000}\x{8001}\x{8003}\x{8004}\x{8005}\x{8006}\x{800B}\x{800C}' . + '\x{8010}\x{8012}\x{8015}\x{8017}\x{8018}\x{8019}\x{801C}\x{8021}\x{8028}' . + '\x{8033}\x{8036}\x{803B}\x{803D}\x{803F}\x{8046}\x{804A}\x{8052}\x{8056}' . + '\x{8058}\x{805A}\x{805E}\x{805F}\x{8061}\x{8062}\x{8068}\x{806F}\x{8070}' . + '\x{8072}\x{8073}\x{8074}\x{8076}\x{8077}\x{8079}\x{807D}\x{807E}\x{807F}' . + '\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808B}\x{808C}\x{8093}\x{8096}' . + '\x{8098}\x{809A}\x{809B}\x{809D}\x{80A1}\x{80A2}\x{80A5}\x{80A9}\x{80AA}' . + '\x{80AC}\x{80AD}\x{80AF}\x{80B1}\x{80B2}\x{80B4}\x{80BA}\x{80C3}\x{80C4}' . + '\x{80C6}\x{80CC}\x{80CE}\x{80D6}\x{80D9}\x{80DA}\x{80DB}\x{80DD}\x{80DE}' . + '\x{80E1}\x{80E4}\x{80E5}\x{80EF}\x{80F1}\x{80F4}\x{80F8}\x{80FC}\x{80FD}' . + '\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{811A}\x{811B}' . + '\x{8123}\x{8129}\x{812F}\x{8131}\x{8133}\x{8139}\x{813E}\x{8146}\x{814B}' . + '\x{814E}\x{8150}\x{8151}\x{8153}\x{8154}\x{8155}\x{815F}\x{8165}\x{8166}' . + '\x{816B}\x{816E}\x{8170}\x{8171}\x{8174}\x{8178}\x{8179}\x{817A}\x{817F}' . + '\x{8180}\x{8182}\x{8183}\x{8188}\x{818A}\x{818F}\x{8193}\x{8195}\x{819A}' . + '\x{819C}\x{819D}\x{81A0}\x{81A3}\x{81A4}\x{81A8}\x{81A9}\x{81B0}\x{81B3}' . + '\x{81B5}\x{81B8}\x{81BA}\x{81BD}\x{81BE}\x{81BF}\x{81C0}\x{81C2}\x{81C6}' . + '\x{81C8}\x{81C9}\x{81CD}\x{81D1}\x{81D3}\x{81D8}\x{81D9}\x{81DA}\x{81DF}' . + '\x{81E0}\x{81E3}\x{81E5}\x{81E7}\x{81E8}\x{81EA}\x{81ED}\x{81F3}\x{81F4}' . + '\x{81FA}\x{81FB}\x{81FC}\x{81FE}\x{8201}\x{8202}\x{8205}\x{8207}\x{8208}' . + '\x{8209}\x{820A}\x{820C}\x{820D}\x{820E}\x{8210}\x{8212}\x{8216}\x{8217}' . + '\x{8218}\x{821B}\x{821C}\x{821E}\x{821F}\x{8229}\x{822A}\x{822B}\x{822C}' . + '\x{822E}\x{8233}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{8240}\x{8247}' . + '\x{8258}\x{8259}\x{825A}\x{825D}\x{825F}\x{8262}\x{8264}\x{8266}\x{8268}' . + '\x{826A}\x{826B}\x{826E}\x{826F}\x{8271}\x{8272}\x{8276}\x{8277}\x{8278}' . + '\x{827E}\x{828B}\x{828D}\x{8292}\x{8299}\x{829D}\x{829F}\x{82A5}\x{82A6}' . + '\x{82AB}\x{82AC}\x{82AD}\x{82AF}\x{82B1}\x{82B3}\x{82B8}\x{82B9}\x{82BB}' . + '\x{82BD}\x{82C5}\x{82D1}\x{82D2}\x{82D3}\x{82D4}\x{82D7}\x{82D9}\x{82DB}' . + '\x{82DC}\x{82DE}\x{82DF}\x{82E1}\x{82E3}\x{82E5}\x{82E6}\x{82E7}\x{82EB}' . + '\x{82F1}\x{82F3}\x{82F4}\x{82F9}\x{82FA}\x{82FB}\x{8302}\x{8303}\x{8304}' . + '\x{8305}\x{8306}\x{8309}\x{830E}\x{8316}\x{8317}\x{8318}\x{831C}\x{8323}' . + '\x{8328}\x{832B}\x{832F}\x{8331}\x{8332}\x{8334}\x{8335}\x{8336}\x{8338}' . + '\x{8339}\x{8340}\x{8345}\x{8349}\x{834A}\x{834F}\x{8350}\x{8352}\x{8358}' . + '\x{8373}\x{8375}\x{8377}\x{837B}\x{837C}\x{8385}\x{8387}\x{8389}\x{838A}' . + '\x{838E}\x{8393}\x{8396}\x{839A}\x{839E}\x{839F}\x{83A0}\x{83A2}\x{83A8}' . + '\x{83AA}\x{83AB}\x{83B1}\x{83B5}\x{83BD}\x{83C1}\x{83C5}\x{83CA}\x{83CC}' . + '\x{83CE}\x{83D3}\x{83D6}\x{83D8}\x{83DC}\x{83DF}\x{83E0}\x{83E9}\x{83EB}' . + '\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F4}\x{83F7}\x{83FB}\x{83FD}\x{8403}' . + '\x{8404}\x{8407}\x{840B}\x{840C}\x{840D}\x{840E}\x{8413}\x{8420}\x{8422}' . + '\x{8429}\x{842A}\x{842C}\x{8431}\x{8435}\x{8438}\x{843C}\x{843D}\x{8446}' . + '\x{8449}\x{844E}\x{8457}\x{845B}\x{8461}\x{8462}\x{8463}\x{8466}\x{8469}' . + '\x{846B}\x{846C}\x{846D}\x{846E}\x{846F}\x{8471}\x{8475}\x{8477}\x{8479}' . + '\x{847A}\x{8482}\x{8484}\x{848B}\x{8490}\x{8494}\x{8499}\x{849C}\x{849F}' . + '\x{84A1}\x{84AD}\x{84B2}\x{84B8}\x{84B9}\x{84BB}\x{84BC}\x{84BF}\x{84C1}' . + '\x{84C4}\x{84C6}\x{84C9}\x{84CA}\x{84CB}\x{84CD}\x{84D0}\x{84D1}\x{84D6}' . + '\x{84D9}\x{84DA}\x{84EC}\x{84EE}\x{84F4}\x{84FC}\x{84FF}\x{8500}\x{8506}' . + '\x{8511}\x{8513}\x{8514}\x{8515}\x{8517}\x{8518}\x{851A}\x{851F}\x{8521}' . + '\x{8526}\x{852C}\x{852D}\x{8535}\x{853D}\x{8540}\x{8541}\x{8543}\x{8548}' . + '\x{8549}\x{854A}\x{854B}\x{854E}\x{8555}\x{8557}\x{8558}\x{855A}\x{8563}' . + '\x{8568}\x{8569}\x{856A}\x{856D}\x{8577}\x{857E}\x{8580}\x{8584}\x{8587}' . + '\x{8588}\x{858A}\x{8590}\x{8591}\x{8594}\x{8597}\x{8599}\x{859B}\x{859C}' . + '\x{85A4}\x{85A6}\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AE}\x{85AF}' . + '\x{85B9}\x{85BA}\x{85C1}\x{85C9}\x{85CD}\x{85CF}\x{85D0}\x{85D5}\x{85DC}' . + '\x{85DD}\x{85E4}\x{85E5}\x{85E9}\x{85EA}\x{85F7}\x{85F9}\x{85FA}\x{85FB}' . + '\x{85FE}\x{8602}\x{8606}\x{8607}\x{860A}\x{860B}\x{8613}\x{8616}\x{8617}' . + '\x{861A}\x{8622}\x{862D}\x{862F}\x{8630}\x{863F}\x{864D}\x{864E}\x{8650}' . + '\x{8654}\x{8655}\x{865A}\x{865C}\x{865E}\x{865F}\x{8667}\x{866B}\x{8671}' . + '\x{8679}\x{867B}\x{868A}\x{868B}\x{868C}\x{8693}\x{8695}\x{86A3}\x{86A4}' . + '\x{86A9}\x{86AA}\x{86AB}\x{86AF}\x{86B0}\x{86B6}\x{86C4}\x{86C6}\x{86C7}' . + '\x{86C9}\x{86CB}\x{86CD}\x{86CE}\x{86D4}\x{86D9}\x{86DB}\x{86DE}\x{86DF}' . + '\x{86E4}\x{86E9}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F8}\x{86F9}\x{86FB}' . + '\x{86FE}\x{8700}\x{8702}\x{8703}\x{8706}\x{8708}\x{8709}\x{870A}\x{870D}' . + '\x{8711}\x{8712}\x{8718}\x{871A}\x{871C}\x{8725}\x{8729}\x{8734}\x{8737}' . + '\x{873B}\x{873F}\x{8749}\x{874B}\x{874C}\x{874E}\x{8753}\x{8755}\x{8757}' . + '\x{8759}\x{875F}\x{8760}\x{8763}\x{8766}\x{8768}\x{876A}\x{876E}\x{8774}' . + '\x{8776}\x{8778}\x{877F}\x{8782}\x{878D}\x{879F}\x{87A2}\x{87AB}\x{87AF}' . + '\x{87B3}\x{87BA}\x{87BB}\x{87BD}\x{87C0}\x{87C4}\x{87C6}\x{87C7}\x{87CB}' . + '\x{87D0}\x{87D2}\x{87E0}\x{87EF}\x{87F2}\x{87F6}\x{87F7}\x{87F9}\x{87FB}' . + '\x{87FE}\x{8805}\x{880D}\x{880E}\x{880F}\x{8811}\x{8815}\x{8816}\x{8821}' . + '\x{8822}\x{8823}\x{8827}\x{8831}\x{8836}\x{8839}\x{883B}\x{8840}\x{8842}' . + '\x{8844}\x{8846}\x{884C}\x{884D}\x{8852}\x{8853}\x{8857}\x{8859}\x{885B}' . + '\x{885D}\x{885E}\x{8861}\x{8862}\x{8863}\x{8868}\x{886B}\x{8870}\x{8872}' . + '\x{8875}\x{8877}\x{887D}\x{887E}\x{887F}\x{8881}\x{8882}\x{8888}\x{888B}' . + '\x{888D}\x{8892}\x{8896}\x{8897}\x{8899}\x{889E}\x{88A2}\x{88A4}\x{88AB}' . + '\x{88AE}\x{88B0}\x{88B1}\x{88B4}\x{88B5}\x{88B7}\x{88BF}\x{88C1}\x{88C2}' . + '\x{88C3}\x{88C4}\x{88C5}\x{88CF}\x{88D4}\x{88D5}\x{88D8}\x{88D9}\x{88DC}' . + '\x{88DD}\x{88DF}\x{88E1}\x{88E8}\x{88F2}\x{88F3}\x{88F4}\x{88F8}\x{88F9}' . + '\x{88FC}\x{88FD}\x{88FE}\x{8902}\x{8904}\x{8907}\x{890A}\x{890C}\x{8910}' . + '\x{8912}\x{8913}\x{891D}\x{891E}\x{8925}\x{892A}\x{892B}\x{8936}\x{8938}' . + '\x{893B}\x{8941}\x{8943}\x{8944}\x{894C}\x{894D}\x{8956}\x{895E}\x{895F}' . + '\x{8960}\x{8964}\x{8966}\x{896A}\x{896D}\x{896F}\x{8972}\x{8974}\x{8977}' . + '\x{897E}\x{897F}\x{8981}\x{8983}\x{8986}\x{8987}\x{8988}\x{898A}\x{898B}' . + '\x{898F}\x{8993}\x{8996}\x{8997}\x{8998}\x{899A}\x{89A1}\x{89A6}\x{89A7}' . + '\x{89A9}\x{89AA}\x{89AC}\x{89AF}\x{89B2}\x{89B3}\x{89BA}\x{89BD}\x{89BF}' . + '\x{89C0}\x{89D2}\x{89DA}\x{89DC}\x{89DD}\x{89E3}\x{89E6}\x{89E7}\x{89F4}' . + '\x{89F8}\x{8A00}\x{8A02}\x{8A03}\x{8A08}\x{8A0A}\x{8A0C}\x{8A0E}\x{8A10}' . + '\x{8A13}\x{8A16}\x{8A17}\x{8A18}\x{8A1B}\x{8A1D}\x{8A1F}\x{8A23}\x{8A25}' . + '\x{8A2A}\x{8A2D}\x{8A31}\x{8A33}\x{8A34}\x{8A36}\x{8A3A}\x{8A3B}\x{8A3C}' . + '\x{8A41}\x{8A46}\x{8A48}\x{8A50}\x{8A51}\x{8A52}\x{8A54}\x{8A55}\x{8A5B}' . + '\x{8A5E}\x{8A60}\x{8A62}\x{8A63}\x{8A66}\x{8A69}\x{8A6B}\x{8A6C}\x{8A6D}' . + '\x{8A6E}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A7C}\x{8A82}\x{8A84}\x{8A85}' . + '\x{8A87}\x{8A89}\x{8A8C}\x{8A8D}\x{8A91}\x{8A93}\x{8A95}\x{8A98}\x{8A9A}' . + '\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA8}\x{8AAC}' . + '\x{8AAD}\x{8AB0}\x{8AB2}\x{8AB9}\x{8ABC}\x{8ABF}\x{8AC2}\x{8AC4}\x{8AC7}' . + '\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACF}\x{8AD2}\x{8AD6}\x{8ADA}\x{8ADB}\x{8ADC}' . + '\x{8ADE}\x{8AE0}\x{8AE1}\x{8AE2}\x{8AE4}\x{8AE6}\x{8AE7}\x{8AEB}\x{8AED}' . + '\x{8AEE}\x{8AF1}\x{8AF3}\x{8AF7}\x{8AF8}\x{8AFA}\x{8AFE}\x{8B00}\x{8B01}' . + '\x{8B02}\x{8B04}\x{8B07}\x{8B0C}\x{8B0E}\x{8B10}\x{8B14}\x{8B16}\x{8B17}' . + '\x{8B19}\x{8B1A}\x{8B1B}\x{8B1D}\x{8B20}\x{8B21}\x{8B26}\x{8B28}\x{8B2B}' . + '\x{8B2C}\x{8B33}\x{8B39}\x{8B3E}\x{8B41}\x{8B49}\x{8B4C}\x{8B4E}\x{8B4F}' . + '\x{8B56}\x{8B58}\x{8B5A}\x{8B5B}\x{8B5C}\x{8B5F}\x{8B66}\x{8B6B}\x{8B6C}' . + '\x{8B6F}\x{8B70}\x{8B71}\x{8B72}\x{8B74}\x{8B77}\x{8B7D}\x{8B80}\x{8B83}' . + '\x{8B8A}\x{8B8C}\x{8B8E}\x{8B90}\x{8B92}\x{8B93}\x{8B96}\x{8B99}\x{8B9A}' . + '\x{8C37}\x{8C3A}\x{8C3F}\x{8C41}\x{8C46}\x{8C48}\x{8C4A}\x{8C4C}\x{8C4E}' . + '\x{8C50}\x{8C55}\x{8C5A}\x{8C61}\x{8C62}\x{8C6A}\x{8C6B}\x{8C6C}\x{8C78}' . + '\x{8C79}\x{8C7A}\x{8C7C}\x{8C82}\x{8C85}\x{8C89}\x{8C8A}\x{8C8C}\x{8C8D}' . + '\x{8C8E}\x{8C94}\x{8C98}\x{8C9D}\x{8C9E}\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA7}' . + '\x{8CA8}\x{8CA9}\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}' . + '\x{8CB2}\x{8CB3}\x{8CB4}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CBB}\x{8CBC}\x{8CBD}' . + '\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}\x{8CC7}\x{8CC8}\x{8CCA}' . + '\x{8CCD}\x{8CCE}\x{8CD1}\x{8CD3}\x{8CDA}\x{8CDB}\x{8CDC}\x{8CDE}\x{8CE0}' . + '\x{8CE2}\x{8CE3}\x{8CE4}\x{8CE6}\x{8CEA}\x{8CED}\x{8CFA}\x{8CFB}\x{8CFC}' . + '\x{8CFD}\x{8D04}\x{8D05}\x{8D07}\x{8D08}\x{8D0A}\x{8D0B}\x{8D0D}\x{8D0F}' . + '\x{8D10}\x{8D13}\x{8D14}\x{8D16}\x{8D64}\x{8D66}\x{8D67}\x{8D6B}\x{8D6D}' . + '\x{8D70}\x{8D71}\x{8D73}\x{8D74}\x{8D77}\x{8D81}\x{8D85}\x{8D8A}\x{8D99}' . + '\x{8DA3}\x{8DA8}\x{8DB3}\x{8DBA}\x{8DBE}\x{8DC2}\x{8DCB}\x{8DCC}\x{8DCF}' . + '\x{8DD6}\x{8DDA}\x{8DDB}\x{8DDD}\x{8DDF}\x{8DE1}\x{8DE3}\x{8DE8}\x{8DEA}' . + '\x{8DEB}\x{8DEF}\x{8DF3}\x{8DF5}\x{8DFC}\x{8DFF}\x{8E08}\x{8E09}\x{8E0A}' . + '\x{8E0F}\x{8E10}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E2A}\x{8E30}\x{8E34}\x{8E35}' . + '\x{8E42}\x{8E44}\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4C}\x{8E50}\x{8E55}' . + '\x{8E59}\x{8E5F}\x{8E60}\x{8E63}\x{8E64}\x{8E72}\x{8E74}\x{8E76}\x{8E7C}' . + '\x{8E81}\x{8E84}\x{8E85}\x{8E87}\x{8E8A}\x{8E8B}\x{8E8D}\x{8E91}\x{8E93}' . + '\x{8E94}\x{8E99}\x{8EA1}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAF}\x{8EB0}\x{8EB1}' . + '\x{8EBE}\x{8EC5}\x{8EC6}\x{8EC8}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ED2}' . + '\x{8EDB}\x{8EDF}\x{8EE2}\x{8EE3}\x{8EEB}\x{8EF8}\x{8EFB}\x{8EFC}\x{8EFD}' . + '\x{8EFE}\x{8F03}\x{8F05}\x{8F09}\x{8F0A}\x{8F0C}\x{8F12}\x{8F13}\x{8F14}' . + '\x{8F15}\x{8F19}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1F}\x{8F26}\x{8F29}\x{8F2A}' . + '\x{8F2F}\x{8F33}\x{8F38}\x{8F39}\x{8F3B}\x{8F3E}\x{8F3F}\x{8F42}\x{8F44}' . + '\x{8F45}\x{8F46}\x{8F49}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F57}\x{8F5C}\x{8F5F}' . + '\x{8F61}\x{8F62}\x{8F63}\x{8F64}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA3}' . + '\x{8FA7}\x{8FA8}\x{8FAD}\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB7}' . + '\x{8FBA}\x{8FBB}\x{8FBC}\x{8FBF}\x{8FC2}\x{8FC4}\x{8FC5}\x{8FCE}\x{8FD1}' . + '\x{8FD4}\x{8FDA}\x{8FE2}\x{8FE5}\x{8FE6}\x{8FE9}\x{8FEA}\x{8FEB}\x{8FED}' . + '\x{8FEF}\x{8FF0}\x{8FF4}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}\x{8FFD}\x{9000}' . + '\x{9001}\x{9003}\x{9005}\x{9006}\x{900B}\x{900D}\x{900E}\x{900F}\x{9010}' . + '\x{9011}\x{9013}\x{9014}\x{9015}\x{9016}\x{9017}\x{9019}\x{901A}\x{901D}' . + '\x{901E}\x{901F}\x{9020}\x{9021}\x{9022}\x{9023}\x{9027}\x{902E}\x{9031}' . + '\x{9032}\x{9035}\x{9036}\x{9038}\x{9039}\x{903C}\x{903E}\x{9041}\x{9042}' . + '\x{9045}\x{9047}\x{9049}\x{904A}\x{904B}\x{904D}\x{904E}\x{904F}\x{9050}' . + '\x{9051}\x{9052}\x{9053}\x{9054}\x{9055}\x{9056}\x{9058}\x{9059}\x{905C}' . + '\x{905E}\x{9060}\x{9061}\x{9063}\x{9065}\x{9068}\x{9069}\x{906D}\x{906E}' . + '\x{906F}\x{9072}\x{9075}\x{9076}\x{9077}\x{9078}\x{907A}\x{907C}\x{907D}' . + '\x{907F}\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9087}\x{9089}\x{908A}' . + '\x{908F}\x{9091}\x{90A3}\x{90A6}\x{90A8}\x{90AA}\x{90AF}\x{90B1}\x{90B5}' . + '\x{90B8}\x{90C1}\x{90CA}\x{90CE}\x{90DB}\x{90E1}\x{90E2}\x{90E4}\x{90E8}' . + '\x{90ED}\x{90F5}\x{90F7}\x{90FD}\x{9102}\x{9112}\x{9119}\x{912D}\x{9130}' . + '\x{9132}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}\x{914E}\x{9152}\x{9154}' . + '\x{9156}\x{9158}\x{9162}\x{9163}\x{9165}\x{9169}\x{916A}\x{916C}\x{9172}' . + '\x{9173}\x{9175}\x{9177}\x{9178}\x{9182}\x{9187}\x{9189}\x{918B}\x{918D}' . + '\x{9190}\x{9192}\x{9197}\x{919C}\x{91A2}\x{91A4}\x{91AA}\x{91AB}\x{91AF}' . + '\x{91B4}\x{91B5}\x{91B8}\x{91BA}\x{91C0}\x{91C1}\x{91C6}\x{91C7}\x{91C8}' . + '\x{91C9}\x{91CB}\x{91CC}\x{91CD}\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D6}' . + '\x{91D8}\x{91DB}\x{91DC}\x{91DD}\x{91DF}\x{91E1}\x{91E3}\x{91E6}\x{91E7}' . + '\x{91F5}\x{91F6}\x{91FC}\x{91FF}\x{920D}\x{920E}\x{9211}\x{9214}\x{9215}' . + '\x{921E}\x{9229}\x{922C}\x{9234}\x{9237}\x{923F}\x{9244}\x{9245}\x{9248}' . + '\x{9249}\x{924B}\x{9250}\x{9257}\x{925A}\x{925B}\x{925E}\x{9262}\x{9264}' . + '\x{9266}\x{9271}\x{927E}\x{9280}\x{9283}\x{9285}\x{9291}\x{9293}\x{9295}' . + '\x{9296}\x{9298}\x{929A}\x{929B}\x{929C}\x{92AD}\x{92B7}\x{92B9}\x{92CF}' . + '\x{92D2}\x{92E4}\x{92E9}\x{92EA}\x{92ED}\x{92F2}\x{92F3}\x{92F8}\x{92FA}' . + '\x{92FC}\x{9306}\x{930F}\x{9310}\x{9318}\x{9319}\x{931A}\x{9320}\x{9322}' . + '\x{9323}\x{9326}\x{9328}\x{932B}\x{932C}\x{932E}\x{932F}\x{9332}\x{9335}' . + '\x{933A}\x{933B}\x{9344}\x{934B}\x{934D}\x{9354}\x{9356}\x{935B}\x{935C}' . + '\x{9360}\x{936C}\x{936E}\x{9375}\x{937C}\x{937E}\x{938C}\x{9394}\x{9396}' . + '\x{9397}\x{939A}\x{93A7}\x{93AC}\x{93AD}\x{93AE}\x{93B0}\x{93B9}\x{93C3}' . + '\x{93C8}\x{93D0}\x{93D1}\x{93D6}\x{93D7}\x{93D8}\x{93DD}\x{93E1}\x{93E4}' . + '\x{93E5}\x{93E8}\x{9403}\x{9407}\x{9410}\x{9413}\x{9414}\x{9418}\x{9419}' . + '\x{941A}\x{9421}\x{942B}\x{9435}\x{9436}\x{9438}\x{943A}\x{9441}\x{9444}' . + '\x{9451}\x{9452}\x{9453}\x{945A}\x{945B}\x{945E}\x{9460}\x{9462}\x{946A}' . + '\x{9470}\x{9475}\x{9477}\x{947C}\x{947D}\x{947E}\x{947F}\x{9481}\x{9577}' . + '\x{9580}\x{9582}\x{9583}\x{9587}\x{9589}\x{958A}\x{958B}\x{958F}\x{9591}' . + '\x{9593}\x{9594}\x{9596}\x{9598}\x{9599}\x{95A0}\x{95A2}\x{95A3}\x{95A4}' . + '\x{95A5}\x{95A7}\x{95A8}\x{95AD}\x{95B2}\x{95B9}\x{95BB}\x{95BC}\x{95BE}' . + '\x{95C3}\x{95C7}\x{95CA}\x{95CC}\x{95CD}\x{95D4}\x{95D5}\x{95D6}\x{95D8}' . + '\x{95DC}\x{95E1}\x{95E2}\x{95E5}\x{961C}\x{9621}\x{9628}\x{962A}\x{962E}' . + '\x{962F}\x{9632}\x{963B}\x{963F}\x{9640}\x{9642}\x{9644}\x{964B}\x{964C}' . + '\x{964D}\x{964F}\x{9650}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9662}' . + '\x{9663}\x{9664}\x{9665}\x{9666}\x{966A}\x{966C}\x{9670}\x{9672}\x{9673}' . + '\x{9675}\x{9676}\x{9677}\x{9678}\x{967A}\x{967D}\x{9685}\x{9686}\x{9688}' . + '\x{968A}\x{968B}\x{968D}\x{968E}\x{968F}\x{9694}\x{9695}\x{9697}\x{9698}' . + '\x{9699}\x{969B}\x{969C}\x{96A0}\x{96A3}\x{96A7}\x{96A8}\x{96AA}\x{96B0}' . + '\x{96B1}\x{96B2}\x{96B4}\x{96B6}\x{96B7}\x{96B8}\x{96B9}\x{96BB}\x{96BC}' . + '\x{96C0}\x{96C1}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C9}\x{96CB}\x{96CC}' . + '\x{96CD}\x{96CE}\x{96D1}\x{96D5}\x{96D6}\x{96D9}\x{96DB}\x{96DC}\x{96E2}' . + '\x{96E3}\x{96E8}\x{96EA}\x{96EB}\x{96F0}\x{96F2}\x{96F6}\x{96F7}\x{96F9}' . + '\x{96FB}\x{9700}\x{9704}\x{9706}\x{9707}\x{9708}\x{970A}\x{970D}\x{970E}' . + '\x{970F}\x{9711}\x{9713}\x{9716}\x{9719}\x{971C}\x{971E}\x{9724}\x{9727}' . + '\x{972A}\x{9730}\x{9732}\x{9738}\x{9739}\x{973D}\x{973E}\x{9742}\x{9744}' . + '\x{9746}\x{9748}\x{9749}\x{9752}\x{9756}\x{9759}\x{975C}\x{975E}\x{9760}' . + '\x{9761}\x{9762}\x{9764}\x{9766}\x{9768}\x{9769}\x{976B}\x{976D}\x{9771}' . + '\x{9774}\x{9779}\x{977A}\x{977C}\x{9781}\x{9784}\x{9785}\x{9786}\x{978B}' . + '\x{978D}\x{978F}\x{9790}\x{9798}\x{979C}\x{97A0}\x{97A3}\x{97A6}\x{97A8}' . + '\x{97AB}\x{97AD}\x{97B3}\x{97B4}\x{97C3}\x{97C6}\x{97C8}\x{97CB}\x{97D3}' . + '\x{97DC}\x{97ED}\x{97EE}\x{97F2}\x{97F3}\x{97F5}\x{97F6}\x{97FB}\x{97FF}' . + '\x{9801}\x{9802}\x{9803}\x{9805}\x{9806}\x{9808}\x{980C}\x{980F}\x{9810}' . + '\x{9811}\x{9812}\x{9813}\x{9817}\x{9818}\x{981A}\x{9821}\x{9824}\x{982C}' . + '\x{982D}\x{9834}\x{9837}\x{9838}\x{983B}\x{983C}\x{983D}\x{9846}\x{984B}' . + '\x{984C}\x{984D}\x{984E}\x{984F}\x{9854}\x{9855}\x{9858}\x{985B}\x{985E}' . + '\x{9867}\x{986B}\x{986F}\x{9870}\x{9871}\x{9873}\x{9874}\x{98A8}\x{98AA}' . + '\x{98AF}\x{98B1}\x{98B6}\x{98C3}\x{98C4}\x{98C6}\x{98DB}\x{98DC}\x{98DF}' . + '\x{98E2}\x{98E9}\x{98EB}\x{98ED}\x{98EE}\x{98EF}\x{98F2}\x{98F4}\x{98FC}' . + '\x{98FD}\x{98FE}\x{9903}\x{9905}\x{9909}\x{990A}\x{990C}\x{9910}\x{9912}' . + '\x{9913}\x{9914}\x{9918}\x{991D}\x{991E}\x{9920}\x{9921}\x{9924}\x{9928}' . + '\x{992C}\x{992E}\x{993D}\x{993E}\x{9942}\x{9945}\x{9949}\x{994B}\x{994C}' . + '\x{9950}\x{9951}\x{9952}\x{9955}\x{9957}\x{9996}\x{9997}\x{9998}\x{9999}' . + '\x{99A5}\x{99A8}\x{99AC}\x{99AD}\x{99AE}\x{99B3}\x{99B4}\x{99BC}\x{99C1}' . + '\x{99C4}\x{99C5}\x{99C6}\x{99C8}\x{99D0}\x{99D1}\x{99D2}\x{99D5}\x{99D8}' . + '\x{99DB}\x{99DD}\x{99DF}\x{99E2}\x{99ED}\x{99EE}\x{99F1}\x{99F2}\x{99F8}' . + '\x{99FB}\x{99FF}\x{9A01}\x{9A05}\x{9A0E}\x{9A0F}\x{9A12}\x{9A13}\x{9A19}' . + '\x{9A28}\x{9A2B}\x{9A30}\x{9A37}\x{9A3E}\x{9A40}\x{9A42}\x{9A43}\x{9A45}' . + '\x{9A4D}\x{9A55}\x{9A57}\x{9A5A}\x{9A5B}\x{9A5F}\x{9A62}\x{9A64}\x{9A65}' . + '\x{9A69}\x{9A6A}\x{9A6B}\x{9AA8}\x{9AAD}\x{9AB0}\x{9AB8}\x{9ABC}\x{9AC0}' . + '\x{9AC4}\x{9ACF}\x{9AD1}\x{9AD3}\x{9AD4}\x{9AD8}\x{9ADE}\x{9ADF}\x{9AE2}' . + '\x{9AE3}\x{9AE6}\x{9AEA}\x{9AEB}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF4}' . + '\x{9AF7}\x{9AFB}\x{9B06}\x{9B18}\x{9B1A}\x{9B1F}\x{9B22}\x{9B23}\x{9B25}' . + '\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2E}\x{9B2F}\x{9B31}\x{9B32}\x{9B3B}' . + '\x{9B3C}\x{9B41}\x{9B42}\x{9B43}\x{9B44}\x{9B45}\x{9B4D}\x{9B4E}\x{9B4F}' . + '\x{9B51}\x{9B54}\x{9B58}\x{9B5A}\x{9B6F}\x{9B74}\x{9B83}\x{9B8E}\x{9B91}' . + '\x{9B92}\x{9B93}\x{9B96}\x{9B97}\x{9B9F}\x{9BA0}\x{9BA8}\x{9BAA}\x{9BAB}' . + '\x{9BAD}\x{9BAE}\x{9BB4}\x{9BB9}\x{9BC0}\x{9BC6}\x{9BC9}\x{9BCA}\x{9BCF}' . + '\x{9BD1}\x{9BD2}\x{9BD4}\x{9BD6}\x{9BDB}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}' . + '\x{9BE8}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF5}\x{9C04}\x{9C06}\x{9C08}\x{9C09}' . + '\x{9C0A}\x{9C0C}\x{9C0D}\x{9C10}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C1B}' . + '\x{9C21}\x{9C24}\x{9C25}\x{9C2D}\x{9C2E}\x{9C2F}\x{9C30}\x{9C32}\x{9C39}' . + '\x{9C3A}\x{9C3B}\x{9C3E}\x{9C46}\x{9C47}\x{9C48}\x{9C52}\x{9C57}\x{9C5A}' . + '\x{9C60}\x{9C67}\x{9C76}\x{9C78}\x{9CE5}\x{9CE7}\x{9CE9}\x{9CEB}\x{9CEC}' . + '\x{9CF0}\x{9CF3}\x{9CF4}\x{9CF6}\x{9D03}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' . + '\x{9D0E}\x{9D12}\x{9D15}\x{9D1B}\x{9D1F}\x{9D23}\x{9D26}\x{9D28}\x{9D2A}' . + '\x{9D2B}\x{9D2C}\x{9D3B}\x{9D3E}\x{9D3F}\x{9D41}\x{9D44}\x{9D46}\x{9D48}' . + '\x{9D50}\x{9D51}\x{9D59}\x{9D5C}\x{9D5D}\x{9D5E}\x{9D60}\x{9D61}\x{9D64}' . + '\x{9D6C}\x{9D6F}\x{9D72}\x{9D7A}\x{9D87}\x{9D89}\x{9D8F}\x{9D9A}\x{9DA4}' . + '\x{9DA9}\x{9DAB}\x{9DAF}\x{9DB2}\x{9DB4}\x{9DB8}\x{9DBA}\x{9DBB}\x{9DC1}' . + '\x{9DC2}\x{9DC4}\x{9DC6}\x{9DCF}\x{9DD3}\x{9DD9}\x{9DE6}\x{9DED}\x{9DEF}' . + '\x{9DF2}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFD}\x{9E1A}\x{9E1B}\x{9E1E}\x{9E75}' . + '\x{9E78}\x{9E79}\x{9E7D}\x{9E7F}\x{9E81}\x{9E88}\x{9E8B}\x{9E8C}\x{9E91}' . + '\x{9E92}\x{9E93}\x{9E95}\x{9E97}\x{9E9D}\x{9E9F}\x{9EA5}\x{9EA6}\x{9EA9}' . + '\x{9EAA}\x{9EAD}\x{9EB8}\x{9EB9}\x{9EBA}\x{9EBB}\x{9EBC}\x{9EBE}\x{9EBF}' . + '\x{9EC4}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED2}\x{9ED4}\x{9ED8}' . + '\x{9ED9}\x{9EDB}\x{9EDC}\x{9EDD}\x{9EDE}\x{9EE0}\x{9EE5}\x{9EE8}\x{9EEF}' . + '\x{9EF4}\x{9EF6}\x{9EF7}\x{9EF9}\x{9EFB}\x{9EFC}\x{9EFD}\x{9F07}\x{9F08}' . + '\x{9F0E}\x{9F13}\x{9F15}\x{9F20}\x{9F21}\x{9F2C}\x{9F3B}\x{9F3E}\x{9F4A}' . + '\x{9F4B}\x{9F4E}\x{9F4F}\x{9F52}\x{9F54}\x{9F5F}\x{9F60}\x{9F61}\x{9F62}' . + '\x{9F63}\x{9F66}\x{9F67}\x{9F6A}\x{9F6C}\x{9F72}\x{9F76}\x{9F77}\x{9F8D}' . + '\x{9F95}\x{9F9C}\x{9F9D}\x{9FA0}]{1,15}$/iu', +]; diff --git a/lib/laminas/laminas-validator/src/Iban.php b/lib/laminas/laminas-validator/src/Iban.php new file mode 100644 index 000000000..55bd49c7f --- /dev/null +++ b/lib/laminas/laminas-validator/src/Iban.php @@ -0,0 +1,274 @@ + "Unknown country within the IBAN", + self::SEPANOTSUPPORTED => "Countries outside the Single Euro Payments Area (SEPA) are not supported", + self::FALSEFORMAT => "The input has a false IBAN format", + self::CHECKFAILED => "The input has failed the IBAN check", + ]; + + /** + * Optional country code by ISO 3166-1 + * + * @var string|null + */ + protected $countryCode; + + /** + * Optionally allow IBAN codes from non-SEPA countries. Defaults to true + * + * @var bool + */ + protected $allowNonSepa = true; + + /** + * The SEPA country codes + * + * @var array + */ + protected static $sepaCountries = [ + 'AT', 'BE', 'BG', 'CY', 'CZ', 'DK', 'FO', 'GL', 'EE', 'FI', 'FR', 'DE', + 'GI', 'GR', 'HU', 'IS', 'IE', 'IT', 'LV', 'LI', 'LT', 'LU', 'MT', 'MC', + 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'CH', 'GB', 'SM', + 'HR', + ]; + + /** + * IBAN regexes by country code + * + * @var array + */ + protected static $ibanRegex = [ + 'AD' => 'AD[0-9]{2}[0-9]{4}[0-9]{4}[A-Z0-9]{12}', + 'AE' => 'AE[0-9]{2}[0-9]{3}[0-9]{16}', + 'AL' => 'AL[0-9]{2}[0-9]{8}[A-Z0-9]{16}', + 'AT' => 'AT[0-9]{2}[0-9]{5}[0-9]{11}', + 'AZ' => 'AZ[0-9]{2}[A-Z]{4}[A-Z0-9]{20}', + 'BA' => 'BA[0-9]{2}[0-9]{3}[0-9]{3}[0-9]{8}[0-9]{2}', + 'BE' => 'BE[0-9]{2}[0-9]{3}[0-9]{7}[0-9]{2}', + 'BG' => 'BG[0-9]{2}[A-Z]{4}[0-9]{4}[0-9]{2}[A-Z0-9]{8}', + 'BH' => 'BH[0-9]{2}[A-Z]{4}[A-Z0-9]{14}', + 'BR' => 'BR[0-9]{2}[0-9]{8}[0-9]{5}[0-9]{10}[A-Z][A-Z0-9]', + 'BY' => 'BY[0-9]{2}[A-Z0-9]{4}[0-9]{4}[A-Z0-9]{16}', + 'CH' => 'CH[0-9]{2}[0-9]{5}[A-Z0-9]{12}', + 'CR' => 'CR[0-9]{2}[0-9]{3}[0-9]{14}', + 'CY' => 'CY[0-9]{2}[0-9]{3}[0-9]{5}[A-Z0-9]{16}', + 'CZ' => 'CZ[0-9]{2}[0-9]{20}', + 'DE' => 'DE[0-9]{2}[0-9]{8}[0-9]{10}', + 'DO' => 'DO[0-9]{2}[A-Z0-9]{4}[0-9]{20}', + 'DK' => 'DK[0-9]{2}[0-9]{14}', + 'EE' => 'EE[0-9]{2}[0-9]{2}[0-9]{2}[0-9]{11}[0-9]{1}', + 'ES' => 'ES[0-9]{2}[0-9]{4}[0-9]{4}[0-9]{1}[0-9]{1}[0-9]{10}', + 'FI' => 'FI[0-9]{2}[0-9]{6}[0-9]{7}[0-9]{1}', + 'FO' => 'FO[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}', + 'FR' => 'FR[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}', + 'GB' => 'GB[0-9]{2}[A-Z]{4}[0-9]{6}[0-9]{8}', + 'GE' => 'GE[0-9]{2}[A-Z]{2}[0-9]{16}', + 'GI' => 'GI[0-9]{2}[A-Z]{4}[A-Z0-9]{15}', + 'GL' => 'GL[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}', + 'GR' => 'GR[0-9]{2}[0-9]{3}[0-9]{4}[A-Z0-9]{16}', + 'GT' => 'GT[0-9]{2}[A-Z0-9]{4}[A-Z0-9]{20}', + 'HR' => 'HR[0-9]{2}[0-9]{7}[0-9]{10}', + 'HU' => 'HU[0-9]{2}[0-9]{3}[0-9]{4}[0-9]{1}[0-9]{15}[0-9]{1}', + 'IE' => 'IE[0-9]{2}[A-Z]{4}[0-9]{6}[0-9]{8}', + 'IL' => 'IL[0-9]{2}[0-9]{3}[0-9]{3}[0-9]{13}', + 'IS' => 'IS[0-9]{2}[0-9]{4}[0-9]{2}[0-9]{6}[0-9]{10}', + 'IT' => 'IT[0-9]{2}[A-Z]{1}[0-9]{5}[0-9]{5}[A-Z0-9]{12}', + 'KW' => 'KW[0-9]{2}[A-Z]{4}[0-9]{22}', + 'KZ' => 'KZ[0-9]{2}[0-9]{3}[A-Z0-9]{13}', + 'LB' => 'LB[0-9]{2}[0-9]{4}[A-Z0-9]{20}', + 'LI' => 'LI[0-9]{2}[0-9]{5}[A-Z0-9]{12}', + 'LT' => 'LT[0-9]{2}[0-9]{5}[0-9]{11}', + 'LU' => 'LU[0-9]{2}[0-9]{3}[A-Z0-9]{13}', + 'LV' => 'LV[0-9]{2}[A-Z]{4}[A-Z0-9]{13}', + 'MC' => 'MC[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}', + 'MD' => 'MD[0-9]{2}[A-Z0-9]{20}', + 'ME' => 'ME[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}', + 'MK' => 'MK[0-9]{2}[0-9]{3}[A-Z0-9]{10}[0-9]{2}', + 'MR' => 'MR13[0-9]{5}[0-9]{5}[0-9]{11}[0-9]{2}', + 'MT' => 'MT[0-9]{2}[A-Z]{4}[0-9]{5}[A-Z0-9]{18}', + 'MU' => 'MU[0-9]{2}[A-Z]{4}[0-9]{2}[0-9]{2}[0-9]{12}[0-9]{3}[A-Z]{3}', + 'NL' => 'NL[0-9]{2}[A-Z]{4}[0-9]{10}', + 'NO' => 'NO[0-9]{2}[0-9]{4}[0-9]{6}[0-9]{1}', + 'PK' => 'PK[0-9]{2}[A-Z]{4}[A-Z0-9]{16}', + 'PL' => 'PL[0-9]{2}[0-9]{8}[0-9]{16}', + 'PS' => 'PS[0-9]{2}[A-Z]{4}[A-Z0-9]{21}', + 'PT' => 'PT[0-9]{2}[0-9]{4}[0-9]{4}[0-9]{11}[0-9]{2}', + 'RO' => 'RO[0-9]{2}[A-Z]{4}[A-Z0-9]{16}', + 'RS' => 'RS[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}', + 'SA' => 'SA[0-9]{2}[0-9]{2}[A-Z0-9]{18}', + 'SE' => 'SE[0-9]{2}[0-9]{3}[0-9]{16}[0-9]{1}', + 'SI' => 'SI[0-9]{2}[0-9]{5}[0-9]{8}[0-9]{2}', + 'SK' => 'SK[0-9]{2}[0-9]{4}[0-9]{6}[0-9]{10}', + 'SM' => 'SM[0-9]{2}[A-Z]{1}[0-9]{5}[0-9]{5}[A-Z0-9]{12}', + 'TN' => 'TN59[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}', + 'TR' => 'TR[0-9]{2}[0-9]{5}[A-Z0-9]{1}[A-Z0-9]{16}', + 'VG' => 'VG[0-9]{2}[A-Z]{4}[0-9]{16}', + ]; + + /** + * Sets validator options + * + * @param array|Traversable $options OPTIONAL + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if (array_key_exists('country_code', $options)) { + $this->setCountryCode($options['country_code']); + } + + if (array_key_exists('allow_non_sepa', $options)) { + $this->setAllowNonSepa($options['allow_non_sepa']); + } + + parent::__construct($options); + } + + /** + * Returns the optional country code by ISO 3166-1 + * + * @return string|null + */ + public function getCountryCode() + { + return $this->countryCode; + } + + /** + * Sets an optional country code by ISO 3166-1 + * + * @param string|null $countryCode + * @return Iban provides a fluent interface + * @throws Exception\InvalidArgumentException + */ + public function setCountryCode($countryCode = null) + { + if ($countryCode !== null) { + $countryCode = (string) $countryCode; + + if (! isset(static::$ibanRegex[$countryCode])) { + throw new Exception\InvalidArgumentException( + "Country code '{$countryCode}' invalid by ISO 3166-1 or not supported" + ); + } + } + + $this->countryCode = $countryCode; + return $this; + } + + /** + * Returns the optional allow non-sepa countries setting + * + * @return bool + */ + public function allowNonSepa() + { + return $this->allowNonSepa; + } + + /** + * Sets the optional allow non-sepa countries setting + * + * @param bool $allowNonSepa + * @return Iban provides a fluent interface + */ + public function setAllowNonSepa($allowNonSepa) + { + $this->allowNonSepa = (bool) $allowNonSepa; + return $this; + } + + /** + * Returns true if $value is a valid IBAN + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::FALSEFORMAT); + return false; + } + + $value = str_replace(' ', '', strtoupper($value)); + $this->setValue($value); + + $countryCode = $this->getCountryCode(); + if ($countryCode === null) { + $countryCode = substr($value, 0, 2); + } + + if (! array_key_exists($countryCode, static::$ibanRegex)) { + $this->setValue($countryCode); + $this->error(self::NOTSUPPORTED); + return false; + } + + if (! $this->allowNonSepa && ! in_array($countryCode, static::$sepaCountries)) { + $this->setValue($countryCode); + $this->error(self::SEPANOTSUPPORTED); + return false; + } + + if (! preg_match('/^' . static::$ibanRegex[$countryCode] . '$/', $value)) { + $this->error(self::FALSEFORMAT); + return false; + } + + $format = substr($value, 4) . substr($value, 0, 4); + $format = str_replace( + ['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'], + ['10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', + '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35'], + $format + ); + + $temp = intval(substr($format, 0, 1)); + $len = strlen($format); + for ($x = 1; $x < $len; ++$x) { + $temp *= 10; + $temp += intval(substr($format, $x, 1)); + $temp %= 97; + } + + if ($temp != 1) { + $this->error(self::CHECKFAILED); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Identical.php b/lib/laminas/laminas-validator/src/Identical.php new file mode 100644 index 000000000..4c28a027d --- /dev/null +++ b/lib/laminas/laminas-validator/src/Identical.php @@ -0,0 +1,201 @@ + "The two given tokens do not match", + self::MISSING_TOKEN => 'No token was provided to match against', + ]; + + /** + * @var array + */ + protected $messageVariables = [ + 'token' => 'tokenString' + ]; + + /** + * Original token against which to validate + * @var string + */ + protected $tokenString; + protected $token; + protected $strict = true; + protected $literal = false; + + /** + * Sets validator options + * + * @param mixed $token + */ + public function __construct($token = null) + { + if ($token instanceof Traversable) { + $token = ArrayUtils::iteratorToArray($token); + } + + if (is_array($token) && array_key_exists('token', $token)) { + if (array_key_exists('strict', $token)) { + $this->setStrict($token['strict']); + } + + if (array_key_exists('literal', $token)) { + $this->setLiteral($token['literal']); + } + + $this->setToken($token['token']); + } elseif (null !== $token) { + $this->setToken($token); + } + + parent::__construct(is_array($token) ? $token : null); + } + + /** + * Retrieve token + * + * @return mixed + */ + public function getToken() + { + return $this->token; + } + + /** + * Set token against which to compare + * + * @param mixed $token + * @return Identical + */ + public function setToken($token) + { + $this->tokenString = (is_array($token) ? var_export($token, true) : (string) $token); + $this->token = $token; + return $this; + } + + /** + * Returns the strict parameter + * + * @return bool + */ + public function getStrict() + { + return $this->strict; + } + + /** + * Sets the strict parameter + * + * @param bool $strict + * @return Identical + */ + public function setStrict($strict) + { + $this->strict = (bool) $strict; + return $this; + } + + /** + * Returns the literal parameter + * + * @return bool + */ + public function getLiteral() + { + return $this->literal; + } + + /** + * Sets the literal parameter + * + * @param bool $literal + * @return Identical + */ + public function setLiteral($literal) + { + $this->literal = (bool) $literal; + return $this; + } + + /** + * Returns true if and only if a token has been set and the provided value + * matches that token. + * + * @param mixed $value + * @param array|ArrayAccess $context + * @throws Exception\InvalidArgumentException If context is not array or ArrayObject + * @return bool + */ + public function isValid($value, $context = null) + { + $this->setValue($value); + + $token = $this->getToken(); + + if (! $this->getLiteral() && $context !== null) { + if (! is_array($context) && ! ($context instanceof ArrayAccess)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Context passed to %s must be array, ArrayObject or null; received "%s"', + __METHOD__, + is_object($context) ? get_class($context) : gettype($context) + )); + } + + if (is_array($token)) { + while (is_array($token)) { + $key = key($token); + if (! isset($context[$key])) { + break; + } + $context = $context[$key]; + $token = $token[$key]; + } + } + + // if $token is an array it means the above loop didn't went all the way down to the leaf, + // so the $token structure doesn't match the $context structure + if (is_array($token) || ! isset($context[$token])) { + $token = $this->getToken(); + } else { + $token = $context[$token]; + } + } + + if ($token === null) { + $this->error(self::MISSING_TOKEN); + return false; + } + + $strict = $this->getStrict(); + if (($strict && ($value !== $token)) || (! $strict && ($value != $token))) { + $this->error(self::NOT_SAME); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/InArray.php b/lib/laminas/laminas-validator/src/InArray.php new file mode 100644 index 000000000..6a7fbfd4d --- /dev/null +++ b/lib/laminas/laminas-validator/src/InArray.php @@ -0,0 +1,229 @@ + 'The input was not found in the haystack', + ]; + + /** + * Haystack of possible values + * + * @var array + */ + protected $haystack; + + /** + * Type of strict check to be used. Due to "foo" == 0 === TRUE with in_array when strict = false, + * an option has been added to prevent this. When $strict = 0/false, the most + * secure non-strict check is implemented. if $strict = -1, the default in_array non-strict + * behaviour is used + * + * @var int + */ + protected $strict = self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY; + + /** + * Whether a recursive search should be done + * + * @var bool + */ + protected $recursive = false; + + /** + * Returns the haystack option + * + * @return mixed + * @throws Exception\RuntimeException if haystack option is not set + */ + public function getHaystack() + { + if ($this->haystack === null) { + throw new Exception\RuntimeException('haystack option is mandatory'); + } + return $this->haystack; + } + + /** + * Sets the haystack option + * + * @param mixed $haystack + * @return InArray Provides a fluent interface + */ + public function setHaystack(array $haystack) + { + $this->haystack = $haystack; + return $this; + } + + /** + * Returns the strict option + * + * @return bool|int + */ + public function getStrict() + { + // To keep BC with new strict modes + if ($this->strict == self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY + || $this->strict == self::COMPARE_STRICT + ) { + return (bool) $this->strict; + } + return $this->strict; + } + + /** + * Sets the strict option mode + * InArray::COMPARE_STRICT + * InArray::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY + * InArray::COMPARE_NOT_STRICT + * + * @param int $strict + * @return InArray Provides a fluent interface + * @throws Exception\InvalidArgumentException + */ + public function setStrict($strict) + { + $checkTypes = [ + self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY, // 0 + self::COMPARE_STRICT, // 1 + self::COMPARE_NOT_STRICT // -1 + ]; + + // validate strict value + if (! in_array($strict, $checkTypes)) { + throw new Exception\InvalidArgumentException('Strict option must be one of the COMPARE_ constants'); + } + + $this->strict = $strict; + return $this; + } + + /** + * Returns the recursive option + * + * @return bool + */ + public function getRecursive() + { + return $this->recursive; + } + + /** + * Sets the recursive option + * + * @param bool $recursive + * @return InArray Provides a fluent interface + */ + public function setRecursive($recursive) + { + $this->recursive = (bool) $recursive; + return $this; + } + + /** + * Returns true if and only if $value is contained in the haystack option. If the strict + * option is true, then the type of $value is also checked. + * + * @param mixed $value + * See {@link http://php.net/manual/function.in-array.php#104501} + * @return bool + */ + public function isValid($value) + { + // we create a copy of the haystack in case we need to modify it + $haystack = $this->getHaystack(); + + // if the input is a string or float, and vulnerability protection is on + // we type cast the input to a string + if (self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY == $this->strict + && (is_int($value) || is_float($value))) { + $value = (string) $value; + } + + $this->setValue($value); + + if ($this->getRecursive()) { + $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($haystack)); + foreach ($iterator as $element) { + if (self::COMPARE_STRICT == $this->strict) { + if ($element === $value) { + return true; + } + } else { + // add protection to prevent string to int vuln's + $el = $element; + if (self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY == $this->strict + && is_string($value) && (is_int($el) || is_float($el)) + ) { + $el = (string) $el; + } + + if ($el == $value) { + return true; + } + } + } + } else { + /** + * If the check is not strict, then, to prevent "asdf" being converted to 0 + * and returning a false positive if 0 is in haystack, we type cast + * the haystack to strings. To prevent "56asdf" == 56 === TRUE we also + * type cast values like 56 to strings as well. + * + * This occurs only if the input is a string and a haystack member is an int + */ + if (self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY == $this->strict + && is_string($value) + ) { + foreach ($haystack as &$h) { + if (is_int($h) || is_float($h)) { + $h = (string) $h; + } + } + } + + if (in_array($value, $haystack, self::COMPARE_STRICT == $this->strict)) { + return true; + } + } + + $this->error(self::NOT_IN_ARRAY); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Ip.php b/lib/laminas/laminas-validator/src/Ip.php new file mode 100644 index 000000000..aaddc3764 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Ip.php @@ -0,0 +1,189 @@ + 'Invalid type given. String expected', + self::NOT_IP_ADDRESS => "The input does not appear to be a valid IP address", + ]; + + /** + * Internal options + * + * @var array + */ + protected $options = [ + 'allowipv4' => true, // Enable IPv4 Validation + 'allowipv6' => true, // Enable IPv6 Validation + 'allowipvfuture' => false, // Enable IPvFuture Validation + 'allowliteral' => true, // Enable IPs in literal format (only IPv6 and IPvFuture) + ]; + + /** + * Sets the options for this validator + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException If there is any kind of IP allowed or $options is not an array + * or Traversable. + * @return AbstractValidator + */ + public function setOptions($options = []) + { + parent::setOptions($options); + + if (! $this->options['allowipv4'] && ! $this->options['allowipv6'] && ! $this->options['allowipvfuture']) { + throw new Exception\InvalidArgumentException('Nothing to validate. Check your options'); + } + + return $this; + } + + /** + * Returns true if and only if $value is a valid IP address + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + + if ($this->options['allowipv4'] && $this->validateIPv4($value)) { + return true; + } else { + if ((bool) $this->options['allowliteral']) { + static $regex = '/^\[(.*)\]$/'; + if ((bool) preg_match($regex, $value, $matches)) { + $value = $matches[1]; + } + } + + if (($this->options['allowipv6'] && $this->validateIPv6($value)) || + ($this->options['allowipvfuture'] && $this->validateIPvFuture($value)) + ) { + return true; + } + } + $this->error(self::NOT_IP_ADDRESS); + return false; + } + + /** + * Validates an IPv4 address + * + * @param string $value + * @return bool + */ + protected function validateIPv4($value) + { + if (preg_match('/^([01]{8}\.){3}[01]{8}\z/i', $value)) { + // binary format 00000000.00000000.00000000.00000000 + $value = bindec(substr($value, 0, 8)) . '.' . bindec(substr($value, 9, 8)) . '.' + . bindec(substr($value, 18, 8)) . '.' . bindec(substr($value, 27, 8)); + } elseif (preg_match('/^([0-9]{3}\.){3}[0-9]{3}\z/i', $value)) { + // octet format 777.777.777.777 + $value = (int) substr($value, 0, 3) . '.' . (int) substr($value, 4, 3) . '.' + . (int) substr($value, 8, 3) . '.' . (int) substr($value, 12, 3); + } elseif (preg_match('/^([0-9a-f]{2}\.){3}[0-9a-f]{2}\z/i', $value)) { + // hex format ff.ff.ff.ff + $value = hexdec(substr($value, 0, 2)) . '.' . hexdec(substr($value, 3, 2)) . '.' + . hexdec(substr($value, 6, 2)) . '.' . hexdec(substr($value, 9, 2)); + } + + $ip2long = ip2long($value); + if ($ip2long === false) { + return false; + } + + return ($value == long2ip($ip2long)); + } + + /** + * Validates an IPv6 address + * + * @param string $value Value to check against + * @return bool True when $value is a valid ipv6 address + * False otherwise + */ + protected function validateIPv6($value) + { + if (strlen($value) < 3) { + return $value == '::'; + } + + if (strpos($value, '.')) { + $lastcolon = strrpos($value, ':'); + if (! ($lastcolon && $this->validateIPv4(substr($value, $lastcolon + 1)))) { + return false; + } + + $value = substr($value, 0, $lastcolon) . ':0:0'; + } + + if (strpos($value, '::') === false) { + return preg_match('/\A(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}\z/i', $value); + } + + $colonCount = substr_count($value, ':'); + if ($colonCount < 8) { + return preg_match('/\A(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?\z/i', $value); + } + + // special case with ending or starting double colon + if ($colonCount == 8) { + return preg_match('/\A(?:::)?(?:[a-f0-9]{1,4}:){6}[a-f0-9]{1,4}(?:::)?\z/i', $value); + } + + return false; + } + + /** + * Validates an IPvFuture address. + * + * IPvFuture is loosely defined in the Section 3.2.2 of RFC 3986 + * + * @param string $value Value to check against + * @return bool True when $value is a valid IPvFuture address + * False otherwise + */ + protected function validateIPvFuture($value) + { + /* + * ABNF: + * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," + * / ";" / "=" + */ + static $regex = '/^v([[:xdigit:]]+)\.[[:alnum:]\-\._~!\$&\'\(\)\*\+,;=:]+$/'; + + $result = (bool) preg_match($regex, $value, $matches); + + /* + * "As such, implementations must not provide the version flag for the + * existing IPv4 and IPv6 literal address forms described below." + */ + return ($result && $matches[1] != 4 && $matches[1] != 6); + } +} diff --git a/lib/laminas/laminas-validator/src/IsCountable.php b/lib/laminas/laminas-validator/src/IsCountable.php new file mode 100644 index 000000000..72ba94364 --- /dev/null +++ b/lib/laminas/laminas-validator/src/IsCountable.php @@ -0,0 +1,198 @@ + "The input must be an array or an instance of \\Countable", + self::NOT_EQUALS => "The input count must equal '%count%'", + self::GREATER_THAN => "The input count must be less than '%max%', inclusively", + self::LESS_THAN => "The input count must be greater than '%min%', inclusively", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'count' => ['options' => 'count'], + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + ]; + + /** + * Options for the between validator + * + * @var array + */ + protected $options = [ + 'count' => null, + 'min' => null, + 'max' => null, + ]; + + public function setOptions($options = []) + { + foreach (['count', 'min', 'max'] as $option) { + if (! is_array($options) || ! isset($options[$option])) { + continue; + } + + $method = sprintf('set%s', ucfirst($option)); + $this->$method($options[$option]); + unset($options[$option]); + } + + return parent::setOptions($options); + } + + /** + * Returns true if and only if $value is countable (and the count validates against optional values). + * + * @param iterable $value + * @return bool + */ + public function isValid($value) + { + if (! (is_array($value) || $value instanceof Countable)) { + $this->error(self::NOT_COUNTABLE); + return false; + } + + $count = count($value); + + if (is_numeric($this->getCount())) { + if ($count != $this->getCount()) { + $this->error(self::NOT_EQUALS); + return false; + } + + return true; + } + + if (is_numeric($this->getMax()) && $count > $this->getMax()) { + $this->error(self::GREATER_THAN); + return false; + } + + if (is_numeric($this->getMin()) && $count < $this->getMin()) { + $this->error(self::LESS_THAN); + return false; + } + + return true; + } + + /** + * Returns the count option + * + * @return mixed + */ + public function getCount() + { + return $this->options['count']; + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * @param mixed $value + * @return void + * @throws Exception\InvalidArgumentException if either a min or max option + * was previously set. + */ + private function setCount($value) + { + if (isset($this->options['min']) || isset($this->options['max'])) { + throw new Exception\InvalidArgumentException( + 'Cannot set count; conflicts with either a min or max option previously set' + ); + } + $this->options['count'] = $value; + } + + /** + * @param mixed $value + * @return void + * @throws Exception\InvalidArgumentException if either a count or max option + * was previously set. + */ + private function setMin($value) + { + if (isset($this->options['count'])) { + throw new Exception\InvalidArgumentException( + 'Cannot set count; conflicts with either a count option previously set' + ); + } + $this->options['min'] = $value; + } + + /** + * @param mixed $value + * @return void + * @throws Exception\InvalidArgumentException if either a count or min option + * was previously set. + */ + private function setMax($value) + { + if (isset($this->options['count'])) { + throw new Exception\InvalidArgumentException( + 'Cannot set count; conflicts with either a count option previously set' + ); + } + $this->options['max'] = $value; + } +} diff --git a/lib/laminas/laminas-validator/src/IsInstanceOf.php b/lib/laminas/laminas-validator/src/IsInstanceOf.php new file mode 100644 index 000000000..92b21f004 --- /dev/null +++ b/lib/laminas/laminas-validator/src/IsInstanceOf.php @@ -0,0 +1,106 @@ + "The input is not an instance of '%className%'", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'className' => 'className' + ]; + + /** + * Class name + * + * @var string + */ + protected $className; + + /** + * Sets validator options + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } + + // If argument is not an array, consider first argument as class name + if (! is_array($options)) { + $options = func_get_args(); + + $tmpOptions = []; + $tmpOptions['className'] = array_shift($options); + + $options = $tmpOptions; + } + + if (! array_key_exists('className', $options)) { + throw new Exception\InvalidArgumentException('Missing option "className"'); + } + + parent::__construct($options); + } + + /** + * Get class name + * + * @return string + */ + public function getClassName() + { + return $this->className; + } + + /** + * Set class name + * + * @param string $className + * @return self + */ + public function setClassName($className) + { + $this->className = $className; + return $this; + } + + /** + * Returns true if $value is instance of $this->className + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + if ($value instanceof $this->className) { + return true; + } + $this->error(self::NOT_INSTANCE_OF); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Isbn.php b/lib/laminas/laminas-validator/src/Isbn.php new file mode 100644 index 000000000..6d3119af2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Isbn.php @@ -0,0 +1,185 @@ + "Invalid type given. String or integer expected", + self::NO_ISBN => "The input is not a valid ISBN number", + ]; + + protected $options = [ + 'type' => self::AUTO, // Allowed type + 'separator' => '', // Separator character + ]; + + /** + * Detect input format. + * + * @return string + */ + protected function detectFormat() + { + // prepare separator and pattern list + $sep = quotemeta($this->getSeparator()); + $patterns = []; + $lengths = []; + $type = $this->getType(); + + // check for ISBN-10 + if ($type == self::ISBN10 || $type == self::AUTO) { + if (empty($sep)) { + $pattern = '/^[0-9]{9}[0-9X]{1}$/'; + $length = 10; + } else { + $pattern = "/^[0-9]{1,7}[{$sep}]{1}[0-9]{1,7}[{$sep}]{1}[0-9]{1,7}[{$sep}]{1}[0-9X]{1}$/"; + $length = 13; + } + + $patterns[$pattern] = self::ISBN10; + $lengths[$pattern] = $length; + } + + // check for ISBN-13 + if ($type == self::ISBN13 || $type == self::AUTO) { + if (empty($sep)) { + $pattern = '/^[0-9]{13}$/'; + $length = 13; + } else { + // @codingStandardsIgnoreStart + $pattern = "/^[0-9]{1,9}[{$sep}]{1}[0-9]{1,5}[{$sep}]{1}[0-9]{1,9}[{$sep}]{1}[0-9]{1,9}[{$sep}]{1}[0-9]{1}$/"; + // @codingStandardsIgnoreEnd + $length = 17; + } + + $patterns[$pattern] = self::ISBN13; + $lengths[$pattern] = $length; + } + + // check pattern list + foreach ($patterns as $pattern => $type) { + if ((strlen($this->getValue()) == $lengths[$pattern]) && preg_match($pattern, $this->getValue())) { + return $type; + } + } + + return; + } + + /** + * Returns true if and only if $value is a valid ISBN. + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value) && ! is_int($value)) { + $this->error(self::INVALID); + return false; + } + + $value = (string) $value; + $this->setValue($value); + + switch ($this->detectFormat()) { + case self::ISBN10: + $isbn = new Isbn\Isbn10(); + break; + + case self::ISBN13: + $isbn = new Isbn\Isbn13(); + break; + + default: + $this->error(self::NO_ISBN); + return false; + } + + $value = str_replace($this->getSeparator(), '', $value); + $checksum = $isbn->getChecksum($value); + + // validate + if (substr($this->getValue(), -1) != $checksum) { + $this->error(self::NO_ISBN); + return false; + } + return true; + } + + /** + * Set separator characters. + * + * It is allowed only empty string, hyphen and space. + * + * @param string $separator + * @throws Exception\InvalidArgumentException When $separator is not valid + * @return Isbn Provides a fluent interface + */ + public function setSeparator($separator) + { + // check separator + if (! in_array($separator, ['-', ' ', ''])) { + throw new Exception\InvalidArgumentException('Invalid ISBN separator.'); + } + + $this->options['separator'] = $separator; + return $this; + } + + /** + * Get separator characters. + * + * @return string + */ + public function getSeparator() + { + return $this->options['separator']; + } + + /** + * Set allowed ISBN type. + * + * @param string $type + * @throws Exception\InvalidArgumentException When $type is not valid + * @return Isbn Provides a fluent interface + */ + public function setType($type) + { + // check type + if (! in_array($type, [self::AUTO, self::ISBN10, self::ISBN13])) { + throw new Exception\InvalidArgumentException('Invalid ISBN type'); + } + + $this->options['type'] = $type; + return $this; + } + + /** + * Get allowed ISBN type. + * + * @return string + */ + public function getType() + { + return $this->options['type']; + } +} diff --git a/lib/laminas/laminas-validator/src/Isbn/Isbn10.php b/lib/laminas/laminas-validator/src/Isbn/Isbn10.php new file mode 100644 index 000000000..734ac1fc2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Isbn/Isbn10.php @@ -0,0 +1,60 @@ +sum($value); + return $this->checksum($sum); + } + + /** + * Calculate the value sum. + * + * @param int|string $value + * @return int + */ + private function sum($value) + { + $sum = 0; + + for ($i = 0; $i < 9; $i++) { + $sum += (10 - $i) * $value[$i]; + } + + return $sum; + } + + /** + * Calculate the checksum for the value's sum. + * + * @param int $sum + * @return int|string + */ + private function checksum($sum) + { + $checksum = 11 - ($sum % 11); + + if ($checksum == 11) { + return '0'; + } + + if ($checksum == 10) { + return 'X'; + } + + return $checksum; + } +} diff --git a/lib/laminas/laminas-validator/src/Isbn/Isbn13.php b/lib/laminas/laminas-validator/src/Isbn/Isbn13.php new file mode 100644 index 000000000..75b4d6978 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Isbn/Isbn13.php @@ -0,0 +1,61 @@ +sum($value); + return $this->checksum($sum); + } + + /** + * Calculate the value sum. + * + * @param int|string $value + * @return int + */ + private function sum($value) + { + $sum = 0; + + for ($i = 0; $i < 12; $i++) { + if ($i % 2 == 0) { + $sum += $value[$i]; + continue; + } + + $sum += 3 * $value[$i]; + } + + return $sum; + } + + /** + * Calculate the checksum for the value's sum. + * + * @param int $sum + * @return int|string + */ + private function checksum($sum) + { + $checksum = 10 - ($sum % 10); + + if ($checksum == 10) { + return '0'; + } + + return $checksum; + } +} diff --git a/lib/laminas/laminas-validator/src/LessThan.php b/lib/laminas/laminas-validator/src/LessThan.php new file mode 100644 index 000000000..0f6b3bada --- /dev/null +++ b/lib/laminas/laminas-validator/src/LessThan.php @@ -0,0 +1,160 @@ + "The input is not less than '%max%'", + self::NOT_LESS_INCLUSIVE => "The input is not less or equal than '%max%'" + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'max' => 'max' + ]; + + /** + * Maximum value + * + * @var mixed + */ + protected $max; + + /** + * Whether to do inclusive comparisons, allowing equivalence to max + * + * If false, then strict comparisons are done, and the value may equal + * the max option + * + * @var bool + */ + protected $inclusive; + + /** + * Sets validator options + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + if (! is_array($options)) { + $options = func_get_args(); + $temp['max'] = array_shift($options); + + if (! empty($options)) { + $temp['inclusive'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('max', $options)) { + throw new Exception\InvalidArgumentException("Missing option 'max'"); + } + + if (! array_key_exists('inclusive', $options)) { + $options['inclusive'] = false; + } + + $this->setMax($options['max']) + ->setInclusive($options['inclusive']); + + parent::__construct($options); + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->max; + } + + /** + * Sets the max option + * + * @param mixed $max + * @return LessThan Provides a fluent interface + */ + public function setMax($max) + { + $this->max = $max; + return $this; + } + + /** + * Returns the inclusive option + * + * @return bool + */ + public function getInclusive() + { + return $this->inclusive; + } + + /** + * Sets the inclusive option + * + * @param bool $inclusive + * @return LessThan Provides a fluent interface + */ + public function setInclusive($inclusive) + { + $this->inclusive = $inclusive; + return $this; + } + + /** + * Returns true if and only if $value is less than max option, inclusively + * when the inclusive option is true + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if ($this->inclusive) { + if ($value > $this->max) { + $this->error(self::NOT_LESS_INCLUSIVE); + return false; + } + } else { + if ($value >= $this->max) { + $this->error(self::NOT_LESS); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Module.php b/lib/laminas/laminas-validator/src/Module.php new file mode 100644 index 000000000..fb9415166 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Module.php @@ -0,0 +1,44 @@ + $provider->getDependencyConfig(), + ]; + } + + /** + * Register a specification for the ValidatorManager with the ServiceListener. + * + * @param \Laminas\ModuleManager\ModuleManager $moduleManager + * @return void + */ + public function init($moduleManager) + { + $event = $moduleManager->getEvent(); + $container = $event->getParam('ServiceManager'); + $serviceListener = $container->get('ServiceListener'); + + $serviceListener->addServiceManager( + 'ValidatorManager', + 'validators', + ValidatorProviderInterface::class, + 'getValidatorConfig' + ); + } +} diff --git a/lib/laminas/laminas-validator/src/NotEmpty.php b/lib/laminas/laminas-validator/src/NotEmpty.php new file mode 100644 index 000000000..be0e6f867 --- /dev/null +++ b/lib/laminas/laminas-validator/src/NotEmpty.php @@ -0,0 +1,289 @@ + 'boolean', + self::INTEGER => 'integer', + self::FLOAT => 'float', + self::STRING => 'string', + self::ZERO => 'zero', + self::EMPTY_ARRAY => 'array', + self::NULL => 'null', + self::PHP => 'php', + self::SPACE => 'space', + self::OBJECT => 'object', + self::OBJECT_STRING => 'objectstring', + self::OBJECT_COUNT => 'objectcount', + self::ALL => 'all', + ]; + + /** + * Default value for types; value = 0b000111101001 + * + * @var array + */ + protected $defaultType = [ + self::OBJECT, + self::SPACE, + self::NULL, + self::EMPTY_ARRAY, + self::STRING, + self::BOOLEAN + ]; + + /** + * @var array + */ + protected $messageTemplates = [ + self::IS_EMPTY => "Value is required and can't be empty", + self::INVALID => "Invalid type given. String, integer, float, boolean or array expected", + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = []; + + /** + * Constructor + * + * @param array|Traversable|int $options OPTIONAL + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if (! is_array($options)) { + $options = func_get_args(); + $temp = []; + if (! empty($options)) { + $temp['type'] = array_shift($options); + } + + $options = $temp; + } + + if (! isset($options['type'])) { + if (($type = $this->calculateTypeValue($options)) != 0) { + $options['type'] = $type; + } else { + $options['type'] = $this->defaultType; + } + } + + parent::__construct($options); + } + + /** + * Returns the set types + * + * @return array + */ + public function getType() + { + return $this->options['type']; + } + + /** + * @return int + */ + public function getDefaultType() + { + return $this->calculateTypeValue($this->defaultType); + } + + /** + * @param array|int|string $type + * @return int + */ + protected function calculateTypeValue($type) + { + if (is_array($type)) { + $detected = 0; + foreach ($type as $value) { + if (is_int($value)) { + $detected |= $value; + } elseif (in_array($value, $this->constants, true)) { + $detected |= array_search($value, $this->constants, true); + } + } + + $type = $detected; + } elseif (is_string($type) && in_array($type, $this->constants, true)) { + $type = array_search($type, $this->constants, true); + } + + return $type; + } + + /** + * Set the types + * + * @param int|array $type + * @throws Exception\InvalidArgumentException + * @return NotEmpty + */ + public function setType($type = null) + { + $type = $this->calculateTypeValue($type); + + if (! is_int($type) || ($type < 0) || ($type > self::ALL)) { + throw new Exception\InvalidArgumentException('Unknown type'); + } + + $this->options['type'] = $type; + + return $this; + } + + /** + * Returns true if and only if $value is not an empty value. + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if ($value !== null && ! is_string($value) && ! is_int($value) && ! is_float($value) && + ! is_bool($value) && ! is_array($value) && ! is_object($value) + ) { + $this->error(self::INVALID); + return false; + } + + $type = $this->getType(); + $this->setValue($value); + $object = false; + + // OBJECT_COUNT (countable object) + if ($type & self::OBJECT_COUNT) { + $object = true; + + if (is_object($value) && ($value instanceof \Countable) && (count($value) == 0)) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // OBJECT_STRING (object's toString) + if ($type & self::OBJECT_STRING) { + $object = true; + + if ((is_object($value) && (! method_exists($value, '__toString'))) || + (is_object($value) && (method_exists($value, '__toString')) && (((string) $value) == ""))) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // OBJECT (object) + if ($type & self::OBJECT) { + // fall trough, objects are always not empty + } elseif ($object === false) { + // object not allowed but object given -> return false + if (is_object($value)) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // SPACE (' ') + if ($type & self::SPACE) { + if (is_string($value) && (preg_match('/^\s+$/s', $value))) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // NULL (null) + if ($type & self::NULL) { + if ($value === null) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // EMPTY_ARRAY (array()) + if ($type & self::EMPTY_ARRAY) { + if (is_array($value) && ($value == [])) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // ZERO ('0') + if ($type & self::ZERO) { + if (is_string($value) && ($value == '0')) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // STRING ('') + if ($type & self::STRING) { + if (is_string($value) && ($value == '')) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // FLOAT (0.0) + if ($type & self::FLOAT) { + if (is_float($value) && ($value == 0.0)) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // INTEGER (0) + if ($type & self::INTEGER) { + if (is_int($value) && ($value == 0)) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // BOOLEAN (false) + if ($type & self::BOOLEAN) { + if (is_bool($value) && ($value == false)) { + $this->error(self::IS_EMPTY); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Regex.php b/lib/laminas/laminas-validator/src/Regex.php new file mode 100644 index 000000000..ccd6ffe29 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Regex.php @@ -0,0 +1,140 @@ + "Invalid type given. String, integer or float expected", + self::NOT_MATCH => "The input does not match against pattern '%pattern%'", + self::ERROROUS => "There was an internal error while using the pattern '%pattern%'", + ]; + + /** + * @var array + */ + protected $messageVariables = [ + 'pattern' => 'pattern' + ]; + + /** + * Regular expression pattern + * + * @var string + */ + protected $pattern; + + /** + * Sets validator options + * + * @param string|array|Traversable $pattern + * @throws Exception\InvalidArgumentException On missing 'pattern' parameter + */ + public function __construct($pattern) + { + if (is_string($pattern)) { + $this->setPattern($pattern); + parent::__construct([]); + return; + } + + if ($pattern instanceof Traversable) { + $pattern = ArrayUtils::iteratorToArray($pattern); + } + + if (! is_array($pattern)) { + throw new Exception\InvalidArgumentException('Invalid options provided to constructor'); + } + + if (! array_key_exists('pattern', $pattern)) { + throw new Exception\InvalidArgumentException("Missing option 'pattern'"); + } + + $this->setPattern($pattern['pattern']); + unset($pattern['pattern']); + parent::__construct($pattern); + } + + /** + * Returns the pattern option + * + * @return string + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Sets the pattern option + * + * @param string $pattern + * @throws Exception\InvalidArgumentException if there is a fatal error in pattern matching + * @return Regex Provides a fluent interface + */ + public function setPattern($pattern) + { + ErrorHandler::start(); + $this->pattern = (string) $pattern; + $status = preg_match($this->pattern, "Test"); + $error = ErrorHandler::stop(); + + if (false === $status) { + throw new Exception\InvalidArgumentException( + "Internal error parsing the pattern '{$this->pattern}'", + 0, + $error + ); + } + + return $this; + } + + /** + * Returns true if and only if $value matches against the pattern option + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value) && ! is_int($value) && ! is_float($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + + ErrorHandler::start(); + $status = preg_match($this->pattern, $value); + ErrorHandler::stop(); + if (false === $status) { + $this->error(self::ERROROUS); + return false; + } + + if (! $status) { + $this->error(self::NOT_MATCH); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Sitemap/Changefreq.php b/lib/laminas/laminas-validator/src/Sitemap/Changefreq.php new file mode 100644 index 000000000..b8b04ae13 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Sitemap/Changefreq.php @@ -0,0 +1,74 @@ + value + * + * @link http://www.sitemaps.org/protocol.php Sitemaps XML format + */ +class Changefreq extends AbstractValidator +{ + /** + * Validation key for not valid + * + */ + const NOT_VALID = 'sitemapChangefreqNotValid'; + const INVALID = 'sitemapChangefreqInvalid'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $messageTemplates = [ + self::NOT_VALID => "The input is not a valid sitemap changefreq", + self::INVALID => "Invalid type given. String expected", + ]; + + /** + * Valid change frequencies + * + * @var array + */ + protected $changeFreqs = [ + 'always', 'hourly', 'daily', 'weekly', + 'monthly', 'yearly', 'never' + ]; + + /** + * Validates if a string is valid as a sitemap changefreq + * + * @link http://www.sitemaps.org/protocol.php#changefreqdef + * + * @param string $value value to validate + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + if (! is_string($value)) { + return false; + } + + if (! in_array($value, $this->changeFreqs, true)) { + $this->error(self::NOT_VALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Sitemap/Lastmod.php b/lib/laminas/laminas-validator/src/Sitemap/Lastmod.php new file mode 100644 index 000000000..3516c0925 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Sitemap/Lastmod.php @@ -0,0 +1,72 @@ + value + * + * @link http://www.sitemaps.org/protocol.php Sitemaps XML format + */ +class Lastmod extends AbstractValidator +{ + /** + * Regular expression to use when validating + * + */ + // @codingStandardsIgnoreStart + const LASTMOD_REGEX = '/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])(T([0-1][0-9]|2[0-3])(:[0-5][0-9])(:[0-5][0-9])?(\\+|-)([0-1][0-9]|2[0-3]):[0-5][0-9])?$/'; + // @codingStandardsIgnoreEnd + + /** + * Validation key for not valid + * + */ + const NOT_VALID = 'sitemapLastmodNotValid'; + const INVALID = 'sitemapLastmodInvalid'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $messageTemplates = [ + self::NOT_VALID => "The input is not a valid sitemap lastmod", + self::INVALID => "Invalid type given. String expected", + ]; + + /** + * Validates if a string is valid as a sitemap lastmod + * + * @link http://www.sitemaps.org/protocol.php#lastmoddef + * + * @param string $value value to validate + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + ErrorHandler::start(); + $result = preg_match(self::LASTMOD_REGEX, $value); + ErrorHandler::stop(); + if ($result != 1) { + $this->error(self::NOT_VALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Sitemap/Loc.php b/lib/laminas/laminas-validator/src/Sitemap/Loc.php new file mode 100644 index 000000000..b3a2d6e46 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Sitemap/Loc.php @@ -0,0 +1,64 @@ + value + * + * @link http://www.sitemaps.org/protocol.php Sitemaps XML format + * + * @see Laminas\Uri\Uri + */ +class Loc extends AbstractValidator +{ + /** + * Validation key for not valid + * + */ + const NOT_VALID = 'sitemapLocNotValid'; + const INVALID = 'sitemapLocInvalid'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $messageTemplates = [ + self::NOT_VALID => "The input is not a valid sitemap location", + self::INVALID => "Invalid type given. String expected", + ]; + + /** + * Validates if a string is valid as a sitemap location + * + * @link http://www.sitemaps.org/protocol.php#locdef + * + * @param string $value value to validate + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + $uri = Uri\UriFactory::factory($value); + if (! $uri->isValid()) { + $this->error(self::NOT_VALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Sitemap/Priority.php b/lib/laminas/laminas-validator/src/Sitemap/Priority.php new file mode 100644 index 000000000..f841ebf98 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Sitemap/Priority.php @@ -0,0 +1,61 @@ + value + * + * @link http://www.sitemaps.org/protocol.php Sitemaps XML format + */ +class Priority extends AbstractValidator +{ + /** + * Validation key for not valid + * + */ + const NOT_VALID = 'sitemapPriorityNotValid'; + const INVALID = 'sitemapPriorityInvalid'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $messageTemplates = [ + self::NOT_VALID => "The input is not a valid sitemap priority", + self::INVALID => "Invalid type given. Numeric string, integer or float expected", + ]; + + /** + * Validates if a string is valid as a sitemap priority + * + * @link http://www.sitemaps.org/protocol.php#prioritydef + * + * @param string $value value to validate + * @return bool + */ + public function isValid($value) + { + if (! is_numeric($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + $value = (float) $value; + if ($value < 0 || $value > 1) { + $this->error(self::NOT_VALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/StaticValidator.php b/lib/laminas/laminas-validator/src/StaticValidator.php new file mode 100644 index 000000000..bbb58d232 --- /dev/null +++ b/lib/laminas/laminas-validator/src/StaticValidator.php @@ -0,0 +1,74 @@ +configure(['shared_by_default' => false]); + } else { + $plugins->setShareByDefault(false); + } + } + static::$plugins = $plugins; + } + + /** + * Get plugin manager for locating validators + * + * @return ValidatorPluginManager + */ + public static function getPluginManager() + { + if (null === static::$plugins) { + static::setPluginManager(new ValidatorPluginManager(new ServiceManager)); + } + return static::$plugins; + } + + /** + * @param mixed $value + * @param string $classBaseName + * @param array $options OPTIONAL associative array of options to pass as + * the sole argument to the validator constructor. + * @return bool + * @throws Exception\InvalidArgumentException for an invalid $options argument. + */ + public static function execute($value, $classBaseName, array $options = []) + { + if ($options && array_values($options) === $options) { + throw new Exception\InvalidArgumentException( + 'Invalid options provided via $options argument; must be an associative array' + ); + } + + $plugins = static::getPluginManager(); + + $validator = $plugins->get($classBaseName, $options); + return $validator->isValid($value); + } +} diff --git a/lib/laminas/laminas-validator/src/Step.php b/lib/laminas/laminas-validator/src/Step.php new file mode 100644 index 000000000..015b5cee1 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Step.php @@ -0,0 +1,177 @@ + "Invalid value given. Scalar expected", + self::NOT_STEP => "The input is not a valid step" + ]; + + /** + * @var mixed + */ + protected $baseValue = 0; + + /** + * @var mixed + */ + protected $step = 1; + + /** + * Set default options for this instance + * + * @param array $options + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp['baseValue'] = array_shift($options); + if (! empty($options)) { + $temp['step'] = array_shift($options); + } + + $options = $temp; + } + + if (isset($options['baseValue'])) { + $this->setBaseValue($options['baseValue']); + } + if (isset($options['step'])) { + $this->setStep($options['step']); + } + + parent::__construct($options); + } + + /** + * Sets the base value from which the step should be computed + * + * @param mixed $baseValue + * @return Step + */ + public function setBaseValue($baseValue) + { + $this->baseValue = $baseValue; + return $this; + } + + /** + * Returns the base value from which the step should be computed + * + * @return string + */ + public function getBaseValue() + { + return $this->baseValue; + } + + /** + * Sets the step value + * + * @param mixed $step + * @return Step + */ + public function setStep($step) + { + $this->step = (float) $step; + return $this; + } + + /** + * Returns the step value + * + * @return string + */ + public function getStep() + { + return $this->step; + } + + /** + * Returns true if $value is a scalar and a valid step value + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + if (! is_numeric($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + + $substract = $this->sub($value, $this->baseValue); + + $fmod = $this->fmod($substract, $this->step); + + if ($fmod !== 0.0 && $fmod !== $this->step) { + $this->error(self::NOT_STEP); + return false; + } + + return true; + } + + /** + * replaces the internal fmod function which give wrong results on many cases + * + * @param float $x + * @param float $y + * @return float + */ + protected function fmod($x, $y) + { + if ($y == 0.0) { + return 1.0; + } + + //find the maximum precision from both input params to give accurate results + $precision = $this->getPrecision($x) + $this->getPrecision($y); + + return round($x - $y * floor($x / $y), $precision); + } + + /** + * replaces the internal substraction operation which give wrong results on some cases + * + * @param float $x + * @param float $y + * @return float + */ + private function sub($x, $y) + { + $precision = $this->getPrecision($x) + $this->getPrecision($y); + return round($x - $y, $precision); + } + + /** + * @param float $float + * @return int + */ + private function getPrecision($float) + { + $segment = substr($float, strpos($float, '.') + 1); + return $segment ? strlen($segment) : 0; + } +} diff --git a/lib/laminas/laminas-validator/src/StringLength.php b/lib/laminas/laminas-validator/src/StringLength.php new file mode 100644 index 000000000..e664c2cea --- /dev/null +++ b/lib/laminas/laminas-validator/src/StringLength.php @@ -0,0 +1,234 @@ + "Invalid type given. String expected", + self::TOO_SHORT => "The input is less than %min% characters long", + self::TOO_LONG => "The input is more than %max% characters long", + ]; + + /** + * @var array + */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + 'length' => ['options' => 'length'] + ]; + + protected $options = [ + 'min' => 0, // Minimum length + 'max' => null, // Maximum length, null if there is no length limitation + 'encoding' => 'UTF-8', // Encoding to use + 'length' => 0 // Actual length + ]; + + protected $stringWrapper; + + /** + * Sets validator options + * + * @param int|array|\Traversable $options + */ + public function __construct($options = []) + { + if (! is_array($options)) { + $options = func_get_args(); + $temp['min'] = array_shift($options); + if (! empty($options)) { + $temp['max'] = array_shift($options); + } + + if (! empty($options)) { + $temp['encoding'] = array_shift($options); + } + + $options = $temp; + } + + parent::__construct($options); + } + + /** + * Returns the min option + * + * @return int + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Sets the min option + * + * @param int $min + * @throws Exception\InvalidArgumentException + * @return StringLength Provides a fluent interface + */ + public function setMin($min) + { + if (null !== $this->getMax() && $min > $this->getMax()) { + throw new Exception\InvalidArgumentException( + "The minimum must be less than or equal to the maximum length, but {$min} > {$this->getMax()}" + ); + } + + $this->options['min'] = max(0, (int) $min); + return $this; + } + + /** + * Returns the max option + * + * @return int|null + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * Sets the max option + * + * @param int|null $max + * @throws Exception\InvalidArgumentException + * @return StringLength Provides a fluent interface + */ + public function setMax($max) + { + if (null === $max) { + $this->options['max'] = null; + } elseif ($max < $this->getMin()) { + throw new Exception\InvalidArgumentException( + "The maximum must be greater than or equal to the minimum length, but {$max} < {$this->getMin()}" + ); + } else { + $this->options['max'] = (int) $max; + } + + return $this; + } + + /** + * Get the string wrapper to detect the string length + * + * @return StringWrapper + */ + public function getStringWrapper() + { + if (! $this->stringWrapper) { + $this->stringWrapper = StringUtils::getWrapper($this->getEncoding()); + } + return $this->stringWrapper; + } + + /** + * Set the string wrapper to detect the string length + * + * @param StringWrapper $stringWrapper + * @return StringLength + */ + public function setStringWrapper(StringWrapper $stringWrapper) + { + $stringWrapper->setEncoding($this->getEncoding()); + $this->stringWrapper = $stringWrapper; + } + + /** + * Returns the actual encoding + * + * @return string + */ + public function getEncoding() + { + return $this->options['encoding']; + } + + /** + * Sets a new encoding to use + * + * @param string $encoding + * @return StringLength + * @throws Exception\InvalidArgumentException + */ + public function setEncoding($encoding) + { + $this->stringWrapper = StringUtils::getWrapper($encoding); + $this->options['encoding'] = $encoding; + return $this; + } + + /** + * Returns the length option + * + * @return int + */ + private function getLength() + { + return $this->options['length']; + } + + /** + * Sets the length option + * + * @param int $length + * @return StringLength Provides a fluent interface + */ + private function setLength($length) + { + $this->options['length'] = (int) $length; + return $this; + } + + /** + * Returns true if and only if the string length of $value is at least the min option and + * no greater than the max option (when the max option is not null). + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + + $this->setLength($this->getStringWrapper()->strlen($value)); + if ($this->getLength() < $this->getMin()) { + $this->error(self::TOO_SHORT); + } + + if (null !== $this->getMax() && $this->getMax() < $this->getLength()) { + $this->error(self::TOO_LONG); + } + + if ($this->getMessages()) { + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Timezone.php b/lib/laminas/laminas-validator/src/Timezone.php new file mode 100644 index 000000000..d7208e5f1 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Timezone.php @@ -0,0 +1,173 @@ + 'location', + self::ABBREVIATION => 'abbreviation', + ]; + + /** + * Default value for types; value = 3 + * + * @var array + */ + protected $defaultType = [ + self::LOCATION, + self::ABBREVIATION, + ]; + + /** + * @var array + */ + protected $messageTemplates = [ + self::INVALID => 'Invalid timezone given.', + self::INVALID_TIMEZONE_LOCATION => 'Invalid timezone location given.', + self::INVALID_TIMEZONE_ABBREVIATION => 'Invalid timezone abbreviation given.', + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = []; + + /** + * Constructor + * + * @param array|int $options OPTIONAL + */ + public function __construct($options = []) + { + $opts['type'] = $this->defaultType; + + if (is_array($options)) { + if (array_key_exists('type', $options)) { + $opts['type'] = $options['type']; + } + } elseif (! empty($options)) { + $opts['type'] = $options; + } + + // setType called by parent constructor then setOptions method + parent::__construct($opts); + } + + /** + * Set the types + * + * @param int|array $type + * + * @throws Exception\InvalidArgumentException + */ + public function setType($type = null) + { + $type = $this->calculateTypeValue($type); + + if (! is_int($type) || ($type < 1) || ($type > self::ALL)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Unknown type "%s" provided', + (is_string($type) || is_int($type)) + ? $type + : (is_object($type) ? get_class($type) : gettype($type)) + )); + } + + $this->options['type'] = $type; + } + + /** + * Returns true if timezone location or timezone abbreviations is correct. + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + if ($value !== null && ! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $type = $this->options['type']; + $this->setValue($value); + + switch (true) { + // Check in locations and abbreviations + case (($type & self::LOCATION) && ($type & self::ABBREVIATION)): + $abbrs = DateTimeZone::listAbbreviations(); + $locations = DateTimeZone::listIdentifiers(); + + if (! array_key_exists($value, $abbrs) && ! in_array($value, $locations)) { + $this->error(self::INVALID); + return false; + } + break; + + // Check only in locations + case ($type & self::LOCATION): + $locations = DateTimeZone::listIdentifiers(); + + if (! in_array($value, $locations)) { + $this->error(self::INVALID_TIMEZONE_LOCATION); + return false; + } + break; + + // Check only in abbreviations + case ($type & self::ABBREVIATION): + $abbrs = DateTimeZone::listAbbreviations(); + + if (! array_key_exists($value, $abbrs)) { + $this->error(self::INVALID_TIMEZONE_ABBREVIATION); + return false; + } + break; + } + + return true; + } + + /** + * @param array|int|string $type + * + * @return int + */ + protected function calculateTypeValue($type) + { + $types = (array) $type; + $detected = 0; + + foreach ($types as $value) { + if (is_int($value)) { + $detected |= $value; + } elseif (false !== ($position = array_search($value, $this->constants))) { + $detected |= array_search($value, $this->constants); + } + } + + return $detected; + } +} diff --git a/lib/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php b/lib/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php new file mode 100644 index 000000000..84734d835 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php @@ -0,0 +1,68 @@ + "Invalid type given. String expected", + self::NOT_URI => "The input does not appear to be a valid Uri", + ]; + + /** + * @var UriHandler + */ + protected $uriHandler; + + /** + * @var bool + */ + protected $allowRelative = true; + + /** + * @var bool + */ + protected $allowAbsolute = true; + + /** + * Sets default option values for this instance + * + * @param array|Traversable $options + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp['uriHandler'] = array_shift($options); + if (! empty($options)) { + $temp['allowRelative'] = array_shift($options); + } + if (! empty($options)) { + $temp['allowAbsolute'] = array_shift($options); + } + + $options = $temp; + } + + if (isset($options['uriHandler'])) { + $this->setUriHandler($options['uriHandler']); + } + if (isset($options['allowRelative'])) { + $this->setAllowRelative($options['allowRelative']); + } + if (isset($options['allowAbsolute'])) { + $this->setAllowAbsolute($options['allowAbsolute']); + } + + parent::__construct($options); + } + + /** + * @throws InvalidArgumentException + * @return UriHandler + */ + public function getUriHandler() + { + if (null === $this->uriHandler) { + // Lazy load the base Uri handler + $this->uriHandler = new UriHandler(); + } elseif (is_string($this->uriHandler) && class_exists($this->uriHandler)) { + // Instantiate string Uri handler that references a class + $this->uriHandler = new $this->uriHandler; + } + + if (! $this->uriHandler instanceof UriHandler) { + throw new InvalidArgumentException('URI handler is expected to be a Laminas\Uri\Uri object'); + } + + return $this->uriHandler; + } + + /** + * @param UriHandler $uriHandler + * @throws InvalidArgumentException + * @return Uri + */ + public function setUriHandler($uriHandler) + { + if (! is_subclass_of($uriHandler, 'Laminas\Uri\Uri')) { + throw new InvalidArgumentException('Expecting a subclass name or instance of Laminas\Uri\Uri as $uriHandler'); + } + + $this->uriHandler = $uriHandler; + return $this; + } + + /** + * Returns the allowAbsolute option + * + * @return bool + */ + public function getAllowAbsolute() + { + return $this->allowAbsolute; + } + + /** + * Sets the allowAbsolute option + * + * @param bool $allowAbsolute + * @return Uri + */ + public function setAllowAbsolute($allowAbsolute) + { + $this->allowAbsolute = (bool) $allowAbsolute; + return $this; + } + + /** + * Returns the allowRelative option + * + * @return bool + */ + public function getAllowRelative() + { + return $this->allowRelative; + } + + /** + * Sets the allowRelative option + * + * @param bool $allowRelative + * @return Uri + */ + public function setAllowRelative($allowRelative) + { + $this->allowRelative = (bool) $allowRelative; + return $this; + } + + /** + * Returns true if and only if $value validates as a Uri + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $uriHandler = $this->getUriHandler(); + try { + $uriHandler->parse($value); + if ($uriHandler->isValid()) { + // It will either be a valid absolute or relative URI + if (($this->allowRelative && $this->allowAbsolute) + || ($this->allowAbsolute && $uriHandler->isAbsolute()) + || ($this->allowRelative && $uriHandler->isValidRelative()) + ) { + return true; + } + } + } catch (UriException $ex) { + // Error parsing URI, it must be invalid + } + + $this->error(self::NOT_URI); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Uuid.php b/lib/laminas/laminas-validator/src/Uuid.php new file mode 100644 index 000000000..e6f3a3d14 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Uuid.php @@ -0,0 +1,64 @@ + 'Invalid type given; string expected', + self::INVALID => 'Invalid UUID format', + ]; + + /** + * Returns true if and only if $value meets the validation requirements. + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @param mixed $value + * + * @return bool + * + * @throws Exception\RuntimeException If validation of $value is impossible + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::NOT_STRING); + return false; + } + + $this->setValue($value); + + if (empty($value) + || $value !== '00000000-0000-0000-0000-000000000000' + && ! preg_match(self::REGEX_UUID, $value) + ) { + $this->error(self::INVALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/ValidatorChain.php b/lib/laminas/laminas-validator/src/ValidatorChain.php new file mode 100644 index 000000000..7dea5def5 --- /dev/null +++ b/lib/laminas/laminas-validator/src/ValidatorChain.php @@ -0,0 +1,326 @@ +validators = new PriorityQueue(); + } + + /** + * Return the count of attached validators + * + * @return int + */ + public function count() + { + return count($this->validators); + } + + /** + * Get plugin manager instance + * + * @return ValidatorPluginManager + */ + public function getPluginManager() + { + if (! $this->plugins) { + $this->setPluginManager(new ValidatorPluginManager(new ServiceManager)); + } + return $this->plugins; + } + + /** + * Set plugin manager instance + * + * @param ValidatorPluginManager $plugins Plugin manager + * @return ValidatorChain + */ + public function setPluginManager(ValidatorPluginManager $plugins) + { + $this->plugins = $plugins; + return $this; + } + + /** + * Retrieve a validator by name + * + * @param string $name Name of validator to return + * @param null|array $options Options to pass to validator constructor (if not already instantiated) + * @return ValidatorInterface + */ + public function plugin($name, array $options = null) + { + $plugins = $this->getPluginManager(); + return $plugins->get($name, $options); + } + + /** + * Attach a validator to the end of the chain + * + * If $breakChainOnFailure is true, then if the validator fails, the next validator in the chain, + * if one exists, will not be executed. + * + * @param ValidatorInterface $validator + * @param bool $breakChainOnFailure + * @param int $priority Priority at which to enqueue validator; defaults to + * 1 (higher executes earlier) + * + * @throws Exception\InvalidArgumentException + * + * @return self + */ + public function attach( + ValidatorInterface $validator, + $breakChainOnFailure = false, + $priority = self::DEFAULT_PRIORITY + ) { + $this->validators->insert( + [ + 'instance' => $validator, + 'breakChainOnFailure' => (bool) $breakChainOnFailure, + ], + $priority + ); + + return $this; + } + + /** + * Proxy to attach() to keep BC + * + * @deprecated Please use attach() + * @param ValidatorInterface $validator + * @param bool $breakChainOnFailure + * @param int $priority + * @return ValidatorChain Provides a fluent interface + */ + public function addValidator( + ValidatorInterface $validator, + $breakChainOnFailure = false, + $priority = self::DEFAULT_PRIORITY + ) { + return $this->attach($validator, $breakChainOnFailure, $priority); + } + + /** + * Adds a validator to the beginning of the chain + * + * If $breakChainOnFailure is true, then if the validator fails, the next validator in the chain, + * if one exists, will not be executed. + * + * @param ValidatorInterface $validator + * @param bool $breakChainOnFailure + * @return ValidatorChain Provides a fluent interface + */ + public function prependValidator(ValidatorInterface $validator, $breakChainOnFailure = false) + { + $priority = self::DEFAULT_PRIORITY; + + if (! $this->validators->isEmpty()) { + $extractedNodes = $this->validators->toArray(PriorityQueue::EXTR_PRIORITY); + rsort($extractedNodes, SORT_NUMERIC); + $priority = $extractedNodes[0] + 1; + } + + $this->validators->insert( + [ + 'instance' => $validator, + 'breakChainOnFailure' => (bool) $breakChainOnFailure, + ], + $priority + ); + return $this; + } + + /** + * Use the plugin manager to add a validator by name + * + * @param string $name + * @param array $options + * @param bool $breakChainOnFailure + * @param int $priority + * @return ValidatorChain + */ + public function attachByName($name, $options = [], $breakChainOnFailure = false, $priority = self::DEFAULT_PRIORITY) + { + if (isset($options['break_chain_on_failure'])) { + $breakChainOnFailure = (bool) $options['break_chain_on_failure']; + } + + if (isset($options['breakchainonfailure'])) { + $breakChainOnFailure = (bool) $options['breakchainonfailure']; + } + + $this->attach($this->plugin($name, $options), $breakChainOnFailure, $priority); + + return $this; + } + + /** + * Proxy to attachByName() to keep BC + * + * @deprecated Please use attachByName() + * @param string $name + * @param array $options + * @param bool $breakChainOnFailure + * @return ValidatorChain + */ + public function addByName($name, $options = [], $breakChainOnFailure = false) + { + return $this->attachByName($name, $options, $breakChainOnFailure); + } + + /** + * Use the plugin manager to prepend a validator by name + * + * @param string $name + * @param array $options + * @param bool $breakChainOnFailure + * @return ValidatorChain + */ + public function prependByName($name, $options = [], $breakChainOnFailure = false) + { + $validator = $this->plugin($name, $options); + $this->prependValidator($validator, $breakChainOnFailure); + return $this; + } + + /** + * Returns true if and only if $value passes all validations in the chain + * + * Validators are run in the order in which they were added to the chain (FIFO). + * + * @param mixed $value + * @param mixed $context Extra "context" to provide the validator + * @return bool + */ + public function isValid($value, $context = null) + { + $this->messages = []; + $result = true; + foreach ($this->validators as $element) { + $validator = $element['instance']; + if ($validator->isValid($value, $context)) { + continue; + } + $result = false; + $messages = $validator->getMessages(); + $this->messages = array_replace_recursive($this->messages, $messages); + if ($element['breakChainOnFailure']) { + break; + } + } + return $result; + } + + /** + * Merge the validator chain with the one given in parameter + * + * @param ValidatorChain $validatorChain + * @return ValidatorChain + */ + public function merge(ValidatorChain $validatorChain) + { + foreach ($validatorChain->validators->toArray(PriorityQueue::EXTR_BOTH) as $item) { + $this->attach($item['data']['instance'], $item['data']['breakChainOnFailure'], $item['priority']); + } + + return $this; + } + + /** + * Returns array of validation failure messages + * + * @return array + */ + public function getMessages() + { + return $this->messages; + } + + /** + * Get all the validators + * + * @return array + */ + public function getValidators() + { + return $this->validators->toArray(PriorityQueue::EXTR_DATA); + } + + /** + * Invoke chain as command + * + * @param mixed $value + * @return bool + */ + public function __invoke($value) + { + return $this->isValid($value); + } + + /** + * Deep clone handling + */ + public function __clone() + { + $this->validators = clone $this->validators; + } + + /** + * Prepare validator chain for serialization + * + * Plugin manager (property 'plugins') cannot + * be serialized. On wakeup the property remains unset + * and next invocation to getPluginManager() sets + * the default plugin manager instance (ValidatorPluginManager). + * + * @return array + */ + public function __sleep() + { + return ['validators', 'messages']; + } +} diff --git a/lib/laminas/laminas-validator/src/ValidatorInterface.php b/lib/laminas/laminas-validator/src/ValidatorInterface.php new file mode 100644 index 000000000..a6f4c847f --- /dev/null +++ b/lib/laminas/laminas-validator/src/ValidatorInterface.php @@ -0,0 +1,37 @@ + I18nValidator\Alnum::class, + 'Alnum' => I18nValidator\Alnum::class, + 'alpha' => I18nValidator\Alpha::class, + 'Alpha' => I18nValidator\Alpha::class, + 'barcode' => Barcode::class, + 'Barcode' => Barcode::class, + 'between' => Between::class, + 'Between' => Between::class, + 'bitwise' => Bitwise::class, + 'Bitwise' => Bitwise::class, + 'callback' => Callback::class, + 'Callback' => Callback::class, + 'creditcard' => CreditCard::class, + 'creditCard' => CreditCard::class, + 'CreditCard' => CreditCard::class, + 'csrf' => Csrf::class, + 'Csrf' => Csrf::class, + 'date' => Date::class, + 'Date' => Date::class, + 'datestep' => DateStep::class, + 'dateStep' => DateStep::class, + 'DateStep' => DateStep::class, + 'datetime' => I18nValidator\DateTime::class, + 'dateTime' => I18nValidator\DateTime::class, + 'DateTime' => I18nValidator\DateTime::class, + 'dbnorecordexists' => Db\NoRecordExists::class, + 'dbNoRecordExists' => Db\NoRecordExists::class, + 'DbNoRecordExists' => Db\NoRecordExists::class, + 'dbrecordexists' => Db\RecordExists::class, + 'dbRecordExists' => Db\RecordExists::class, + 'DbRecordExists' => Db\RecordExists::class, + 'digits' => Digits::class, + 'Digits' => Digits::class, + 'emailaddress' => EmailAddress::class, + 'emailAddress' => EmailAddress::class, + 'EmailAddress' => EmailAddress::class, + 'explode' => Explode::class, + 'Explode' => Explode::class, + 'filecount' => File\Count::class, + 'fileCount' => File\Count::class, + 'FileCount' => File\Count::class, + 'filecrc32' => File\Crc32::class, + 'fileCrc32' => File\Crc32::class, + 'FileCrc32' => File\Crc32::class, + 'fileexcludeextension' => File\ExcludeExtension::class, + 'fileExcludeExtension' => File\ExcludeExtension::class, + 'FileExcludeExtension' => File\ExcludeExtension::class, + 'fileexcludemimetype' => File\ExcludeMimeType::class, + 'fileExcludeMimeType' => File\ExcludeMimeType::class, + 'FileExcludeMimeType' => File\ExcludeMimeType::class, + 'fileexists' => File\Exists::class, + 'fileExists' => File\Exists::class, + 'FileExists' => File\Exists::class, + 'fileextension' => File\Extension::class, + 'fileExtension' => File\Extension::class, + 'FileExtension' => File\Extension::class, + 'filefilessize' => File\FilesSize::class, + 'fileFilesSize' => File\FilesSize::class, + 'FileFilesSize' => File\FilesSize::class, + 'filehash' => File\Hash::class, + 'fileHash' => File\Hash::class, + 'FileHash' => File\Hash::class, + 'fileimagesize' => File\ImageSize::class, + 'fileImageSize' => File\ImageSize::class, + 'FileImageSize' => File\ImageSize::class, + 'fileiscompressed' => File\IsCompressed::class, + 'fileIsCompressed' => File\IsCompressed::class, + 'FileIsCompressed' => File\IsCompressed::class, + 'fileisimage' => File\IsImage::class, + 'fileIsImage' => File\IsImage::class, + 'FileIsImage' => File\IsImage::class, + 'filemd5' => File\Md5::class, + 'fileMd5' => File\Md5::class, + 'FileMd5' => File\Md5::class, + 'filemimetype' => File\MimeType::class, + 'fileMimeType' => File\MimeType::class, + 'FileMimeType' => File\MimeType::class, + 'filenotexists' => File\NotExists::class, + 'fileNotExists' => File\NotExists::class, + 'FileNotExists' => File\NotExists::class, + 'filesha1' => File\Sha1::class, + 'fileSha1' => File\Sha1::class, + 'FileSha1' => File\Sha1::class, + 'filesize' => File\Size::class, + 'fileSize' => File\Size::class, + 'FileSize' => File\Size::class, + 'fileupload' => File\Upload::class, + 'fileUpload' => File\Upload::class, + 'FileUpload' => File\Upload::class, + 'fileuploadfile' => File\UploadFile::class, + 'fileUploadFile' => File\UploadFile::class, + 'FileUploadFile' => File\UploadFile::class, + 'filewordcount' => File\WordCount::class, + 'fileWordCount' => File\WordCount::class, + 'FileWordCount' => File\WordCount::class, + 'float' => I18nValidator\IsFloat::class, + 'Float' => I18nValidator\IsFloat::class, + 'gpspoint' => GpsPoint::class, + 'gpsPoint' => GpsPoint::class, + 'GpsPoint' => GpsPoint::class, + 'greaterthan' => GreaterThan::class, + 'greaterThan' => GreaterThan::class, + 'GreaterThan' => GreaterThan::class, + 'hex' => Hex::class, + 'Hex' => Hex::class, + 'hostname' => Hostname::class, + 'Hostname' => Hostname::class, + 'iban' => Iban::class, + 'Iban' => Iban::class, + 'identical' => Identical::class, + 'Identical' => Identical::class, + 'inarray' => InArray::class, + 'inArray' => InArray::class, + 'InArray' => InArray::class, + 'int' => I18nValidator\IsInt::class, + 'Int' => I18nValidator\IsInt::class, + 'ip' => Ip::class, + 'Ip' => Ip::class, + 'isbn' => Isbn::class, + 'Isbn' => Isbn::class, + 'isfloat' => I18nValidator\IsFloat::class, + 'isFloat' => I18nValidator\IsFloat::class, + 'IsFloat' => I18nValidator\IsFloat::class, + 'isinstanceof' => IsInstanceOf::class, + 'isInstanceOf' => IsInstanceOf::class, + 'IsInstanceOf' => IsInstanceOf::class, + 'isint' => I18nValidator\IsInt::class, + 'isInt' => I18nValidator\IsInt::class, + 'IsInt' => I18nValidator\IsInt::class, + 'lessthan' => LessThan::class, + 'lessThan' => LessThan::class, + 'LessThan' => LessThan::class, + 'notempty' => NotEmpty::class, + 'notEmpty' => NotEmpty::class, + 'NotEmpty' => NotEmpty::class, + 'phonenumber' => I18nValidator\PhoneNumber::class, + 'phoneNumber' => I18nValidator\PhoneNumber::class, + 'PhoneNumber' => I18nValidator\PhoneNumber::class, + 'postcode' => I18nValidator\PostCode::class, + 'postCode' => I18nValidator\PostCode::class, + 'PostCode' => I18nValidator\PostCode::class, + 'regex' => Regex::class, + 'Regex' => Regex::class, + 'sitemapchangefreq' => Sitemap\Changefreq::class, + 'sitemapChangefreq' => Sitemap\Changefreq::class, + 'SitemapChangefreq' => Sitemap\Changefreq::class, + 'sitemaplastmod' => Sitemap\Lastmod::class, + 'sitemapLastmod' => Sitemap\Lastmod::class, + 'SitemapLastmod' => Sitemap\Lastmod::class, + 'sitemaploc' => Sitemap\Loc::class, + 'sitemapLoc' => Sitemap\Loc::class, + 'SitemapLoc' => Sitemap\Loc::class, + 'sitemappriority' => Sitemap\Priority::class, + 'sitemapPriority' => Sitemap\Priority::class, + 'SitemapPriority' => Sitemap\Priority::class, + 'stringlength' => StringLength::class, + 'stringLength' => StringLength::class, + 'StringLength' => StringLength::class, + 'step' => Step::class, + 'Step' => Step::class, + 'timezone' => Timezone::class, + 'Timezone' => Timezone::class, + 'uri' => Uri::class, + 'Uri' => Uri::class, + 'uuid' => Uuid::class, + 'Uuid' => Uuid::class, + + // Legacy Zend Framework aliases + \Zend\I18n\Validator\Alnum::class => I18nValidator\Alnum::class, + \Zend\I18n\Validator\Alpha::class => I18nValidator\Alpha::class, + \Zend\Validator\Barcode::class => Barcode::class, + \Zend\Validator\Between::class => Between::class, + \Zend\Validator\Bitwise::class => Bitwise::class, + \Zend\Validator\Callback::class => Callback::class, + \Zend\Validator\CreditCard::class => CreditCard::class, + \Zend\Validator\Csrf::class => Csrf::class, + \Zend\Validator\DateStep::class => DateStep::class, + \Zend\Validator\Date::class => Date::class, + \Zend\I18n\Validator\DateTime::class => I18nValidator\DateTime::class, + \Zend\Validator\Db\NoRecordExists::class => Db\NoRecordExists::class, + \Zend\Validator\Db\RecordExists::class => Db\RecordExists::class, + \Zend\Validator\Digits::class => Digits::class, + \Zend\Validator\EmailAddress::class => EmailAddress::class, + \Zend\Validator\Explode::class => Explode::class, + \Zend\Validator\File\Count::class => File\Count::class, + \Zend\Validator\File\Crc32::class => File\Crc32::class, + \Zend\Validator\File\ExcludeExtension::class => File\ExcludeExtension::class, + \Zend\Validator\File\ExcludeMimeType::class => File\ExcludeMimeType::class, + \Zend\Validator\File\Exists::class => File\Exists::class, + \Zend\Validator\File\Extension::class => File\Extension::class, + \Zend\Validator\File\FilesSize::class => File\FilesSize::class, + \Zend\Validator\File\Hash::class => File\Hash::class, + \Zend\Validator\File\ImageSize::class => File\ImageSize::class, + \Zend\Validator\File\IsCompressed::class => File\IsCompressed::class, + \Zend\Validator\File\IsImage::class => File\IsImage::class, + \Zend\Validator\File\Md5::class => File\Md5::class, + \Zend\Validator\File\MimeType::class => File\MimeType::class, + \Zend\Validator\File\NotExists::class => File\NotExists::class, + \Zend\Validator\File\Sha1::class => File\Sha1::class, + \Zend\Validator\File\Size::class => File\Size::class, + \Zend\Validator\File\Upload::class => File\Upload::class, + \Zend\Validator\File\UploadFile::class => File\UploadFile::class, + \Zend\Validator\File\WordCount::class => File\WordCount::class, + \Zend\I18n\Validator\IsFloat::class => I18nValidator\IsFloat::class, + \Zend\Validator\GpsPoint::class => GpsPoint::class, + \Zend\Validator\GreaterThan::class => GreaterThan::class, + \Zend\Validator\Hex::class => Hex::class, + \Zend\Validator\Hostname::class => Hostname::class, + \Zend\Validator\Iban::class => Iban::class, + \Zend\Validator\Identical::class => Identical::class, + \Zend\Validator\InArray::class => InArray::class, + \Zend\I18n\Validator\IsInt::class => I18nValidator\IsInt::class, + \Zend\Validator\Ip::class => Ip::class, + \Zend\Validator\Isbn::class => Isbn::class, + \Zend\Validator\IsInstanceOf::class => IsInstanceOf::class, + \Zend\Validator\LessThan::class => LessThan::class, + \Zend\Validator\NotEmpty::class => NotEmpty::class, + \Zend\I18n\Validator\PhoneNumber::class => I18nValidator\PhoneNumber::class, + \Zend\I18n\Validator\PostCode::class => I18nValidator\PostCode::class, + \Zend\Validator\Regex::class => Regex::class, + \Zend\Validator\Sitemap\Changefreq::class => Sitemap\Changefreq::class, + \Zend\Validator\Sitemap\Lastmod::class => Sitemap\Lastmod::class, + \Zend\Validator\Sitemap\Loc::class => Sitemap\Loc::class, + \Zend\Validator\Sitemap\Priority::class => Sitemap\Priority::class, + \Zend\Validator\StringLength::class => StringLength::class, + \Zend\Validator\Step::class => Step::class, + \Zend\Validator\Timezone::class => Timezone::class, + \Zend\Validator\Uri::class => Uri::class, + \Zend\Validator\Uuid::class => Uuid::class, + + // v2 normalized FQCNs + 'zendvalidatorbarcode' => Barcode::class, + 'zendvalidatorbetween' => Between::class, + 'zendvalidatorbitwise' => Bitwise::class, + 'zendvalidatorcallback' => Callback::class, + 'zendvalidatorcreditcard' => CreditCard::class, + 'zendvalidatorcsrf' => Csrf::class, + 'zendvalidatordatestep' => DateStep::class, + 'zendvalidatordate' => Date::class, + 'zendvalidatordbnorecordexists' => Db\NoRecordExists::class, + 'zendvalidatordbrecordexists' => Db\RecordExists::class, + 'zendvalidatordigits' => Digits::class, + 'zendvalidatoremailaddress' => EmailAddress::class, + 'zendvalidatorexplode' => Explode::class, + 'zendvalidatorfilecount' => File\Count::class, + 'zendvalidatorfilecrc32' => File\Crc32::class, + 'zendvalidatorfileexcludeextension' => File\ExcludeExtension::class, + 'zendvalidatorfileexcludemimetype' => File\ExcludeMimeType::class, + 'zendvalidatorfileexists' => File\Exists::class, + 'zendvalidatorfileextension' => File\Extension::class, + 'zendvalidatorfilefilessize' => File\FilesSize::class, + 'zendvalidatorfilehash' => File\Hash::class, + 'zendvalidatorfileimagesize' => File\ImageSize::class, + 'zendvalidatorfileiscompressed' => File\IsCompressed::class, + 'zendvalidatorfileisimage' => File\IsImage::class, + 'zendvalidatorfilemd5' => File\Md5::class, + 'zendvalidatorfilemimetype' => File\MimeType::class, + 'zendvalidatorfilenotexists' => File\NotExists::class, + 'zendvalidatorfilesha1' => File\Sha1::class, + 'zendvalidatorfilesize' => File\Size::class, + 'zendvalidatorfileupload' => File\Upload::class, + 'zendvalidatorfileuploadfile' => File\UploadFile::class, + 'zendvalidatorfilewordcount' => File\WordCount::class, + 'zendvalidatorgpspoint' => GpsPoint::class, + 'zendvalidatorgreaterthan' => GreaterThan::class, + 'zendvalidatorhex' => Hex::class, + 'zendvalidatorhostname' => Hostname::class, + 'zendi18nvalidatoralnum' => I18nValidator\Alnum::class, + 'zendi18nvalidatoralpha' => I18nValidator\Alpha::class, + 'zendi18nvalidatordatetime' => I18nValidator\DateTime::class, + 'zendi18nvalidatorisfloat' => I18nValidator\IsFloat::class, + 'zendi18nvalidatorisint' => I18nValidator\IsInt::class, + 'zendi18nvalidatorphonenumber' => I18nValidator\PhoneNumber::class, + 'zendi18nvalidatorpostcode' => I18nValidator\PostCode::class, + 'zendvalidatoriban' => Iban::class, + 'zendvalidatoridentical' => Identical::class, + 'zendvalidatorinarray' => InArray::class, + 'zendvalidatorip' => Ip::class, + 'zendvalidatorisbn' => Isbn::class, + 'zendvalidatorisinstanceof' => IsInstanceOf::class, + 'zendvalidatorlessthan' => LessThan::class, + 'zendvalidatornotempty' => NotEmpty::class, + 'zendvalidatorregex' => Regex::class, + 'zendvalidatorsitemapchangefreq' => Sitemap\Changefreq::class, + 'zendvalidatorsitemaplastmod' => Sitemap\Lastmod::class, + 'zendvalidatorsitemaploc' => Sitemap\Loc::class, + 'zendvalidatorsitemappriority' => Sitemap\Priority::class, + 'zendvalidatorstringlength' => StringLength::class, + 'zendvalidatorstep' => Step::class, + 'zendvalidatortimezone' => Timezone::class, + 'zendvalidatoruri' => Uri::class, + 'zendvalidatoruuid' => Uuid::class, + ]; + + /** + * Default set of factories + * + * @var array + */ + protected $factories = [ + I18nValidator\Alnum::class => InvokableFactory::class, + I18nValidator\Alpha::class => InvokableFactory::class, + Barcode::class => InvokableFactory::class, + Between::class => InvokableFactory::class, + Bitwise::class => InvokableFactory::class, + Callback::class => InvokableFactory::class, + CreditCard::class => InvokableFactory::class, + Csrf::class => InvokableFactory::class, + DateStep::class => InvokableFactory::class, + Date::class => InvokableFactory::class, + I18nValidator\DateTime::class => InvokableFactory::class, + Db\NoRecordExists::class => InvokableFactory::class, + Db\RecordExists::class => InvokableFactory::class, + Digits::class => InvokableFactory::class, + EmailAddress::class => InvokableFactory::class, + Explode::class => InvokableFactory::class, + File\Count::class => InvokableFactory::class, + File\Crc32::class => InvokableFactory::class, + File\ExcludeExtension::class => InvokableFactory::class, + File\ExcludeMimeType::class => InvokableFactory::class, + File\Exists::class => InvokableFactory::class, + File\Extension::class => InvokableFactory::class, + File\FilesSize::class => InvokableFactory::class, + File\Hash::class => InvokableFactory::class, + File\ImageSize::class => InvokableFactory::class, + File\IsCompressed::class => InvokableFactory::class, + File\IsImage::class => InvokableFactory::class, + File\Md5::class => InvokableFactory::class, + File\MimeType::class => InvokableFactory::class, + File\NotExists::class => InvokableFactory::class, + File\Sha1::class => InvokableFactory::class, + File\Size::class => InvokableFactory::class, + File\Upload::class => InvokableFactory::class, + File\UploadFile::class => InvokableFactory::class, + File\WordCount::class => InvokableFactory::class, + I18nValidator\IsFloat::class => InvokableFactory::class, + GpsPoint::class => InvokableFactory::class, + GreaterThan::class => InvokableFactory::class, + Hex::class => InvokableFactory::class, + Hostname::class => InvokableFactory::class, + Iban::class => InvokableFactory::class, + Identical::class => InvokableFactory::class, + InArray::class => InvokableFactory::class, + I18nValidator\IsInt::class => InvokableFactory::class, + Ip::class => InvokableFactory::class, + Isbn::class => InvokableFactory::class, + I18nValidator\IsFloat::class => InvokableFactory::class, + IsInstanceOf::class => InvokableFactory::class, + I18nValidator\IsInt::class => InvokableFactory::class, + LessThan::class => InvokableFactory::class, + NotEmpty::class => InvokableFactory::class, + I18nValidator\PhoneNumber::class => InvokableFactory::class, + I18nValidator\PostCode::class => InvokableFactory::class, + Regex::class => InvokableFactory::class, + Sitemap\Changefreq::class => InvokableFactory::class, + Sitemap\Lastmod::class => InvokableFactory::class, + Sitemap\Loc::class => InvokableFactory::class, + Sitemap\Priority::class => InvokableFactory::class, + StringLength::class => InvokableFactory::class, + Step::class => InvokableFactory::class, + Timezone::class => InvokableFactory::class, + Uri::class => InvokableFactory::class, + Uuid::class => InvokableFactory::class, + + // v2 canonical FQCNs + + 'laminasvalidatorbarcodecode25interleaved' => InvokableFactory::class, + 'laminasvalidatorbarcodecode25' => InvokableFactory::class, + 'laminasvalidatorbarcodecode39ext' => InvokableFactory::class, + 'laminasvalidatorbarcodecode39' => InvokableFactory::class, + 'laminasvalidatorbarcodecode93ext' => InvokableFactory::class, + 'laminasvalidatorbarcodecode93' => InvokableFactory::class, + 'laminasvalidatorbarcodeean12' => InvokableFactory::class, + 'laminasvalidatorbarcodeean13' => InvokableFactory::class, + 'laminasvalidatorbarcodeean14' => InvokableFactory::class, + 'laminasvalidatorbarcodeean18' => InvokableFactory::class, + 'laminasvalidatorbarcodeean2' => InvokableFactory::class, + 'laminasvalidatorbarcodeean5' => InvokableFactory::class, + 'laminasvalidatorbarcodeean8' => InvokableFactory::class, + 'laminasvalidatorbarcodegtin12' => InvokableFactory::class, + 'laminasvalidatorbarcodegtin13' => InvokableFactory::class, + 'laminasvalidatorbarcodegtin14' => InvokableFactory::class, + 'laminasvalidatorbarcodeidentcode' => InvokableFactory::class, + 'laminasvalidatorbarcodeintelligentmail' => InvokableFactory::class, + 'laminasvalidatorbarcodeissn' => InvokableFactory::class, + 'laminasvalidatorbarcodeitf14' => InvokableFactory::class, + 'laminasvalidatorbarcodeleitcode' => InvokableFactory::class, + 'laminasvalidatorbarcodeplanet' => InvokableFactory::class, + 'laminasvalidatorbarcodepostnet' => InvokableFactory::class, + 'laminasvalidatorbarcoderoyalmail' => InvokableFactory::class, + 'laminasvalidatorbarcodesscc' => InvokableFactory::class, + 'laminasvalidatorbarcodeupca' => InvokableFactory::class, + 'laminasvalidatorbarcodeupce' => InvokableFactory::class, + 'laminasvalidatorbarcode' => InvokableFactory::class, + 'laminasvalidatorbetween' => InvokableFactory::class, + 'laminasvalidatorbitwise' => InvokableFactory::class, + 'laminasvalidatorcallback' => InvokableFactory::class, + 'laminasvalidatorcreditcard' => InvokableFactory::class, + 'laminasvalidatorcsrf' => InvokableFactory::class, + 'laminasvalidatordatestep' => InvokableFactory::class, + 'laminasvalidatordate' => InvokableFactory::class, + 'laminasvalidatordbnorecordexists' => InvokableFactory::class, + 'laminasvalidatordbrecordexists' => InvokableFactory::class, + 'laminasvalidatordigits' => InvokableFactory::class, + 'laminasvalidatoremailaddress' => InvokableFactory::class, + 'laminasvalidatorexplode' => InvokableFactory::class, + 'laminasvalidatorfilecount' => InvokableFactory::class, + 'laminasvalidatorfilecrc32' => InvokableFactory::class, + 'laminasvalidatorfileexcludeextension' => InvokableFactory::class, + 'laminasvalidatorfileexcludemimetype' => InvokableFactory::class, + 'laminasvalidatorfileexists' => InvokableFactory::class, + 'laminasvalidatorfileextension' => InvokableFactory::class, + 'laminasvalidatorfilefilessize' => InvokableFactory::class, + 'laminasvalidatorfilehash' => InvokableFactory::class, + 'laminasvalidatorfileimagesize' => InvokableFactory::class, + 'laminasvalidatorfileiscompressed' => InvokableFactory::class, + 'laminasvalidatorfileisimage' => InvokableFactory::class, + 'laminasvalidatorfilemd5' => InvokableFactory::class, + 'laminasvalidatorfilemimetype' => InvokableFactory::class, + 'laminasvalidatorfilenotexists' => InvokableFactory::class, + 'laminasvalidatorfilesha1' => InvokableFactory::class, + 'laminasvalidatorfilesize' => InvokableFactory::class, + 'laminasvalidatorfileupload' => InvokableFactory::class, + 'laminasvalidatorfileuploadfile' => InvokableFactory::class, + 'laminasvalidatorfilewordcount' => InvokableFactory::class, + 'laminasvalidatorgpspoint' => InvokableFactory::class, + 'laminasvalidatorgreaterthan' => InvokableFactory::class, + 'laminasvalidatorhex' => InvokableFactory::class, + 'laminasvalidatorhostname' => InvokableFactory::class, + 'laminasi18nvalidatoralnum' => InvokableFactory::class, + 'laminasi18nvalidatoralpha' => InvokableFactory::class, + 'laminasi18nvalidatordatetime' => InvokableFactory::class, + 'laminasi18nvalidatorisfloat' => InvokableFactory::class, + 'laminasi18nvalidatorisint' => InvokableFactory::class, + 'laminasi18nvalidatorphonenumber' => InvokableFactory::class, + 'laminasi18nvalidatorpostcode' => InvokableFactory::class, + 'laminasvalidatoriban' => InvokableFactory::class, + 'laminasvalidatoridentical' => InvokableFactory::class, + 'laminasvalidatorinarray' => InvokableFactory::class, + 'laminasvalidatorip' => InvokableFactory::class, + 'laminasvalidatorisbn' => InvokableFactory::class, + 'laminasvalidatorisinstanceof' => InvokableFactory::class, + 'laminasvalidatorlessthan' => InvokableFactory::class, + 'laminasvalidatornotempty' => InvokableFactory::class, + 'laminasvalidatorregex' => InvokableFactory::class, + 'laminasvalidatorsitemapchangefreq' => InvokableFactory::class, + 'laminasvalidatorsitemaplastmod' => InvokableFactory::class, + 'laminasvalidatorsitemaploc' => InvokableFactory::class, + 'laminasvalidatorsitemappriority' => InvokableFactory::class, + 'laminasvalidatorstringlength' => InvokableFactory::class, + 'laminasvalidatorstep' => InvokableFactory::class, + 'laminasvalidatortimezone' => InvokableFactory::class, + 'laminasvalidatoruri' => InvokableFactory::class, + 'laminasvalidatoruuid' => InvokableFactory::class, + ]; + + /** + * Whether or not to share by default; default to false (v2) + * + * @var bool + */ + protected $shareByDefault = false; + + /** + * Whether or not to share by default; default to false (v3) + * + * @var bool + */ + protected $sharedByDefault = false; + + /** + * Default instance type + * + * @var string + */ + protected $instanceOf = ValidatorInterface::class; + + /** + * Constructor + * + * After invoking parent constructor, add an initializer to inject the + * attached translator, if any, to the currently requested helper. + * + * {@inheritDoc} + */ + public function __construct($configOrContainerInstance = null, array $v3config = []) + { + parent::__construct($configOrContainerInstance, $v3config); + + $this->addInitializer([$this, 'injectTranslator']); + $this->addInitializer([$this, 'injectValidatorPluginManager']); + } + + /** + * Validate plugin instance + * + * {@inheritDoc} + */ + public function validate($plugin) + { + if (! $plugin instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + '%s expects only to create instances of %s; %s is invalid', + get_class($this), + $this->instanceOf, + (is_object($plugin) ? get_class($plugin) : gettype($plugin)) + )); + } + } + + /** + * For v2 compatibility: validate plugin instance. + * + * Proxies to `validate()`. + * + * @param mixed $plugin + * @throws Exception\RuntimeException + */ + public function validatePlugin($plugin) + { + try { + $this->validate($plugin); + } catch (InvalidServiceException $e) { + throw new Exception\RuntimeException(sprintf( + 'Plugin of type %s is invalid; must implement %s', + (is_object($plugin) ? get_class($plugin) : gettype($plugin)), + ValidatorInterface::class + ), $e->getCode(), $e); + } + } + + /** + * Inject a validator instance with the registered translator + * + * @param ContainerInterface|object $first + * @param ContainerInterface|object $second + * @return void + */ + public function injectTranslator($first, $second) + { + if ($first instanceof ContainerInterface) { + $container = $first; + $validator = $second; + } else { + $container = $second; + $validator = $first; + } + + // V2 means we pull it from the parent container + if ($container === $this && method_exists($container, 'getServiceLocator') && $container->getServiceLocator()) { + $container = $container->getServiceLocator(); + } + + if ($validator instanceof Translator\TranslatorAwareInterface) { + if ($container && $container->has('MvcTranslator')) { + $validator->setTranslator($container->get('MvcTranslator')); + } + } + } + + /** + * Inject a validator plugin manager + * + * @param ContainerInterface|object $first + * @param ContainerInterface|object $second + * @return void + */ + public function injectValidatorPluginManager($first, $second) + { + if ($first instanceof ContainerInterface) { + $container = $first; + $validator = $second; + } else { + $container = $second; + $validator = $first; + } + if ($validator instanceof ValidatorPluginManagerAwareInterface) { + $validator->setValidatorPluginManager($this); + } + } +} diff --git a/lib/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php b/lib/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php new file mode 100644 index 000000000..48144127e --- /dev/null +++ b/lib/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php @@ -0,0 +1,26 @@ +has('ServiceListener')) { + return $pluginManager; + } + + // If we do not have a config service, nothing more to do + if (! $container->has('config')) { + return $pluginManager; + } + + $config = $container->get('config'); + + // If we do not have validators configuration, nothing more to do + if (! isset($config['validators']) || ! is_array($config['validators'])) { + return $pluginManager; + } + + // Wire service configuration for validators + (new Config($config['validators']))->configureServiceManager($pluginManager); + + return $pluginManager; + } + + /** + * {@inheritDoc} + * + * @return ValidatorPluginManager + */ + public function createService(ServiceLocatorInterface $container, $name = null, $requestedName = null) + { + return $this($container, $requestedName ?: ValidatorPluginManager::class, $this->creationOptions); + } + + /** + * laminas-servicemanager v2 support for invocation options. + * + * @param array $options + * @return void + */ + public function setCreationOptions(array $options) + { + $this->creationOptions = $options; + } +} diff --git a/lib/laminas/laminas-validator/src/ValidatorProviderInterface.php b/lib/laminas/laminas-validator/src/ValidatorProviderInterface.php new file mode 100644 index 000000000..4640dfa66 --- /dev/null +++ b/lib/laminas/laminas-validator/src/ValidatorProviderInterface.php @@ -0,0 +1,30 @@ + laminas-project.flf. + +## 0.3.5 - 2019-11-06 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#25](https://github.com/laminas/laminas-zendframework-bridge/pull/25) adds entries for ZendHttp and ZendModule, which are file name segments in files from the zend-feed and zend-config-aggregator-module packages, respectively. + +## 0.3.4 - 2019-11-06 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#24](https://github.com/laminas/laminas-zendframework-bridge/pull/24) adds a rule to never rewrite the string `Doctrine\Zend`. + +- [#23](https://github.com/laminas/laminas-zendframework-bridge/pull/23) adds a missing map for each of ZendAcl and ZendRbac, which occur in the zend-expressive-authorization-acl and zend-expressive-authorization-rbac packages, respectively. + +## 0.3.3 - 2019-11-06 + +### Added + +- [#22](https://github.com/laminas/laminas-zendframework-bridge/pull/22) adds configuration post-processing features, exposed both as a laminas-config-aggregator post processor (for use with Expressive applications) and as a laminas-modulemanager `EVENT_MERGE_CONFIG` listener (for use with MVC applications). When registered, it will post-process the configuration, replacing known Zend Framework-specific strings with their Laminas replacements. A ruleset is provided that ensures dependency configuration is rewritten in a safe manner, routing configuration is skipped, and certain top-level configuration keys are matched exactly (instead of potentially as substrings or word stems). A later release of laminas-migration will auto-register these tools in applications when possible. + +### Changed + +- [#22](https://github.com/laminas/laminas-zendframework-bridge/pull/22) removes support for PHP versions prior to PHP 5.6. We have decided to only support supported PHP versions, whether that support is via php.net or commercial. The lowest supported PHP version we have found is 5.6. Users wishing to migrate to Laminas must at least update to PHP 5.6 before doing so. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 0.3.2 - 2019-10-30 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- [#21](https://github.com/laminas/laminas-zendframework-bridge/pull/21) removes rewriting of the Amazon library, as it is not moving to Laminas. + +- [#21](https://github.com/laminas/laminas-zendframework-bridge/pull/21) removes rewriting of the GCM and APNS libraries, as they are not moving to Laminas. + +### Fixed + +- [#21](https://github.com/laminas/laminas-zendframework-bridge/pull/21) fixes how the recaptcha and twitter library package and namespaces are rewritten. + +## 0.3.1 - 2019-04-25 + +### Added + +- [#20](https://github.com/laminas/laminas-zendframework-bridge/pull/20) provides an additional autoloader that is _prepended_ to the autoloader + stack. This new autoloader will create class aliases for interfaces, classes, + and traits referenced in type hints and class declarations, ensuring PHP is + able to resolve them correctly during class_alias operations. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 0.3.0 - 2019-04-12 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- [#16](https://github.com/laminas/laminas-zendframework-bridge/pull/16) removes the `RewriteRules::classRewrite()` method, as it is no longer + needed due to internal refactoring. + +### Fixed + +- [#16](https://github.com/laminas/laminas-zendframework-bridge/pull/16) fixes how the rewrite rules detect the word `Zend` in subnamespaces and + class names to be both more robust and simpler. + +## 0.2.5 - 2019-04-11 + +### Added + +- [#12](https://github.com/laminas/laminas-zendframework-bridge/pull/12) adds functionality for ensuring we alias namespaces and classes that + include the word `Zend` in them; e.g., `Zend\Expressive\ZendView\ZendViewRendererFactory` + will now alias to `Expressive\LaminasView\LaminasViewRendererFactory`. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 0.2.4 - 2019-04-11 + +### Added + +- [#11](https://github.com/laminas/laminas-zendframework-bridge/pull/11) adds maps for the Expressive router adapter packages. + +- [#10](https://github.com/laminas/laminas-zendframework-bridge/pull/10) adds a map for the Psr7Bridge package, as it used `Zend` within a subnamespace. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 0.2.3 - 2019-04-10 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#9](https://github.com/laminas/laminas-zendframework-bridge/pull/9) fixes the mapping for the Problem Details package. + +## 0.2.2 - 2019-04-10 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Added a check that the discovered alias exists as a class, interface, or trait + before attempting to call `class_alias()`. + +## 0.2.1 - 2019-04-10 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#8](https://github.com/laminas/laminas-zendframework-bridge/pull/8) fixes mappings for each of zend-expressive-authentication-zendauthentication, + zend-expressive-zendrouter, and zend-expressive-zendviewrenderer. + +## 0.2.0 - 2019-04-01 + +### Added + +- Nothing. + +### Changed + +- [#4](https://github.com/laminas/laminas-zendframework-bridge/pull/4) rewrites the autoloader to be class-based, via the class + `Laminas\ZendFrameworkBridge\Autoloader`. Additionally, the new approach + provides a performance boost by using a balanced tree algorithm, ensuring + matches occur faster. + +### Deprecated + +- Nothing. + +### Removed + +- [#4](https://github.com/laminas/laminas-zendframework-bridge/pull/4) removes function aliasing. Function aliasing will move to the packages that + provide functions. + +### Fixed + +- Nothing. + +## 0.1.0 - 2019-03-27 + +### Added + +- Adds an autoloader file that registers with `spl_autoload_register` a routine + for aliasing legacy ZF class/interface/trait names to Laminas Project + equivalents. + +- Adds autoloader files for aliasing legacy ZF package functions to Laminas + Project equivalents. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. diff --git a/lib/laminas/laminas-zendframework-bridge/COPYRIGHT.md b/lib/laminas/laminas-zendframework-bridge/COPYRIGHT.md new file mode 100644 index 000000000..0a8cccc06 --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/COPYRIGHT.md @@ -0,0 +1 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) diff --git a/lib/laminas/laminas-zendframework-bridge/LICENSE.md b/lib/laminas/laminas-zendframework-bridge/LICENSE.md new file mode 100644 index 000000000..10b40f142 --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/LICENSE.md @@ -0,0 +1,26 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Laminas Foundation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/laminas/laminas-zendframework-bridge/README.md b/lib/laminas/laminas-zendframework-bridge/README.md new file mode 100644 index 000000000..fd7953823 --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/README.md @@ -0,0 +1,24 @@ +# laminas-zendframework-bridge + +[![Build Status](https://travis-ci.com/laminas/laminas-zendframework-bridge.svg?branch=master)](https://travis-ci.com/laminas/laminas-zendframework-bridge) +[![Coverage Status](https://coveralls.io/repos/github/laminas/laminas-zendframework-bridge/badge.svg?branch=master)](https://coveralls.io/github/laminas/laminas-zendframework-bridge?branch=master) + +This library provides a custom autoloader that aliases legacy Zend Framework, +Apigility, and Expressive classes to their replacements under the Laminas +Project. + +This package should be installed only if you are also using the composer plugin +that installs Laminas packages to replace ZF/Apigility/Expressive packages. + +## Installation + +Run the following to install this library: + +```bash +$ composer require laminas/laminas-zendframework-bridge +``` + +## Support + +* [Issues](https://github.com/laminas/laminas-zendframework-bridge/issues/) +* [Forum](https://discourse.laminas.dev/) diff --git a/lib/laminas/laminas-zendframework-bridge/composer.json b/lib/laminas/laminas-zendframework-bridge/composer.json new file mode 100644 index 000000000..34af15a43 --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/composer.json @@ -0,0 +1,58 @@ +{ + "name": "laminas/laminas-zendframework-bridge", + "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "license": "BSD-3-Clause", + "keywords": [ + "autoloading", + "laminas", + "zf", + "zendframework" + ], + "support": { + "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", + "source": "https://github.com/laminas/laminas-zendframework-bridge", + "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", + "forum": "https://discourse.laminas.dev/" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1 || ^9.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ZendFrameworkBridge\\": "src//" + } + }, + "autoload-dev": { + "files": [ + "test/classes.php" + ], + "psr-4": { + "LaminasTest\\ZendFrameworkBridge\\": "test/", + "LaminasTest\\ZendFrameworkBridge\\TestAsset\\": "test/TestAsset/classes/", + "Laminas\\ApiTools\\": "test/TestAsset/LaminasApiTools/", + "Mezzio\\": "test/TestAsset/Mezzio/", + "Laminas\\": "test/TestAsset/Laminas/" + } + }, + "extra": { + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, + "config": { + "sort-packages": true + }, + "scripts": { + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" + } +} diff --git a/lib/laminas/laminas-zendframework-bridge/config/replacements.php b/lib/laminas/laminas-zendframework-bridge/config/replacements.php new file mode 100644 index 000000000..f5344355f --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/config/replacements.php @@ -0,0 +1,372 @@ + 'zendframework/zendframework', + 'zend-developer-tools/toolbar/bjy' => 'zend-developer-tools/toolbar/bjy', + 'zend-developer-tools/toolbar/doctrine' => 'zend-developer-tools/toolbar/doctrine', + + // NAMESPACES + // Zend Framework components + 'Zend\\AuraDi\\Config' => 'Laminas\\AuraDi\\Config', + 'Zend\\Authentication' => 'Laminas\\Authentication', + 'Zend\\Barcode' => 'Laminas\\Barcode', + 'Zend\\Cache' => 'Laminas\\Cache', + 'Zend\\Captcha' => 'Laminas\\Captcha', + 'Zend\\Code' => 'Laminas\\Code', + 'ZendCodingStandard\\Sniffs' => 'LaminasCodingStandard\\Sniffs', + 'ZendCodingStandard\\Utils' => 'LaminasCodingStandard\\Utils', + 'Zend\\ComponentInstaller' => 'Laminas\\ComponentInstaller', + 'Zend\\Config' => 'Laminas\\Config', + 'Zend\\ConfigAggregator' => 'Laminas\\ConfigAggregator', + 'Zend\\ConfigAggregatorModuleManager' => 'Laminas\\ConfigAggregatorModuleManager', + 'Zend\\ConfigAggregatorParameters' => 'Laminas\\ConfigAggregatorParameters', + 'Zend\\Console' => 'Laminas\\Console', + 'Zend\\ContainerConfigTest' => 'Laminas\\ContainerConfigTest', + 'Zend\\Crypt' => 'Laminas\\Crypt', + 'Zend\\Db' => 'Laminas\\Db', + 'ZendDeveloperTools' => 'Laminas\\DeveloperTools', + 'Zend\\Di' => 'Laminas\\Di', + 'Zend\\Diactoros' => 'Laminas\\Diactoros', + 'ZendDiagnostics\\Check' => 'Laminas\\Diagnostics\\Check', + 'ZendDiagnostics\\Result' => 'Laminas\\Diagnostics\\Result', + 'ZendDiagnostics\\Runner' => 'Laminas\\Diagnostics\\Runner', + 'Zend\\Dom' => 'Laminas\\Dom', + 'Zend\\Escaper' => 'Laminas\\Escaper', + 'Zend\\EventManager' => 'Laminas\\EventManager', + 'Zend\\Feed' => 'Laminas\\Feed', + 'Zend\\File' => 'Laminas\\File', + 'Zend\\Filter' => 'Laminas\\Filter', + 'Zend\\Form' => 'Laminas\\Form', + 'Zend\\Http' => 'Laminas\\Http', + 'Zend\\HttpHandlerRunner' => 'Laminas\\HttpHandlerRunner', + 'Zend\\Hydrator' => 'Laminas\\Hydrator', + 'Zend\\I18n' => 'Laminas\\I18n', + 'Zend\\InputFilter' => 'Laminas\\InputFilter', + 'Zend\\Json' => 'Laminas\\Json', + 'Zend\\Ldap' => 'Laminas\\Ldap', + 'Zend\\Loader' => 'Laminas\\Loader', + 'Zend\\Log' => 'Laminas\\Log', + 'Zend\\Mail' => 'Laminas\\Mail', + 'Zend\\Math' => 'Laminas\\Math', + 'Zend\\Memory' => 'Laminas\\Memory', + 'Zend\\Mime' => 'Laminas\\Mime', + 'Zend\\ModuleManager' => 'Laminas\\ModuleManager', + 'Zend\\Mvc' => 'Laminas\\Mvc', + 'Zend\\Navigation' => 'Laminas\\Navigation', + 'Zend\\Paginator' => 'Laminas\\Paginator', + 'Zend\\Permissions' => 'Laminas\\Permissions', + 'Zend\\Pimple\\Config' => 'Laminas\\Pimple\\Config', + 'Zend\\ProblemDetails' => 'Mezzio\\ProblemDetails', + 'Zend\\ProgressBar' => 'Laminas\\ProgressBar', + 'Zend\\Psr7Bridge' => 'Laminas\\Psr7Bridge', + 'Zend\\Router' => 'Laminas\\Router', + 'Zend\\Serializer' => 'Laminas\\Serializer', + 'Zend\\Server' => 'Laminas\\Server', + 'Zend\\ServiceManager' => 'Laminas\\ServiceManager', + 'ZendService\\ReCaptcha' => 'Laminas\\ReCaptcha', + 'ZendService\\Twitter' => 'Laminas\\Twitter', + 'Zend\\Session' => 'Laminas\\Session', + 'Zend\\SkeletonInstaller' => 'Laminas\\SkeletonInstaller', + 'Zend\\Soap' => 'Laminas\\Soap', + 'Zend\\Stdlib' => 'Laminas\\Stdlib', + 'Zend\\Stratigility' => 'Laminas\\Stratigility', + 'Zend\\Tag' => 'Laminas\\Tag', + 'Zend\\Test' => 'Laminas\\Test', + 'Zend\\Text' => 'Laminas\\Text', + 'Zend\\Uri' => 'Laminas\\Uri', + 'Zend\\Validator' => 'Laminas\\Validator', + 'Zend\\View' => 'Laminas\\View', + 'ZendXml' => 'Laminas\\Xml', + 'Zend\\Xml2Json' => 'Laminas\\Xml2Json', + 'Zend\\XmlRpc' => 'Laminas\\XmlRpc', + 'ZendOAuth' => 'Laminas\\OAuth', + + // class ZendAcl in zend-expressive-authorization-acl + 'ZendAcl' => 'LaminasAcl', + 'Zend\\Expressive\\Authorization\\Acl\\ZendAcl' => 'Mezzio\\Authorization\\Acl\\LaminasAcl', + // class ZendHttpClientDecorator in zend-feed + 'ZendHttp' => 'LaminasHttp', + // class ZendModuleProvider in zend-config-aggregator-modulemanager + 'ZendModule' => 'LaminasModule', + // class ZendRbac in zend-expressive-authorization-rbac + 'ZendRbac' => 'LaminasRbac', + 'Zend\\Expressive\\Authorization\\Rbac\\ZendRbac' => 'Mezzio\\Authorization\\Rbac\\LaminasRbac', + // class ZendRouter in zend-expressive-router-zendrouter + 'ZendRouter' => 'LaminasRouter', + 'Zend\\Expressive\\Router\\ZendRouter' => 'Mezzio\\Router\\LaminasRouter', + // class ZendViewRenderer in zend-expressive-zendviewrenderer + 'ZendViewRenderer' => 'LaminasViewRenderer', + 'Zend\\Expressive\\ZendView\\ZendViewRenderer' => 'Mezzio\\LaminasView\\LaminasViewRenderer', + 'a\\Zend' => 'a\\Zend', + 'b\\Zend' => 'b\\Zend', + 'c\\Zend' => 'c\\Zend', + 'd\\Zend' => 'd\\Zend', + 'e\\Zend' => 'e\\Zend', + 'f\\Zend' => 'f\\Zend', + 'g\\Zend' => 'g\\Zend', + 'h\\Zend' => 'h\\Zend', + 'i\\Zend' => 'i\\Zend', + 'j\\Zend' => 'j\\Zend', + 'k\\Zend' => 'k\\Zend', + 'l\\Zend' => 'l\\Zend', + 'm\\Zend' => 'm\\Zend', + 'n\\Zend' => 'n\\Zend', + 'o\\Zend' => 'o\\Zend', + 'p\\Zend' => 'p\\Zend', + 'q\\Zend' => 'q\\Zend', + 'r\\Zend' => 'r\\Zend', + 's\\Zend' => 's\\Zend', + 't\\Zend' => 't\\Zend', + 'u\\Zend' => 'u\\Zend', + 'v\\Zend' => 'v\\Zend', + 'w\\Zend' => 'w\\Zend', + 'x\\Zend' => 'x\\Zend', + 'y\\Zend' => 'y\\Zend', + 'z\\Zend' => 'z\\Zend', + + // Expressive + 'Zend\\Expressive' => 'Mezzio', + 'ZendAuthentication' => 'LaminasAuthentication', + 'ZendAcl' => 'LaminasAcl', + 'ZendRbac' => 'LaminasRbac', + 'ZendRouter' => 'LaminasRouter', + 'ExpressiveUrlGenerator' => 'MezzioUrlGenerator', + 'ExpressiveInstaller' => 'MezzioInstaller', + + // Apigility + 'ZF\\Apigility' => 'Laminas\\ApiTools', + 'ZF\\ApiProblem' => 'Laminas\\ApiTools\\ApiProblem', + 'ZF\\AssetManager' => 'Laminas\\ApiTools\\AssetManager', + 'ZF\\ComposerAutoloading' => 'Laminas\\ComposerAutoloading', + 'ZF\\Configuration' => 'Laminas\\ApiTools\\Configuration', + 'ZF\\ContentNegotiation' => 'Laminas\\ApiTools\\ContentNegotiation', + 'ZF\\ContentValidation' => 'Laminas\\ApiTools\\ContentValidation', + 'ZF\\DevelopmentMode' => 'Laminas\\DevelopmentMode', + 'ZF\\Doctrine\\QueryBuilder' => 'Laminas\\ApiTools\\Doctrine\\QueryBuilder', + 'ZF\\Hal' => 'Laminas\\ApiTools\\Hal', + 'ZF\\HttpCache' => 'Laminas\\ApiTools\\HttpCache', + 'ZF\\MvcAuth' => 'Laminas\\ApiTools\\MvcAuth', + 'ZF\\OAuth2' => 'Laminas\\ApiTools\\OAuth2', + 'ZF\\Rest' => 'Laminas\\ApiTools\\Rest', + 'ZF\\Rpc' => 'Laminas\\ApiTools\\Rpc', + 'ZF\\Versioning' => 'Laminas\\ApiTools\\Versioning', + 'a\\ZF' => 'a\\ZF', + 'b\\ZF' => 'b\\ZF', + 'c\\ZF' => 'c\\ZF', + 'd\\ZF' => 'd\\ZF', + 'e\\ZF' => 'e\\ZF', + 'f\\ZF' => 'f\\ZF', + 'g\\ZF' => 'g\\ZF', + 'h\\ZF' => 'h\\ZF', + 'i\\ZF' => 'i\\ZF', + 'j\\ZF' => 'j\\ZF', + 'k\\ZF' => 'k\\ZF', + 'l\\ZF' => 'l\\ZF', + 'm\\ZF' => 'm\\ZF', + 'n\\ZF' => 'n\\ZF', + 'o\\ZF' => 'o\\ZF', + 'p\\ZF' => 'p\\ZF', + 'q\\ZF' => 'q\\ZF', + 'r\\ZF' => 'r\\ZF', + 's\\ZF' => 's\\ZF', + 't\\ZF' => 't\\ZF', + 'u\\ZF' => 'u\\ZF', + 'v\\ZF' => 'v\\ZF', + 'w\\ZF' => 'w\\ZF', + 'x\\ZF' => 'x\\ZF', + 'y\\ZF' => 'y\\ZF', + 'z\\ZF' => 'z\\ZF', + + 'ApigilityModuleInterface' => 'ApiToolsModuleInterface', + 'ApigilityProviderInterface' => 'ApiToolsProviderInterface', + 'ApigilityVersionController' => 'ApiToolsVersionController', + + // PACKAGES + // ZF components, MVC + 'zendframework/skeleton-application' => 'laminas/skeleton-application', + 'zendframework/zend-auradi-config' => 'laminas/laminas-auradi-config', + 'zendframework/zend-authentication' => 'laminas/laminas-authentication', + 'zendframework/zend-barcode' => 'laminas/laminas-barcode', + 'zendframework/zend-cache' => 'laminas/laminas-cache', + 'zendframework/zend-captcha' => 'laminas/laminas-captcha', + 'zendframework/zend-code' => 'laminas/laminas-code', + 'zendframework/zend-coding-standard' => 'laminas/laminas-coding-standard', + 'zendframework/zend-component-installer' => 'laminas/laminas-component-installer', + 'zendframework/zend-composer-autoloading' => 'laminas/laminas-composer-autoloading', + 'zendframework/zend-config-aggregator' => 'laminas/laminas-config-aggregator', + 'zendframework/zend-config' => 'laminas/laminas-config', + 'zendframework/zend-console' => 'laminas/laminas-console', + 'zendframework/zend-container-config-test' => 'laminas/laminas-container-config-test', + 'zendframework/zend-crypt' => 'laminas/laminas-crypt', + 'zendframework/zend-db' => 'laminas/laminas-db', + 'zendframework/zend-developer-tools' => 'laminas/laminas-developer-tools', + 'zendframework/zend-diactoros' => 'laminas/laminas-diactoros', + 'zendframework/zenddiagnostics' => 'laminas/laminas-diagnostics', + 'zendframework/zend-di' => 'laminas/laminas-di', + 'zendframework/zend-dom' => 'laminas/laminas-dom', + 'zendframework/zend-escaper' => 'laminas/laminas-escaper', + 'zendframework/zend-eventmanager' => 'laminas/laminas-eventmanager', + 'zendframework/zend-feed' => 'laminas/laminas-feed', + 'zendframework/zend-file' => 'laminas/laminas-file', + 'zendframework/zend-filter' => 'laminas/laminas-filter', + 'zendframework/zend-form' => 'laminas/laminas-form', + 'zendframework/zend-httphandlerrunner' => 'laminas/laminas-httphandlerrunner', + 'zendframework/zend-http' => 'laminas/laminas-http', + 'zendframework/zend-hydrator' => 'laminas/laminas-hydrator', + 'zendframework/zend-i18n' => 'laminas/laminas-i18n', + 'zendframework/zend-i18n-resources' => 'laminas/laminas-i18n-resources', + 'zendframework/zend-inputfilter' => 'laminas/laminas-inputfilter', + 'zendframework/zend-json' => 'laminas/laminas-json', + 'zendframework/zend-json-server' => 'laminas/laminas-json-server', + 'zendframework/zend-ldap' => 'laminas/laminas-ldap', + 'zendframework/zend-loader' => 'laminas/laminas-loader', + 'zendframework/zend-log' => 'laminas/laminas-log', + 'zendframework/zend-mail' => 'laminas/laminas-mail', + 'zendframework/zend-math' => 'laminas/laminas-math', + 'zendframework/zend-memory' => 'laminas/laminas-memory', + 'zendframework/zend-mime' => 'laminas/laminas-mime', + 'zendframework/zend-modulemanager' => 'laminas/laminas-modulemanager', + 'zendframework/zend-mvc' => 'laminas/laminas-mvc', + 'zendframework/zend-navigation' => 'laminas/laminas-navigation', + 'zendframework/zend-oauth' => 'laminas/laminas-oauth', + 'zendframework/zend-paginator' => 'laminas/laminas-paginator', + 'zendframework/zend-permissions-acl' => 'laminas/laminas-permissions-acl', + 'zendframework/zend-permissions-rbac' => 'laminas/laminas-permissions-rbac', + 'zendframework/zend-pimple-config' => 'laminas/laminas-pimple-config', + 'zendframework/zend-progressbar' => 'laminas/laminas-progressbar', + 'zendframework/zend-psr7bridge' => 'laminas/laminas-psr7bridge', + 'zendframework/zend-recaptcha' => 'laminas/laminas-recaptcha', + 'zendframework/zend-router' => 'laminas/laminas-router', + 'zendframework/zend-serializer' => 'laminas/laminas-serializer', + 'zendframework/zend-server' => 'laminas/laminas-server', + 'zendframework/zend-servicemanager' => 'laminas/laminas-servicemanager', + 'zendframework/zendservice-recaptcha' => 'laminas/laminas-recaptcha', + 'zendframework/zendservice-twitter' => 'laminas/laminas-twitter', + 'zendframework/zend-session' => 'laminas/laminas-session', + 'zendframework/zend-skeleton-installer' => 'laminas/laminas-skeleton-installer', + 'zendframework/zend-soap' => 'laminas/laminas-soap', + 'zendframework/zend-stdlib' => 'laminas/laminas-stdlib', + 'zendframework/zend-stratigility' => 'laminas/laminas-stratigility', + 'zendframework/zend-tag' => 'laminas/laminas-tag', + 'zendframework/zend-test' => 'laminas/laminas-test', + 'zendframework/zend-text' => 'laminas/laminas-text', + 'zendframework/zend-uri' => 'laminas/laminas-uri', + 'zendframework/zend-validator' => 'laminas/laminas-validator', + 'zendframework/zend-view' => 'laminas/laminas-view', + 'zendframework/zend-xml2json' => 'laminas/laminas-xml2json', + 'zendframework/zend-xml' => 'laminas/laminas-xml', + 'zendframework/zend-xmlrpc' => 'laminas/laminas-xmlrpc', + + // Expressive packages + 'zendframework/zend-expressive' => 'mezzio/mezzio', + 'zendframework/zend-expressive-zendrouter' => 'mezzio/mezzio-laminasrouter', + 'zendframework/zend-problem-details' => 'mezzio/mezzio-problem-details', + 'zendframework/zend-expressive-zendviewrenderer' => 'mezzio/mezzio-laminasviewrenderer', + + // Apigility packages + 'zfcampus/apigility-documentation' => 'laminas-api-tools/documentation', + 'zfcampus/statuslib-example' => 'laminas-api-tools/statuslib-example', + 'zfcampus/zf-apigility' => 'laminas-api-tools/api-tools', + 'zfcampus/zf-api-problem' => 'laminas-api-tools/api-tools-api-problem', + 'zfcampus/zf-asset-manager' => 'laminas-api-tools/api-tools-asset-manager', + 'zfcampus/zf-configuration' => 'laminas-api-tools/api-tools-configuration', + 'zfcampus/zf-content-negotiation' => 'laminas-api-tools/api-tools-content-negotiation', + 'zfcampus/zf-content-validation' => 'laminas-api-tools/api-tools-content-validation', + 'zfcampus/zf-development-mode' => 'laminas/laminas-development-mode', + 'zfcampus/zf-doctrine-querybuilder' => 'laminas-api-tools/api-tools-doctrine-querybuilder', + 'zfcampus/zf-hal' => 'laminas-api-tools/api-tools-hal', + 'zfcampus/zf-http-cache' => 'laminas-api-tools/api-tools-http-cache', + 'zfcampus/zf-mvc-auth' => 'laminas-api-tools/api-tools-mvc-auth', + 'zfcampus/zf-oauth2' => 'laminas-api-tools/api-tools-oauth2', + 'zfcampus/zf-rest' => 'laminas-api-tools/api-tools-rest', + 'zfcampus/zf-rpc' => 'laminas-api-tools/api-tools-rpc', + 'zfcampus/zf-versioning' => 'laminas-api-tools/api-tools-versioning', + + // CONFIG KEYS, SCRIPT NAMES, ETC + // ZF components + '::fromZend' => '::fromLaminas', // psr7bridge + '::toZend' => '::toLaminas', // psr7bridge + 'use_zend_loader' => 'use_laminas_loader', // zend-modulemanager + 'zend-config' => 'laminas-config', + 'zend-developer-tools/' => 'laminas-developer-tools/', + 'zend-tag-cloud' => 'laminas-tag-cloud', + 'zenddevelopertools' => 'laminas-developer-tools', + 'zendbarcode' => 'laminasbarcode', + 'ZendBarcode' => 'LaminasBarcode', + 'zendcache' => 'laminascache', + 'ZendCache' => 'LaminasCache', + 'zendconfig' => 'laminasconfig', + 'ZendConfig' => 'LaminasConfig', + 'zendfeed' => 'laminasfeed', + 'ZendFeed' => 'LaminasFeed', + 'zendfilter' => 'laminasfilter', + 'ZendFilter' => 'LaminasFilter', + 'zendform' => 'laminasform', + 'ZendForm' => 'LaminasForm', + 'zendi18n' => 'laminasi18n', + 'ZendI18n' => 'LaminasI18n', + 'zendinputfilter' => 'laminasinputfilter', + 'ZendInputFilter' => 'LaminasInputFilter', + 'zendlog' => 'laminaslog', + 'ZendLog' => 'LaminasLog', + 'zendmail' => 'laminasmail', + 'ZendMail' => 'LaminasMail', + 'zendmvc' => 'laminasmvc', + 'ZendMvc' => 'LaminasMvc', + 'zendpaginator' => 'laminaspaginator', + 'ZendPaginator' => 'LaminasPaginator', + 'zendserializer' => 'laminasserializer', + 'ZendSerializer' => 'LaminasSerializer', + 'zendtag' => 'laminastag', + 'ZendTag' => 'LaminasTag', + 'zendtext' => 'laminastext', + 'ZendText' => 'LaminasText', + 'zendvalidator' => 'laminasvalidator', + 'ZendValidator' => 'LaminasValidator', + 'zendview' => 'laminasview', + 'ZendView' => 'LaminasView', + 'zend-framework.flf' => 'laminas-project.flf', + + // Expressive-related + "'zend-expressive'" => "'mezzio'", + '"zend-expressive"' => '"mezzio"', + 'zend-expressive.' => 'mezzio.', + 'zend-expressive-authorization' => 'mezzio-authorization', + 'zend-expressive-hal' => 'mezzio-hal', + 'zend-expressive-session' => 'mezzio-session', + 'zend-expressive-swoole' => 'mezzio-swoole', + 'zend-expressive-tooling' => 'mezzio-tooling', + + // Apigility-related + "'zf-apigility'" => "'api-tools'", + '"zf-apigility"' => '"api-tools"', + 'zf-apigility/' => 'api-tools/', + 'zf-apigility-admin' => 'api-tools-admin', + 'zf-content-negotiation' => 'api-tools-content-negotiation', + 'zf-hal' => 'api-tools-hal', + 'zf-rest' => 'api-tools-rest', + 'zf-rpc' => 'api-tools-rpc', + 'zf-content-validation' => 'api-tools-content-validation', + 'zf-apigility-ui' => 'api-tools-ui', + 'zf-apigility-documentation-blueprint' => 'api-tools-documentation-blueprint', + 'zf-apigility-documentation-swagger' => 'api-tools-documentation-swagger', + 'zf-apigility-welcome' => 'api-tools-welcome', + 'zf-api-problem' => 'api-tools-api-problem', + 'zf-configuration' => 'api-tools-configuration', + 'zf-http-cache' => 'api-tools-http-cache', + 'zf-mvc-auth' => 'api-tools-mvc-auth', + 'zf-oauth2' => 'api-tools-oauth2', + 'zf-versioning' => 'api-tools-versioning', + 'ZfApigilityDoctrineQueryProviderManager' => 'LaminasApiToolsDoctrineQueryProviderManager', + 'ZfApigilityDoctrineQueryCreateFilterManager' => 'LaminasApiToolsDoctrineQueryCreateFilterManager', + 'zf-apigility-doctrine' => 'api-tools-doctrine', + 'zf-development-mode' => 'laminas-development-mode', + 'zf-doctrine-querybuilder' => 'api-tools-doctrine-querybuilder', + + // 3rd party Apigility packages + 'api-skeletons/zf-' => 'api-skeletons/zf-', // api-skeletons packages + 'zf-oauth2-' => 'zf-oauth2-', // api-skeletons OAuth2-related packages + 'ZF\\OAuth2\\Client' => 'ZF\\OAuth2\\Client', // api-skeletons/zf-oauth2-client + 'ZF\\OAuth2\\Doctrine' => 'ZF\\OAuth2\\Doctrine', // api-skeletons/zf-oauth2-doctrine +]; diff --git a/lib/laminas/laminas-zendframework-bridge/src/Autoloader.php b/lib/laminas/laminas-zendframework-bridge/src/Autoloader.php new file mode 100644 index 000000000..6048766a2 --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/src/Autoloader.php @@ -0,0 +1,172 @@ +loadClass($class)) { + $legacy = $namespaces[$check] + . strtr(substr($class, strlen($check)), [ + 'ApiTools' => 'Apigility', + 'Mezzio' => 'Expressive', + 'Laminas' => 'Zend', + ]); + class_alias($class, $legacy); + } + }; + } + + /** + * @return callable + */ + private static function createAppendAutoloader(array $namespaces, ArrayObject $loaded) + { + /** + * @param string $class Class name to autoload + * @return void + */ + return static function ($class) use ($namespaces, $loaded) { + $segments = explode('\\', $class); + + if ($segments[0] === 'ZendService' && isset($segments[1])) { + $segments[0] .= '\\' . $segments[1]; + unset($segments[1]); + $segments = array_values($segments); + } + + $i = 0; + $check = ''; + + // We are checking segments of the namespace to match quicker + while (isset($segments[$i + 1], $namespaces[$check . $segments[$i] . '\\'])) { + $check .= $segments[$i] . '\\'; + ++$i; + } + + if ($check === '') { + return; + } + + $alias = $namespaces[$check] + . strtr(substr($class, strlen($check)), [ + 'Apigility' => 'ApiTools', + 'Expressive' => 'Mezzio', + 'Zend' => 'Laminas', + 'AbstractZendServer' => 'AbstractZendServer', + 'ZendServerDisk' => 'ZendServerDisk', + 'ZendServerShm' => 'ZendServerShm', + 'ZendMonitor' => 'ZendMonitor', + ]); + + $loaded[$alias] = true; + if (class_exists($alias) || interface_exists($alias) || trait_exists($alias)) { + class_alias($alias, $class); + } + }; + } +} diff --git a/lib/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php b/lib/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php new file mode 100644 index 000000000..bac7b9747 --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php @@ -0,0 +1,434 @@ + true, + 'factories' => true, + 'invokables' => true, + 'services' => true, + ]; + + /** @var array String keys => string values */ + private $exactReplacements = [ + 'zend-expressive' => 'mezzio', + 'zf-apigility' => 'api-tools', + ]; + + /** @var Replacements */ + private $replacements; + + /** @var callable[] */ + private $rulesets; + + public function __construct() + { + $this->replacements = new Replacements(); + + /* Define the rulesets for replacements. + * + * Each ruleset has the following signature: + * + * @param mixed $value + * @param string[] $keys Full nested key hierarchy leading to the value + * @return null|callable + * + * If no match is made, a null is returned, allowing it to fallback to + * the next ruleset in the list. If a match is made, a callback is returned, + * and that will be used to perform the replacement on the value. + * + * The callback should have the following signature: + * + * @param mixed $value + * @param string[] $keys + * @return mixed The transformed value + */ + $this->rulesets = [ + // Exact values + function ($value) { + return is_string($value) && isset($this->exactReplacements[$value]) + ? [$this, 'replaceExactValue'] + : null; + }, + + // Router (MVC applications) + // We do not want to rewrite these. + function ($value, array $keys) { + $key = array_pop($keys); + // Only worried about a top-level "router" key. + return $key === 'router' && count($keys) === 0 && is_array($value) + ? [$this, 'noopReplacement'] + : null; + }, + + // service- and pluginmanager handling + function ($value) { + return is_array($value) && array_intersect_key(self::SERVICE_MANAGER_KEYS_OF_INTEREST, $value) !== [] + ? [$this, 'replaceDependencyConfiguration'] + : null; + }, + + // Array values + function ($value, array $keys) { + return 0 !== count($keys) && is_array($value) + ? [$this, '__invoke'] + : null; + }, + ]; + } + + /** + * @param string[] $keys Hierarchy of keys, for determining location in + * nested configuration. + * @return array + */ + public function __invoke(array $config, array $keys = []) + { + $rewritten = []; + + foreach ($config as $key => $value) { + // Determine new key from replacements + $newKey = is_string($key) ? $this->replace($key, $keys) : $key; + + // Keep original values with original key, if the key has changed, but only at the top-level. + if (empty($keys) && $newKey !== $key) { + $rewritten[$key] = $value; + } + + // Perform value replacements, if any + $newValue = $this->replace($value, $keys, $newKey); + + // Key does not already exist and/or is not an array value + if (! array_key_exists($newKey, $rewritten) || ! is_array($rewritten[$newKey])) { + // Do not overwrite existing values with null values + $rewritten[$newKey] = array_key_exists($newKey, $rewritten) && null === $newValue + ? $rewritten[$newKey] + : $newValue; + continue; + } + + // New value is null; nothing to do. + if (null === $newValue) { + continue; + } + + // Key already exists as an array value, but $value is not an array + if (! is_array($newValue)) { + $rewritten[$newKey][] = $newValue; + continue; + } + + // Key already exists as an array value, and $value is also an array + $rewritten[$newKey] = static::merge($rewritten[$newKey], $newValue); + } + + return $rewritten; + } + + /** + * Perform substitutions as needed on an individual value. + * + * The $key is provided to allow fine-grained selection of rewrite rules. + * + * @param mixed $value + * @param string[] $keys Key hierarchy + * @param null|int|string $key + * @return mixed + */ + private function replace($value, array $keys, $key = null) + { + // Add new key to the list of keys. + // We do not need to remove it later, as we are working on a copy of the array. + array_push($keys, $key); + + // Identify rewrite strategy and perform replacements + $rewriteRule = $this->replacementRuleMatch($value, $keys); + return $rewriteRule($value, $keys); + } + + /** + * Merge two arrays together. + * + * If an integer key exists in both arrays, the value from the second array + * will be appended to the first array. If both values are arrays, they are + * merged together, else the value of the second array overwrites the one + * of the first array. + * + * Based on zend-stdlib Zend\Stdlib\ArrayUtils::merge + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * + * @return array + */ + public static function merge(array $a, array $b) + { + foreach ($b as $key => $value) { + if (! isset($a[$key]) && ! array_key_exists($key, $a)) { + $a[$key] = $value; + continue; + } + + if (null === $value && array_key_exists($key, $a)) { + // Leave as-is if value from $b is null + continue; + } + + if (is_int($key)) { + $a[] = $value; + continue; + } + + if (is_array($value) && is_array($a[$key])) { + $a[$key] = static::merge($a[$key], $value); + continue; + } + + $a[$key] = $value; + } + + return $a; + } + + /** + * @param mixed $value + * @param null|int|string $key + * @return callable Callable to invoke with value + */ + private function replacementRuleMatch($value, $key = null) + { + foreach ($this->rulesets as $ruleset) { + $result = $ruleset($value, $key); + if (is_callable($result)) { + return $result; + } + } + return [$this, 'fallbackReplacement']; + } + + /** + * Replace a value using the translation table, if the value is a string. + * + * @param mixed $value + * @return mixed + */ + private function fallbackReplacement($value) + { + return is_string($value) + ? $this->replacements->replace($value) + : $value; + } + + /** + * Replace a value matched exactly. + * + * @param mixed $value + * @return mixed + */ + private function replaceExactValue($value) + { + return $this->exactReplacements[$value]; + } + + private function replaceDependencyConfiguration(array $config) + { + $aliases = isset($config['aliases']) && is_array($config['aliases']) + ? $this->replaceDependencyAliases($config['aliases']) + : []; + + if ($aliases) { + $config['aliases'] = $aliases; + } + + $config = $this->replaceDependencyInvokables($config); + $config = $this->replaceDependencyFactories($config); + $config = $this->replaceDependencyServices($config); + + $keys = self::SERVICE_MANAGER_KEYS_OF_INTEREST; + foreach ($config as $key => $data) { + if (isset($keys[$key])) { + continue; + } + + $config[$key] = is_array($data) ? $this->__invoke($data, [$key]) : $data; + } + + return $config; + } + + /** + * Rewrite dependency aliases array + * + * In this case, we want to keep the alias as-is, but rewrite the target. + * + * We need also provide an additional alias if the alias key is a legacy class. + * + * @return array + */ + private function replaceDependencyAliases(array $aliases) + { + foreach ($aliases as $alias => $target) { + if (! is_string($alias) || ! is_string($target)) { + continue; + } + + $newTarget = $this->replacements->replace($target); + $newAlias = $this->replacements->replace($alias); + + $notIn = [$newTarget]; + $name = $newTarget; + while (isset($aliases[$name])) { + $notIn[] = $aliases[$name]; + $name = $aliases[$name]; + } + + if ($newAlias === $alias && ! in_array($alias, $notIn, true)) { + $aliases[$alias] = $newTarget; + continue; + } + + if (isset($aliases[$newAlias])) { + continue; + } + + if (! in_array($newAlias, $notIn, true)) { + $aliases[$alias] = $newAlias; + $aliases[$newAlias] = $newTarget; + } + } + + return $aliases; + } + + /** + * Rewrite dependency invokables array + * + * In this case, we want to keep the alias as-is, but rewrite the target. + * + * We need also provide an additional alias if invokable is defined with + * an alias which is a legacy class. + * + * @return array + */ + private function replaceDependencyInvokables(array $config) + { + if (empty($config['invokables']) || ! is_array($config['invokables'])) { + return $config; + } + + foreach ($config['invokables'] as $alias => $target) { + if (! is_string($alias)) { + continue; + } + + $newTarget = $this->replacements->replace($target); + $newAlias = $this->replacements->replace($alias); + + if ($alias === $target || isset($config['aliases'][$newAlias])) { + $config['invokables'][$alias] = $newTarget; + continue; + } + + $config['invokables'][$newAlias] = $newTarget; + + if ($newAlias === $alias) { + continue; + } + + $config['aliases'][$alias] = $newAlias; + + unset($config['invokables'][$alias]); + } + + return $config; + } + + /** + * @param mixed $value + * @return mixed Returns $value verbatim. + */ + private function noopReplacement($value) + { + return $value; + } + + private function replaceDependencyFactories(array $config) + { + if (empty($config['factories']) || ! is_array($config['factories'])) { + return $config; + } + + foreach ($config['factories'] as $service => $factory) { + if (! is_string($service)) { + continue; + } + + $replacedService = $this->replacements->replace($service); + $factory = is_string($factory) ? $this->replacements->replace($factory) : $factory; + $config['factories'][$replacedService] = $factory; + + if ($replacedService === $service) { + continue; + } + + unset($config['factories'][$service]); + if (isset($config['aliases'][$service])) { + continue; + } + + $config['aliases'][$service] = $replacedService; + } + + return $config; + } + + private function replaceDependencyServices(array $config) + { + if (empty($config['services']) || ! is_array($config['services'])) { + return $config; + } + + foreach ($config['services'] as $service => $serviceInstance) { + if (! is_string($service)) { + continue; + } + + $replacedService = $this->replacements->replace($service); + $serviceInstance = is_array($serviceInstance) ? $this->__invoke($serviceInstance) : $serviceInstance; + + $config['services'][$replacedService] = $serviceInstance; + + if ($service === $replacedService) { + continue; + } + + unset($config['services'][$service]); + + if (isset($config['aliases'][$service])) { + continue; + } + + $config['aliases'][$service] = $replacedService; + } + + return $config; + } +} diff --git a/lib/laminas/laminas-zendframework-bridge/src/Module.php b/lib/laminas/laminas-zendframework-bridge/src/Module.php new file mode 100644 index 000000000..d10cb43dd --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/src/Module.php @@ -0,0 +1,54 @@ +getEventManager() + ->attach('mergeConfig', [$this, 'onMergeConfig']); + } + + /** + * Perform substitutions in the merged configuration. + * + * Rewrites keys and values matching known ZF classes, namespaces, and + * configuration keys to their Laminas equivalents. + * + * Type-hinting deliberately omitted to allow unit testing + * without dependencies on packages that do not exist yet. + * + * @param ModuleEvent $event + */ + public function onMergeConfig($event) + { + /** @var ConfigMergerInterface */ + $configMerger = $event->getConfigListener(); + $processor = new ConfigPostProcessor(); + $configMerger->setMergedConfig( + $processor( + $configMerger->getMergedConfig($returnAsObject = false) + ) + ); + } +} diff --git a/lib/laminas/laminas-zendframework-bridge/src/Replacements.php b/lib/laminas/laminas-zendframework-bridge/src/Replacements.php new file mode 100644 index 000000000..ca445c01f --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/src/Replacements.php @@ -0,0 +1,46 @@ +replacements = array_merge( + require __DIR__ . '/../config/replacements.php', + $additionalReplacements + ); + + // Provide multiple variants of strings containing namespace separators + foreach ($this->replacements as $original => $replacement) { + if (false === strpos($original, '\\')) { + continue; + } + $this->replacements[str_replace('\\', '\\\\', $original)] = str_replace('\\', '\\\\', $replacement); + $this->replacements[str_replace('\\', '\\\\\\\\', $original)] = str_replace('\\', '\\\\\\\\', $replacement); + } + } + + /** + * @param string $value + * @return string + */ + public function replace($value) + { + return strtr($value, $this->replacements); + } +} diff --git a/lib/laminas/laminas-zendframework-bridge/src/RewriteRules.php b/lib/laminas/laminas-zendframework-bridge/src/RewriteRules.php new file mode 100644 index 000000000..8dc999f45 --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/src/RewriteRules.php @@ -0,0 +1,79 @@ + 'Mezzio\\ProblemDetails\\', + 'Zend\\Expressive\\' => 'Mezzio\\', + + // Laminas + 'Zend\\' => 'Laminas\\', + 'ZF\\ComposerAutoloading\\' => 'Laminas\\ComposerAutoloading\\', + 'ZF\\DevelopmentMode\\' => 'Laminas\\DevelopmentMode\\', + + // Apigility + 'ZF\\Apigility\\' => 'Laminas\\ApiTools\\', + 'ZF\\' => 'Laminas\\ApiTools\\', + + // ZendXml, API wrappers, zend-http OAuth support, zend-diagnostics, ZendDeveloperTools + 'ZendXml\\' => 'Laminas\\Xml\\', + 'ZendOAuth\\' => 'Laminas\\OAuth\\', + 'ZendDiagnostics\\' => 'Laminas\\Diagnostics\\', + 'ZendService\\ReCaptcha\\' => 'Laminas\\ReCaptcha\\', + 'ZendService\\Twitter\\' => 'Laminas\\Twitter\\', + 'ZendDeveloperTools\\' => 'Laminas\\DeveloperTools\\', + ]; + } + + /** + * @return array + */ + public static function namespaceReverse() + { + return [ + // ZendXml, ZendOAuth, ZendDiagnostics, ZendDeveloperTools + 'Laminas\\Xml\\' => 'ZendXml\\', + 'Laminas\\OAuth\\' => 'ZendOAuth\\', + 'Laminas\\Diagnostics\\' => 'ZendDiagnostics\\', + 'Laminas\\DeveloperTools\\' => 'ZendDeveloperTools\\', + + // Zend Service + 'Laminas\\ReCaptcha\\' => 'ZendService\\ReCaptcha\\', + 'Laminas\\Twitter\\' => 'ZendService\\Twitter\\', + + // Zend + 'Laminas\\' => 'Zend\\', + + // Expressive + 'Mezzio\\ProblemDetails\\' => 'Zend\\ProblemDetails\\', + 'Mezzio\\' => 'Zend\\Expressive\\', + + // Laminas to ZfCampus + 'Laminas\\ComposerAutoloading\\' => 'ZF\\ComposerAutoloading\\', + 'Laminas\\DevelopmentMode\\' => 'ZF\\DevelopmentMode\\', + + // Apigility + 'Laminas\\ApiTools\\Admin\\' => 'ZF\\Apigility\\Admin\\', + 'Laminas\\ApiTools\\Doctrine\\' => 'ZF\\Apigility\\Doctrine\\', + 'Laminas\\ApiTools\\Documentation\\' => 'ZF\\Apigility\\Documentation\\', + 'Laminas\\ApiTools\\Example\\' => 'ZF\\Apigility\\Example\\', + 'Laminas\\ApiTools\\Provider\\' => 'ZF\\Apigility\\Provider\\', + 'Laminas\\ApiTools\\Welcome\\' => 'ZF\\Apiglity\\Welcome\\', + 'Laminas\\ApiTools\\' => 'ZF\\', + ]; + } +} diff --git a/lib/laminas/laminas-zendframework-bridge/src/autoload.php b/lib/laminas/laminas-zendframework-bridge/src/autoload.php new file mode 100644 index 000000000..9f2f2adf8 --- /dev/null +++ b/lib/laminas/laminas-zendframework-bridge/src/autoload.php @@ -0,0 +1,9 @@ + + +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/lib/league/oauth2-client/README.md b/lib/league/oauth2-client/README.md new file mode 100644 index 000000000..f35d53e8a --- /dev/null +++ b/lib/league/oauth2-client/README.md @@ -0,0 +1,58 @@ +# OAuth 2.0 Client + +This package provides a base for integrating with [OAuth 2.0](http://oauth.net/2/) service providers. + +[![Gitter Chat](https://img.shields.io/badge/gitter-join_chat-brightgreen.svg?style=flat-square)](https://gitter.im/thephpleague/oauth2-client) +[![Source Code](https://img.shields.io/badge/source-thephpleague/oauth2--client-blue.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client) +[![Latest Version](https://img.shields.io/github/release/thephpleague/oauth2-client.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) +[![Build Status](https://img.shields.io/github/workflow/status/thephpleague/oauth2-client/CI?label=CI&logo=github&style=flat-square)](https://github.com/thephpleague/oauth2-client/actions?query=workflow%3ACI) +[![Codecov Code Coverage](https://img.shields.io/codecov/c/gh/thephpleague/oauth2-client?label=codecov&logo=codecov&style=flat-square)](https://codecov.io/gh/thephpleague/oauth2-client) +[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-client.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-client) + +--- + +The OAuth 2.0 login flow, seen commonly around the web in the form of "Connect with Facebook/Google/etc." buttons, is a common integration added to web applications, but it can be tricky and tedious to do right. To help, we've created the `league/oauth2-client` package, which provides a base for integrating with various OAuth 2.0 providers, without overburdening your application with the concerns of [RFC 6749](http://tools.ietf.org/html/rfc6749). + +This OAuth 2.0 client library will work with any OAuth 2.0 provider that conforms to the OAuth 2.0 Authorization Framework. Out-of-the-box, we provide a `GenericProvider` class to connect to any service provider that uses [Bearer tokens](http://tools.ietf.org/html/rfc6750). See our [basic usage guide](https://oauth2-client.thephpleague.com/usage/) for examples using `GenericProvider`. + +Many service providers provide additional functionality above and beyond the OAuth 2.0 specification. For this reason, you may extend and wrap this library to support additional behavior. There are already many [official](https://oauth2-client.thephpleague.com/providers/league/) and [third-party](https://oauth2-client.thephpleague.com/providers/thirdparty/) provider clients available (e.g., Facebook, GitHub, Google, Instagram, LinkedIn, etc.). If your provider isn't in the list, feel free to add it. + +This package is compliant with [PSR-1][], [PSR-2][], [PSR-4][], and [PSR-7][]. If you notice compliance oversights, please send a patch via pull request. If you're interested in contributing to this library, please take a look at our [contributing guidelines](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md). + +## Requirements + +We support the following versions of PHP: + +* PHP 8.1 +* PHP 8.0 +* PHP 7.4 +* PHP 7.3 +* PHP 7.2 +* PHP 7.1 +* PHP 7.0 +* PHP 5.6 + +## Provider Clients + +We provide a list of [official PHP League provider clients](https://oauth2-client.thephpleague.com/providers/league/), as well as [third-party provider clients](https://oauth2-client.thephpleague.com/providers/thirdparty/). + +To build your own provider client, please refer to "[Implementing a Provider Client](https://oauth2-client.thephpleague.com/providers/implementing/)." + +## Usage + +For usage and code examples, check out our [basic usage guide](https://oauth2-client.thephpleague.com/usage/). + +## Contributing + +Please see [our contributing guidelines](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md) for details. + +## License + +The MIT License (MIT). Please see [LICENSE](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) for more information. + + +[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md +[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md +[PSR-7]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md diff --git a/lib/league/oauth2-client/composer.json b/lib/league/oauth2-client/composer.json new file mode 100644 index 000000000..59201f48f --- /dev/null +++ b/lib/league/oauth2-client/composer.json @@ -0,0 +1,58 @@ +{ + "name": "league/oauth2-client", + "description": "OAuth 2.0 Client Library", + "license": "MIT", + "config": { + "sort-packages": true + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", + "squizlabs/php_codesniffer": "^2.3 || ^3.0" + }, + "keywords": [ + "oauth", + "oauth2", + "authorization", + "authentication", + "idp", + "identity", + "sso", + "single sign on" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + + ], + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "test/src/" + } + }, + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + } +} diff --git a/lib/league/oauth2-client/src/Grant/AbstractGrant.php b/lib/league/oauth2-client/src/Grant/AbstractGrant.php new file mode 100644 index 000000000..2c0244ba3 --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/AbstractGrant.php @@ -0,0 +1,80 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +use League\OAuth2\Client\Tool\RequiredParameterTrait; + +/** + * Represents a type of authorization grant. + * + * An authorization grant is a credential representing the resource + * owner's authorization (to access its protected resources) used by the + * client to obtain an access token. OAuth 2.0 defines four + * grant types -- authorization code, implicit, resource owner password + * credentials, and client credentials -- as well as an extensibility + * mechanism for defining additional types. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3 Authorization Grant (RFC 6749, §1.3) + */ +abstract class AbstractGrant +{ + use RequiredParameterTrait; + + /** + * Returns the name of this grant, eg. 'grant_name', which is used as the + * grant type when encoding URL query parameters. + * + * @return string + */ + abstract protected function getName(); + + /** + * Returns a list of all required request parameters. + * + * @return array + */ + abstract protected function getRequiredRequestParameters(); + + /** + * Returns this grant's name as its string representation. This allows for + * string interpolation when building URL query parameters. + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Prepares an access token request's parameters by checking that all + * required parameters are set, then merging with any given defaults. + * + * @param array $defaults + * @param array $options + * @return array + */ + public function prepareRequestParameters(array $defaults, array $options) + { + $defaults['grant_type'] = $this->getName(); + + $required = $this->getRequiredRequestParameters(); + $provided = array_merge($defaults, $options); + + $this->checkRequiredParameters($required, $provided); + + return $provided; + } +} diff --git a/lib/league/oauth2-client/src/Grant/AuthorizationCode.php b/lib/league/oauth2-client/src/Grant/AuthorizationCode.php new file mode 100644 index 000000000..c49460c02 --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/AuthorizationCode.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents an authorization code grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.1 Authorization Code (RFC 6749, §1.3.1) + */ +class AuthorizationCode extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'authorization_code'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'code', + ]; + } +} diff --git a/lib/league/oauth2-client/src/Grant/ClientCredentials.php b/lib/league/oauth2-client/src/Grant/ClientCredentials.php new file mode 100644 index 000000000..dc78c4fda --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/ClientCredentials.php @@ -0,0 +1,39 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a client credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.4 Client Credentials (RFC 6749, §1.3.4) + */ +class ClientCredentials extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'client_credentials'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return []; + } +} diff --git a/lib/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php b/lib/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php new file mode 100644 index 000000000..c3c4e677b --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php @@ -0,0 +1,26 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant\Exception; + +use InvalidArgumentException; + +/** + * Exception thrown if the grant does not extend from AbstractGrant. + * + * @see League\OAuth2\Client\Grant\AbstractGrant + */ +class InvalidGrantException extends InvalidArgumentException +{ +} diff --git a/lib/league/oauth2-client/src/Grant/GrantFactory.php b/lib/league/oauth2-client/src/Grant/GrantFactory.php new file mode 100644 index 000000000..71990e83d --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/GrantFactory.php @@ -0,0 +1,104 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +use League\OAuth2\Client\Grant\Exception\InvalidGrantException; + +/** + * Represents a factory used when retrieving an authorization grant type. + */ +class GrantFactory +{ + /** + * @var array + */ + protected $registry = []; + + /** + * Defines a grant singleton in the registry. + * + * @param string $name + * @param AbstractGrant $grant + * @return self + */ + public function setGrant($name, AbstractGrant $grant) + { + $this->registry[$name] = $grant; + + return $this; + } + + /** + * Returns a grant singleton by name. + * + * If the grant has not be registered, a default grant will be loaded. + * + * @param string $name + * @return AbstractGrant + */ + public function getGrant($name) + { + if (empty($this->registry[$name])) { + $this->registerDefaultGrant($name); + } + + return $this->registry[$name]; + } + + /** + * Registers a default grant singleton by name. + * + * @param string $name + * @return self + */ + protected function registerDefaultGrant($name) + { + // PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode' + $class = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name))); + $class = 'League\\OAuth2\\Client\\Grant\\' . $class; + + $this->checkGrant($class); + + return $this->setGrant($name, new $class); + } + + /** + * Determines if a variable is a valid grant. + * + * @param mixed $class + * @return boolean + */ + public function isGrant($class) + { + return is_subclass_of($class, AbstractGrant::class); + } + + /** + * Checks if a variable is a valid grant. + * + * @throws InvalidGrantException + * @param mixed $class + * @return void + */ + public function checkGrant($class) + { + if (!$this->isGrant($class)) { + throw new InvalidGrantException(sprintf( + 'Grant "%s" must extend AbstractGrant', + is_object($class) ? get_class($class) : $class + )); + } + } +} diff --git a/lib/league/oauth2-client/src/Grant/Password.php b/lib/league/oauth2-client/src/Grant/Password.php new file mode 100644 index 000000000..6543b2ebd --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/Password.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a resource owner password credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.3 Resource Owner Password Credentials (RFC 6749, §1.3.3) + */ +class Password extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'password'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'username', + 'password', + ]; + } +} diff --git a/lib/league/oauth2-client/src/Grant/RefreshToken.php b/lib/league/oauth2-client/src/Grant/RefreshToken.php new file mode 100644 index 000000000..819218230 --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/RefreshToken.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a refresh token grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-6 Refreshing an Access Token (RFC 6749, §6) + */ +class RefreshToken extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'refresh_token'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'refresh_token', + ]; + } +} diff --git a/lib/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php b/lib/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php new file mode 100644 index 000000000..3da406568 --- /dev/null +++ b/lib/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\OptionProvider; + +use InvalidArgumentException; + +/** + * Add http basic auth into access token request options + * @link https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ +class HttpBasicAuthOptionProvider extends PostAuthOptionProvider +{ + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + if (empty($params['client_id']) || empty($params['client_secret'])) { + throw new InvalidArgumentException('clientId and clientSecret are required for http basic auth'); + } + + $encodedCredentials = base64_encode(sprintf('%s:%s', $params['client_id'], $params['client_secret'])); + unset($params['client_id'], $params['client_secret']); + + $options = parent::getAccessTokenOptions($method, $params); + $options['headers']['Authorization'] = 'Basic ' . $encodedCredentials; + + return $options; + } +} diff --git a/lib/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php b/lib/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php new file mode 100644 index 000000000..1126d25aa --- /dev/null +++ b/lib/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php @@ -0,0 +1,30 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\OptionProvider; + +/** + * Interface for access token options provider + */ +interface OptionProviderInterface +{ + /** + * Builds request options used for requesting an access token. + * + * @param string $method + * @param array $params + * @return array + */ + public function getAccessTokenOptions($method, array $params); +} diff --git a/lib/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php b/lib/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php new file mode 100644 index 000000000..12d920ecf --- /dev/null +++ b/lib/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php @@ -0,0 +1,51 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\OptionProvider; + +use League\OAuth2\Client\Provider\AbstractProvider; +use League\OAuth2\Client\Tool\QueryBuilderTrait; + +/** + * Provide options for access token + */ +class PostAuthOptionProvider implements OptionProviderInterface +{ + use QueryBuilderTrait; + + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + $options = ['headers' => ['content-type' => 'application/x-www-form-urlencoded']]; + + if ($method === AbstractProvider::METHOD_POST) { + $options['body'] = $this->getAccessTokenBody($params); + } + + return $options; + } + + /** + * Returns the request body for requesting an access token. + * + * @param array $params + * @return string + */ + protected function getAccessTokenBody(array $params) + { + return $this->buildQueryString($params); + } +} diff --git a/lib/league/oauth2-client/src/Provider/AbstractProvider.php b/lib/league/oauth2-client/src/Provider/AbstractProvider.php new file mode 100644 index 000000000..d1679998c --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/AbstractProvider.php @@ -0,0 +1,843 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +use GuzzleHttp\Client as HttpClient; +use GuzzleHttp\ClientInterface as HttpClientInterface; +use GuzzleHttp\Exception\BadResponseException; +use League\OAuth2\Client\Grant\AbstractGrant; +use League\OAuth2\Client\Grant\GrantFactory; +use League\OAuth2\Client\OptionProvider\OptionProviderInterface; +use League\OAuth2\Client\OptionProvider\PostAuthOptionProvider; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Token\AccessTokenInterface; +use League\OAuth2\Client\Tool\ArrayAccessorTrait; +use League\OAuth2\Client\Tool\GuardedPropertyTrait; +use League\OAuth2\Client\Tool\QueryBuilderTrait; +use League\OAuth2\Client\Tool\RequestFactory; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use UnexpectedValueException; + +/** + * Represents a service provider (authorization server). + * + * @link http://tools.ietf.org/html/rfc6749#section-1.1 Roles (RFC 6749, §1.1) + */ +abstract class AbstractProvider +{ + use ArrayAccessorTrait; + use GuardedPropertyTrait; + use QueryBuilderTrait; + + /** + * @var string Key used in a token response to identify the resource owner. + */ + const ACCESS_TOKEN_RESOURCE_OWNER_ID = null; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_GET = 'GET'; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_POST = 'POST'; + + /** + * @var string + */ + protected $clientId; + + /** + * @var string + */ + protected $clientSecret; + + /** + * @var string + */ + protected $redirectUri; + + /** + * @var string + */ + protected $state; + + /** + * @var GrantFactory + */ + protected $grantFactory; + + /** + * @var RequestFactory + */ + protected $requestFactory; + + /** + * @var HttpClientInterface + */ + protected $httpClient; + + /** + * @var OptionProviderInterface + */ + protected $optionProvider; + + /** + * Constructs an OAuth 2.0 service provider. + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @param array $collaborators An array of collaborators that may be used to + * override this provider's default behavior. Collaborators include + * `grantFactory`, `requestFactory`, and `httpClient`. + * Individual providers may introduce more collaborators, as needed. + */ + public function __construct(array $options = [], array $collaborators = []) + { + // We'll let the GuardedPropertyTrait handle mass assignment of incoming + // options, skipping any blacklisted properties defined in the provider + $this->fillProperties($options); + + if (empty($collaborators['grantFactory'])) { + $collaborators['grantFactory'] = new GrantFactory(); + } + $this->setGrantFactory($collaborators['grantFactory']); + + if (empty($collaborators['requestFactory'])) { + $collaborators['requestFactory'] = new RequestFactory(); + } + $this->setRequestFactory($collaborators['requestFactory']); + + if (empty($collaborators['httpClient'])) { + $client_options = $this->getAllowedClientOptions($options); + + $collaborators['httpClient'] = new HttpClient( + array_intersect_key($options, array_flip($client_options)) + ); + } + $this->setHttpClient($collaborators['httpClient']); + + if (empty($collaborators['optionProvider'])) { + $collaborators['optionProvider'] = new PostAuthOptionProvider(); + } + $this->setOptionProvider($collaborators['optionProvider']); + } + + /** + * Returns the list of options that can be passed to the HttpClient + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @return array The options to pass to the HttpClient constructor + */ + protected function getAllowedClientOptions(array $options) + { + $client_options = ['timeout', 'proxy']; + + // Only allow turning off ssl verification if it's for a proxy + if (!empty($options['proxy'])) { + $client_options[] = 'verify'; + } + + return $client_options; + } + + /** + * Sets the grant factory instance. + * + * @param GrantFactory $factory + * @return self + */ + public function setGrantFactory(GrantFactory $factory) + { + $this->grantFactory = $factory; + + return $this; + } + + /** + * Returns the current grant factory instance. + * + * @return GrantFactory + */ + public function getGrantFactory() + { + return $this->grantFactory; + } + + /** + * Sets the request factory instance. + * + * @param RequestFactory $factory + * @return self + */ + public function setRequestFactory(RequestFactory $factory) + { + $this->requestFactory = $factory; + + return $this; + } + + /** + * Returns the request factory instance. + * + * @return RequestFactory + */ + public function getRequestFactory() + { + return $this->requestFactory; + } + + /** + * Sets the HTTP client instance. + * + * @param HttpClientInterface $client + * @return self + */ + public function setHttpClient(HttpClientInterface $client) + { + $this->httpClient = $client; + + return $this; + } + + /** + * Returns the HTTP client instance. + * + * @return HttpClientInterface + */ + public function getHttpClient() + { + return $this->httpClient; + } + + /** + * Sets the option provider instance. + * + * @param OptionProviderInterface $provider + * @return self + */ + public function setOptionProvider(OptionProviderInterface $provider) + { + $this->optionProvider = $provider; + + return $this; + } + + /** + * Returns the option provider instance. + * + * @return OptionProviderInterface + */ + public function getOptionProvider() + { + return $this->optionProvider; + } + + /** + * Returns the current value of the state parameter. + * + * This can be accessed by the redirect handler during authorization. + * + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * Returns the base URL for authorizing a client. + * + * Eg. https://oauth.service.com/authorize + * + * @return string + */ + abstract public function getBaseAuthorizationUrl(); + + /** + * Returns the base URL for requesting an access token. + * + * Eg. https://oauth.service.com/token + * + * @param array $params + * @return string + */ + abstract public function getBaseAccessTokenUrl(array $params); + + /** + * Returns the URL for requesting the resource owner's details. + * + * @param AccessToken $token + * @return string + */ + abstract public function getResourceOwnerDetailsUrl(AccessToken $token); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected function getRandomState($length = 32) + { + // Converting bytes to hex will always double length. Hence, we can reduce + // the amount of bytes by half to produce the correct length. + return bin2hex(random_bytes($length / 2)); + } + + /** + * Returns the default scopes used by this provider. + * + * This should only be the scopes that are required to request the details + * of the resource owner, rather than all the available scopes. + * + * @return array + */ + abstract protected function getDefaultScopes(); + + /** + * Returns the string that should be used to separate scopes when building + * the URL for requesting an access token. + * + * @return string Scope separator, defaults to ',' + */ + protected function getScopeSeparator() + { + return ','; + } + + /** + * Returns authorization parameters based on provided options. + * + * @param array $options + * @return array Authorization parameters + */ + protected function getAuthorizationParameters(array $options) + { + if (empty($options['state'])) { + $options['state'] = $this->getRandomState(); + } + + if (empty($options['scope'])) { + $options['scope'] = $this->getDefaultScopes(); + } + + $options += [ + 'response_type' => 'code', + 'approval_prompt' => 'auto' + ]; + + if (is_array($options['scope'])) { + $separator = $this->getScopeSeparator(); + $options['scope'] = implode($separator, $options['scope']); + } + + // Store the state as it may need to be accessed later on. + $this->state = $options['state']; + + // Business code layer might set a different redirect_uri parameter + // depending on the context, leave it as-is + if (!isset($options['redirect_uri'])) { + $options['redirect_uri'] = $this->redirectUri; + } + + $options['client_id'] = $this->clientId; + + return $options; + } + + /** + * Builds the authorization URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAuthorizationQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Builds the authorization URL. + * + * @param array $options + * @return string Authorization URL + */ + public function getAuthorizationUrl(array $options = []) + { + $base = $this->getBaseAuthorizationUrl(); + $params = $this->getAuthorizationParameters($options); + $query = $this->getAuthorizationQuery($params); + + return $this->appendQuery($base, $query); + } + + /** + * Redirects the client for authorization. + * + * @param array $options + * @param callable|null $redirectHandler + * @return mixed + */ + public function authorize( + array $options = [], + callable $redirectHandler = null + ) { + $url = $this->getAuthorizationUrl($options); + if ($redirectHandler) { + return $redirectHandler($url, $this); + } + + // @codeCoverageIgnoreStart + header('Location: ' . $url); + exit; + // @codeCoverageIgnoreEnd + } + + /** + * Appends a query string to a URL. + * + * @param string $url The URL to append the query to + * @param string $query The HTTP query string + * @return string The resulting URL + */ + protected function appendQuery($url, $query) + { + $query = trim($query, '?&'); + + if ($query) { + $glue = strstr($url, '?') === false ? '?' : '&'; + return $url . $glue . $query; + } + + return $url; + } + + /** + * Returns the method to use when requesting an access token. + * + * @return string HTTP method + */ + protected function getAccessTokenMethod() + { + return self::METHOD_POST; + } + + /** + * Returns the key used in the access token response to identify the resource owner. + * + * @return string|null Resource owner identifier key + */ + protected function getAccessTokenResourceOwnerId() + { + return static::ACCESS_TOKEN_RESOURCE_OWNER_ID; + } + + /** + * Builds the access token URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAccessTokenQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Checks that a provided grant is valid, or attempts to produce one if the + * provided grant is a string. + * + * @param AbstractGrant|string $grant + * @return AbstractGrant + */ + protected function verifyGrant($grant) + { + if (is_string($grant)) { + return $this->grantFactory->getGrant($grant); + } + + $this->grantFactory->checkGrant($grant); + return $grant; + } + + /** + * Returns the full URL to use when requesting an access token. + * + * @param array $params Query parameters + * @return string + */ + protected function getAccessTokenUrl(array $params) + { + $url = $this->getBaseAccessTokenUrl($params); + + if ($this->getAccessTokenMethod() === self::METHOD_GET) { + $query = $this->getAccessTokenQuery($params); + return $this->appendQuery($url, $query); + } + + return $url; + } + + /** + * Returns a prepared request for requesting an access token. + * + * @param array $params Query string parameters + * @return RequestInterface + */ + protected function getAccessTokenRequest(array $params) + { + $method = $this->getAccessTokenMethod(); + $url = $this->getAccessTokenUrl($params); + $options = $this->optionProvider->getAccessTokenOptions($this->getAccessTokenMethod(), $params); + + return $this->getRequest($method, $url, $options); + } + + /** + * Requests an access token using a specified grant and option set. + * + * @param mixed $grant + * @param array $options + * @throws IdentityProviderException + * @return AccessTokenInterface + */ + public function getAccessToken($grant, array $options = []) + { + $grant = $this->verifyGrant($grant); + + $params = [ + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'redirect_uri' => $this->redirectUri, + ]; + + $params = $grant->prepareRequestParameters($params, $options); + $request = $this->getAccessTokenRequest($params); + $response = $this->getParsedResponse($request); + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + $prepared = $this->prepareAccessTokenResponse($response); + $token = $this->createAccessToken($prepared, $grant); + + return $token; + } + + /** + * Returns a PSR-7 request instance that is not authenticated. + * + * @param string $method + * @param string $url + * @param array $options + * @return RequestInterface + */ + public function getRequest($method, $url, array $options = []) + { + return $this->createRequest($method, $url, null, $options); + } + + /** + * Returns an authenticated PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string $token + * @param array $options Any of "headers", "body", and "protocolVersion". + * @return RequestInterface + */ + public function getAuthenticatedRequest($method, $url, $token, array $options = []) + { + return $this->createRequest($method, $url, $token, $options); + } + + /** + * Creates a PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string|null $token + * @param array $options + * @return RequestInterface + */ + protected function createRequest($method, $url, $token, array $options) + { + $defaults = [ + 'headers' => $this->getHeaders($token), + ]; + + $options = array_merge_recursive($defaults, $options); + $factory = $this->getRequestFactory(); + + return $factory->getRequestWithOptions($method, $url, $options); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + return $this->getHttpClient()->send($request); + } + + /** + * Sends a request and returns the parsed response. + * + * @param RequestInterface $request + * @throws IdentityProviderException + * @return mixed + */ + public function getParsedResponse(RequestInterface $request) + { + try { + $response = $this->getResponse($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + $parsed = $this->parseResponse($response); + + $this->checkResponse($response, $parsed); + + return $parsed; + } + + /** + * Attempts to parse a JSON response. + * + * @param string $content JSON content from response body + * @return array Parsed JSON data + * @throws UnexpectedValueException if the content could not be parsed + */ + protected function parseJson($content) + { + $content = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new UnexpectedValueException(sprintf( + "Failed to parse JSON response: %s", + json_last_error_msg() + )); + } + + return $content; + } + + /** + * Returns the content type header of a response. + * + * @param ResponseInterface $response + * @return string Semi-colon separated join of content-type headers. + */ + protected function getContentType(ResponseInterface $response) + { + return join(';', (array) $response->getHeader('content-type')); + } + + /** + * Parses the response according to its content-type header. + * + * @throws UnexpectedValueException + * @param ResponseInterface $response + * @return array + */ + protected function parseResponse(ResponseInterface $response) + { + $content = (string) $response->getBody(); + $type = $this->getContentType($response); + + if (strpos($type, 'urlencoded') !== false) { + parse_str($content, $parsed); + return $parsed; + } + + // Attempt to parse the string as JSON regardless of content type, + // since some providers use non-standard content types. Only throw an + // exception if the JSON could not be parsed when it was expected to. + try { + return $this->parseJson($content); + } catch (UnexpectedValueException $e) { + if (strpos($type, 'json') !== false) { + throw $e; + } + + if ($response->getStatusCode() == 500) { + throw new UnexpectedValueException( + 'An OAuth server error was encountered that did not contain a JSON body', + 0, + $e + ); + } + + return $content; + } + } + + /** + * Checks a provider response for errors. + * + * @throws IdentityProviderException + * @param ResponseInterface $response + * @param array|string $data Parsed response data + * @return void + */ + abstract protected function checkResponse(ResponseInterface $response, $data); + + /** + * Prepares an parsed access token response for a grant. + * + * Custom mapping of expiration, etc should be done here. Always call the + * parent method when overloading this method. + * + * @param mixed $result + * @return array + */ + protected function prepareAccessTokenResponse(array $result) + { + if ($this->getAccessTokenResourceOwnerId() !== null) { + $result['resource_owner_id'] = $this->getValueByKey( + $result, + $this->getAccessTokenResourceOwnerId() + ); + } + return $result; + } + + /** + * Creates an access token from a response. + * + * The grant that was used to fetch the response can be used to provide + * additional context. + * + * @param array $response + * @param AbstractGrant $grant + * @return AccessTokenInterface + */ + protected function createAccessToken(array $response, AbstractGrant $grant) + { + return new AccessToken($response); + } + + /** + * Generates a resource owner object from a successful resource owner + * details request. + * + * @param array $response + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + abstract protected function createResourceOwner(array $response, AccessToken $token); + + /** + * Requests and returns the resource owner of given access token. + * + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + public function getResourceOwner(AccessToken $token) + { + $response = $this->fetchResourceOwnerDetails($token); + + return $this->createResourceOwner($response, $token); + } + + /** + * Requests resource owner details. + * + * @param AccessToken $token + * @return mixed + */ + protected function fetchResourceOwnerDetails(AccessToken $token) + { + $url = $this->getResourceOwnerDetailsUrl($token); + + $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token); + + $response = $this->getParsedResponse($request); + + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + + return $response; + } + + /** + * Returns the default headers used by this provider. + * + * Typically this is used to set 'Accept' or 'Content-Type' headers. + * + * @return array + */ + protected function getDefaultHeaders() + { + return []; + } + + /** + * Returns the authorization headers used by this provider. + * + * Typically this is "Bearer" or "MAC". For more information see: + * http://tools.ietf.org/html/rfc6749#section-7.1 + * + * No default is provided, providers must overload this method to activate + * authorization headers. + * + * @param mixed|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return []; + } + + /** + * Returns all headers used by this provider for a request. + * + * The request will be authenticated if an access token is provided. + * + * @param mixed|null $token object or string + * @return array + */ + public function getHeaders($token = null) + { + if ($token) { + return array_merge( + $this->getDefaultHeaders(), + $this->getAuthorizationHeaders($token) + ); + } + + return $this->getDefaultHeaders(); + } +} diff --git a/lib/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php b/lib/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php new file mode 100644 index 000000000..52b7e0353 --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php @@ -0,0 +1,48 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider\Exception; + +/** + * Exception thrown if the provider response contains errors. + */ +class IdentityProviderException extends \Exception +{ + /** + * @var mixed + */ + protected $response; + + /** + * @param string $message + * @param int $code + * @param array|string $response The response body + */ + public function __construct($message, $code, $response) + { + $this->response = $response; + + parent::__construct($message, $code); + } + + /** + * Returns the exception's response body. + * + * @return array|string + */ + public function getResponseBody() + { + return $this->response; + } +} diff --git a/lib/league/oauth2-client/src/Provider/GenericProvider.php b/lib/league/oauth2-client/src/Provider/GenericProvider.php new file mode 100644 index 000000000..74393ffda --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/GenericProvider.php @@ -0,0 +1,233 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +use InvalidArgumentException; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Tool\BearerAuthorizationTrait; +use Psr\Http\Message\ResponseInterface; + +/** + * Represents a generic service provider that may be used to interact with any + * OAuth 2.0 service provider, using Bearer token authentication. + */ +class GenericProvider extends AbstractProvider +{ + use BearerAuthorizationTrait; + + /** + * @var string + */ + private $urlAuthorize; + + /** + * @var string + */ + private $urlAccessToken; + + /** + * @var string + */ + private $urlResourceOwnerDetails; + + /** + * @var string + */ + private $accessTokenMethod; + + /** + * @var string + */ + private $accessTokenResourceOwnerId; + + /** + * @var array|null + */ + private $scopes = null; + + /** + * @var string + */ + private $scopeSeparator; + + /** + * @var string + */ + private $responseError = 'error'; + + /** + * @var string + */ + private $responseCode; + + /** + * @var string + */ + private $responseResourceOwnerId = 'id'; + + /** + * @param array $options + * @param array $collaborators + */ + public function __construct(array $options = [], array $collaborators = []) + { + $this->assertRequiredOptions($options); + + $possible = $this->getConfigurableOptions(); + $configured = array_intersect_key($options, array_flip($possible)); + + foreach ($configured as $key => $value) { + $this->$key = $value; + } + + // Remove all options that are only used locally + $options = array_diff_key($options, $configured); + + parent::__construct($options, $collaborators); + } + + /** + * Returns all options that can be configured. + * + * @return array + */ + protected function getConfigurableOptions() + { + return array_merge($this->getRequiredOptions(), [ + 'accessTokenMethod', + 'accessTokenResourceOwnerId', + 'scopeSeparator', + 'responseError', + 'responseCode', + 'responseResourceOwnerId', + 'scopes', + ]); + } + + /** + * Returns all options that are required. + * + * @return array + */ + protected function getRequiredOptions() + { + return [ + 'urlAuthorize', + 'urlAccessToken', + 'urlResourceOwnerDetails', + ]; + } + + /** + * Verifies that all required options have been passed. + * + * @param array $options + * @return void + * @throws InvalidArgumentException + */ + private function assertRequiredOptions(array $options) + { + $missing = array_diff_key(array_flip($this->getRequiredOptions()), $options); + + if (!empty($missing)) { + throw new InvalidArgumentException( + 'Required options not defined: ' . implode(', ', array_keys($missing)) + ); + } + } + + /** + * @inheritdoc + */ + public function getBaseAuthorizationUrl() + { + return $this->urlAuthorize; + } + + /** + * @inheritdoc + */ + public function getBaseAccessTokenUrl(array $params) + { + return $this->urlAccessToken; + } + + /** + * @inheritdoc + */ + public function getResourceOwnerDetailsUrl(AccessToken $token) + { + return $this->urlResourceOwnerDetails; + } + + /** + * @inheritdoc + */ + public function getDefaultScopes() + { + return $this->scopes; + } + + /** + * @inheritdoc + */ + protected function getAccessTokenMethod() + { + return $this->accessTokenMethod ?: parent::getAccessTokenMethod(); + } + + /** + * @inheritdoc + */ + protected function getAccessTokenResourceOwnerId() + { + return $this->accessTokenResourceOwnerId ?: parent::getAccessTokenResourceOwnerId(); + } + + /** + * @inheritdoc + */ + protected function getScopeSeparator() + { + return $this->scopeSeparator ?: parent::getScopeSeparator(); + } + + /** + * @inheritdoc + */ + protected function checkResponse(ResponseInterface $response, $data) + { + if (!empty($data[$this->responseError])) { + $error = $data[$this->responseError]; + if (!is_string($error)) { + $error = var_export($error, true); + } + $code = $this->responseCode && !empty($data[$this->responseCode])? $data[$this->responseCode] : 0; + if (!is_int($code)) { + $code = intval($code); + } + throw new IdentityProviderException($error, $code, $data); + } + } + + /** + * @inheritdoc + */ + protected function createResourceOwner(array $response, AccessToken $token) + { + return new GenericResourceOwner($response, $this->responseResourceOwnerId); + } +} diff --git a/lib/league/oauth2-client/src/Provider/GenericResourceOwner.php b/lib/league/oauth2-client/src/Provider/GenericResourceOwner.php new file mode 100644 index 000000000..f87661485 --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/GenericResourceOwner.php @@ -0,0 +1,61 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +/** + * Represents a generic resource owner for use with the GenericProvider. + */ +class GenericResourceOwner implements ResourceOwnerInterface +{ + /** + * @var array + */ + protected $response; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @param array $response + * @param string $resourceOwnerId + */ + public function __construct(array $response, $resourceOwnerId) + { + $this->response = $response; + $this->resourceOwnerId = $resourceOwnerId; + } + + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId() + { + return $this->response[$this->resourceOwnerId]; + } + + /** + * Returns the raw resource owner response. + * + * @return array + */ + public function toArray() + { + return $this->response; + } +} diff --git a/lib/league/oauth2-client/src/Provider/ResourceOwnerInterface.php b/lib/league/oauth2-client/src/Provider/ResourceOwnerInterface.php new file mode 100644 index 000000000..828442425 --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/ResourceOwnerInterface.php @@ -0,0 +1,36 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +/** + * Classes implementing `ResourceOwnerInterface` may be used to represent + * the resource owner authenticated with a service provider. + */ +interface ResourceOwnerInterface +{ + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId(); + + /** + * Return all of the owner details available as an array. + * + * @return array + */ + public function toArray(); +} diff --git a/lib/league/oauth2-client/src/Token/AccessToken.php b/lib/league/oauth2-client/src/Token/AccessToken.php new file mode 100644 index 000000000..81533c307 --- /dev/null +++ b/lib/league/oauth2-client/src/Token/AccessToken.php @@ -0,0 +1,243 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +use InvalidArgumentException; +use RuntimeException; + +/** + * Represents an access token. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, §1.4) + */ +class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInterface +{ + /** + * @var string + */ + protected $accessToken; + + /** + * @var int + */ + protected $expires; + + /** + * @var string + */ + protected $refreshToken; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @var array + */ + protected $values = []; + + /** + * @var int + */ + private static $timeNow; + + /** + * Set the time now. This should only be used for testing purposes. + * + * @param int $timeNow the time in seconds since epoch + * @return void + */ + public static function setTimeNow($timeNow) + { + self::$timeNow = $timeNow; + } + + /** + * Reset the time now if it was set for test purposes. + * + * @return void + */ + public static function resetTimeNow() + { + self::$timeNow = null; + } + + /** + * @return int + */ + public function getTimeNow() + { + return self::$timeNow ? self::$timeNow : time(); + } + + /** + * Constructs an access token. + * + * @param array $options An array of options returned by the service provider + * in the access token request. The `access_token` option is required. + * @throws InvalidArgumentException if `access_token` is not provided in `$options`. + */ + public function __construct(array $options = []) + { + if (empty($options['access_token'])) { + throw new InvalidArgumentException('Required option not passed: "access_token"'); + } + + $this->accessToken = $options['access_token']; + + if (!empty($options['resource_owner_id'])) { + $this->resourceOwnerId = $options['resource_owner_id']; + } + + if (!empty($options['refresh_token'])) { + $this->refreshToken = $options['refresh_token']; + } + + // We need to know when the token expires. Show preference to + // 'expires_in' since it is defined in RFC6749 Section 5.1. + // Defer to 'expires' if it is provided instead. + if (isset($options['expires_in'])) { + if (!is_numeric($options['expires_in'])) { + throw new \InvalidArgumentException('expires_in value must be an integer'); + } + + $this->expires = $options['expires_in'] != 0 ? $this->getTimeNow() + $options['expires_in'] : 0; + } elseif (!empty($options['expires'])) { + // Some providers supply the seconds until expiration rather than + // the exact timestamp. Take a best guess at which we received. + $expires = $options['expires']; + + if (!$this->isExpirationTimestamp($expires)) { + $expires += $this->getTimeNow(); + } + + $this->expires = $expires; + } + + // Capture any additional values that might exist in the token but are + // not part of the standard response. Vendors will sometimes pass + // additional user data this way. + $this->values = array_diff_key($options, array_flip([ + 'access_token', + 'resource_owner_id', + 'refresh_token', + 'expires_in', + 'expires', + ])); + } + + /** + * Check if a value is an expiration timestamp or second value. + * + * @param integer $value + * @return bool + */ + protected function isExpirationTimestamp($value) + { + // If the given value is larger than the original OAuth 2 draft date, + // assume that it is meant to be a (possible expired) timestamp. + $oauth2InceptionDate = 1349067600; // 2012-10-01 + return ($value > $oauth2InceptionDate); + } + + /** + * @inheritdoc + */ + public function getToken() + { + return $this->accessToken; + } + + /** + * @inheritdoc + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + + /** + * @inheritdoc + */ + public function getExpires() + { + return $this->expires; + } + + /** + * @inheritdoc + */ + public function getResourceOwnerId() + { + return $this->resourceOwnerId; + } + + /** + * @inheritdoc + */ + public function hasExpired() + { + $expires = $this->getExpires(); + + if (empty($expires)) { + throw new RuntimeException('"expires" is not set on the token'); + } + + return $expires < time(); + } + + /** + * @inheritdoc + */ + public function getValues() + { + return $this->values; + } + + /** + * @inheritdoc + */ + public function __toString() + { + return (string) $this->getToken(); + } + + /** + * @inheritdoc + */ + public function jsonSerialize() + { + $parameters = $this->values; + + if ($this->accessToken) { + $parameters['access_token'] = $this->accessToken; + } + + if ($this->refreshToken) { + $parameters['refresh_token'] = $this->refreshToken; + } + + if ($this->expires) { + $parameters['expires'] = $this->expires; + } + + if ($this->resourceOwnerId) { + $parameters['resource_owner_id'] = $this->resourceOwnerId; + } + + return $parameters; + } +} diff --git a/lib/league/oauth2-client/src/Token/AccessTokenInterface.php b/lib/league/oauth2-client/src/Token/AccessTokenInterface.php new file mode 100644 index 000000000..5fd219ffc --- /dev/null +++ b/lib/league/oauth2-client/src/Token/AccessTokenInterface.php @@ -0,0 +1,74 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +use JsonSerializable; +use ReturnTypeWillChange; +use RuntimeException; + +interface AccessTokenInterface extends JsonSerializable +{ + /** + * Returns the access token string of this instance. + * + * @return string + */ + public function getToken(); + + /** + * Returns the refresh token, if defined. + * + * @return string|null + */ + public function getRefreshToken(); + + /** + * Returns the expiration timestamp in seconds, if defined. + * + * @return integer|null + */ + public function getExpires(); + + /** + * Checks if this token has expired. + * + * @return boolean true if the token has expired, false otherwise. + * @throws RuntimeException if 'expires' is not set on the token. + */ + public function hasExpired(); + + /** + * Returns additional vendor values stored in the token. + * + * @return array + */ + public function getValues(); + + /** + * Returns a string representation of the access token + * + * @return string + */ + public function __toString(); + + /** + * Returns an array of parameters to serialize when this is serialized with + * json_encode(). + * + * @return array + */ + #[ReturnTypeWillChange] + public function jsonSerialize(); +} diff --git a/lib/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php b/lib/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php new file mode 100644 index 000000000..51e4ce413 --- /dev/null +++ b/lib/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php @@ -0,0 +1,25 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +interface ResourceOwnerAccessTokenInterface extends AccessTokenInterface +{ + /** + * Returns the resource owner identifier, if defined. + * + * @return string|null + */ + public function getResourceOwnerId(); +} diff --git a/lib/league/oauth2-client/src/Tool/ArrayAccessorTrait.php b/lib/league/oauth2-client/src/Tool/ArrayAccessorTrait.php new file mode 100644 index 000000000..a18198cf3 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/ArrayAccessorTrait.php @@ -0,0 +1,52 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides generic array navigation tools. + */ +trait ArrayAccessorTrait +{ + /** + * Returns a value by key using dot notation. + * + * @param array $data + * @param string $key + * @param mixed|null $default + * @return mixed + */ + private function getValueByKey(array $data, $key, $default = null) + { + if (!is_string($key) || empty($key) || !count($data)) { + return $default; + } + + if (strpos($key, '.') !== false) { + $keys = explode('.', $key); + + foreach ($keys as $innerKey) { + if (!is_array($data) || !array_key_exists($innerKey, $data)) { + return $default; + } + + $data = $data[$innerKey]; + } + + return $data; + } + + return array_key_exists($key, $data) ? $data[$key] : $default; + } +} diff --git a/lib/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php b/lib/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php new file mode 100644 index 000000000..081c7c861 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php @@ -0,0 +1,36 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use League\OAuth2\Client\Token\AccessTokenInterface; + +/** + * Enables `Bearer` header authorization for providers. + * + * @link http://tools.ietf.org/html/rfc6750 Bearer Token Usage (RFC 6750) + */ +trait BearerAuthorizationTrait +{ + /** + * Returns authorization headers for the 'bearer' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return ['Authorization' => 'Bearer ' . $token]; + } +} diff --git a/lib/league/oauth2-client/src/Tool/GuardedPropertyTrait.php b/lib/league/oauth2-client/src/Tool/GuardedPropertyTrait.php new file mode 100644 index 000000000..02c9ba5fb --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/GuardedPropertyTrait.php @@ -0,0 +1,70 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides support for blacklisting explicit properties from the + * mass assignment behavior. + */ +trait GuardedPropertyTrait +{ + /** + * The properties that aren't mass assignable. + * + * @var array + */ + protected $guarded = []; + + /** + * Attempts to mass assign the given options to explicitly defined properties, + * skipping over any properties that are defined in the guarded array. + * + * @param array $options + * @return mixed + */ + protected function fillProperties(array $options = []) + { + if (isset($options['guarded'])) { + unset($options['guarded']); + } + + foreach ($options as $option => $value) { + if (property_exists($this, $option) && !$this->isGuarded($option)) { + $this->{$option} = $value; + } + } + } + + /** + * Returns current guarded properties. + * + * @return array + */ + public function getGuarded() + { + return $this->guarded; + } + + /** + * Determines if the given property is guarded. + * + * @param string $property + * @return bool + */ + public function isGuarded($property) + { + return in_array($property, $this->getGuarded()); + } +} diff --git a/lib/league/oauth2-client/src/Tool/MacAuthorizationTrait.php b/lib/league/oauth2-client/src/Tool/MacAuthorizationTrait.php new file mode 100644 index 000000000..f8dcd77c5 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/MacAuthorizationTrait.php @@ -0,0 +1,83 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Token\AccessTokenInterface; + +/** + * Enables `MAC` header authorization for providers. + * + * @link http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05 Message Authentication Code (MAC) Tokens + */ +trait MacAuthorizationTrait +{ + /** + * Returns the id of this token for MAC generation. + * + * @param AccessToken $token + * @return string + */ + abstract protected function getTokenId(AccessToken $token); + + /** + * Returns the MAC signature for the current request. + * + * @param string $id + * @param integer $ts + * @param string $nonce + * @return string + */ + abstract protected function getMacSignature($id, $ts, $nonce); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + abstract protected function getRandomState($length = 32); + + /** + * Returns the authorization headers for the 'mac' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + * @codeCoverageIgnore + * + * @todo This is currently untested and provided only as an example. If you + * complete the implementation, please create a pull request for + * https://github.com/thephpleague/oauth2-client + */ + protected function getAuthorizationHeaders($token = null) + { + if ($token === null) { + return []; + } + + $ts = time(); + $id = $this->getTokenId($token); + $nonce = $this->getRandomState(16); + $mac = $this->getMacSignature($id, $ts, $nonce); + + $parts = []; + foreach (compact('id', 'ts', 'nonce', 'mac') as $key => $value) { + $parts[] = sprintf('%s="%s"', $key, $value); + } + + return ['Authorization' => 'MAC ' . implode(', ', $parts)]; + } +} diff --git a/lib/league/oauth2-client/src/Tool/ProviderRedirectTrait.php b/lib/league/oauth2-client/src/Tool/ProviderRedirectTrait.php new file mode 100644 index 000000000..f81b511f9 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/ProviderRedirectTrait.php @@ -0,0 +1,122 @@ +redirectLimit) { + $attempts++; + $response = $this->getHttpClient()->send($request, [ + 'allow_redirects' => false + ]); + + if ($this->isRedirect($response)) { + $redirectUrl = new Uri($response->getHeader('Location')[0]); + $request = $request->withUri($redirectUrl); + } else { + break; + } + } + + return $response; + } + + /** + * Returns the HTTP client instance. + * + * @return GuzzleHttp\ClientInterface + */ + abstract public function getHttpClient(); + + /** + * Retrieves current redirect limit. + * + * @return integer + */ + public function getRedirectLimit() + { + return $this->redirectLimit; + } + + /** + * Determines if a given response is a redirect. + * + * @param ResponseInterface $response + * + * @return boolean + */ + protected function isRedirect(ResponseInterface $response) + { + $statusCode = $response->getStatusCode(); + + return $statusCode > 300 && $statusCode < 400 && $response->hasHeader('Location'); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + try { + $response = $this->followRequestRedirects($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + return $response; + } + + /** + * Updates the redirect limit. + * + * @param integer $limit + * @return League\OAuth2\Client\Provider\AbstractProvider + * @throws InvalidArgumentException + */ + public function setRedirectLimit($limit) + { + if (!is_int($limit)) { + throw new InvalidArgumentException('redirectLimit must be an integer.'); + } + + if ($limit < 1) { + throw new InvalidArgumentException('redirectLimit must be greater than or equal to one.'); + } + + $this->redirectLimit = $limit; + + return $this; + } +} diff --git a/lib/league/oauth2-client/src/Tool/QueryBuilderTrait.php b/lib/league/oauth2-client/src/Tool/QueryBuilderTrait.php new file mode 100644 index 000000000..bdda3e79e --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/QueryBuilderTrait.php @@ -0,0 +1,33 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides a standard way to generate query strings. + */ +trait QueryBuilderTrait +{ + /** + * Build a query string from an array. + * + * @param array $params + * + * @return string + */ + protected function buildQueryString(array $params) + { + return http_build_query($params, '', '&', \PHP_QUERY_RFC3986); + } +} diff --git a/lib/league/oauth2-client/src/Tool/RequestFactory.php b/lib/league/oauth2-client/src/Tool/RequestFactory.php new file mode 100644 index 000000000..1af434297 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/RequestFactory.php @@ -0,0 +1,87 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use GuzzleHttp\Psr7\Request; + +/** + * Used to produce PSR-7 Request instances. + * + * @link https://github.com/guzzle/guzzle/pull/1101 + */ +class RequestFactory +{ + /** + * Creates a PSR-7 Request instance. + * + * @param null|string $method HTTP method for the request. + * @param null|string $uri URI for the request. + * @param array $headers Headers for the message. + * @param string|resource|StreamInterface $body Message body. + * @param string $version HTTP protocol version. + * + * @return Request + */ + public function getRequest( + $method, + $uri, + array $headers = [], + $body = null, + $version = '1.1' + ) { + return new Request($method, $uri, $headers, $body, $version); + } + + /** + * Parses simplified options. + * + * @param array $options Simplified options. + * + * @return array Extended options for use with getRequest. + */ + protected function parseOptions(array $options) + { + // Should match default values for getRequest + $defaults = [ + 'headers' => [], + 'body' => null, + 'version' => '1.1', + ]; + + return array_merge($defaults, $options); + } + + /** + * Creates a request using a simplified array of options. + * + * @param null|string $method + * @param null|string $uri + * @param array $options + * + * @return Request + */ + public function getRequestWithOptions($method, $uri, array $options = []) + { + $options = $this->parseOptions($options); + + return $this->getRequest( + $method, + $uri, + $options['headers'], + $options['body'], + $options['version'] + ); + } +} diff --git a/lib/league/oauth2-client/src/Tool/RequiredParameterTrait.php b/lib/league/oauth2-client/src/Tool/RequiredParameterTrait.php new file mode 100644 index 000000000..47da97717 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/RequiredParameterTrait.php @@ -0,0 +1,56 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use BadMethodCallException; + +/** + * Provides functionality to check for required parameters. + */ +trait RequiredParameterTrait +{ + /** + * Checks for a required parameter in a hash. + * + * @throws BadMethodCallException + * @param string $name + * @param array $params + * @return void + */ + private function checkRequiredParameter($name, array $params) + { + if (!isset($params[$name])) { + throw new BadMethodCallException(sprintf( + 'Required parameter not passed: "%s"', + $name + )); + } + } + + /** + * Checks for multiple required parameters in a hash. + * + * @throws InvalidArgumentException + * @param array $names + * @param array $params + * @return void + */ + private function checkRequiredParameters(array $names, array $params) + { + foreach ($names as $name) { + $this->checkRequiredParameter($name, $params); + } + } +} diff --git a/lib/league/oauth2-google/CHANGELOG.md b/lib/league/oauth2-google/CHANGELOG.md new file mode 100644 index 000000000..18b1a9a6e --- /dev/null +++ b/lib/league/oauth2-google/CHANGELOG.md @@ -0,0 +1,72 @@ +OAuth 2.0 Google Provider Changelog + +## 3.0.4 - 2021-01-27 + +### Fixed + +- Correct OAuth endpoint, #94 by @Slamdunk + +## 3.0.3 - 2020-07-24 + +### Fixed + +- Remove the `approval_prompt` from default parameters, #90 + +## 3.0.2 - 2019-11-16 + +### Fixed + +- Allow for `family_name` to be undefined in user information, #79 by @majkel89 + +## 3.0.1 - 2018-12-28 + +### Fixed + +- Correct conflict handling for prompt option, #69 by @mxdpeep + +## 3.0.0 - 2018-12-23 + +### Changed + +- Update to latest version of Google OAuth +- Use only OpenID Connect for user details + +### Fixed + +- Correct handling of selecting from multiple user accounts, #45 +- Prevent conflict when using prompt option, #42 + +### Added + +- Add "locale" to user details, #60 +- Support additional scopes at construction + +### Removed + +- Dropped support for Google+ user details, #34 and #63 + +## 2.2.0 - 2018-03-19 + +### Added + +- Hosted domain validation, #54 by @pradtke + +## 2.1.0 - 2018-03-09 + +### Added + +- OpenID Connect support, #48 by @pradtke + +## 2.0.0 - 2017-01-24 + +### Added + +- PHP 7.1 support + +### Removed + +- Dropped PHP 5.5 support + +## 1.0.0 - 2015-08-12 + +- Initial release diff --git a/lib/league/oauth2-google/CONTRIBUTING.md b/lib/league/oauth2-google/CONTRIBUTING.md new file mode 100644 index 000000000..84554556e --- /dev/null +++ b/lib/league/oauth2-google/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth2-google). + + +## Pull Requests + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option. + +- **Create topic branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + +- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass. + +- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails. + + +## Running Tests + +```sh +composer test +``` + + +## Running PHP Code Sniffer + +```sh +composer check +``` + +**Happy coding**! diff --git a/lib/league/oauth2-google/LICENSE b/lib/league/oauth2-google/LICENSE new file mode 100644 index 000000000..6d451561e --- /dev/null +++ b/lib/league/oauth2-google/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Woody Gilk + +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/lib/league/oauth2-google/README.md b/lib/league/oauth2-google/README.md new file mode 100644 index 000000000..df69dcd12 --- /dev/null +++ b/lib/league/oauth2-google/README.md @@ -0,0 +1,242 @@ +# Google Provider for OAuth 2.0 Client + +[![Join the chat](https://img.shields.io/badge/gitter-join-1DCE73.svg)](https://gitter.im/thephpleague/oauth2-google) +[![Build Status](https://img.shields.io/travis/thephpleague/oauth2-google.svg)](https://travis-ci.org/thephpleague/oauth2-google) +[![Code Coverage](https://img.shields.io/coveralls/thephpleague/oauth2-google.svg)](https://coveralls.io/r/thephpleague/oauth2-google) +[![Code Quality](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-google.svg)](https://scrutinizer-ci.com/g/thephpleague/oauth2-google/) +[![License](https://img.shields.io/packagist/l/league/oauth2-google.svg)](https://github.com/thephpleague/oauth2-google/blob/master/LICENSE) +[![Latest Stable Version](https://img.shields.io/packagist/v/league/oauth2-google.svg)](https://packagist.org/packages/league/oauth2-google) + +This package provides Google OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client). + +This package is compliant with [PSR-1][], [PSR-2][] and [PSR-4][]. If you notice compliance oversights, please send +a patch via pull request. + +[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md +[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md + +## Requirements + +The following versions of PHP are supported. + +* PHP 7.0 +* PHP 7.1 +* PHP 7.2 +* PHP 7.3 +* PHP 7.4 + +This package uses [OpenID Connect][openid-connect] to authenticate users with +Google accounts. + +To use this package, it will be necessary to have a Google client ID and client +secret. These are referred to as `{google-client-id}` and `{google-client-secret}` +in the documentation. + +Please follow the [Google instructions][oauth-setup] to create the required credentials. + +[openid-connect]: https://developers.google.com/identity/protocols/OpenIDConnect +[oauth-setup]: https://developers.google.com/identity/protocols/OpenIDConnect#registeringyourapp + +## Installation + +To install, use composer: + +```sh +composer require league/oauth2-google +``` + +## Usage + +### Authorization Code Flow + +```php +require __DIR__ . '/vendor/autoload.php'; + +use League\OAuth2\Client\Provider\Google; + +session_start(); // Remove if session.auto_start=1 in php.ini + +$provider = new Google([ + 'clientId' => '{google-client-id}', + 'clientSecret' => '{google-client-secret}', + 'redirectUri' => 'https://example.com/callback-url', + 'hostedDomain' => 'example.com', // optional; used to restrict access to users on your G Suite/Google Apps for Business accounts +]); + +if (!empty($_GET['error'])) { + + // Got an error, probably user denied access + exit('Got error: ' . htmlspecialchars($_GET['error'], ENT_QUOTES, 'UTF-8')); + +} elseif (empty($_GET['code'])) { + + // If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + + // State is invalid, possible CSRF attack in progress + unset($_SESSION['oauth2state']); + exit('Invalid state'); + +} else { + + // Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken('authorization_code', [ + 'code' => $_GET['code'] + ]); + + // Optional: Now you have a token you can look up a users profile data + try { + + // We got an access token, let's now get the owner details + $ownerDetails = $provider->getResourceOwner($token); + + // Use these details to create a new profile + printf('Hello %s!', $ownerDetails->getFirstName()); + + } catch (Exception $e) { + + // Failed to get user details + exit('Something went wrong: ' . $e->getMessage()); + + } + + // Use this to interact with an API on the users behalf + echo $token->getToken(); + + // Use this to get a new access token if the old one expires + echo $token->getRefreshToken(); + + // Unix timestamp at which the access token expires + echo $token->getExpires(); +} +``` + +#### Available Options + +The `Google` provider has the following [options][auth-params]: + +- `accessType` to use online or offline access +- `hostedDomain` to authenticate G Suite users +- `prompt` to modify the prompt that the user will see +- `scopes` to request access to additional user information + +[auth-params]: https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters + +#### Accessing Token JWT + +Google provides a [JSON Web Token][jwt] (JWT) with all access tokens. This token +[contains basic information][openid-jwt] about the authenticated user. The JWT +can be accessed from the `id_token` value of the access token: + +```php +/** @var League\OAuth2\Client\Token\AccessToken $token */ +$values = $token->getValues(); + +/** @var string */ +$jwt = $values['id_token']; +``` + +Parsing the JWT will require a [JWT parser][jwt-parsers]. Refer to parser +documentation for instructions. + +[jwt]: https://jwt.io/ +[openid-jwt]: https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo +[jwt-parsers]: https://packagist.org/search/?q=jwt + +### Refreshing a Token + +Refresh tokens are only provided to applications which request offline access. You can specify offline access by setting the `accessType` option in your provider: + +```php +use League\OAuth2\Client\Provider\Google; + +$provider = new Google([ + 'clientId' => '{google-client-id}', + 'clientSecret' => '{google-client-secret}', + 'redirectUri' => 'https://example.com/callback-url', + 'accessType' => 'offline', +]); +``` + +It is important to note that the refresh token is only returned on the first request after this it will be `null`. You should securely store the refresh token when it is returned: + +```php +$token = $provider->getAccessToken('authorization_code', [ + 'code' => $code +]); + +// persist the token in a database +$refreshToken = $token->getRefreshToken(); +``` + +If you ever need to get a new refresh token you can request one by forcing the consent prompt: + +```php +$authUrl = $provider->getAuthorizationUrl(['prompt' => 'consent']); +``` + +Now you have everything you need to refresh an access token using a refresh token: + +```php +use League\OAuth2\Client\Provider\Google; +use League\OAuth2\Client\Grant\RefreshToken; + +$provider = new Google([ + 'clientId' => '{google-client-id}', + 'clientSecret' => '{google-client-secret}', + 'redirectUri' => 'https://example.com/callback-url', +]); + +$grant = new RefreshToken(); +$token = $provider->getAccessToken($grant, ['refresh_token' => $refreshToken]); +``` + +## Scopes + +Additional [scopes][scopes] can be set by using the `scope` parameter when +generating the authorization URL: + +```php +$authorizationUrl = $provider->getAuthorizationUrl([ + 'scope' => [ + 'scope-url-here' + ], +]); +``` + +[scopes]: https://developers.google.com/identity/protocols/googlescopes + +## Testing + +Tests can be run with: + +```sh +composer test +``` + +Style checks can be run with: + +```sh +composer check +``` + +## Contributing + +Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-google/blob/master/CONTRIBUTING.md) for details. + + +## Credits + +- [Woody Gilk](https://github.com/shadowhand) +- [All Contributors](https://github.com/thephpleague/oauth2-google/contributors) + + +## License + +The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-google/blob/master/LICENSE) for more information. diff --git a/lib/league/oauth2-google/composer.json b/lib/league/oauth2-google/composer.json new file mode 100644 index 000000000..f34a1cec9 --- /dev/null +++ b/lib/league/oauth2-google/composer.json @@ -0,0 +1,44 @@ +{ + "name": "league/oauth2-google", + "description": "Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "license": "MIT", + "authors": [ + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me" + } + ], + "keywords": [ + "oauth", + "oauth2", + "client", + "authorization", + "authentication", + "google" + ], + "minimum-stability": "stable", + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^2.0", + "phpunit/phpunit": "^6.0", + "php-coveralls/php-coveralls": "^2.1", + "squizlabs/php_codesniffer": "^2.0" + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "test/src/" + } + }, + "scripts": { + "test": "phpunit", + "check": "phpcs src --standard=psr2 -sp" + } +} diff --git a/lib/league/oauth2-google/examples/index.php b/lib/league/oauth2-google/examples/index.php new file mode 100644 index 000000000..913843777 --- /dev/null +++ b/lib/league/oauth2-google/examples/index.php @@ -0,0 +1,35 @@ +getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + + // State is invalid, possible CSRF attack in progress + unset($_SESSION['oauth2state']); + exit('Invalid state'); + +} else { + + // Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken('authorization_code', [ + 'code' => $_GET['code'] + ]); + + $_SESSION['token'] = serialize($token); + + // Optional: Now you have a token you can look up a users profile data + header('Location: /user.php'); +} diff --git a/lib/league/oauth2-google/examples/provider.php b/lib/league/oauth2-google/examples/provider.php new file mode 100644 index 000000000..4001f6857 --- /dev/null +++ b/lib/league/oauth2-google/examples/provider.php @@ -0,0 +1,24 @@ +getResourceOwner($token); + + // Use these details to create a new profile + printf('Hello %s!
', $userDetails->getFirstname()); +} catch (Exception $e) { + // Failed to get user details + exit('Something went wrong: ' . $e->getMessage()); +} + +// Use this to interact with an API on the users behalf +echo "Token is: ", $token->getToken(), "
"; + +// Use this to get a new access token if the old one expires +echo "Refresh token is: ", $token->getRefreshToken(), "
"; + +// Number of seconds until the access token will expire, and need refreshing +echo "Expires at ", date('r', $token->getExpires()), "
"; + +// Allow the user to logout +echo 'Logout
'; diff --git a/lib/league/oauth2-google/phpunit.xml.dist b/lib/league/oauth2-google/phpunit.xml.dist new file mode 100644 index 000000000..7f37586aa --- /dev/null +++ b/lib/league/oauth2-google/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + ./test + + + + + src/ + + + + + + + + diff --git a/lib/league/oauth2-google/src/Exception/HostedDomainException.php b/lib/league/oauth2-google/src/Exception/HostedDomainException.php new file mode 100644 index 000000000..b38a41580 --- /dev/null +++ b/lib/league/oauth2-google/src/Exception/HostedDomainException.php @@ -0,0 +1,15 @@ +hostedDomain) { + $options['hd'] = $this->hostedDomain; + } + + if (empty($options['access_type']) && $this->accessType) { + $options['access_type'] = $this->accessType; + } + + if (empty($options['prompt']) && $this->prompt) { + $options['prompt'] = $this->prompt; + } + + // Default scopes MUST be included for OpenID Connect. + // Additional scopes MAY be added by constructor or option. + $scopes = array_merge($this->getDefaultScopes(), $this->scopes); + + if (!empty($options['scope'])) { + $scopes = array_merge($scopes, $options['scope']); + } + + $options['scope'] = array_unique($scopes); + + $options = parent::getAuthorizationParameters($options); + + // The "approval_prompt" MUST be removed as it is not supported by Google, use "prompt" instead: + // https://developers.google.com/identity/protocols/oauth2/openid-connect#prompt + unset($options['approval_prompt']); + + return $options; + } + + protected function getDefaultScopes() + { + // "openid" MUST be the first scope in the list. + return [ + 'openid', + 'email', + 'profile', + ]; + } + + protected function getScopeSeparator() + { + return ' '; + } + + protected function checkResponse(ResponseInterface $response, $data) + { + // @codeCoverageIgnoreStart + if (empty($data['error'])) { + return; + } + // @codeCoverageIgnoreEnd + + $code = 0; + $error = $data['error']; + + if (is_array($error)) { + $code = $error['code']; + $error = $error['message']; + } + + throw new IdentityProviderException($error, $code, $data); + } + + protected function createResourceOwner(array $response, AccessToken $token) + { + $user = new GoogleUser($response); + + $this->assertMatchingDomain($user->getHostedDomain()); + + return $user; + } + + /** + * @throws HostedDomainException If the domain does not match the configured domain. + */ + protected function assertMatchingDomain($hostedDomain) + { + if ($this->hostedDomain === null) { + // No hosted domain configured. + return; + } + + if ($this->hostedDomain === '*' && $hostedDomain) { + // Any hosted domain is allowed. + return; + } + + if ($this->hostedDomain === $hostedDomain) { + // Hosted domain is correct. + return; + } + + throw HostedDomainException::notMatchingDomain($this->hostedDomain); + } +} diff --git a/lib/league/oauth2-google/src/Provider/GoogleUser.php b/lib/league/oauth2-google/src/Provider/GoogleUser.php new file mode 100644 index 000000000..1100b1dbe --- /dev/null +++ b/lib/league/oauth2-google/src/Provider/GoogleUser.php @@ -0,0 +1,112 @@ +response = $response; + } + + public function getId() + { + return $this->response['sub']; + } + + /** + * Get preferred display name. + * + * @return string + */ + public function getName() + { + return $this->response['name']; + } + + /** + * Get preferred first name. + * + * @return string|null + */ + public function getFirstName() + { + return $this->getResponseValue('given_name'); + } + + /** + * Get preferred last name. + * + * @return string|null + */ + public function getLastName() + { + return $this->getResponseValue('family_name'); + } + + /** + * Get locale. + * + * @return string|null + */ + public function getLocale() + { + return $this->getResponseValue('locale'); + } + + /** + * Get email address. + * + * @return string|null + */ + public function getEmail() + { + return $this->getResponseValue('email'); + } + + /** + * Get hosted domain. + * + * @return string|null + */ + public function getHostedDomain() + { + return $this->getResponseValue('hd'); + } + + /** + * Get avatar image URL. + * + * @return string|null + */ + public function getAvatar() + { + return $this->getResponseValue('picture'); + } + + /** + * Get user data as an array. + * + * @return array + */ + public function toArray() + { + return $this->response; + } + + private function getResponseValue($key) + { + if (array_key_exists($key, $this->response)) { + return $this->response[$key]; + } + return null; + } +} diff --git a/lib/ralouphie/getallheaders/LICENSE b/lib/ralouphie/getallheaders/LICENSE new file mode 100644 index 000000000..be5540c2a --- /dev/null +++ b/lib/ralouphie/getallheaders/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ralph Khattar + +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/lib/ralouphie/getallheaders/README.md b/lib/ralouphie/getallheaders/README.md new file mode 100644 index 000000000..9430d76bb --- /dev/null +++ b/lib/ralouphie/getallheaders/README.md @@ -0,0 +1,27 @@ +getallheaders +============= + +PHP `getallheaders()` polyfill. Compatible with PHP >= 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/lib/ralouphie/getallheaders/composer.json b/lib/ralouphie/getallheaders/composer.json new file mode 100644 index 000000000..de8ce62e4 --- /dev/null +++ b/lib/ralouphie/getallheaders/composer.json @@ -0,0 +1,26 @@ +{ + "name": "ralouphie/getallheaders", + "description": "A polyfill for getallheaders.", + "license": "MIT", + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5 || ^6.5", + "php-coveralls/php-coveralls": "^2.1" + }, + "autoload": { + "files": ["src/getallheaders.php"] + }, + "autoload-dev": { + "psr-4": { + "getallheaders\\Tests\\": "tests/" + } + } +} diff --git a/lib/ralouphie/getallheaders/src/getallheaders.php b/lib/ralouphie/getallheaders/src/getallheaders.php new file mode 100644 index 000000000..c7285a5ba --- /dev/null +++ b/lib/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/lib/thenetworg/oauth2-azure/.devcontainer/Dockerfile b/lib/thenetworg/oauth2-azure/.devcontainer/Dockerfile new file mode 100644 index 000000000..35ab8f9c4 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.devcontainer/Dockerfile @@ -0,0 +1,15 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/php/.devcontainer/base.Dockerfile +ARG VARIANT="7" +FROM mcr.microsoft.com/vscode/devcontainers/php:0-${VARIANT} + +# [Optional] Install a version of Node.js using nvm for front end dev +ARG INSTALL_NODE="true" +ARG NODE_VERSION="lts/*" +RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file diff --git a/lib/thenetworg/oauth2-azure/.devcontainer/devcontainer.json b/lib/thenetworg/oauth2-azure/.devcontainer/devcontainer.json new file mode 100644 index 000000000..676de3dcb --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/php +{ + "name": "PHP", + "build": { + "dockerfile": "Dockerfile", + "args": { + // Update VARIANT to pick a PHP version: 7, 7.4, 7.3 + "VARIANT": "7", + "INSTALL_NODE": "false", + "NODE_VERSION": "lts/*" + } + }, + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "felixfbecker.php-debug", + "felixfbecker.php-intellisense" + ], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "php -v", + // Comment out to connect as root instead. + "remoteUser": "vscode" +} \ No newline at end of file diff --git a/lib/thenetworg/oauth2-azure/.gitignore b/lib/thenetworg/oauth2-azure/.gitignore new file mode 100644 index 000000000..9c9c8f271 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.gitignore @@ -0,0 +1,5 @@ +/build +/vendor +composer.phar +composer.lock +.DS_Store diff --git a/lib/thenetworg/oauth2-azure/.php_cs b/lib/thenetworg/oauth2-azure/.php_cs new file mode 100644 index 000000000..47be3dfc0 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.php_cs @@ -0,0 +1,70 @@ +in(__DIR__ . '/src'); + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setUsingCache(false) + ->setRules([ + '@PSR2' => true, + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => ['default' => 'align_single_space_minimal'], + 'blank_line_after_opening_tag' => true, + 'class_attributes_separation' => true, + 'combine_consecutive_issets' => true, + 'general_phpdoc_annotation_remove' => ['annotations' => ['author', 'package', 'subpackage']], + 'declare_equal_normalize' => ['space' => 'single'], + 'dir_constant' => true, + 'fully_qualified_strict_types' => true, + 'function_typehint_space' => true, + 'heredoc_to_nowdoc' => true, + 'include' => true, + 'is_null' => ['use_yoda_style' => true], + 'linebreak_after_opening_tag' => true, + 'lowercase_cast' => true, + 'modernize_types_casting' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_alternative_syntax' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => ['use' => 'echo'], + 'no_multiline_whitespace_before_semicolons' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_short_echo_tag' => false, + 'no_unreachable_default_argument_value' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => true, + 'ordered_imports' => true, + 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_to_comment' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last'], + 'phpdoc_var_without_name' => true, + 'short_scalar_cast' => true, + 'simplified_null_return' => true, + 'single_blank_line_before_namespace' => true, + 'single_line_comment_style' => true, + 'single_quote' => ['strings_containing_single_quote_chars' => true], + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'trailing_comma_in_multiline_array' => true, + 'trim_array_spaces' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => true, + ]) + ->setFinder($finder); diff --git a/lib/thenetworg/oauth2-azure/.vscode/launch.json b/lib/thenetworg/oauth2-azure/.vscode/launch.json new file mode 100644 index 000000000..c69965a9e --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch application", + "type": "php", + "request": "launch", + "program": "${workspaceFolder}/index.php", + "cwd": "${workspaceFolder}", + "port": 9000 + }, + { + "name": "Listen for XDebug", + "type": "php", + "request": "launch", + "port": 9000 + }, + { + "name": "Launch currently open script", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 9000 + } + ] +} \ No newline at end of file diff --git a/lib/thenetworg/oauth2-azure/CHANGELOG.md b/lib/thenetworg/oauth2-azure/CHANGELOG.md new file mode 100644 index 000000000..689c66685 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog +All Notable changes to `oauth2-azure` will be documented in this file + +## v1.0.0 - 16NOV2015 +- Initial release \ No newline at end of file diff --git a/lib/thenetworg/oauth2-azure/LICENSE.md b/lib/thenetworg/oauth2-azure/LICENSE.md new file mode 100644 index 000000000..196dd8655 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 TheNetw.org + +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/lib/thenetworg/oauth2-azure/README.md b/lib/thenetworg/oauth2-azure/README.md new file mode 100644 index 000000000..fbf296595 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/README.md @@ -0,0 +1,283 @@ +# Azure Active Directory Provider for OAuth 2.0 Client +[![Latest Version](https://img.shields.io/github/release/thenetworg/oauth2-azure.svg?style=flat-square)](https://github.com/thenetworg/oauth2-azure/releases) +[![Total Downloads](https://img.shields.io/packagist/dt/thenetworg/oauth2-azure.svg?style=flat-square)](https://packagist.org/packages/thenetworg/oauth2-azure) +[![Software License](https://img.shields.io/packagist/l/thenetworg/oauth2-azure.svg?style=flat-square)](LICENSE.md) + +This package provides [Azure Active Directory](https://azure.microsoft.com/en-us/services/active-directory/) OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client). + +## Table of Contents +- [Installation](#installation) +- [Usage](#usage) + - [Authorization Code Flow](#authorization-code-flow) + - [Advanced flow](#advanced-flow) + - [Using custom parameters](#using-custom-parameters) + - [**NEW** - Call on behalf of a token provided by another app](#call-on-behalf-of-a-token-provided-by-another-app) + - [**NEW** - Logging out](#logging-out) +- [Making API Requests](#making-api-requests) + - [Variables](#variables) +- [Resource Owner](#resource-owner) +- [**UPDATED** - Microsoft Graph](#microsoft-graph) +- [**NEW** - Protecting your API - *experimental*](#protecting-your-api---experimental) +- [Azure Active Directory B2C - *experimental*](#azure-active-directory-b2c---experimental) +- [Multipurpose refresh tokens - *experimental*](#multipurpose-refresh-tokens---experimental) +- [Known users](#known-users) +- [Contributing](#contributing) +- [Credits](#credits) +- [Support](#support) +- [License](#license) + +## Installation + +To install, use composer: + +``` +composer require thenetworg/oauth2-azure +``` + +## Usage + +Usage is the same as The League's OAuth client, using `\TheNetworg\OAuth2\Client\Provider\Azure` as the provider. + +### Authorization Code Flow + +```php +$provider = new TheNetworg\OAuth2\Client\Provider\Azure([ + 'clientId' => '{azure-client-id}', + 'clientSecret' => '{azure-client-secret}', + 'redirectUri' => 'https://example.com/callback-url', + //Optional + 'scopes' => 'openid', + //Optional + 'defaultEndPointVersion' => '2.0' +]); + +// Set to use v2 API, skip the line or set the value to Azure::ENDPOINT_VERSION_1_0 if willing to use v1 API +$provider->defaultEndPointVersion = TheNetworg\OAuth2\Client\Provider\Azure::ENDPOINT_VERSION_2_0; + +$baseGraphUri = $provider->getRootMicrosoftGraphUri(null); +$provider->scope = 'openid profile email offline_access ' . $baseGraphUri . '/User.Read'; + +if (isset($_GET['code']) && isset($_SESSION['OAuth2.state']) && isset($_GET['state'])) { + if ($_GET['state'] == $_SESSION['OAuth2.state']) { + unset($_SESSION['OAuth2.state']); + + // Try to get an access token (using the authorization code grant) + /** @var AccessToken $token */ + $token = $provider->getAccessToken('authorization_code', [ + 'scope' => $provider->scope, + 'code' => $_GET['code'], + ]); + + // Verify token + // Save it to local server session data + + return $token->getToken(); + } else { + echo 'Invalid state'; + + return null; + } +} else { + // // Check local server's session data for a token + // // and verify if still valid + // /** @var ?AccessToken $token */ + // $token = // token cached in session data, null if not found; + // + // if (isset($token)) { + // $me = $provider->get($provider->getRootMicrosoftGraphUri($token) . '/v1.0/me', $token); + // $userEmail = $me['mail']; + // + // if ($token->hasExpired()) { + // if (!is_null($token->getRefreshToken())) { + // $token = $provider->getAccessToken('refresh_token', [ + // 'scope' => $provider->scope, + // 'refresh_token' => $token->getRefreshToken() + // ]); + // } else { + // $token = null; + // } + // } + //} + // + // If the token is not found in + // if (!isset($token)) { + $authorizationUrl = $provider->getAuthorizationUrl(['scope' => $provider->scope]); + + $_SESSION['OAuth2.state'] = $provider->getState(); + + header('Location: ' . $authorizationUrl); + + exit; + // } + + return $token->getToken(); +} +``` + +#### Advanced flow + +The [Authorization Code Grant Flow](https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx) is a little bit different for Azure Active Directory. Instead of scopes, you specify the resource which you would like to access - there is a param `$provider->authWithResource` which will automatically populate the `resource` param of request with the value of either `$provider->resource` or `$provider->urlAPI`. This feature is mostly intended for v2.0 endpoint of Azure AD (see more [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/azure-ad-endpoint-comparison#scopes-not-resources)). + +#### Using custom parameters + +With [oauth2-client](https://github.com/thephpleague/oauth2-client) of version 1.3.0 and higher, it is now possible to specify custom parameters for the authorization URL, so you can now make use of options like `prompt`, `login_hint` and similar. See the following example of obtaining an authorization URL which will force the user to reauthenticate: +```php +$authUrl = $provider->getAuthorizationUrl([ + 'prompt' => 'login' +]); +``` +You can find additional parameters [here](https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx). + +### Logging out +If you need to quickly generate a logout URL for the user, you can do following: +```php +// Assuming you have provider properly initialized. +$post_logout_redirect_uri = 'https://www.msn.com'; // The logout destination after the user is logged out from their account. +$logoutUrl = $provider->getLogoutUrl($post_logout_redirect_uri); +header('Location: '.$logoutUrl); // Redirect the user to the generated URL +``` + +#### Call on behalf of a token provided by another app + +```php +// Use token provided by the other app +// Make sure the other app mentioned this app in the scope when requesting the token +$suppliedToken = ''; + +$provider = xxxxx;// Initialize provider + +// Call this to get claims +// $claims = $provider->validateAccessToken($suppliedToken); + +/** @var AccessToken $token */ +$token = $provider->getAccessToken('jwt_bearer', [ + 'scope' => $provider->scope, + 'assertion' => $suppliedToken, + 'requested_token_use' => 'on_behalf_of', +]); +``` + +## Making API Requests + +This library also provides easy interface to make it easier to interact with [Azure Graph API](https://msdn.microsoft.com/en-us/library/azure/hh974476.aspx) and [Microsoft Graph](http://graph.microsoft.io), the following methods are available on `provider` object (it also handles automatic token refresh flow should it be needed during making the request): + +- `get($ref, $accessToken, $headers = [])` +- `post($ref, $body, $accessToken, $headers = [])` +- `put($ref, $body, $accessToken, $headers = [])` +- `delete($ref, $body, $accessToken, $headers = [])` +- `patch($ref, $body, $accessToken, $headers = [])` +- `getObjects($tenant, $ref, $accessToken, $headers = [])` This is used for example for listing large amount of data - where you need to list all users for example - it automatically follows `odata.nextLink` until the end. + - `$tenant` tenant has to be provided since the `odata.nextLink` doesn't contain it. +- `request($method, $ref, $accessToken, $options = [])` See [#36](https://github.com/TheNetworg/oauth2-azure/issues/36) for use case. + +*Please note that if you need to create a custom request, the method getAuthenticatedRequest and getResponse can still be used.* + +### Variables +- `$ref` The URL reference without the leading `/`, for example `myOrganization/groups` +- `$body` The contents of the request, make has to be either string (so make sure to use `json_encode` to encode the request)s or stream (see [Guzzle HTTP](http://docs.guzzlephp.org/en/latest/request-options.html#body)) +- `$accessToken` The access token object obtained by using `getAccessToken` method +- `$headers` Ability to set custom headers for the request (see [Guzzle HTTP](http://docs.guzzlephp.org/en/latest/request-options.html#headers)) + +## Resource Owner +With version 1.1.0 and onward, the Resource Owner information is parsed from the JWT passed in `access_token` by Azure Active Directory. It exposes few attributes and one function. + +**Example:** +```php +$resourceOwner = $provider->getResourceOwner($token); +echo 'Hello, '.$resourceOwner->getFirstName().'!'; +``` +The exposed attributes and function are: +- `getId()` - Gets user's object id - unique for each user +- `getFirstName()` - Gets user's first name +- `getLastName()` - Gets user's family name/surname +- `getTenantId()` - Gets id of tenant which the user is member of +- `getUpn()` - Gets user's User Principal Name, which can be also used as user's e-mail address +- `claim($name)` - Gets any other claim (specified as `$name`) from the JWT, full list can be found [here](https://azure.microsoft.com/en-us/documentation/articles/active-directory-token-and-claims/) + +## Microsoft Graph +Calling [Microsoft Graph](http://graph.microsoft.io/) is very simple with this library. After provider initialization simply change the API URL followingly (replace `v1.0` with your desired version): +```php +// Mention Microsoft Graph scope when initializing the provider +$baseGraphUri = $provider->getRootMicrosoftGraphUri(null); +$provider->scope = 'your scope ' . $baseGraphUri . '/User.Read'; + +// Call a query +$provider->get($provider->getRootMicrosoftGraphUri($token) . '/v1.0/me', $token); +``` +After that, when requesting access token, refresh token or so, provide the `resource` with value `https://graph.microsoft.com/` in order to be able to make calls to the Graph (see more about `resource` [here](#advanced-flow)). + +## Protecting your API - *experimental* +With version 1.2.0 you can now use this library to protect your API with Azure Active Directory authentication very easily. The Provider now also exposes `validateAccessToken(string $token)` which lets you pass an access token inside which you for example received in the `Authorization` header of the request on your API. You can use the function followingly (in vanilla PHP): +```php +// Assuming you have already initialized the $provider + +// Obtain the accessToken - in this case, we are getting it from Authorization header +$headers = getallheaders(); +// Assuming you got the value of Authorization header as "Bearer [the_access_token]" we parse it +$authorization = explode(' ', $headers['Authorization']); +$accessToken = $authorization[1]; + +try { + $claims = $provider->validateAccessToken($accessToken); +} catch (Exception $e) { + // Something happened, handle the error +} + +// The access token is valid, you can now proceed with your code. You can also access the $claims as defined in JWT - for example roles, group memberships etc. +``` + +You may also need to access some other resource from the API like the Microsoft Graph to get some additional information. In order to do that, there is `urn:ietf:params:oauth:grant-type:jwt-bearer` grant available ([RFC](https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03)). An example (assuming you have the code above working and you have the required permissions configured correctly in the Azure AD application): +```php +$graphAccessToken = $provider->getAccessToken('jwt_bearer', [ + 'resource' => 'https://graph.microsoft.com/v1.0/', + 'assertion' => $accessToken, + 'requested_token_use' => 'on_behalf_of' +]); + +$me = $provider->get('https://graph.microsoft.com/v1.0/me', $graphAccessToken); +print_r($me); +``` +Just to make it easier so you don't have to remember entire name for `grant_type` (`urn:ietf:params:oauth:grant-type:jwt-bearer`), you just use short `jwt_bearer` instead. + +## Azure Active Directory B2C - *experimental* +You can also now very simply make use of [Azure Active Directory B2C](https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-reference-oauth-code/). Before authentication, change the endpoints using `pathAuthorize`, `pathToken` and `scope` and additionally specify your [login policy](https://azure.microsoft.com/en-gb/documentation/articles/active-directory-b2c-reference-policies/). **Please note that the B2C support is still experimental and wasn't fully tested.** +```php +$provider->pathAuthorize = "/oauth2/v2.0/authorize"; +$provider->pathToken = "/oauth2/v2.0/token"; +$provider->scope = ["idtoken"]; + +// Specify custom policy in our authorization URL +$authUrl = $provider->getAuthorizationUrl([ + 'p' => 'b2c_1_siup' +]); +``` + +## Multipurpose refresh tokens - *experimental* +In cause that you need to access multiple resources (like your API and Microsoft Graph), you can use multipurpose [refresh tokens](https://msdn.microsoft.com/en-us/library/azure/dn645538.aspx). Once obtaining a token for first resource, you can simply request another token for different resource like so: +```php +$accessToken2 = $provider->getAccessToken('refresh_token', [ + 'refresh_token' => $accessToken1->getRefreshToken(), + 'resource' => 'http://urlOfYourSecondResource' +]); +``` +At the moment, there is one issue: When you make a call to your API and the token has expired, it will have the value of `$provider->urlAPI` which is obviously wrong for `$accessToken2`. The solution is very simple - set the `$provider->urlAPI` to the resource which you want to call. This issue will be addressed in future release. **Please note that this is experimental and wasn't fully tested.** + +## Known users +If you are using this library and would like to be listed here, please let us know! +- [TheNetworg/DreamSpark-SSO](https://github.com/thenetworg/dreamspark-sso) + +## Contributing +We accept contributions via [Pull Requests on Github](https://github.com/thenetworg/oauth2-azure). + +## Credits +- [Jan Hajek](https://github.com/hajekj) ([TheNetw.org](https://thenetw.org)) +- [Vittorio Bertocci](https://github.com/vibronet) (Microsoft) + - Thanks for the splendid support while implementing #16 +- [Martin Cetkovský](https://github.com/mcetkovsky) ([cetkovsky.eu](https://www.cetkovsky.eu)] +- [All Contributors](https://github.com/thenetworg/oauth2-azure/contributors) + +## Support +If you find a bug or encounter any issue or have a problem/question with this library please create a [new issue](https://github.com/TheNetworg/oauth2-azure/issues). + +## License +The MIT License (MIT). Please see [License File](https://github.com/thenetworg/oauth2-azure/blob/master/LICENSE) for more information. diff --git a/lib/thenetworg/oauth2-azure/composer.json b/lib/thenetworg/oauth2-azure/composer.json new file mode 100644 index 000000000..e2e3b922d --- /dev/null +++ b/lib/thenetworg/oauth2-azure/composer.json @@ -0,0 +1,34 @@ +{ + "name": "thenetworg/oauth2-azure", + "description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "license": "MIT", + "authors": [ + { + "name": "Jan Hajek", + "email": "jan.hajek@thenetw.org", + "homepage": "https://thenetw.org" + } + ], + "keywords": [ + "oauth", + "oauth2", + "client", + "authorization", + "microsoft", + "windows azure", + "azure", + "azure active directory", + "aad", + "sso" + ], + "require": { + "php": "^5.6|^7.0|^8.0", + "league/oauth2-client": "~2.0", + "firebase/php-jwt": "~3.0||~4.0||~5.0" + }, + "autoload": { + "psr-4": { + "TheNetworg\\OAuth2\\Client\\": "src/" + } + } +} diff --git a/lib/thenetworg/oauth2-azure/src/Grant/JwtBearer.php b/lib/thenetworg/oauth2-azure/src/Grant/JwtBearer.php new file mode 100644 index 000000000..c23772855 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/src/Grant/JwtBearer.php @@ -0,0 +1,19 @@ +scope = array_merge($options['scopes'], $this->scope); + } + if (isset($options['defaultEndPointVersion']) && + in_array($options['defaultEndPointVersion'], self::ENDPOINT_VERSIONS, true)) { + $this->defaultEndPointVersion = $options['defaultEndPointVersion']; + } + $this->grantFactory->setGrant('jwt_bearer', new JwtBearer()); + } + + /** + * @param string $tenant + * @param string $version + */ + protected function getOpenIdConfiguration($tenant, $version) { + if (!is_array($this->openIdConfiguration)) { + $this->openIdConfiguration = []; + } + if (!array_key_exists($tenant, $this->openIdConfiguration)) { + $this->openIdConfiguration[$tenant] = []; + } + if (!array_key_exists($version, $this->openIdConfiguration[$tenant])) { + $versionInfix = $this->getVersionUriInfix($version); + $openIdConfigurationUri = 'https://login.microsoftonline.com/' . $tenant . $versionInfix . '/.well-known/openid-configuration'; + $factory = $this->getRequestFactory(); + $request = $factory->getRequestWithOptions( + 'get', + $openIdConfigurationUri, + [] + ); + $response = $this->getParsedResponse($request); + $this->openIdConfiguration[$tenant][$version] = $response; + } + + return $this->openIdConfiguration[$tenant][$version]; + } + + public function getBaseAuthorizationUrl() + { + $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + return $openIdConfiguration['authorization_endpoint']; + } + + public function getBaseAccessTokenUrl(array $params) + { + $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + return $openIdConfiguration['token_endpoint']; + } + + public function getAccessToken($grant, array $options = []) + { + if ($this->defaultEndPointVersion != self::ENDPOINT_VERSION_2_0) { + // Version 2.0 does not support the resources parameter + // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow + // while version 1.0 does recommend it + // https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code + if ($this->authWithResource) { + $options['resource'] = $this->resource ? $this->resource : $this->urlAPI; + } + } + return parent::getAccessToken($grant, $options); + } + + public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token) + { + $data = $token->getIdTokenClaims(); + return $this->createResourceOwner($data, $token); + } + + public function getResourceOwnerDetailsUrl(\League\OAuth2\Client\Token\AccessToken $token) + { + } + + public function getObjects($tenant, $ref, &$accessToken, $headers = []) + { + $objects = []; + + $response = null; + do { + if (false === filter_var($ref, FILTER_VALIDATE_URL)) { + $ref = $tenant . '/' . $ref; + } + + $response = $this->request('get', $ref, $accessToken, ['headers' => $headers]); + $values = $response; + if (isset($response['value'])) { + $values = $response['value']; + } + foreach ($values as $value) { + $objects[] = $value; + } + if (isset($response['odata.nextLink'])) { + $ref = $response['odata.nextLink']; + } elseif (isset($response['@odata.nextLink'])) { + $ref = $response['@odata.nextLink']; + } else { + $ref = null; + } + } while (null != $ref); + + return $objects; + } + + /** + * @param $accessToken AccessToken|null + * @return string + */ + public function getRootMicrosoftGraphUri($accessToken) + { + if (is_null($accessToken)) { + $tenant = $this->tenant; + $version = $this->defaultEndPointVersion; + } else { + $idTokenClaims = $accessToken->getIdTokenClaims(); + $tenant = array_key_exists('tid', $idTokenClaims) ? $idTokenClaims['tid'] : $this->tenant; + $version = array_key_exists('ver', $idTokenClaims) ? $idTokenClaims['ver'] : $this->defaultEndPointVersion; + } + $openIdConfiguration = $this->getOpenIdConfiguration($tenant, $version); + return 'https://' . $openIdConfiguration['msgraph_host']; + } + + public function get($ref, &$accessToken, $headers = []) + { + $response = $this->request('get', $ref, $accessToken, ['headers' => $headers]); + + return $this->wrapResponse($response); + } + + public function post($ref, $body, &$accessToken, $headers = []) + { + $response = $this->request('post', $ref, $accessToken, ['body' => $body, 'headers' => $headers]); + + return $this->wrapResponse($response); + } + + public function put($ref, $body, &$accessToken, $headers = []) + { + $response = $this->request('put', $ref, $accessToken, ['body' => $body, 'headers' => $headers]); + + return $this->wrapResponse($response); + } + + public function delete($ref, &$accessToken, $headers = []) + { + $response = $this->request('delete', $ref, $accessToken, ['headers' => $headers]); + + return $this->wrapResponse($response); + } + + public function patch($ref, $body, &$accessToken, $headers = []) + { + $response = $this->request('patch', $ref, $accessToken, ['body' => $body, 'headers' => $headers]); + + return $this->wrapResponse($response); + } + + public function request($method, $ref, &$accessToken, $options = []) + { + if ($accessToken->hasExpired()) { + $accessToken = $this->getAccessToken('refresh_token', [ + 'refresh_token' => $accessToken->getRefreshToken(), + ]); + } + + $url = null; + if (false !== filter_var($ref, FILTER_VALIDATE_URL)) { + $url = $ref; + } else { + if (false !== strpos($this->urlAPI, 'graph.windows.net')) { + $tenant = 'common'; + if (property_exists($this, 'tenant')) { + $tenant = $this->tenant; + } + $ref = "$tenant/$ref"; + + $url = $this->urlAPI . $ref; + + $url .= (false === strrpos($url, '?')) ? '?' : '&'; + $url .= 'api-version=' . $this->API_VERSION; + } else { + $url = $this->urlAPI . $ref; + } + } + + if (isset($options['body']) && ('array' == gettype($options['body']) || 'object' == gettype($options['body']))) { + $options['body'] = json_encode($options['body']); + } + if (!isset($options['headers']['Content-Type']) && isset($options['body'])) { + $options['headers']['Content-Type'] = 'application/json'; + } + + $request = $this->getAuthenticatedRequest($method, $url, $accessToken, $options); + $response = $this->getParsedResponse($request); + + return $response; + } + + public function getClientId() + { + return $this->clientId; + } + + /** + * Obtain URL for logging out the user. + * + * @param $post_logout_redirect_uri string The URL which the user should be redirected to after logout + * + * @return string + */ + public function getLogoutUrl($post_logout_redirect_uri = "") + { + $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + $logoutUri = $openIdConfiguration['end_session_endpoint']; + + if (!empty($post_logout_redirect_uri)) { + $logoutUri .= '?post_logout_redirect_uri=' . rawurlencode($post_logout_redirect_uri); + } + + return $logoutUri; + } + + /** + * Validate the access token you received in your application. + * + * @param $accessToken string The access token you received in the authorization header. + * + * @return array + */ + public function validateAccessToken($accessToken) + { + $keys = $this->getJwtVerificationKeys(); + $tokenClaims = (array)JWT::decode($accessToken, $keys, ['RS256']); + + $this->validateTokenClaims($tokenClaims); + + return $tokenClaims; + } + + /** + * Validate the access token claims from an access token you received in your application. + * + * @param $tokenClaims array The token claims from an access token you received in the authorization header. + * + * @return void + */ + public function validateTokenClaims($tokenClaims) { + if ($this->getClientId() != $tokenClaims['aud']) { + throw new \RuntimeException('The client_id / audience is invalid!'); + } + if ($tokenClaims['nbf'] > time() || $tokenClaims['exp'] < time()) { + // Additional validation is being performed in firebase/JWT itself + throw new \RuntimeException('The id_token is invalid!'); + } + + if ('common' == $this->tenant) { + $this->tenant = $tokenClaims['tid']; + } + + $version = array_key_exists('ver', $tokenClaims) ? $tokenClaims['ver'] : $this->defaultEndPointVersion; + $tenant = $this->getTenantDetails($this->tenant, $version); + if ($tokenClaims['iss'] != $tenant['issuer']) { + throw new \RuntimeException('Invalid token issuer (tokenClaims[iss]' . $tokenClaims['iss'] . ', tenant[issuer] ' . $tenant['issuer'] . ')!'); + } + } + + /** + * Get JWT verification keys from Azure Active Directory. + * + * @return array + */ + public function getJwtVerificationKeys() + { + $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + $keysUri = $openIdConfiguration['jwks_uri']; + + $factory = $this->getRequestFactory(); + $request = $factory->getRequestWithOptions('get', $keysUri, []); + + $response = $this->getParsedResponse($request); + + $keys = []; + foreach ($response['keys'] as $i => $keyinfo) { + if (isset($keyinfo['x5c']) && is_array($keyinfo['x5c'])) { + foreach ($keyinfo['x5c'] as $encodedkey) { + $cert = + '-----BEGIN CERTIFICATE-----' . PHP_EOL + . chunk_split($encodedkey, 64, PHP_EOL) + . '-----END CERTIFICATE-----' . PHP_EOL; + + $cert_object = openssl_x509_read($cert); + + if ($cert_object === false) { + throw new \RuntimeException('An attempt to read ' . $encodedkey . ' as a certificate failed.'); + } + + $pkey_object = openssl_pkey_get_public($cert_object); + + if ($pkey_object === false) { + throw new \RuntimeException('An attempt to read a public key from a ' . $encodedkey . ' certificate failed.'); + } + + $pkey_array = openssl_pkey_get_details($pkey_object); + + if ($pkey_array === false) { + throw new \RuntimeException('An attempt to get a public key as an array from a ' . $encodedkey . ' certificate failed.'); + } + + $publicKey = $pkey_array ['key']; + + $keys[$keyinfo['kid']] = $publicKey; + } + } + } + + return $keys; + } + + protected function getVersionUriInfix($version) + { + return + ($version == self::ENDPOINT_VERSION_2_0) + ? '/v' . self::ENDPOINT_VERSION_2_0 + : ''; + } + + /** + * Get the specified tenant's details. + * + * @param string $tenant + * @param string|null $version + * + * @return array + * @throws IdentityProviderException + */ + public function getTenantDetails($tenant, $version) + { + return $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + } + + protected function checkResponse(ResponseInterface $response, $data) + { + if (isset($data['odata.error']) || isset($data['error'])) { + if (isset($data['odata.error']['message']['value'])) { + $message = $data['odata.error']['message']['value']; + } elseif (isset($data['error']['message'])) { + $message = $data['error']['message']; + } elseif (isset($data['error']) && !is_array($data['error'])) { + $message = $data['error']; + } else { + $message = $response->getReasonPhrase(); + } + + if (isset($data['error_description']) && !is_array($data['error_description'])) { + $message .= PHP_EOL . $data['error_description']; + } + + throw new IdentityProviderException( + $message, + $response->getStatusCode(), + $response + ); + } + } + + protected function getDefaultScopes() + { + return $this->scope; + } + + protected function getScopeSeparator() + { + return $this->scopeSeparator; + } + + protected function createAccessToken(array $response, AbstractGrant $grant) + { + return new AccessToken($response, $this); + } + + protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token) + { + return new AzureResourceOwner($response); + } + + private function wrapResponse($response) + { + if (empty($response)) { + return; + } elseif (isset($response['value'])) { + return $response['value']; + } + + return $response; + } +} diff --git a/lib/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php b/lib/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php new file mode 100644 index 000000000..337ae5125 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php @@ -0,0 +1,97 @@ +data = $data; + } + + /** + * Retrieves id of resource owner. + * + * @return string|null + */ + public function getId() + { + return $this->claim('oid'); + } + + /** + * Retrieves first name of resource owner. + * + * @return string|null + */ + public function getFirstName() + { + return $this->claim('given_name'); + } + + /** + * Retrieves last name of resource owner. + * + * @return string|null + */ + public function getLastName() + { + return $this->claim('family_name'); + } + + /** + * Retrieves user principal name of resource owner. + * + * @return string|null + */ + public function getUpn() + { + return $this->claim('upn'); + } + + /** + * Retrieves tenant id of resource owner. + * + * @return string|null + */ + public function getTenantId() + { + return $this->claim('tid'); + } + + /** + * Returns a field from the parsed JWT data. + * + * @param string $name + * + * @return mixed|null + */ + public function claim($name) + { + return isset($this->data[$name]) ? $this->data[$name] : null; + } + + /** + * Returns all the data obtained about the user. + * + * @return array + */ + public function toArray() + { + return $this->data; + } +} diff --git a/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php b/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php new file mode 100644 index 000000000..3899f8377 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php @@ -0,0 +1,52 @@ +idToken = $options['id_token']; + + $keys = $provider->getJwtVerificationKeys(); + $idTokenClaims = null; + try { + $tks = explode('.', $this->idToken); + // Check if the id_token contains signature + if (3 == count($tks) && !empty($tks[2])) { + $idTokenClaims = (array)JWT::decode($this->idToken, $keys, ['RS256']); + } else { + // The id_token is unsigned (coming from v1.0 endpoint) - https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx + + // Since idToken is not signed, we just do OAuth2 flow without validating the id_token + // // Validate the access_token signature first by parsing it as JWT into claims + // $accessTokenClaims = (array)JWT::decode($options['access_token'], $keys, ['RS256']); + // Then parse the idToken claims only without validating the signature + $idTokenClaims = (array)JWT::jsonDecode(JWT::urlsafeB64Decode($tks[1])); + } + } catch (JWT_Exception $e) { + throw new RuntimeException('Unable to parse the id_token!'); + } + + $provider->validateTokenClaims($idTokenClaims); + + $this->idTokenClaims = $idTokenClaims; + } + } + + public function getIdTokenClaims() + { + return $this->idTokenClaims; + } +} diff --git a/lib/true/punycode/CHANGELOG.md b/lib/true/punycode/CHANGELOG.md new file mode 100644 index 000000000..39b44af14 --- /dev/null +++ b/lib/true/punycode/CHANGELOG.md @@ -0,0 +1,45 @@ +# Changelog + +## 2.1.0 - 2016-08-09 + +- [Enhancement] Increase rfc compliance (#20) + - Thanks to [@skroczek](https://github.com/skroczek) for the full patch. + +## 2.0.3 - 2016-05-23 + +- [Fix] Exclude development stuff from repository autogenerated ZIP archives (#18) + - Thanks to [@mlocati](https://github.com/mlocati) for the full patch. + +## 2.0.2 - 2016-01-07 + +- [Fix] Encode and decode domains regardless of their casing (#16) + - Thanks to [@abcdmitry](https://github.com/abcdmitry) for the full patch. + + +## 2.0.1 - 2015-09-01 + +- [Fix] Removed `version` property from `composer.json` file + - Thanks to [@GrahamCampbell](https://github.com/GrahamCampbell) for the patch. + + +## 2.0.0 - 2015-06-24 + +- [Enhancement] PHP 7 support +- [Fix] Renamed `True` namespace to `TrueBV` as it is a reserved word in PHP 7 + + +## 1.1.0 - 2015-03-12 + +- [Enhancement] Character encoding is now passed to the constructor, defaulting to UTF-8, as opposite to relying on `mb_internal_encoding` function call (#9). + + +## 1.0.1 - 2014-08-26 + +- [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) compliant and automation on Travis-CI + - Thanks to [@nyamsprod](https://github.com/nyamsprod) for initial patch. +- [Fix] Domain containing `x`, `n` or `-` would result in failures while decoding (#6). + + +## 1.0.0 - 2014-03-10 + +- Initial release diff --git a/lib/true/punycode/LICENSE b/lib/true/punycode/LICENSE new file mode 100644 index 000000000..963c72ad7 --- /dev/null +++ b/lib/true/punycode/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 TrueServer B.V. + +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/lib/true/punycode/README.md b/lib/true/punycode/README.md new file mode 100644 index 000000000..c290f97a7 --- /dev/null +++ b/lib/true/punycode/README.md @@ -0,0 +1,45 @@ +# Punycode + +[![Build Status](https://secure.travis-ci.org/true/php-punycode.png?branch=master)](http://travis-ci.org/true/php-punycode) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/true/php-punycode/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/true/php-punycode/?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/true/php-punycode/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/true/php-punycode/?branch=master) +[![Latest Stable Version](https://poser.pugx.org/true/punycode/version.png)](https://packagist.org/packages/true/punycode) + +A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA). + + +## Install + +``` +composer require true/punycode:~2.0 +``` + + +## Usage + +```php +encode('renangonçalves.com')); +// outputs: xn--renangonalves-pgb.com + +var_dump($Punycode->decode('xn--renangonalves-pgb.com')); +// outputs: renangonçalves.com +``` + + +## FAQ + +### 1. What is this library for? + +This library converts a Unicode encoded domain name to a IDNA ASCII form and vice-versa. + + +### 2. Why should I use this instead of [PHP's IDN Functions](http://php.net/manual/en/ref.intl.idn.php)? + +If you can compile the needed dependencies (intl, libidn) there is not much difference. +But if you want to write portable code between hosts (including Windows and Mac OS), or can't install PECL extensions, this is the right library for you. diff --git a/lib/true/punycode/composer.json b/lib/true/punycode/composer.json new file mode 100644 index 000000000..5b2815a60 --- /dev/null +++ b/lib/true/punycode/composer.json @@ -0,0 +1,26 @@ +{ + "name": "true/punycode", + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "keywords": ["IDNA", "punycode"], + "homepage": "https://github.com/true/php-punycode", + "license": "MIT", + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "autoload": { + "psr-4": { + "TrueBV\\": "src/" + } + }, + "require": { + "symfony/polyfill-mbstring": "^1.3", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" + } +} diff --git a/lib/true/punycode/src/Exception/DomainOutOfBoundsException.php b/lib/true/punycode/src/Exception/DomainOutOfBoundsException.php new file mode 100644 index 000000000..f50fdc94f --- /dev/null +++ b/lib/true/punycode/src/Exception/DomainOutOfBoundsException.php @@ -0,0 +1,13 @@ + + */ +class DomainOutOfBoundsException extends OutOfBoundsException +{ + +} diff --git a/lib/true/punycode/src/Exception/LabelOutOfBoundsException.php b/lib/true/punycode/src/Exception/LabelOutOfBoundsException.php new file mode 100644 index 000000000..e23b1411c --- /dev/null +++ b/lib/true/punycode/src/Exception/LabelOutOfBoundsException.php @@ -0,0 +1,13 @@ + + */ +class LabelOutOfBoundsException extends OutOfBoundsException +{ + +} diff --git a/lib/true/punycode/src/Exception/OutOfBoundsException.php b/lib/true/punycode/src/Exception/OutOfBoundsException.php new file mode 100644 index 000000000..73b7229a5 --- /dev/null +++ b/lib/true/punycode/src/Exception/OutOfBoundsException.php @@ -0,0 +1,13 @@ + + */ +class OutOfBoundsException extends \RuntimeException +{ + +} diff --git a/lib/true/punycode/src/Punycode.php b/lib/true/punycode/src/Punycode.php new file mode 100644 index 000000000..fbc54dd55 --- /dev/null +++ b/lib/true/punycode/src/Punycode.php @@ -0,0 +1,360 @@ + 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5, + 'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11, + 'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17, + 's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23, + 'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29, + '4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35 + ); + + /** + * Character encoding + * + * @param string + */ + protected $encoding; + + /** + * Constructor + * + * @param string $encoding Character encoding + */ + public function __construct($encoding = 'UTF-8') + { + $this->encoding = $encoding; + } + + /** + * Encode a domain to its Punycode version + * + * @param string $input Domain name in Unicode to be encoded + * @return string Punycode representation in ASCII + */ + public function encode($input) + { + $input = mb_strtolower($input, $this->encoding); + $parts = explode('.', $input); + foreach ($parts as &$part) { + $length = strlen($part); + if ($length < 1) { + throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length)); + } + $part = $this->encodePart($part); + } + $output = implode('.', $parts); + $length = strlen($output); + if ($length > 255) { + throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length)); + } + + return $output; + } + + /** + * Encode a part of a domain name, such as tld, to its Punycode version + * + * @param string $input Part of a domain name + * @return string Punycode representation of a domain part + */ + protected function encodePart($input) + { + $codePoints = $this->listCodePoints($input); + + $n = static::INITIAL_N; + $bias = static::INITIAL_BIAS; + $delta = 0; + $h = $b = count($codePoints['basic']); + + $output = ''; + foreach ($codePoints['basic'] as $code) { + $output .= $this->codePointToChar($code); + } + if ($input === $output) { + return $output; + } + if ($b > 0) { + $output .= static::DELIMITER; + } + + $codePoints['nonBasic'] = array_unique($codePoints['nonBasic']); + sort($codePoints['nonBasic']); + + $i = 0; + $length = mb_strlen($input, $this->encoding); + while ($h < $length) { + $m = $codePoints['nonBasic'][$i++]; + $delta = $delta + ($m - $n) * ($h + 1); + $n = $m; + + foreach ($codePoints['all'] as $c) { + if ($c < $n || $c < static::INITIAL_N) { + $delta++; + } + if ($c === $n) { + $q = $delta; + for ($k = static::BASE;; $k += static::BASE) { + $t = $this->calculateThreshold($k, $bias); + if ($q < $t) { + break; + } + + $code = $t + (($q - $t) % (static::BASE - $t)); + $output .= static::$encodeTable[$code]; + + $q = ($q - $t) / (static::BASE - $t); + } + + $output .= static::$encodeTable[$q]; + $bias = $this->adapt($delta, $h + 1, ($h === $b)); + $delta = 0; + $h++; + } + } + + $delta++; + $n++; + } + $out = static::PREFIX . $output; + $length = strlen($out); + if ($length > 63 || $length < 1) { + throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length)); + } + + return $out; + } + + /** + * Decode a Punycode domain name to its Unicode counterpart + * + * @param string $input Domain name in Punycode + * @return string Unicode domain name + */ + public function decode($input) + { + $input = strtolower($input); + $parts = explode('.', $input); + foreach ($parts as &$part) { + $length = strlen($part); + if ($length > 63 || $length < 1) { + throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length)); + } + if (strpos($part, static::PREFIX) !== 0) { + continue; + } + + $part = substr($part, strlen(static::PREFIX)); + $part = $this->decodePart($part); + } + $output = implode('.', $parts); + $length = strlen($output); + if ($length > 255) { + throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length)); + } + + return $output; + } + + /** + * Decode a part of domain name, such as tld + * + * @param string $input Part of a domain name + * @return string Unicode domain part + */ + protected function decodePart($input) + { + $n = static::INITIAL_N; + $i = 0; + $bias = static::INITIAL_BIAS; + $output = ''; + + $pos = strrpos($input, static::DELIMITER); + if ($pos !== false) { + $output = substr($input, 0, $pos++); + } else { + $pos = 0; + } + + $outputLength = strlen($output); + $inputLength = strlen($input); + while ($pos < $inputLength) { + $oldi = $i; + $w = 1; + + for ($k = static::BASE;; $k += static::BASE) { + $digit = static::$decodeTable[$input[$pos++]]; + $i = $i + ($digit * $w); + $t = $this->calculateThreshold($k, $bias); + + if ($digit < $t) { + break; + } + + $w = $w * (static::BASE - $t); + } + + $bias = $this->adapt($i - $oldi, ++$outputLength, ($oldi === 0)); + $n = $n + (int) ($i / $outputLength); + $i = $i % ($outputLength); + $output = mb_substr($output, 0, $i, $this->encoding) . $this->codePointToChar($n) . mb_substr($output, $i, $outputLength - 1, $this->encoding); + + $i++; + } + + return $output; + } + + /** + * Calculate the bias threshold to fall between TMIN and TMAX + * + * @param integer $k + * @param integer $bias + * @return integer + */ + protected function calculateThreshold($k, $bias) + { + if ($k <= $bias + static::TMIN) { + return static::TMIN; + } elseif ($k >= $bias + static::TMAX) { + return static::TMAX; + } + return $k - $bias; + } + + /** + * Bias adaptation + * + * @param integer $delta + * @param integer $numPoints + * @param boolean $firstTime + * @return integer + */ + protected function adapt($delta, $numPoints, $firstTime) + { + $delta = (int) ( + ($firstTime) + ? $delta / static::DAMP + : $delta / 2 + ); + $delta += (int) ($delta / $numPoints); + + $k = 0; + while ($delta > ((static::BASE - static::TMIN) * static::TMAX) / 2) { + $delta = (int) ($delta / (static::BASE - static::TMIN)); + $k = $k + static::BASE; + } + $k = $k + (int) (((static::BASE - static::TMIN + 1) * $delta) / ($delta + static::SKEW)); + + return $k; + } + + /** + * List code points for a given input + * + * @param string $input + * @return array Multi-dimension array with basic, non-basic and aggregated code points + */ + protected function listCodePoints($input) + { + $codePoints = array( + 'all' => array(), + 'basic' => array(), + 'nonBasic' => array(), + ); + + $length = mb_strlen($input, $this->encoding); + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($input, $i, 1, $this->encoding); + $code = $this->charToCodePoint($char); + if ($code < 128) { + $codePoints['all'][] = $codePoints['basic'][] = $code; + } else { + $codePoints['all'][] = $codePoints['nonBasic'][] = $code; + } + } + + return $codePoints; + } + + /** + * Convert a single or multi-byte character to its code point + * + * @param string $char + * @return integer + */ + protected function charToCodePoint($char) + { + $code = ord($char[0]); + if ($code < 128) { + return $code; + } elseif ($code < 224) { + return (($code - 192) * 64) + (ord($char[1]) - 128); + } elseif ($code < 240) { + return (($code - 224) * 4096) + ((ord($char[1]) - 128) * 64) + (ord($char[2]) - 128); + } else { + return (($code - 240) * 262144) + ((ord($char[1]) - 128) * 4096) + ((ord($char[2]) - 128) * 64) + (ord($char[3]) - 128); + } + } + + /** + * Convert a code point to its single or multi-byte character + * + * @param integer $code + * @return string + */ + protected function codePointToChar($code) + { + if ($code <= 0x7F) { + return chr($code); + } elseif ($code <= 0x7FF) { + return chr(($code >> 6) + 192) . chr(($code & 63) + 128); + } elseif ($code <= 0xFFFF) { + return chr(($code >> 12) + 224) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128); + } else { + return chr(($code >> 18) + 240) . chr((($code >> 12) & 63) + 128) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128); + } + } +} diff --git a/pages/ajax.oauth.wizard.php b/pages/ajax.oauth.wizard.php new file mode 100644 index 000000000..e0eded32a --- /dev/null +++ b/pages/ajax.oauth.wizard.php @@ -0,0 +1,74 @@ +AllowOnlyAdmin(); +$oUpdateController->SetDefaultOperation('Default'); +$oUpdateController->HandleOperation(); + + + + + +//require_once(APPROOT.'application/utils.inc.php'); +//require_once(APPROOT.'/application/application.inc.php'); +// +//require_once(APPROOT.'/application/loginwebpage.class.inc.php'); +// +//$oPage = new JsonPage(); +//$oPage->SetOutputDataOnly(true); +//$aResult = ['status' => 'success', 'data' => []]; +//try { +// $operation = utils::ReadParam('operation', ''); +// +// switch ($operation) { +// case 'get_authorization_url': +// $sProvider = utils::ReadParam('provider', '', false, 'raw'); +// $sClientId = utils::ReadParam('client_id', '', false, 'raw'); +// $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); +// $sScope = utils::ReadParam('scope', '', false, 'raw'); +// $sAdditional = utils::ReadParam('additional', '', false, 'raw'); +// $aAdditional = []; +// parse_str($sAdditional, $aAdditional); +// $sAuthorizationUrl = OAuthClientProviderFactory::getVendorProviderForAccessUrl($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); +// $aResult['data']['authorization_url'] = $sAuthorizationUrl; +// break; +// case 'get_display_authentication_results': +// $sProvider = utils::ReadParam('provider', '', false, 'raw'); +// $sRedirectUrl = utils::ReadParam('redirect_url', '', false, 'raw'); +// $sClientId = utils::ReadParam('client_id', '', false, 'raw'); +// $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); +// $sScope = utils::ReadParam('scope', '', false, 'raw'); +// $sAdditional = utils::ReadParam('additional', '', false, 'raw'); +// +// $sRedirectUrlQuery = parse_url($sRedirectUrl)['query']; +// $aOAuthResultDisplayClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); +// $aAdditional = []; +// parse_str($sAdditional, $aAdditional); +// +// $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProvider; +// $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); +// +// $aQuery = []; +// parse_str($sRedirectUrlQuery, $aQuery); +// $sCode = $aQuery['code']; +// $oProvider = OAuthClientProviderFactory::getVendorProvider($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); +// $oAccessToken = OAuthClientProviderFactory::getAccessTokenFromCode($oProvider, $sCode); +// +// foreach($aOAuthResultDisplayClasses as $sOAuthClass) { +// $aResult['data'][] = $sOAuthClass::GetResultDisplayScript($sClientId, $sClientSecret, $sProvider, $oAccessToken); +// } +// } +//} +//catch(Exception $e){ +// $aResult['status'] = 'error'; +// IssueLog::Error($e->getMessage()); +//} +//$oPage->SetData($aResult); +//$oPage->output(); \ No newline at end of file diff --git a/pages/oauth/landing.php b/pages/oauth.landing.php similarity index 100% rename from pages/oauth/landing.php rename to pages/oauth.landing.php diff --git a/pages/oauth.wizard.php b/pages/oauth.wizard.php new file mode 100644 index 000000000..507e6a401 --- /dev/null +++ b/pages/oauth.wizard.php @@ -0,0 +1,14 @@ +AllowOnlyAdmin(); +$oUpdateController->SetDefaultOperation('Wizard'); +$oUpdateController->HandleOperation(); + diff --git a/pages/oauth/ajax.wizard.php b/pages/oauth/ajax.wizard.php deleted file mode 100644 index 1d3ca1ce8..000000000 --- a/pages/oauth/ajax.wizard.php +++ /dev/null @@ -1,63 +0,0 @@ -SetOutputDataOnly(true); -$aResult = ['status' => 'success', 'data' => []]; -try { - $operation = utils::ReadParam('operation', ''); - - switch ($operation) { - case 'get_authorization_url': - $sProvider = utils::ReadParam('provider', '', false, 'raw'); - $sClientId = utils::ReadParam('client_id', '', false, 'raw'); - $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); - $sScope = utils::ReadParam('scope', '', false, 'raw'); - $sAdditional = utils::ReadParam('additional', '', false, 'raw'); - $aAdditional = []; - parse_str($sAdditional, $aAdditional); - $sAuthorizationUrl = OAuthClientProviderFactory::getVendorProviderForAccessUrl($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); - $aResult['data']['authorization_url'] = $sAuthorizationUrl; - break; - case 'get_display_authentication_results': - $sProvider = utils::ReadParam('provider', '', false, 'raw'); - $sRedirectUrl = utils::ReadParam('redirect_url', '', false, 'raw'); - $sClientId = utils::ReadParam('client_id', '', false, 'raw'); - $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); - $sScope = utils::ReadParam('scope', '', false, 'raw'); - $sAdditional = utils::ReadParam('additional', '', false, 'raw'); - - $sRedirectUrlQuery = parse_url($sRedirectUrl)['query']; - $aOAuthResultDisplayClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); - $aAdditional = []; - parse_str($sAdditional, $aAdditional); - - $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProvider; - $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); - - $aQuery = []; - parse_str($sRedirectUrlQuery, $aQuery); - $sCode = $aQuery['code']; - $oProvider = OAuthClientProviderFactory::getVendorProvider($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); - $oAccessToken = OAuthClientProviderFactory::getAccessTokenFromCode($oProvider, $sCode); - - foreach($aOAuthResultDisplayClasses as $sOAuthClass) { - $aResult['data'][] = $sOAuthClass::GetResultDisplayScript($sClientId, $sClientSecret, $sProvider, $oAccessToken); - } - } -} -catch(Exception $e){ - $aResult['status'] = 'error'; - IssueLog::Error($e->getMessage()); -} -$oPage->SetData($aResult); -$oPage->output(); \ No newline at end of file diff --git a/pages/oauth/wizard.php b/pages/oauth/wizard.php deleted file mode 100644 index a88c90950..000000000 --- a/pages/oauth/wizard.php +++ /dev/null @@ -1,159 +0,0 @@ -AddCSSClass('ibo-oauth-wizard'); -$oPage = new iTopWebPage(Dict::S('UI:OAuth:Wizard:Page:Title')); -$oPage->SetContentLayout($oLayout); -$sReturnUri = utils::GetAbsoluteUrlAppRoot().'pages/oauth.landing.php'; -$sAjaxUri = utils::GetAbsoluteUrlAppRoot().'pages/ajax.oauth.wizard.php'; - -$sJS = << { - // remove any existing event listeners - - // window features - const strWindowFeatures = - 'toolbar=no, menubar=no, width=600, height=700, top=100, left=100'; - - if (windowObjectReference === null || windowObjectReference.closed) { - /* if the pointer to the window object in memory does not exist - or if such pointer exists but the window was closed */ - windowObjectReference = window.open(url, name, strWindowFeatures); - } else if (previousUrl !== url) { - /* if the resource to load is different, - then we load it in the already opened secondary window and then - we bring such window back on top/in front of its parent window. */ - windowObjectReference = window.open(url, name, strWindowFeatures); - windowObjectReference.focus(); - } else { - /* else the window reference must exist and the window - is not closed; therefore, we can bring it back on top of any other - window with the focus() method. There would be no need to re-create - the window or to reload the referenced resource. */ - windowObjectReference.focus(); - } - let oListener = window.setInterval(function(){ - windowObjectReference.postMessage('anyone', '$sReturnUri'); - }, 1000); - - - window.addEventListener("message", function (event){ - clearInterval(oListener); - $.post( - '$sAjaxUri', - { - operation: 'get_display_authentication_results', - provider: $('[name="provider"]:checked').val(), - client_id: $('[name="client_id"]').val(), - client_secret: $('[name="client_secret"]').val(), - scope: $(this).find('[name="scope"]').val(), - additional: $(this).find('[name="additional"]').val(), - redirect_url: event.data, - }, - function(oData){ - if(oData.status == 'success') - { - oData.data.forEach(function(item, index){ - new Function(item)(); - }); - } - $('.ibo-oauth-wizard--form--submit').trigger('leave_loading_state.button.itop'); - } - ); - }, false); - // add the listener for receiving a message from the popup - // assign the previous URL - previousUrl = url; - }; -JS; - -$oPage->add_script($sJS); -$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'/js/pages/backoffice/oauth.wizard.js'); - -$oOauthInputsPanel = PanelUIBlockFactory::MakeNeutral(Dict::S('UI:OAuth:Wizard:Form:Panel:Title')); -$oOauthInputsPanel->AddCSSClass('ibo-oauth-wizard'); - -$aOAuthClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientProvider', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); -$sFormJs = <<
'); -$oOauthInputsPanel->AddSubBlock($oProviderSelect); -$sIsChecked = ' checked '; -foreach($aOAuthClasses as $sOAuthClass){ - $aColors = $sOAuthClass::GetVendorColors(); - $oProviderSelect->AddHtml(''); - $sIsChecked = ''; -} -$oForm = FormUIBlockFactory::MakeStandard(); -$oForm->AddCSSClasses(['ibo-oauth-wizard--form', 'ibo-oauth-wizard--form-'.strtolower($sOAuthClass::GetVendorName())]); -$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('url', '')); -foreach (['client_id' => Dict::S('UI:OAuth:Wizard:Form:Input:ClientId:Label'), - 'client_secret' => Dict::S('UI:OAuth:Wizard:Form:Input:ClientSecret:Label'), - 'scope' => Dict::S('UI:OAuth:Wizard:Form:Input:Scope:Label'), - 'additional' => Dict::S('UI:OAuth:Wizard:Form:Input:Additional:Label')] as $sName => $sLabel){ - $oForm->AddSubBlock(InputUIBlockFactory::MakeForInputWithLabel($sLabel, $sName, null, null, 'text')); -} -$oRedirectUriInput = InputUIBlockFactory::MakeForInputWithLabel(Dict::S('UI:OAuth:Wizard:Form:Input:RedirectUri:Label'), 'redirect_uri', OAuthClientProviderGoogle::GetRedirectUri(), null, 'text'); -$oRedirectUriInput->GetInput()->SetIsReadonly(true); -$oForm->AddSubBlock($oRedirectUriInput); - -$oForm->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:OAuth:Wizard:Form:Button:Submit:Label'),'submit', '', true)->AddCSSClass('ibo-oauth-wizard--form--submit')); -$oForm->SetOnSubmitJsCode($sFormJs); -$oOauthInputsPanel->AddSubBlock($oForm); -$oProviderSelect->AddHtml('
'); -$oOauthInputsPanel->AddHtml(''); - -$sOnReadyJs = "$('#select_layout').controlgroup(); $('.ibo-oauth-wizard--result--panel .ibo-panel--collapsible-toggler').click();"; -$oPage->add_ready_script($sOnReadyJs); -$oOauthInputsPanel->AddHtml('
'.file_get_contents(APPROOT.'images/illustrations/undraw_access_account.svg').'
'); -$oPage->AddSubBlock($oOauthInputsPanel); - -$aOAuthResultDisplayClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); -foreach($aOAuthResultDisplayClasses as $sOAuthClass) { - $oPage->AddSubBlock($sOAuthClass::GetResultDisplayBlock()); -} - - -$oPage->output(); diff --git a/sources/Controller/OAuth/OAuthAjaxController.php b/sources/Controller/OAuth/OAuthAjaxController.php new file mode 100644 index 000000000..73954e26a --- /dev/null +++ b/sources/Controller/OAuth/OAuthAjaxController.php @@ -0,0 +1,26 @@ + 'success', 'data' => []]; + $sProvider = utils::ReadParam('provider', '', false, 'raw'); + $sClientId = utils::ReadParam('client_id', '', false, 'raw'); + $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); + $sScope = utils::ReadParam('scope', '', false, 'raw'); + $sAdditional = utils::ReadParam('additional', '', false, 'raw'); + $aAdditional = []; + parse_str($sAdditional, $aAdditional); + $sAuthorizationUrl = OAuthClientProviderFactory::getVendorProviderForAccessUrl($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); + $aResult['data']['authorization_url'] = $sAuthorizationUrl; + + $this->DisplayJSONPage($aResult); + } +} \ No newline at end of file diff --git a/sources/Controller/OAuth/OAuthWizardController.php b/sources/Controller/OAuth/OAuthWizardController.php index f109a2370..b2e53641a 100644 --- a/sources/Controller/OAuth/OAuthWizardController.php +++ b/sources/Controller/OAuth/OAuthWizardController.php @@ -8,25 +8,25 @@ namespace Combodo\iTop\Controller\OAuth; use Combodo\iTop\Application\TwigBase\Controller\Controller; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderAbstract; +use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientResultDisplayConf; use Dict; use utils; class OAuthWizardController extends Controller { - public function WizardOperation() + public function OperationWizard() { $aParams = []; $aParams['sReturnUri'] = OAuthClientProviderAbstract::GetRedirectUri(); - $aParams['sAjaxUri'] = utils::GetAbsoluteUrlAppRoot().'pages/oauth/ajax.wizard.php'; - - + $aParams['sAjaxUri'] = utils::GetAbsoluteUrlAppRoot().'pages/ajax.oauth.wizard.php'; //$this->AddLinkedScript(utils::GetAbsoluteUrlAppRoot().'/js/pages/backoffice/oauth.wizard.js'); $aOAuthClasses = [ 'Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderAzure', 'Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderGoogle', ]; + foreach ($aOAuthClasses as $sOAuthClass) { $aParams['aProviders'][] = [ 'name' => $sOAuthClass::GetVendorName(), @@ -43,6 +43,10 @@ class OAuthWizardController extends Controller 'redirect_uri' => ['type' => 'text', 'label' => Dict::S('UI:OAuth:Wizard:Form:Input:RedirectUri:Label'), 'read_only' => true, 'value' => OAuthClientProviderAbstract::GetRedirectUri()], ]; + $aParams['aAdditionalBlocks'] = [ + OAuthClientResultDisplayConf::GetResultDisplayTemplate(), + ]; + $this->DisplayPage($aParams); } } \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php b/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php index 8fa70b5ff..cf7701f39 100644 --- a/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php +++ b/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php @@ -5,4 +5,7 @@ use League\OAuth2\Client\Token\AccessToken; interface IOAuthClientResultDisplay{ public static function GetResultDisplayBlock(); public static function GetResultDisplayScript($sClientId, $sClientSecret, $sVendor, AccessToken $oAccessToken); + + public static function GetResultDisplayTemplate(); + //public static function GetResultDisplayParams($sClientId, $sClientSecret, $sVendor, AccessToken $oAccessToken); } \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php index ef719e487..f99106713 100644 --- a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php @@ -105,7 +105,7 @@ EOF; */ public static function InitizalizeRedirectUri() { - static::$sRedirectUri = utils::GetAbsoluteUrlAppRoot().'pages/oauth/landing.php'; + static::$sRedirectUri = utils::GetAbsoluteUrlAppRoot().'pages/oauth.landing.php'; } /** diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php b/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php index 92c1bdaa4..36ae42a26 100644 --- a/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php @@ -37,4 +37,9 @@ $('#ibo-oauth-wizard--conf--result').text($sConf); JS; } + + public static function GetResultDisplayTemplate() + { + return 'DisplayConfig.html.twig'; + } } \ No newline at end of file diff --git a/sources/application/TwigBase/Twig/TwigHelper.php b/sources/application/TwigBase/Twig/TwigHelper.php index 9637d54d7..99253e701 100644 --- a/sources/application/TwigBase/Twig/TwigHelper.php +++ b/sources/application/TwigBase/Twig/TwigHelper.php @@ -24,8 +24,10 @@ class TwigHelper Extension::RegisterTwigExtensions($oTwig); $sLocalPath = utils::LocalPath($sViewPath); $sLocalPath = str_replace('env-'.utils::GetCurrentEnvironment(), 'twig', $sLocalPath); - $sCachePath = utils::GetCachePath().$sLocalPath; - $oTwig->setCache($sCachePath); + if (!utils::IsDevelopmentEnvironment()) { + $sCachePath = utils::GetCachePath().$sLocalPath; + $oTwig->setCache($sCachePath); + } return $oTwig; } diff --git a/templates/pages/backoffice/oauth/DisplayConfig.html.twig b/templates/pages/backoffice/oauth/DisplayConfig.html.twig new file mode 100644 index 000000000..ed018faee --- /dev/null +++ b/templates/pages/backoffice/oauth/DisplayConfig.html.twig @@ -0,0 +1,8 @@ +{# @copyright Copyright (C) 2010-2022 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} + +
+ {{ 'UI:OAuth:Wizard:ResultConf:Panel:Title'|dict_s }} +

{{ 'UI:OAuthEmailSynchro:Wizard:ResultConf:Panel:Description'|dict_s }}

+
+
\ No newline at end of file diff --git a/templates/pages/backoffice/oauth/Wizard.html.twig b/templates/pages/backoffice/oauth/Wizard.html.twig index b2b70f92f..5d4a2555f 100644 --- a/templates/pages/backoffice/oauth/Wizard.html.twig +++ b/templates/pages/backoffice/oauth/Wizard.html.twig @@ -45,5 +45,8 @@ + {% for sTemplate in aAdditionalBlocks %} + {% include sTemplate %} + {% endfor %} \ No newline at end of file diff --git a/templates/pages/backoffice/oauth/Wizard.ready.js.twig b/templates/pages/backoffice/oauth/Wizard.ready.js.twig index bac63f187..e33478887 100644 --- a/templates/pages/backoffice/oauth/Wizard.ready.js.twig +++ b/templates/pages/backoffice/oauth/Wizard.ready.js.twig @@ -6,7 +6,7 @@ var oListener = null const oOnOauthSuccess = function (event){ clearInterval(oListener); $.post( - {{ sAjaxUri }}, + '{{ sAjaxUri }}', { operation: 'get_display_authentication_results', provider: $('[name="provider"]:checked').val(), @@ -58,7 +58,7 @@ const oOpenSignInWindow = function (url, name){ once we reach our landing page, it'll send us a reply */ oListener = window.setInterval(function(){ - oWindowObjectReference.postMessage('anyone', {{ sReturnUri }}); + oWindowObjectReference.postMessage('anyone', '{{ sReturnUri }}'); }, 1000); /* Once we receive a response, transmit it to the server to get authenticate and display results @@ -73,9 +73,9 @@ const oOpenSignInWindow = function (url, name){ const oOnFormSubmit = function(){ $('.ibo-oauth-wizard--form--submit').trigger('enter_loading_state.button.itop'); $.post( - {{ sAjaxUri }}, + '{{ sAjaxUri }}', { - operation: 'get_authorization_url', + operation: 'GetAuthorizationUrl', provider: $('[name="provider"]:checked').val(), client_id: $(this).find('[name="client_id"]').val(), client_secret: $(this).find('[name="client_secret"]').val(), @@ -123,4 +123,6 @@ $('body').on('click', '.ui-checkboxradio-radio-label', function(){ oUpdateProviderImage($('[name="provider"]:checked').next()); // Initialize provider buttons -$('#select_layout').controlgroup(); +$('#select_layout').buttonset(); + +$('.ibo-oauth-wizard--form').on('submit', oOnFormSubmit); From 6b80bbeaa25aa5523a1cdf50a9c870c1866c02de Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Fri, 13 May 2022 11:45:42 +0200 Subject: [PATCH 04/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=202.7=20migration=20(wip)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 1 + composer.lock | 2 +- lib/composer/InstalledVersions.php | 15 - lib/composer/autoload_classmap.php | 447 ++++++ lib/composer/autoload_files.php | 5 + lib/composer/autoload_psr4.php | 16 + lib/composer/autoload_static.php | 545 ++++++++ lib/composer/installed.json | 1227 +++++++++++++++++ lib/composer/installed.php | 215 ++- .../Controller/OAuth/OAuthAjaxController.php | 32 + 10 files changed, 2487 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index ec3e9df39..fc0230f6a 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "ext-mysqli": "*", "ext-soap": "*", "combodo/tcpdf": "~6.4.4", + "guzzlehttp/guzzle": "^6.5", "laminas/laminas-mail": "^2.11", "laminas/laminas-servicemanager": "^3.5", "league/oauth2-google": "^3.0", diff --git a/composer.lock b/composer.lock index f753cd18b..f69883a43 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "16a1d6529177e5ab83a163236d9602ea", + "content-hash": "070937bbd448424d38382295e72909d6", "packages": [ { "name": "combodo/tcpdf", diff --git a/lib/composer/InstalledVersions.php b/lib/composer/InstalledVersions.php index 41bc143c1..7c5502ca4 100644 --- a/lib/composer/InstalledVersions.php +++ b/lib/composer/InstalledVersions.php @@ -21,26 +21,11 @@ use Composer\Semver\VersionParser; * 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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null - */ private static $installed; - - /** - * @var bool|null - */ private static $canGetVendors; - - /** - * @var array[] - * @psalm-var array}> - */ private static $installedByVendor = array(); /** diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 82cbbe76f..872d9138e 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -338,11 +338,97 @@ return array( 'FilterDefinition' => $baseDir . '/core/filterdef.class.inc.php', 'FilterFromAttribute' => $baseDir . '/core/filterdef.class.inc.php', 'FilterPrivateKey' => $baseDir . '/core/filterdef.class.inc.php', + 'Firebase\\JWT\\BeforeValidException' => $vendorDir . '/firebase/php-jwt/src/BeforeValidException.php', + 'Firebase\\JWT\\ExpiredException' => $vendorDir . '/firebase/php-jwt/src/ExpiredException.php', + 'Firebase\\JWT\\JWK' => $vendorDir . '/firebase/php-jwt/src/JWK.php', + 'Firebase\\JWT\\JWT' => $vendorDir . '/firebase/php-jwt/src/JWT.php', + 'Firebase\\JWT\\Key' => $vendorDir . '/firebase/php-jwt/src/Key.php', + 'Firebase\\JWT\\SignatureInvalidException' => $vendorDir . '/firebase/php-jwt/src/SignatureInvalidException.php', 'FunctionExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'FunctionOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'GraphEdge' => $baseDir . '/core/simplegraph.class.inc.php', 'GraphElement' => $baseDir . '/core/simplegraph.class.inc.php', 'GraphNode' => $baseDir . '/core/simplegraph.class.inc.php', + 'GuzzleHttp\\Client' => $vendorDir . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => $vendorDir . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\Cookie\\CookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\InvalidArgumentException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php', + 'GuzzleHttp\\Exception\\RequestException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\SeekException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/SeekException.php', + 'GuzzleHttp\\Exception\\ServerException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => $vendorDir . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\MockHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => $vendorDir . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\Middleware' => $vendorDir . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => $vendorDir . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => $vendorDir . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => $vendorDir . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => $vendorDir . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\Create' => $vendorDir . '/guzzlehttp/promises/src/Create.php', + 'GuzzleHttp\\Promise\\Each' => $vendorDir . '/guzzlehttp/promises/src/Each.php', + 'GuzzleHttp\\Promise\\EachPromise' => $vendorDir . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => $vendorDir . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Is' => $vendorDir . '/guzzlehttp/promises/src/Is.php', + 'GuzzleHttp\\Promise\\Promise' => $vendorDir . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => $vendorDir . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => $vendorDir . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => $vendorDir . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => $vendorDir . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => $vendorDir . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => $vendorDir . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Promise\\Utils' => $vendorDir . '/guzzlehttp/promises/src/Utils.php', + 'GuzzleHttp\\Psr7\\AppendStream' => $vendorDir . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => $vendorDir . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => $vendorDir . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => $vendorDir . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\FnStream' => $vendorDir . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\Header' => $vendorDir . '/guzzlehttp/psr7/src/Header.php', + 'GuzzleHttp\\Psr7\\InflateStream' => $vendorDir . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => $vendorDir . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => $vendorDir . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\Message' => $vendorDir . '/guzzlehttp/psr7/src/Message.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => $vendorDir . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MimeType' => $vendorDir . '/guzzlehttp/psr7/src/MimeType.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => $vendorDir . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => $vendorDir . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => $vendorDir . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Query' => $vendorDir . '/guzzlehttp/psr7/src/Query.php', + 'GuzzleHttp\\Psr7\\Request' => $vendorDir . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => $vendorDir . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\Rfc7230' => $vendorDir . '/guzzlehttp/psr7/src/Rfc7230.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => $vendorDir . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => $vendorDir . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => $vendorDir . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => $vendorDir . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => $vendorDir . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => $vendorDir . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => $vendorDir . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => $vendorDir . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\Psr7\\Utils' => $vendorDir . '/guzzlehttp/psr7/src/Utils.php', + 'GuzzleHttp\\RedirectMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => $vendorDir . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => $vendorDir . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\UriTemplate' => $vendorDir . '/guzzlehttp/guzzle/src/UriTemplate.php', + 'GuzzleHttp\\Utils' => $vendorDir . '/guzzlehttp/guzzle/src/Utils.php', 'HTMLBulkExport' => $baseDir . '/core/htmlbulkexport.class.inc.php', 'HTMLDOMSanitizer' => $baseDir . '/core/htmlsanitizer.class.inc.php', 'HTMLNullSanitizer' => $baseDir . '/core/htmlsanitizer.class.inc.php', @@ -354,6 +440,9 @@ return array( 'InlineImage' => $baseDir . '/core/inlineimage.class.inc.php', 'InlineImageGC' => $baseDir . '/core/inlineimage.class.inc.php', 'InputOutputTask' => $baseDir . '/application/iotask.class.inc.php', + 'Interop\\Container\\ContainerInterface' => $vendorDir . '/container-interop/container-interop/src/Interop/Container/ContainerInterface.php', + 'Interop\\Container\\Exception\\ContainerException' => $vendorDir . '/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php', + 'Interop\\Container\\Exception\\NotFoundException' => $vendorDir . '/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php', 'IntervalExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'IntervalOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'Introspection' => $baseDir . '/core/introspection.class.inc.php', @@ -364,7 +453,350 @@ return array( 'JSButtonItem' => $baseDir . '/application/applicationextension.inc.php', 'JSPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', 'KeyValueStore' => $baseDir . '/core/counter.class.inc.php', + 'Laminas\\Loader\\AutoloaderFactory' => $vendorDir . '/laminas/laminas-loader/src/AutoloaderFactory.php', + 'Laminas\\Loader\\ClassMapAutoloader' => $vendorDir . '/laminas/laminas-loader/src/ClassMapAutoloader.php', + 'Laminas\\Loader\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-loader/src/Exception/BadMethodCallException.php', + 'Laminas\\Loader\\Exception\\DomainException' => $vendorDir . '/laminas/laminas-loader/src/Exception/DomainException.php', + 'Laminas\\Loader\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-loader/src/Exception/ExceptionInterface.php', + 'Laminas\\Loader\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-loader/src/Exception/InvalidArgumentException.php', + 'Laminas\\Loader\\Exception\\InvalidPathException' => $vendorDir . '/laminas/laminas-loader/src/Exception/InvalidPathException.php', + 'Laminas\\Loader\\Exception\\MissingResourceNamespaceException' => $vendorDir . '/laminas/laminas-loader/src/Exception/MissingResourceNamespaceException.php', + 'Laminas\\Loader\\Exception\\PluginLoaderException' => $vendorDir . '/laminas/laminas-loader/src/Exception/PluginLoaderException.php', + 'Laminas\\Loader\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-loader/src/Exception/RuntimeException.php', + 'Laminas\\Loader\\Exception\\SecurityException' => $vendorDir . '/laminas/laminas-loader/src/Exception/SecurityException.php', + 'Laminas\\Loader\\ModuleAutoloader' => $vendorDir . '/laminas/laminas-loader/src/ModuleAutoloader.php', + 'Laminas\\Loader\\PluginClassLoader' => $vendorDir . '/laminas/laminas-loader/src/PluginClassLoader.php', + 'Laminas\\Loader\\PluginClassLocator' => $vendorDir . '/laminas/laminas-loader/src/PluginClassLocator.php', + 'Laminas\\Loader\\ShortNameLocator' => $vendorDir . '/laminas/laminas-loader/src/ShortNameLocator.php', + 'Laminas\\Loader\\SplAutoloader' => $vendorDir . '/laminas/laminas-loader/src/SplAutoloader.php', + 'Laminas\\Loader\\StandardAutoloader' => $vendorDir . '/laminas/laminas-loader/src/StandardAutoloader.php', + 'Laminas\\Mail\\Address' => $vendorDir . '/laminas/laminas-mail/src/Address.php', + 'Laminas\\Mail\\AddressList' => $vendorDir . '/laminas/laminas-mail/src/AddressList.php', + 'Laminas\\Mail\\Address\\AddressInterface' => $vendorDir . '/laminas/laminas-mail/src/Address/AddressInterface.php', + 'Laminas\\Mail\\ConfigProvider' => $vendorDir . '/laminas/laminas-mail/src/ConfigProvider.php', + 'Laminas\\Mail\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-mail/src/Exception/BadMethodCallException.php', + 'Laminas\\Mail\\Exception\\DomainException' => $vendorDir . '/laminas/laminas-mail/src/Exception/DomainException.php', + 'Laminas\\Mail\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Exception\\OutOfBoundsException' => $vendorDir . '/laminas/laminas-mail/src/Exception/OutOfBoundsException.php', + 'Laminas\\Mail\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Exception/RuntimeException.php', + 'Laminas\\Mail\\Header\\AbstractAddressList' => $vendorDir . '/laminas/laminas-mail/src/Header/AbstractAddressList.php', + 'Laminas\\Mail\\Header\\Bcc' => $vendorDir . '/laminas/laminas-mail/src/Header/Bcc.php', + 'Laminas\\Mail\\Header\\Cc' => $vendorDir . '/laminas/laminas-mail/src/Header/Cc.php', + 'Laminas\\Mail\\Header\\ContentDisposition' => $vendorDir . '/laminas/laminas-mail/src/Header/ContentDisposition.php', + 'Laminas\\Mail\\Header\\ContentTransferEncoding' => $vendorDir . '/laminas/laminas-mail/src/Header/ContentTransferEncoding.php', + 'Laminas\\Mail\\Header\\ContentType' => $vendorDir . '/laminas/laminas-mail/src/Header/ContentType.php', + 'Laminas\\Mail\\Header\\Date' => $vendorDir . '/laminas/laminas-mail/src/Header/Date.php', + 'Laminas\\Mail\\Header\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php', + 'Laminas\\Mail\\Header\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Header\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Header/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Header\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Header/Exception/RuntimeException.php', + 'Laminas\\Mail\\Header\\From' => $vendorDir . '/laminas/laminas-mail/src/Header/From.php', + 'Laminas\\Mail\\Header\\GenericHeader' => $vendorDir . '/laminas/laminas-mail/src/Header/GenericHeader.php', + 'Laminas\\Mail\\Header\\GenericMultiHeader' => $vendorDir . '/laminas/laminas-mail/src/Header/GenericMultiHeader.php', + 'Laminas\\Mail\\Header\\HeaderInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderInterface.php', + 'Laminas\\Mail\\Header\\HeaderLoader' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderLoader.php', + 'Laminas\\Mail\\Header\\HeaderName' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderName.php', + 'Laminas\\Mail\\Header\\HeaderValue' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderValue.php', + 'Laminas\\Mail\\Header\\HeaderWrap' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderWrap.php', + 'Laminas\\Mail\\Header\\IdentificationField' => $vendorDir . '/laminas/laminas-mail/src/Header/IdentificationField.php', + 'Laminas\\Mail\\Header\\InReplyTo' => $vendorDir . '/laminas/laminas-mail/src/Header/InReplyTo.php', + 'Laminas\\Mail\\Header\\ListParser' => $vendorDir . '/laminas/laminas-mail/src/Header/ListParser.php', + 'Laminas\\Mail\\Header\\MessageId' => $vendorDir . '/laminas/laminas-mail/src/Header/MessageId.php', + 'Laminas\\Mail\\Header\\MimeVersion' => $vendorDir . '/laminas/laminas-mail/src/Header/MimeVersion.php', + 'Laminas\\Mail\\Header\\MultipleHeadersInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php', + 'Laminas\\Mail\\Header\\Received' => $vendorDir . '/laminas/laminas-mail/src/Header/Received.php', + 'Laminas\\Mail\\Header\\References' => $vendorDir . '/laminas/laminas-mail/src/Header/References.php', + 'Laminas\\Mail\\Header\\ReplyTo' => $vendorDir . '/laminas/laminas-mail/src/Header/ReplyTo.php', + 'Laminas\\Mail\\Header\\Sender' => $vendorDir . '/laminas/laminas-mail/src/Header/Sender.php', + 'Laminas\\Mail\\Header\\StructuredInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/StructuredInterface.php', + 'Laminas\\Mail\\Header\\Subject' => $vendorDir . '/laminas/laminas-mail/src/Header/Subject.php', + 'Laminas\\Mail\\Header\\To' => $vendorDir . '/laminas/laminas-mail/src/Header/To.php', + 'Laminas\\Mail\\Header\\UnstructuredInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/UnstructuredInterface.php', + 'Laminas\\Mail\\Headers' => $vendorDir . '/laminas/laminas-mail/src/Headers.php', + 'Laminas\\Mail\\Message' => $vendorDir . '/laminas/laminas-mail/src/Message.php', + 'Laminas\\Mail\\MessageFactory' => $vendorDir . '/laminas/laminas-mail/src/MessageFactory.php', + 'Laminas\\Mail\\Module' => $vendorDir . '/laminas/laminas-mail/src/Module.php', + 'Laminas\\Mail\\Protocol\\AbstractProtocol' => $vendorDir . '/laminas/laminas-mail/src/Protocol/AbstractProtocol.php', + 'Laminas\\Mail\\Protocol\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Protocol\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Protocol\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Exception/RuntimeException.php', + 'Laminas\\Mail\\Protocol\\Imap' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Imap.php', + 'Laminas\\Mail\\Protocol\\Pop3' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Pop3.php', + 'Laminas\\Mail\\Protocol\\ProtocolTrait' => $vendorDir . '/laminas/laminas-mail/src/Protocol/ProtocolTrait.php', + 'Laminas\\Mail\\Protocol\\Smtp' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Smtp.php', + 'Laminas\\Mail\\Protocol\\SmtpPluginManager' => $vendorDir . '/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php', + 'Laminas\\Mail\\Protocol\\SmtpPluginManagerFactory' => $vendorDir . '/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Crammd5' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Login' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php', 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Oauth' => $baseDir . '/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Plain' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php', + 'Laminas\\Mail\\Storage' => $vendorDir . '/laminas/laminas-mail/src/Storage.php', + 'Laminas\\Mail\\Storage\\AbstractStorage' => $vendorDir . '/laminas/laminas-mail/src/Storage/AbstractStorage.php', + 'Laminas\\Mail\\Storage\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Storage\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Storage\\Exception\\OutOfBoundsException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Exception/OutOfBoundsException.php', + 'Laminas\\Mail\\Storage\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Exception/RuntimeException.php', + 'Laminas\\Mail\\Storage\\Folder' => $vendorDir . '/laminas/laminas-mail/src/Storage/Folder.php', + 'Laminas\\Mail\\Storage\\Folder\\FolderInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php', + 'Laminas\\Mail\\Storage\\Folder\\Maildir' => $vendorDir . '/laminas/laminas-mail/src/Storage/Folder/Maildir.php', + 'Laminas\\Mail\\Storage\\Folder\\Mbox' => $vendorDir . '/laminas/laminas-mail/src/Storage/Folder/Mbox.php', + 'Laminas\\Mail\\Storage\\Imap' => $vendorDir . '/laminas/laminas-mail/src/Storage/Imap.php', + 'Laminas\\Mail\\Storage\\Maildir' => $vendorDir . '/laminas/laminas-mail/src/Storage/Maildir.php', + 'Laminas\\Mail\\Storage\\Mbox' => $vendorDir . '/laminas/laminas-mail/src/Storage/Mbox.php', + 'Laminas\\Mail\\Storage\\Message' => $vendorDir . '/laminas/laminas-mail/src/Storage/Message.php', + 'Laminas\\Mail\\Storage\\Message\\File' => $vendorDir . '/laminas/laminas-mail/src/Storage/Message/File.php', + 'Laminas\\Mail\\Storage\\Message\\MessageInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Message/MessageInterface.php', + 'Laminas\\Mail\\Storage\\Part' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/Exception/RuntimeException.php', + 'Laminas\\Mail\\Storage\\Part\\File' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/File.php', + 'Laminas\\Mail\\Storage\\Part\\PartInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/PartInterface.php', + 'Laminas\\Mail\\Storage\\Pop3' => $vendorDir . '/laminas/laminas-mail/src/Storage/Pop3.php', + 'Laminas\\Mail\\Storage\\Writable\\Maildir' => $vendorDir . '/laminas/laminas-mail/src/Storage/Writable/Maildir.php', + 'Laminas\\Mail\\Storage\\Writable\\WritableInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php', + 'Laminas\\Mail\\Transport\\Envelope' => $vendorDir . '/laminas/laminas-mail/src/Transport/Envelope.php', + 'Laminas\\Mail\\Transport\\Exception\\DomainException' => $vendorDir . '/laminas/laminas-mail/src/Transport/Exception/DomainException.php', + 'Laminas\\Mail\\Transport\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Transport/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Transport\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Transport/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Transport\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Transport/Exception/RuntimeException.php', + 'Laminas\\Mail\\Transport\\Factory' => $vendorDir . '/laminas/laminas-mail/src/Transport/Factory.php', + 'Laminas\\Mail\\Transport\\File' => $vendorDir . '/laminas/laminas-mail/src/Transport/File.php', + 'Laminas\\Mail\\Transport\\FileOptions' => $vendorDir . '/laminas/laminas-mail/src/Transport/FileOptions.php', + 'Laminas\\Mail\\Transport\\InMemory' => $vendorDir . '/laminas/laminas-mail/src/Transport/InMemory.php', + 'Laminas\\Mail\\Transport\\Null' => $vendorDir . '/laminas/laminas-mail/src/Transport/Null.php', + 'Laminas\\Mail\\Transport\\Sendmail' => $vendorDir . '/laminas/laminas-mail/src/Transport/Sendmail.php', + 'Laminas\\Mail\\Transport\\Smtp' => $vendorDir . '/laminas/laminas-mail/src/Transport/Smtp.php', + 'Laminas\\Mail\\Transport\\SmtpOptions' => $vendorDir . '/laminas/laminas-mail/src/Transport/SmtpOptions.php', + 'Laminas\\Mail\\Transport\\TransportInterface' => $vendorDir . '/laminas/laminas-mail/src/Transport/TransportInterface.php', + 'Laminas\\Mime\\Decode' => $vendorDir . '/laminas/laminas-mime/src/Decode.php', + 'Laminas\\Mime\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mime/src/Exception/ExceptionInterface.php', + 'Laminas\\Mime\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mime/src/Exception/InvalidArgumentException.php', + 'Laminas\\Mime\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mime/src/Exception/RuntimeException.php', + 'Laminas\\Mime\\Message' => $vendorDir . '/laminas/laminas-mime/src/Message.php', + 'Laminas\\Mime\\Mime' => $vendorDir . '/laminas/laminas-mime/src/Mime.php', + 'Laminas\\Mime\\Part' => $vendorDir . '/laminas/laminas-mime/src/Part.php', + 'Laminas\\ServiceManager\\AbstractFactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php', + 'Laminas\\ServiceManager\\AbstractFactory\\ConfigAbstractFactory' => $vendorDir . '/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php', + 'Laminas\\ServiceManager\\AbstractFactory\\ReflectionBasedAbstractFactory' => $vendorDir . '/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php', + 'Laminas\\ServiceManager\\AbstractPluginManager' => $vendorDir . '/laminas/laminas-servicemanager/src/AbstractPluginManager.php', + 'Laminas\\ServiceManager\\Config' => $vendorDir . '/laminas/laminas-servicemanager/src/Config.php', + 'Laminas\\ServiceManager\\ConfigInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/ConfigInterface.php', + 'Laminas\\ServiceManager\\DelegatorFactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php', + 'Laminas\\ServiceManager\\Exception\\ContainerModificationsNotAllowedException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/ContainerModificationsNotAllowedException.php', + 'Laminas\\ServiceManager\\Exception\\CyclicAliasException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/CyclicAliasException.php', + 'Laminas\\ServiceManager\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php', + 'Laminas\\ServiceManager\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/InvalidArgumentException.php', + 'Laminas\\ServiceManager\\Exception\\InvalidServiceException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/InvalidServiceException.php', + 'Laminas\\ServiceManager\\Exception\\ServiceNotCreatedException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/ServiceNotCreatedException.php', + 'Laminas\\ServiceManager\\Exception\\ServiceNotFoundException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/ServiceNotFoundException.php', + 'Laminas\\ServiceManager\\FactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/FactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\AbstractFactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Factory/AbstractFactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\DelegatorFactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Factory/DelegatorFactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\FactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Factory/FactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\InvokableFactory' => $vendorDir . '/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php', + 'Laminas\\ServiceManager\\InitializerInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/InitializerInterface.php', + 'Laminas\\ServiceManager\\Initializer\\InitializerInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Initializer/InitializerInterface.php', + 'Laminas\\ServiceManager\\PluginManagerInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/PluginManagerInterface.php', + 'Laminas\\ServiceManager\\Proxy\\LazyServiceFactory' => $vendorDir . '/laminas/laminas-servicemanager/src/Proxy/LazyServiceFactory.php', + 'Laminas\\ServiceManager\\PsrContainerDecorator' => $vendorDir . '/laminas/laminas-servicemanager/src/PsrContainerDecorator.php', + 'Laminas\\ServiceManager\\ServiceLocatorInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php', + 'Laminas\\ServiceManager\\ServiceManager' => $vendorDir . '/laminas/laminas-servicemanager/src/ServiceManager.php', + 'Laminas\\ServiceManager\\Test\\CommonPluginManagerTrait' => $vendorDir . '/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php', + 'Laminas\\ServiceManager\\Tool\\ConfigDumper' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php', + 'Laminas\\ServiceManager\\Tool\\ConfigDumperCommand' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php', + 'Laminas\\ServiceManager\\Tool\\FactoryCreator' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php', + 'Laminas\\ServiceManager\\Tool\\FactoryCreatorCommand' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php', + 'Laminas\\Stdlib\\AbstractOptions' => $vendorDir . '/laminas/laminas-stdlib/src/AbstractOptions.php', + 'Laminas\\Stdlib\\ArrayObject' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayObject.php', + 'Laminas\\Stdlib\\ArraySerializableInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ArraySerializableInterface.php', + 'Laminas\\Stdlib\\ArrayStack' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayStack.php', + 'Laminas\\Stdlib\\ArrayUtils' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayUtils.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeRemoveKey' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeReplaceKey' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKey.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeReplaceKeyInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php', + 'Laminas\\Stdlib\\ConsoleHelper' => $vendorDir . '/laminas/laminas-stdlib/src/ConsoleHelper.php', + 'Laminas\\Stdlib\\DispatchableInterface' => $vendorDir . '/laminas/laminas-stdlib/src/DispatchableInterface.php', + 'Laminas\\Stdlib\\ErrorHandler' => $vendorDir . '/laminas/laminas-stdlib/src/ErrorHandler.php', + 'Laminas\\Stdlib\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/BadMethodCallException.php', + 'Laminas\\Stdlib\\Exception\\DomainException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/DomainException.php', + 'Laminas\\Stdlib\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/ExceptionInterface.php', + 'Laminas\\Stdlib\\Exception\\ExtensionNotLoadedException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/ExtensionNotLoadedException.php', + 'Laminas\\Stdlib\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/InvalidArgumentException.php', + 'Laminas\\Stdlib\\Exception\\LogicException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/LogicException.php', + 'Laminas\\Stdlib\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/RuntimeException.php', + 'Laminas\\Stdlib\\FastPriorityQueue' => $vendorDir . '/laminas/laminas-stdlib/src/FastPriorityQueue.php', + 'Laminas\\Stdlib\\Glob' => $vendorDir . '/laminas/laminas-stdlib/src/Glob.php', + 'Laminas\\Stdlib\\Guard\\AllGuardsTrait' => $vendorDir . '/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php', + 'Laminas\\Stdlib\\Guard\\ArrayOrTraversableGuardTrait' => $vendorDir . '/laminas/laminas-stdlib/src/Guard/ArrayOrTraversableGuardTrait.php', + 'Laminas\\Stdlib\\Guard\\EmptyGuardTrait' => $vendorDir . '/laminas/laminas-stdlib/src/Guard/EmptyGuardTrait.php', + 'Laminas\\Stdlib\\Guard\\NullGuardTrait' => $vendorDir . '/laminas/laminas-stdlib/src/Guard/NullGuardTrait.php', + 'Laminas\\Stdlib\\InitializableInterface' => $vendorDir . '/laminas/laminas-stdlib/src/InitializableInterface.php', + 'Laminas\\Stdlib\\JsonSerializable' => $vendorDir . '/laminas/laminas-stdlib/src/JsonSerializable.php', + 'Laminas\\Stdlib\\Message' => $vendorDir . '/laminas/laminas-stdlib/src/Message.php', + 'Laminas\\Stdlib\\MessageInterface' => $vendorDir . '/laminas/laminas-stdlib/src/MessageInterface.php', + 'Laminas\\Stdlib\\ParameterObjectInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ParameterObjectInterface.php', + 'Laminas\\Stdlib\\Parameters' => $vendorDir . '/laminas/laminas-stdlib/src/Parameters.php', + 'Laminas\\Stdlib\\ParametersInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ParametersInterface.php', + 'Laminas\\Stdlib\\PriorityList' => $vendorDir . '/laminas/laminas-stdlib/src/PriorityList.php', + 'Laminas\\Stdlib\\PriorityQueue' => $vendorDir . '/laminas/laminas-stdlib/src/PriorityQueue.php', + 'Laminas\\Stdlib\\Request' => $vendorDir . '/laminas/laminas-stdlib/src/Request.php', + 'Laminas\\Stdlib\\RequestInterface' => $vendorDir . '/laminas/laminas-stdlib/src/RequestInterface.php', + 'Laminas\\Stdlib\\Response' => $vendorDir . '/laminas/laminas-stdlib/src/Response.php', + 'Laminas\\Stdlib\\ResponseInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ResponseInterface.php', + 'Laminas\\Stdlib\\SplPriorityQueue' => $vendorDir . '/laminas/laminas-stdlib/src/SplPriorityQueue.php', + 'Laminas\\Stdlib\\SplQueue' => $vendorDir . '/laminas/laminas-stdlib/src/SplQueue.php', + 'Laminas\\Stdlib\\SplStack' => $vendorDir . '/laminas/laminas-stdlib/src/SplStack.php', + 'Laminas\\Stdlib\\StringUtils' => $vendorDir . '/laminas/laminas-stdlib/src/StringUtils.php', + 'Laminas\\Stdlib\\StringWrapper\\AbstractStringWrapper' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php', + 'Laminas\\Stdlib\\StringWrapper\\Iconv' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/Iconv.php', + 'Laminas\\Stdlib\\StringWrapper\\Intl' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/Intl.php', + 'Laminas\\Stdlib\\StringWrapper\\MbString' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/MbString.php', + 'Laminas\\Stdlib\\StringWrapper\\Native' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/Native.php', + 'Laminas\\Stdlib\\StringWrapper\\StringWrapperInterface' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php', + 'Laminas\\Validator\\AbstractValidator' => $vendorDir . '/laminas/laminas-validator/src/AbstractValidator.php', + 'Laminas\\Validator\\Barcode' => $vendorDir . '/laminas/laminas-validator/src/Barcode.php', + 'Laminas\\Validator\\Barcode\\AbstractAdapter' => $vendorDir . '/laminas/laminas-validator/src/Barcode/AbstractAdapter.php', + 'Laminas\\Validator\\Barcode\\AdapterInterface' => $vendorDir . '/laminas/laminas-validator/src/Barcode/AdapterInterface.php', + 'Laminas\\Validator\\Barcode\\Codabar' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Codabar.php', + 'Laminas\\Validator\\Barcode\\Code128' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code128.php', + 'Laminas\\Validator\\Barcode\\Code25' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code25.php', + 'Laminas\\Validator\\Barcode\\Code25interleaved' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code25interleaved.php', + 'Laminas\\Validator\\Barcode\\Code39' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code39.php', + 'Laminas\\Validator\\Barcode\\Code39ext' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code39ext.php', + 'Laminas\\Validator\\Barcode\\Code93' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code93.php', + 'Laminas\\Validator\\Barcode\\Code93ext' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code93ext.php', + 'Laminas\\Validator\\Barcode\\Ean12' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean12.php', + 'Laminas\\Validator\\Barcode\\Ean13' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean13.php', + 'Laminas\\Validator\\Barcode\\Ean14' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean14.php', + 'Laminas\\Validator\\Barcode\\Ean18' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean18.php', + 'Laminas\\Validator\\Barcode\\Ean2' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean2.php', + 'Laminas\\Validator\\Barcode\\Ean5' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean5.php', + 'Laminas\\Validator\\Barcode\\Ean8' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean8.php', + 'Laminas\\Validator\\Barcode\\Gtin12' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Gtin12.php', + 'Laminas\\Validator\\Barcode\\Gtin13' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Gtin13.php', + 'Laminas\\Validator\\Barcode\\Gtin14' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Gtin14.php', + 'Laminas\\Validator\\Barcode\\Identcode' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Identcode.php', + 'Laminas\\Validator\\Barcode\\Intelligentmail' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Intelligentmail.php', + 'Laminas\\Validator\\Barcode\\Issn' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Issn.php', + 'Laminas\\Validator\\Barcode\\Itf14' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Itf14.php', + 'Laminas\\Validator\\Barcode\\Leitcode' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Leitcode.php', + 'Laminas\\Validator\\Barcode\\Planet' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Planet.php', + 'Laminas\\Validator\\Barcode\\Postnet' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Postnet.php', + 'Laminas\\Validator\\Barcode\\Royalmail' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Royalmail.php', + 'Laminas\\Validator\\Barcode\\Sscc' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Sscc.php', + 'Laminas\\Validator\\Barcode\\Upca' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Upca.php', + 'Laminas\\Validator\\Barcode\\Upce' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Upce.php', + 'Laminas\\Validator\\Between' => $vendorDir . '/laminas/laminas-validator/src/Between.php', + 'Laminas\\Validator\\Bitwise' => $vendorDir . '/laminas/laminas-validator/src/Bitwise.php', + 'Laminas\\Validator\\Callback' => $vendorDir . '/laminas/laminas-validator/src/Callback.php', + 'Laminas\\Validator\\ConfigProvider' => $vendorDir . '/laminas/laminas-validator/src/ConfigProvider.php', + 'Laminas\\Validator\\CreditCard' => $vendorDir . '/laminas/laminas-validator/src/CreditCard.php', + 'Laminas\\Validator\\Csrf' => $vendorDir . '/laminas/laminas-validator/src/Csrf.php', + 'Laminas\\Validator\\Date' => $vendorDir . '/laminas/laminas-validator/src/Date.php', + 'Laminas\\Validator\\DateStep' => $vendorDir . '/laminas/laminas-validator/src/DateStep.php', + 'Laminas\\Validator\\Db\\AbstractDb' => $vendorDir . '/laminas/laminas-validator/src/Db/AbstractDb.php', + 'Laminas\\Validator\\Db\\NoRecordExists' => $vendorDir . '/laminas/laminas-validator/src/Db/NoRecordExists.php', + 'Laminas\\Validator\\Db\\RecordExists' => $vendorDir . '/laminas/laminas-validator/src/Db/RecordExists.php', + 'Laminas\\Validator\\Digits' => $vendorDir . '/laminas/laminas-validator/src/Digits.php', + 'Laminas\\Validator\\EmailAddress' => $vendorDir . '/laminas/laminas-validator/src/EmailAddress.php', + 'Laminas\\Validator\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-validator/src/Exception/BadMethodCallException.php', + 'Laminas\\Validator\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-validator/src/Exception/ExceptionInterface.php', + 'Laminas\\Validator\\Exception\\ExtensionNotLoadedException' => $vendorDir . '/laminas/laminas-validator/src/Exception/ExtensionNotLoadedException.php', + 'Laminas\\Validator\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-validator/src/Exception/InvalidArgumentException.php', + 'Laminas\\Validator\\Exception\\InvalidMagicMimeFileException' => $vendorDir . '/laminas/laminas-validator/src/Exception/InvalidMagicMimeFileException.php', + 'Laminas\\Validator\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-validator/src/Exception/RuntimeException.php', + 'Laminas\\Validator\\Explode' => $vendorDir . '/laminas/laminas-validator/src/Explode.php', + 'Laminas\\Validator\\File\\Count' => $vendorDir . '/laminas/laminas-validator/src/File/Count.php', + 'Laminas\\Validator\\File\\Crc32' => $vendorDir . '/laminas/laminas-validator/src/File/Crc32.php', + 'Laminas\\Validator\\File\\ExcludeExtension' => $vendorDir . '/laminas/laminas-validator/src/File/ExcludeExtension.php', + 'Laminas\\Validator\\File\\ExcludeMimeType' => $vendorDir . '/laminas/laminas-validator/src/File/ExcludeMimeType.php', + 'Laminas\\Validator\\File\\Exists' => $vendorDir . '/laminas/laminas-validator/src/File/Exists.php', + 'Laminas\\Validator\\File\\Extension' => $vendorDir . '/laminas/laminas-validator/src/File/Extension.php', + 'Laminas\\Validator\\File\\FileInformationTrait' => $vendorDir . '/laminas/laminas-validator/src/File/FileInformationTrait.php', + 'Laminas\\Validator\\File\\FilesSize' => $vendorDir . '/laminas/laminas-validator/src/File/FilesSize.php', + 'Laminas\\Validator\\File\\Hash' => $vendorDir . '/laminas/laminas-validator/src/File/Hash.php', + 'Laminas\\Validator\\File\\ImageSize' => $vendorDir . '/laminas/laminas-validator/src/File/ImageSize.php', + 'Laminas\\Validator\\File\\IsCompressed' => $vendorDir . '/laminas/laminas-validator/src/File/IsCompressed.php', + 'Laminas\\Validator\\File\\IsImage' => $vendorDir . '/laminas/laminas-validator/src/File/IsImage.php', + 'Laminas\\Validator\\File\\Md5' => $vendorDir . '/laminas/laminas-validator/src/File/Md5.php', + 'Laminas\\Validator\\File\\MimeType' => $vendorDir . '/laminas/laminas-validator/src/File/MimeType.php', + 'Laminas\\Validator\\File\\NotExists' => $vendorDir . '/laminas/laminas-validator/src/File/NotExists.php', + 'Laminas\\Validator\\File\\Sha1' => $vendorDir . '/laminas/laminas-validator/src/File/Sha1.php', + 'Laminas\\Validator\\File\\Size' => $vendorDir . '/laminas/laminas-validator/src/File/Size.php', + 'Laminas\\Validator\\File\\Upload' => $vendorDir . '/laminas/laminas-validator/src/File/Upload.php', + 'Laminas\\Validator\\File\\UploadFile' => $vendorDir . '/laminas/laminas-validator/src/File/UploadFile.php', + 'Laminas\\Validator\\File\\WordCount' => $vendorDir . '/laminas/laminas-validator/src/File/WordCount.php', + 'Laminas\\Validator\\GpsPoint' => $vendorDir . '/laminas/laminas-validator/src/GpsPoint.php', + 'Laminas\\Validator\\GreaterThan' => $vendorDir . '/laminas/laminas-validator/src/GreaterThan.php', + 'Laminas\\Validator\\Hex' => $vendorDir . '/laminas/laminas-validator/src/Hex.php', + 'Laminas\\Validator\\Hostname' => $vendorDir . '/laminas/laminas-validator/src/Hostname.php', + 'Laminas\\Validator\\Iban' => $vendorDir . '/laminas/laminas-validator/src/Iban.php', + 'Laminas\\Validator\\Identical' => $vendorDir . '/laminas/laminas-validator/src/Identical.php', + 'Laminas\\Validator\\InArray' => $vendorDir . '/laminas/laminas-validator/src/InArray.php', + 'Laminas\\Validator\\Ip' => $vendorDir . '/laminas/laminas-validator/src/Ip.php', + 'Laminas\\Validator\\IsCountable' => $vendorDir . '/laminas/laminas-validator/src/IsCountable.php', + 'Laminas\\Validator\\IsInstanceOf' => $vendorDir . '/laminas/laminas-validator/src/IsInstanceOf.php', + 'Laminas\\Validator\\Isbn' => $vendorDir . '/laminas/laminas-validator/src/Isbn.php', + 'Laminas\\Validator\\Isbn\\Isbn10' => $vendorDir . '/laminas/laminas-validator/src/Isbn/Isbn10.php', + 'Laminas\\Validator\\Isbn\\Isbn13' => $vendorDir . '/laminas/laminas-validator/src/Isbn/Isbn13.php', + 'Laminas\\Validator\\LessThan' => $vendorDir . '/laminas/laminas-validator/src/LessThan.php', + 'Laminas\\Validator\\Module' => $vendorDir . '/laminas/laminas-validator/src/Module.php', + 'Laminas\\Validator\\NotEmpty' => $vendorDir . '/laminas/laminas-validator/src/NotEmpty.php', + 'Laminas\\Validator\\Regex' => $vendorDir . '/laminas/laminas-validator/src/Regex.php', + 'Laminas\\Validator\\Sitemap\\Changefreq' => $vendorDir . '/laminas/laminas-validator/src/Sitemap/Changefreq.php', + 'Laminas\\Validator\\Sitemap\\Lastmod' => $vendorDir . '/laminas/laminas-validator/src/Sitemap/Lastmod.php', + 'Laminas\\Validator\\Sitemap\\Loc' => $vendorDir . '/laminas/laminas-validator/src/Sitemap/Loc.php', + 'Laminas\\Validator\\Sitemap\\Priority' => $vendorDir . '/laminas/laminas-validator/src/Sitemap/Priority.php', + 'Laminas\\Validator\\StaticValidator' => $vendorDir . '/laminas/laminas-validator/src/StaticValidator.php', + 'Laminas\\Validator\\Step' => $vendorDir . '/laminas/laminas-validator/src/Step.php', + 'Laminas\\Validator\\StringLength' => $vendorDir . '/laminas/laminas-validator/src/StringLength.php', + 'Laminas\\Validator\\Timezone' => $vendorDir . '/laminas/laminas-validator/src/Timezone.php', + 'Laminas\\Validator\\Translator\\TranslatorAwareInterface' => $vendorDir . '/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php', + 'Laminas\\Validator\\Translator\\TranslatorInterface' => $vendorDir . '/laminas/laminas-validator/src/Translator/TranslatorInterface.php', + 'Laminas\\Validator\\Uri' => $vendorDir . '/laminas/laminas-validator/src/Uri.php', + 'Laminas\\Validator\\Uuid' => $vendorDir . '/laminas/laminas-validator/src/Uuid.php', + 'Laminas\\Validator\\ValidatorChain' => $vendorDir . '/laminas/laminas-validator/src/ValidatorChain.php', + 'Laminas\\Validator\\ValidatorInterface' => $vendorDir . '/laminas/laminas-validator/src/ValidatorInterface.php', + 'Laminas\\Validator\\ValidatorPluginManager' => $vendorDir . '/laminas/laminas-validator/src/ValidatorPluginManager.php', + 'Laminas\\Validator\\ValidatorPluginManagerAwareInterface' => $vendorDir . '/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php', + 'Laminas\\Validator\\ValidatorPluginManagerFactory' => $vendorDir . '/laminas/laminas-validator/src/ValidatorPluginManagerFactory.php', + 'Laminas\\Validator\\ValidatorProviderInterface' => $vendorDir . '/laminas/laminas-validator/src/ValidatorProviderInterface.php', + 'Laminas\\ZendFrameworkBridge\\Autoloader' => $vendorDir . '/laminas/laminas-zendframework-bridge/src/Autoloader.php', + 'Laminas\\ZendFrameworkBridge\\ConfigPostProcessor' => $vendorDir . '/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php', + 'Laminas\\ZendFrameworkBridge\\Module' => $vendorDir . '/laminas/laminas-zendframework-bridge/src/Module.php', + 'Laminas\\ZendFrameworkBridge\\Replacements' => $vendorDir . '/laminas/laminas-zendframework-bridge/src/Replacements.php', + 'Laminas\\ZendFrameworkBridge\\RewriteRules' => $vendorDir . '/laminas/laminas-zendframework-bridge/src/RewriteRules.php', + 'League\\OAuth2\\Client\\Exception\\HostedDomainException' => $vendorDir . '/league/oauth2-google/src/Exception/HostedDomainException.php', + 'League\\OAuth2\\Client\\Grant\\AbstractGrant' => $vendorDir . '/league/oauth2-client/src/Grant/AbstractGrant.php', + 'League\\OAuth2\\Client\\Grant\\AuthorizationCode' => $vendorDir . '/league/oauth2-client/src/Grant/AuthorizationCode.php', + 'League\\OAuth2\\Client\\Grant\\ClientCredentials' => $vendorDir . '/league/oauth2-client/src/Grant/ClientCredentials.php', + 'League\\OAuth2\\Client\\Grant\\Exception\\InvalidGrantException' => $vendorDir . '/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php', + 'League\\OAuth2\\Client\\Grant\\GrantFactory' => $vendorDir . '/league/oauth2-client/src/Grant/GrantFactory.php', + 'League\\OAuth2\\Client\\Grant\\Password' => $vendorDir . '/league/oauth2-client/src/Grant/Password.php', + 'League\\OAuth2\\Client\\Grant\\RefreshToken' => $vendorDir . '/league/oauth2-client/src/Grant/RefreshToken.php', + 'League\\OAuth2\\Client\\OptionProvider\\HttpBasicAuthOptionProvider' => $vendorDir . '/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php', + 'League\\OAuth2\\Client\\OptionProvider\\OptionProviderInterface' => $vendorDir . '/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php', + 'League\\OAuth2\\Client\\OptionProvider\\PostAuthOptionProvider' => $vendorDir . '/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php', + 'League\\OAuth2\\Client\\Provider\\AbstractProvider' => $vendorDir . '/league/oauth2-client/src/Provider/AbstractProvider.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\IdentityProviderException' => $vendorDir . '/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php', + 'League\\OAuth2\\Client\\Provider\\GenericProvider' => $vendorDir . '/league/oauth2-client/src/Provider/GenericProvider.php', + 'League\\OAuth2\\Client\\Provider\\GenericResourceOwner' => $vendorDir . '/league/oauth2-client/src/Provider/GenericResourceOwner.php', + 'League\\OAuth2\\Client\\Provider\\Google' => $vendorDir . '/league/oauth2-google/src/Provider/Google.php', + 'League\\OAuth2\\Client\\Provider\\GoogleUser' => $vendorDir . '/league/oauth2-google/src/Provider/GoogleUser.php', + 'League\\OAuth2\\Client\\Provider\\ResourceOwnerInterface' => $vendorDir . '/league/oauth2-client/src/Provider/ResourceOwnerInterface.php', + 'League\\OAuth2\\Client\\Token\\AccessToken' => $vendorDir . '/league/oauth2-client/src/Token/AccessToken.php', + 'League\\OAuth2\\Client\\Token\\AccessTokenInterface' => $vendorDir . '/league/oauth2-client/src/Token/AccessTokenInterface.php', + 'League\\OAuth2\\Client\\Token\\ResourceOwnerAccessTokenInterface' => $vendorDir . '/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php', + 'League\\OAuth2\\Client\\Tool\\ArrayAccessorTrait' => $vendorDir . '/league/oauth2-client/src/Tool/ArrayAccessorTrait.php', + 'League\\OAuth2\\Client\\Tool\\BearerAuthorizationTrait' => $vendorDir . '/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\GuardedPropertyTrait' => $vendorDir . '/league/oauth2-client/src/Tool/GuardedPropertyTrait.php', + 'League\\OAuth2\\Client\\Tool\\MacAuthorizationTrait' => $vendorDir . '/league/oauth2-client/src/Tool/MacAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\ProviderRedirectTrait' => $vendorDir . '/league/oauth2-client/src/Tool/ProviderRedirectTrait.php', + 'League\\OAuth2\\Client\\Tool\\QueryBuilderTrait' => $vendorDir . '/league/oauth2-client/src/Tool/QueryBuilderTrait.php', + 'League\\OAuth2\\Client\\Tool\\RequestFactory' => $vendorDir . '/league/oauth2-client/src/Tool/RequestFactory.php', + 'League\\OAuth2\\Client\\Tool\\RequiredParameterTrait' => $vendorDir . '/league/oauth2-client/src/Tool/RequiredParameterTrait.php', 'ListExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'ListOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'LogAPI' => $baseDir . '/core/log.class.inc.php', @@ -733,6 +1165,13 @@ return array( 'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php', 'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php', 'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => $vendorDir . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => $vendorDir . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => $vendorDir . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriInterface' => $vendorDir . '/psr/http-message/src/UriInterface.php', 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', @@ -1836,6 +2275,10 @@ return array( 'TemplateMenuNode' => $baseDir . '/application/menunode.class.inc.php', 'TemplateString' => $baseDir . '/core/templatestring.class.inc.php', 'TemplateStringPlaceholder' => $baseDir . '/core/templatestring.class.inc.php', + 'TheNetworg\\OAuth2\\Client\\Grant\\JwtBearer' => $vendorDir . '/thenetworg/oauth2-azure/src/Grant/JwtBearer.php', + 'TheNetworg\\OAuth2\\Client\\Provider\\Azure' => $vendorDir . '/thenetworg/oauth2-azure/src/Provider/Azure.php', + 'TheNetworg\\OAuth2\\Client\\Provider\\AzureResourceOwner' => $vendorDir . '/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php', + 'TheNetworg\\OAuth2\\Client\\Token\\AccessToken' => $vendorDir . '/thenetworg/oauth2-azure/src/Token/AccessToken.php', 'ThemeHandler' => $baseDir . '/application/themehandler.class.inc.php', 'ToolsLog' => $baseDir . '/core/log.class.inc.php', 'Trigger' => $baseDir . '/core/trigger.class.inc.php', @@ -1848,6 +2291,10 @@ return array( 'TriggerOnStateEnter' => $baseDir . '/core/trigger.class.inc.php', 'TriggerOnStateLeave' => $baseDir . '/core/trigger.class.inc.php', 'TriggerOnThresholdReached' => $baseDir . '/core/trigger.class.inc.php', + 'TrueBV\\Exception\\DomainOutOfBoundsException' => $vendorDir . '/true/punycode/src/Exception/DomainOutOfBoundsException.php', + 'TrueBV\\Exception\\LabelOutOfBoundsException' => $vendorDir . '/true/punycode/src/Exception/LabelOutOfBoundsException.php', + 'TrueBV\\Exception\\OutOfBoundsException' => $vendorDir . '/true/punycode/src/Exception/OutOfBoundsException.php', + 'TrueBV\\Punycode' => $vendorDir . '/true/punycode/src/Punycode.php', 'TrueExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'Twig\\Cache\\CacheInterface' => $vendorDir . '/twig/twig/src/Cache/CacheInterface.php', 'Twig\\Cache\\FilesystemCache' => $vendorDir . '/twig/twig/src/Cache/FilesystemCache.php', diff --git a/lib/composer/autoload_files.php b/lib/composer/autoload_files.php index af2e18883..7be757bea 100644 --- a/lib/composer/autoload_files.php +++ b/lib/composer/autoload_files.php @@ -13,9 +13,14 @@ return array( '32dcc8afd4335739640db7d200c1971d' => $vendorDir . '/symfony/polyfill-apcu/bootstrap.php', '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', 'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php', + '7e9bd612cc444b3eed788ebbe46263a0' => $vendorDir . '/laminas/laminas-zendframework-bridge/src/autoload.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php', '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', ); diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php index 28fab5d05..ca8b4b9f6 100644 --- a/lib/composer/autoload_psr4.php +++ b/lib/composer/autoload_psr4.php @@ -7,6 +7,8 @@ $baseDir = dirname($vendorDir); return array( 'Twig\\' => array($vendorDir . '/twig/twig/src'), + 'TrueBV\\' => array($vendorDir . '/true/punycode/src'), + 'TheNetworg\\OAuth2\\Client\\' => array($vendorDir . '/thenetworg/oauth2-azure/src'), 'Symfony\\Polyfill\\Util\\' => array($vendorDir . '/symfony/polyfill-util'), 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), 'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'), @@ -41,10 +43,24 @@ return array( 'ScssPhp\\ScssPhp\\' => array($vendorDir . '/scssphp/scssphp/src'), 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), 'Pelago\\' => array($vendorDir . '/pelago/emogrifier/src'), + 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'), + 'Laminas\\ZendFrameworkBridge\\' => array($vendorDir . '/laminas/laminas-zendframework-bridge/src'), + 'Laminas\\Validator\\' => array($vendorDir . '/laminas/laminas-validator/src'), + 'Laminas\\Stdlib\\' => array($vendorDir . '/laminas/laminas-stdlib/src'), + 'Laminas\\ServiceManager\\' => array($vendorDir . '/laminas/laminas-servicemanager/src'), + 'Laminas\\Mime\\' => array($vendorDir . '/laminas/laminas-mime/src'), + 'Laminas\\Mail\\' => array($vendorDir . '/laminas/laminas-mail/src'), + 'Laminas\\Loader\\' => array($vendorDir . '/laminas/laminas-loader/src'), + 'Interop\\Container\\' => array($vendorDir . '/container-interop/container-interop/src/Interop/Container'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'), 'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'), 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'), ); diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index b94fbf549..4d1e7a5bd 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -14,9 +14,14 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b '32dcc8afd4335739640db7d200c1971d' => __DIR__ . '/..' . '/symfony/polyfill-apcu/bootstrap.php', '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', 'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php', + '7e9bd612cc444b3eed788ebbe46263a0' => __DIR__ . '/..' . '/laminas/laminas-zendframework-bridge/src/autoload.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php', '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', ); @@ -25,6 +30,8 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'T' => array ( 'Twig\\' => 5, + 'TrueBV\\' => 7, + 'TheNetworg\\OAuth2\\Client\\' => 25, ), 'S' => array ( @@ -65,11 +72,37 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b array ( 'Psr\\SimpleCache\\' => 16, 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, 'Psr\\Container\\' => 14, 'Psr\\Cache\\' => 10, 'PhpParser\\' => 10, 'Pelago\\' => 7, ), + 'L' => + array ( + 'League\\OAuth2\\Client\\' => 21, + 'Laminas\\ZendFrameworkBridge\\' => 28, + 'Laminas\\Validator\\' => 18, + 'Laminas\\Stdlib\\' => 15, + 'Laminas\\ServiceManager\\' => 23, + 'Laminas\\Mime\\' => 13, + 'Laminas\\Mail\\' => 13, + 'Laminas\\Loader\\' => 15, + ), + 'I' => + array ( + 'Interop\\Container\\' => 18, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + 'F' => + array ( + 'Firebase\\JWT\\' => 13, + ), 'E' => array ( 'Egulias\\EmailValidator\\' => 23, @@ -85,6 +118,14 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b array ( 0 => __DIR__ . '/..' . '/twig/twig/src', ), + 'TrueBV\\' => + array ( + 0 => __DIR__ . '/..' . '/true/punycode/src', + ), + 'TheNetworg\\OAuth2\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src', + ), 'Symfony\\Polyfill\\Util\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-util', @@ -221,6 +262,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-message/src', + ), 'Psr\\Container\\' => array ( 0 => __DIR__ . '/..' . '/psr/container/src', @@ -237,6 +282,59 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b array ( 0 => __DIR__ . '/..' . '/pelago/emogrifier/src', ), + 'League\\OAuth2\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/league/oauth2-client/src', + 1 => __DIR__ . '/..' . '/league/oauth2-google/src', + ), + 'Laminas\\ZendFrameworkBridge\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-zendframework-bridge/src', + ), + 'Laminas\\Validator\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-validator/src', + ), + 'Laminas\\Stdlib\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-stdlib/src', + ), + 'Laminas\\ServiceManager\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src', + ), + 'Laminas\\Mime\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-mime/src', + ), + 'Laminas\\Mail\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-mail/src', + ), + 'Laminas\\Loader\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-loader/src', + ), + 'Interop\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'Firebase\\JWT\\' => + array ( + 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', + ), 'Egulias\\EmailValidator\\' => array ( 0 => __DIR__ . '/..' . '/egulias/email-validator/src', @@ -608,11 +706,97 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'FilterDefinition' => __DIR__ . '/../..' . '/core/filterdef.class.inc.php', 'FilterFromAttribute' => __DIR__ . '/../..' . '/core/filterdef.class.inc.php', 'FilterPrivateKey' => __DIR__ . '/../..' . '/core/filterdef.class.inc.php', + 'Firebase\\JWT\\BeforeValidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/BeforeValidException.php', + 'Firebase\\JWT\\ExpiredException' => __DIR__ . '/..' . '/firebase/php-jwt/src/ExpiredException.php', + 'Firebase\\JWT\\JWK' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWK.php', + 'Firebase\\JWT\\JWT' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWT.php', + 'Firebase\\JWT\\Key' => __DIR__ . '/..' . '/firebase/php-jwt/src/Key.php', + 'Firebase\\JWT\\SignatureInvalidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/SignatureInvalidException.php', 'FunctionExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'FunctionOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'GraphEdge' => __DIR__ . '/../..' . '/core/simplegraph.class.inc.php', 'GraphElement' => __DIR__ . '/../..' . '/core/simplegraph.class.inc.php', 'GraphNode' => __DIR__ . '/../..' . '/core/simplegraph.class.inc.php', + 'GuzzleHttp\\Client' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\Cookie\\CookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php', + 'GuzzleHttp\\Exception\\RequestException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\SeekException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/SeekException.php', + 'GuzzleHttp\\Exception\\ServerException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\MockHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\Middleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\Create' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Create.php', + 'GuzzleHttp\\Promise\\Each' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Each.php', + 'GuzzleHttp\\Promise\\EachPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Is' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Is.php', + 'GuzzleHttp\\Promise\\Promise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Promise\\Utils' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Utils.php', + 'GuzzleHttp\\Psr7\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\FnStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\Header' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Header.php', + 'GuzzleHttp\\Psr7\\InflateStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\Message' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Message.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MimeType' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MimeType.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Query' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Query.php', + 'GuzzleHttp\\Psr7\\Request' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\Rfc7230' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Rfc7230.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\Psr7\\Utils' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Utils.php', + 'GuzzleHttp\\RedirectMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\UriTemplate' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/UriTemplate.php', + 'GuzzleHttp\\Utils' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Utils.php', 'HTMLBulkExport' => __DIR__ . '/../..' . '/core/htmlbulkexport.class.inc.php', 'HTMLDOMSanitizer' => __DIR__ . '/../..' . '/core/htmlsanitizer.class.inc.php', 'HTMLNullSanitizer' => __DIR__ . '/../..' . '/core/htmlsanitizer.class.inc.php', @@ -624,6 +808,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'InlineImage' => __DIR__ . '/../..' . '/core/inlineimage.class.inc.php', 'InlineImageGC' => __DIR__ . '/../..' . '/core/inlineimage.class.inc.php', 'InputOutputTask' => __DIR__ . '/../..' . '/application/iotask.class.inc.php', + 'Interop\\Container\\ContainerInterface' => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container/ContainerInterface.php', + 'Interop\\Container\\Exception\\ContainerException' => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php', + 'Interop\\Container\\Exception\\NotFoundException' => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php', 'IntervalExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'IntervalOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'Introspection' => __DIR__ . '/../..' . '/core/introspection.class.inc.php', @@ -634,7 +821,350 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'JSButtonItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', 'JSPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', 'KeyValueStore' => __DIR__ . '/../..' . '/core/counter.class.inc.php', + 'Laminas\\Loader\\AutoloaderFactory' => __DIR__ . '/..' . '/laminas/laminas-loader/src/AutoloaderFactory.php', + 'Laminas\\Loader\\ClassMapAutoloader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/ClassMapAutoloader.php', + 'Laminas\\Loader\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/BadMethodCallException.php', + 'Laminas\\Loader\\Exception\\DomainException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/DomainException.php', + 'Laminas\\Loader\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/ExceptionInterface.php', + 'Laminas\\Loader\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/InvalidArgumentException.php', + 'Laminas\\Loader\\Exception\\InvalidPathException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/InvalidPathException.php', + 'Laminas\\Loader\\Exception\\MissingResourceNamespaceException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/MissingResourceNamespaceException.php', + 'Laminas\\Loader\\Exception\\PluginLoaderException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/PluginLoaderException.php', + 'Laminas\\Loader\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/RuntimeException.php', + 'Laminas\\Loader\\Exception\\SecurityException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/SecurityException.php', + 'Laminas\\Loader\\ModuleAutoloader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/ModuleAutoloader.php', + 'Laminas\\Loader\\PluginClassLoader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/PluginClassLoader.php', + 'Laminas\\Loader\\PluginClassLocator' => __DIR__ . '/..' . '/laminas/laminas-loader/src/PluginClassLocator.php', + 'Laminas\\Loader\\ShortNameLocator' => __DIR__ . '/..' . '/laminas/laminas-loader/src/ShortNameLocator.php', + 'Laminas\\Loader\\SplAutoloader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/SplAutoloader.php', + 'Laminas\\Loader\\StandardAutoloader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/StandardAutoloader.php', + 'Laminas\\Mail\\Address' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Address.php', + 'Laminas\\Mail\\AddressList' => __DIR__ . '/..' . '/laminas/laminas-mail/src/AddressList.php', + 'Laminas\\Mail\\Address\\AddressInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Address/AddressInterface.php', + 'Laminas\\Mail\\ConfigProvider' => __DIR__ . '/..' . '/laminas/laminas-mail/src/ConfigProvider.php', + 'Laminas\\Mail\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/BadMethodCallException.php', + 'Laminas\\Mail\\Exception\\DomainException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/DomainException.php', + 'Laminas\\Mail\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/OutOfBoundsException.php', + 'Laminas\\Mail\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/RuntimeException.php', + 'Laminas\\Mail\\Header\\AbstractAddressList' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/AbstractAddressList.php', + 'Laminas\\Mail\\Header\\Bcc' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Bcc.php', + 'Laminas\\Mail\\Header\\Cc' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Cc.php', + 'Laminas\\Mail\\Header\\ContentDisposition' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ContentDisposition.php', + 'Laminas\\Mail\\Header\\ContentTransferEncoding' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ContentTransferEncoding.php', + 'Laminas\\Mail\\Header\\ContentType' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ContentType.php', + 'Laminas\\Mail\\Header\\Date' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Date.php', + 'Laminas\\Mail\\Header\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php', + 'Laminas\\Mail\\Header\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Header\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Header\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Exception/RuntimeException.php', + 'Laminas\\Mail\\Header\\From' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/From.php', + 'Laminas\\Mail\\Header\\GenericHeader' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/GenericHeader.php', + 'Laminas\\Mail\\Header\\GenericMultiHeader' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/GenericMultiHeader.php', + 'Laminas\\Mail\\Header\\HeaderInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderInterface.php', + 'Laminas\\Mail\\Header\\HeaderLoader' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderLoader.php', + 'Laminas\\Mail\\Header\\HeaderName' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderName.php', + 'Laminas\\Mail\\Header\\HeaderValue' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderValue.php', + 'Laminas\\Mail\\Header\\HeaderWrap' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderWrap.php', + 'Laminas\\Mail\\Header\\IdentificationField' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/IdentificationField.php', + 'Laminas\\Mail\\Header\\InReplyTo' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/InReplyTo.php', + 'Laminas\\Mail\\Header\\ListParser' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ListParser.php', + 'Laminas\\Mail\\Header\\MessageId' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/MessageId.php', + 'Laminas\\Mail\\Header\\MimeVersion' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/MimeVersion.php', + 'Laminas\\Mail\\Header\\MultipleHeadersInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php', + 'Laminas\\Mail\\Header\\Received' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Received.php', + 'Laminas\\Mail\\Header\\References' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/References.php', + 'Laminas\\Mail\\Header\\ReplyTo' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ReplyTo.php', + 'Laminas\\Mail\\Header\\Sender' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Sender.php', + 'Laminas\\Mail\\Header\\StructuredInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/StructuredInterface.php', + 'Laminas\\Mail\\Header\\Subject' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Subject.php', + 'Laminas\\Mail\\Header\\To' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/To.php', + 'Laminas\\Mail\\Header\\UnstructuredInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/UnstructuredInterface.php', + 'Laminas\\Mail\\Headers' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Headers.php', + 'Laminas\\Mail\\Message' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Message.php', + 'Laminas\\Mail\\MessageFactory' => __DIR__ . '/..' . '/laminas/laminas-mail/src/MessageFactory.php', + 'Laminas\\Mail\\Module' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Module.php', + 'Laminas\\Mail\\Protocol\\AbstractProtocol' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/AbstractProtocol.php', + 'Laminas\\Mail\\Protocol\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Protocol\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Protocol\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Exception/RuntimeException.php', + 'Laminas\\Mail\\Protocol\\Imap' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Imap.php', + 'Laminas\\Mail\\Protocol\\Pop3' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Pop3.php', + 'Laminas\\Mail\\Protocol\\ProtocolTrait' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/ProtocolTrait.php', + 'Laminas\\Mail\\Protocol\\Smtp' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Smtp.php', + 'Laminas\\Mail\\Protocol\\SmtpPluginManager' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php', + 'Laminas\\Mail\\Protocol\\SmtpPluginManagerFactory' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Crammd5' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Login' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php', 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Oauth' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Plain' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php', + 'Laminas\\Mail\\Storage' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage.php', + 'Laminas\\Mail\\Storage\\AbstractStorage' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/AbstractStorage.php', + 'Laminas\\Mail\\Storage\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Storage\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Storage\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Exception/OutOfBoundsException.php', + 'Laminas\\Mail\\Storage\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Exception/RuntimeException.php', + 'Laminas\\Mail\\Storage\\Folder' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Folder.php', + 'Laminas\\Mail\\Storage\\Folder\\FolderInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php', + 'Laminas\\Mail\\Storage\\Folder\\Maildir' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Folder/Maildir.php', + 'Laminas\\Mail\\Storage\\Folder\\Mbox' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Folder/Mbox.php', + 'Laminas\\Mail\\Storage\\Imap' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Imap.php', + 'Laminas\\Mail\\Storage\\Maildir' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Maildir.php', + 'Laminas\\Mail\\Storage\\Mbox' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Mbox.php', + 'Laminas\\Mail\\Storage\\Message' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Message.php', + 'Laminas\\Mail\\Storage\\Message\\File' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Message/File.php', + 'Laminas\\Mail\\Storage\\Message\\MessageInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Message/MessageInterface.php', + 'Laminas\\Mail\\Storage\\Part' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/Exception/RuntimeException.php', + 'Laminas\\Mail\\Storage\\Part\\File' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/File.php', + 'Laminas\\Mail\\Storage\\Part\\PartInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/PartInterface.php', + 'Laminas\\Mail\\Storage\\Pop3' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Pop3.php', + 'Laminas\\Mail\\Storage\\Writable\\Maildir' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Writable/Maildir.php', + 'Laminas\\Mail\\Storage\\Writable\\WritableInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php', + 'Laminas\\Mail\\Transport\\Envelope' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Envelope.php', + 'Laminas\\Mail\\Transport\\Exception\\DomainException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Exception/DomainException.php', + 'Laminas\\Mail\\Transport\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Transport\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Transport\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Exception/RuntimeException.php', + 'Laminas\\Mail\\Transport\\Factory' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Factory.php', + 'Laminas\\Mail\\Transport\\File' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/File.php', + 'Laminas\\Mail\\Transport\\FileOptions' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/FileOptions.php', + 'Laminas\\Mail\\Transport\\InMemory' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/InMemory.php', + 'Laminas\\Mail\\Transport\\Null' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Null.php', + 'Laminas\\Mail\\Transport\\Sendmail' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Sendmail.php', + 'Laminas\\Mail\\Transport\\Smtp' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Smtp.php', + 'Laminas\\Mail\\Transport\\SmtpOptions' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/SmtpOptions.php', + 'Laminas\\Mail\\Transport\\TransportInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/TransportInterface.php', + 'Laminas\\Mime\\Decode' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Decode.php', + 'Laminas\\Mime\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Exception/ExceptionInterface.php', + 'Laminas\\Mime\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Exception/InvalidArgumentException.php', + 'Laminas\\Mime\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Exception/RuntimeException.php', + 'Laminas\\Mime\\Message' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Message.php', + 'Laminas\\Mime\\Mime' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Mime.php', + 'Laminas\\Mime\\Part' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Part.php', + 'Laminas\\ServiceManager\\AbstractFactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php', + 'Laminas\\ServiceManager\\AbstractFactory\\ConfigAbstractFactory' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php', + 'Laminas\\ServiceManager\\AbstractFactory\\ReflectionBasedAbstractFactory' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php', + 'Laminas\\ServiceManager\\AbstractPluginManager' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/AbstractPluginManager.php', + 'Laminas\\ServiceManager\\Config' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Config.php', + 'Laminas\\ServiceManager\\ConfigInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ConfigInterface.php', + 'Laminas\\ServiceManager\\DelegatorFactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php', + 'Laminas\\ServiceManager\\Exception\\ContainerModificationsNotAllowedException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/ContainerModificationsNotAllowedException.php', + 'Laminas\\ServiceManager\\Exception\\CyclicAliasException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/CyclicAliasException.php', + 'Laminas\\ServiceManager\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php', + 'Laminas\\ServiceManager\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/InvalidArgumentException.php', + 'Laminas\\ServiceManager\\Exception\\InvalidServiceException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/InvalidServiceException.php', + 'Laminas\\ServiceManager\\Exception\\ServiceNotCreatedException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/ServiceNotCreatedException.php', + 'Laminas\\ServiceManager\\Exception\\ServiceNotFoundException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/ServiceNotFoundException.php', + 'Laminas\\ServiceManager\\FactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/FactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\AbstractFactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Factory/AbstractFactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\DelegatorFactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Factory/DelegatorFactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\FactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Factory/FactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\InvokableFactory' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php', + 'Laminas\\ServiceManager\\InitializerInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/InitializerInterface.php', + 'Laminas\\ServiceManager\\Initializer\\InitializerInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Initializer/InitializerInterface.php', + 'Laminas\\ServiceManager\\PluginManagerInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/PluginManagerInterface.php', + 'Laminas\\ServiceManager\\Proxy\\LazyServiceFactory' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Proxy/LazyServiceFactory.php', + 'Laminas\\ServiceManager\\PsrContainerDecorator' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/PsrContainerDecorator.php', + 'Laminas\\ServiceManager\\ServiceLocatorInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php', + 'Laminas\\ServiceManager\\ServiceManager' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ServiceManager.php', + 'Laminas\\ServiceManager\\Test\\CommonPluginManagerTrait' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php', + 'Laminas\\ServiceManager\\Tool\\ConfigDumper' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php', + 'Laminas\\ServiceManager\\Tool\\ConfigDumperCommand' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php', + 'Laminas\\ServiceManager\\Tool\\FactoryCreator' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php', + 'Laminas\\ServiceManager\\Tool\\FactoryCreatorCommand' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php', + 'Laminas\\Stdlib\\AbstractOptions' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/AbstractOptions.php', + 'Laminas\\Stdlib\\ArrayObject' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayObject.php', + 'Laminas\\Stdlib\\ArraySerializableInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArraySerializableInterface.php', + 'Laminas\\Stdlib\\ArrayStack' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayStack.php', + 'Laminas\\Stdlib\\ArrayUtils' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayUtils.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeRemoveKey' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeReplaceKey' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKey.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeReplaceKeyInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php', + 'Laminas\\Stdlib\\ConsoleHelper' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ConsoleHelper.php', + 'Laminas\\Stdlib\\DispatchableInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/DispatchableInterface.php', + 'Laminas\\Stdlib\\ErrorHandler' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ErrorHandler.php', + 'Laminas\\Stdlib\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/BadMethodCallException.php', + 'Laminas\\Stdlib\\Exception\\DomainException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/DomainException.php', + 'Laminas\\Stdlib\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/ExceptionInterface.php', + 'Laminas\\Stdlib\\Exception\\ExtensionNotLoadedException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/ExtensionNotLoadedException.php', + 'Laminas\\Stdlib\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/InvalidArgumentException.php', + 'Laminas\\Stdlib\\Exception\\LogicException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/LogicException.php', + 'Laminas\\Stdlib\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/RuntimeException.php', + 'Laminas\\Stdlib\\FastPriorityQueue' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/FastPriorityQueue.php', + 'Laminas\\Stdlib\\Glob' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Glob.php', + 'Laminas\\Stdlib\\Guard\\AllGuardsTrait' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php', + 'Laminas\\Stdlib\\Guard\\ArrayOrTraversableGuardTrait' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Guard/ArrayOrTraversableGuardTrait.php', + 'Laminas\\Stdlib\\Guard\\EmptyGuardTrait' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Guard/EmptyGuardTrait.php', + 'Laminas\\Stdlib\\Guard\\NullGuardTrait' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Guard/NullGuardTrait.php', + 'Laminas\\Stdlib\\InitializableInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/InitializableInterface.php', + 'Laminas\\Stdlib\\JsonSerializable' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/JsonSerializable.php', + 'Laminas\\Stdlib\\Message' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Message.php', + 'Laminas\\Stdlib\\MessageInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/MessageInterface.php', + 'Laminas\\Stdlib\\ParameterObjectInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ParameterObjectInterface.php', + 'Laminas\\Stdlib\\Parameters' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Parameters.php', + 'Laminas\\Stdlib\\ParametersInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ParametersInterface.php', + 'Laminas\\Stdlib\\PriorityList' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/PriorityList.php', + 'Laminas\\Stdlib\\PriorityQueue' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/PriorityQueue.php', + 'Laminas\\Stdlib\\Request' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Request.php', + 'Laminas\\Stdlib\\RequestInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/RequestInterface.php', + 'Laminas\\Stdlib\\Response' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Response.php', + 'Laminas\\Stdlib\\ResponseInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ResponseInterface.php', + 'Laminas\\Stdlib\\SplPriorityQueue' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/SplPriorityQueue.php', + 'Laminas\\Stdlib\\SplQueue' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/SplQueue.php', + 'Laminas\\Stdlib\\SplStack' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/SplStack.php', + 'Laminas\\Stdlib\\StringUtils' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringUtils.php', + 'Laminas\\Stdlib\\StringWrapper\\AbstractStringWrapper' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php', + 'Laminas\\Stdlib\\StringWrapper\\Iconv' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/Iconv.php', + 'Laminas\\Stdlib\\StringWrapper\\Intl' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/Intl.php', + 'Laminas\\Stdlib\\StringWrapper\\MbString' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/MbString.php', + 'Laminas\\Stdlib\\StringWrapper\\Native' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/Native.php', + 'Laminas\\Stdlib\\StringWrapper\\StringWrapperInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php', + 'Laminas\\Validator\\AbstractValidator' => __DIR__ . '/..' . '/laminas/laminas-validator/src/AbstractValidator.php', + 'Laminas\\Validator\\Barcode' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode.php', + 'Laminas\\Validator\\Barcode\\AbstractAdapter' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/AbstractAdapter.php', + 'Laminas\\Validator\\Barcode\\AdapterInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/AdapterInterface.php', + 'Laminas\\Validator\\Barcode\\Codabar' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Codabar.php', + 'Laminas\\Validator\\Barcode\\Code128' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code128.php', + 'Laminas\\Validator\\Barcode\\Code25' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code25.php', + 'Laminas\\Validator\\Barcode\\Code25interleaved' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code25interleaved.php', + 'Laminas\\Validator\\Barcode\\Code39' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code39.php', + 'Laminas\\Validator\\Barcode\\Code39ext' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code39ext.php', + 'Laminas\\Validator\\Barcode\\Code93' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code93.php', + 'Laminas\\Validator\\Barcode\\Code93ext' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code93ext.php', + 'Laminas\\Validator\\Barcode\\Ean12' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean12.php', + 'Laminas\\Validator\\Barcode\\Ean13' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean13.php', + 'Laminas\\Validator\\Barcode\\Ean14' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean14.php', + 'Laminas\\Validator\\Barcode\\Ean18' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean18.php', + 'Laminas\\Validator\\Barcode\\Ean2' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean2.php', + 'Laminas\\Validator\\Barcode\\Ean5' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean5.php', + 'Laminas\\Validator\\Barcode\\Ean8' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean8.php', + 'Laminas\\Validator\\Barcode\\Gtin12' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Gtin12.php', + 'Laminas\\Validator\\Barcode\\Gtin13' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Gtin13.php', + 'Laminas\\Validator\\Barcode\\Gtin14' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Gtin14.php', + 'Laminas\\Validator\\Barcode\\Identcode' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Identcode.php', + 'Laminas\\Validator\\Barcode\\Intelligentmail' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Intelligentmail.php', + 'Laminas\\Validator\\Barcode\\Issn' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Issn.php', + 'Laminas\\Validator\\Barcode\\Itf14' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Itf14.php', + 'Laminas\\Validator\\Barcode\\Leitcode' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Leitcode.php', + 'Laminas\\Validator\\Barcode\\Planet' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Planet.php', + 'Laminas\\Validator\\Barcode\\Postnet' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Postnet.php', + 'Laminas\\Validator\\Barcode\\Royalmail' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Royalmail.php', + 'Laminas\\Validator\\Barcode\\Sscc' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Sscc.php', + 'Laminas\\Validator\\Barcode\\Upca' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Upca.php', + 'Laminas\\Validator\\Barcode\\Upce' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Upce.php', + 'Laminas\\Validator\\Between' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Between.php', + 'Laminas\\Validator\\Bitwise' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Bitwise.php', + 'Laminas\\Validator\\Callback' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Callback.php', + 'Laminas\\Validator\\ConfigProvider' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ConfigProvider.php', + 'Laminas\\Validator\\CreditCard' => __DIR__ . '/..' . '/laminas/laminas-validator/src/CreditCard.php', + 'Laminas\\Validator\\Csrf' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Csrf.php', + 'Laminas\\Validator\\Date' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Date.php', + 'Laminas\\Validator\\DateStep' => __DIR__ . '/..' . '/laminas/laminas-validator/src/DateStep.php', + 'Laminas\\Validator\\Db\\AbstractDb' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Db/AbstractDb.php', + 'Laminas\\Validator\\Db\\NoRecordExists' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Db/NoRecordExists.php', + 'Laminas\\Validator\\Db\\RecordExists' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Db/RecordExists.php', + 'Laminas\\Validator\\Digits' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Digits.php', + 'Laminas\\Validator\\EmailAddress' => __DIR__ . '/..' . '/laminas/laminas-validator/src/EmailAddress.php', + 'Laminas\\Validator\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/BadMethodCallException.php', + 'Laminas\\Validator\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/ExceptionInterface.php', + 'Laminas\\Validator\\Exception\\ExtensionNotLoadedException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/ExtensionNotLoadedException.php', + 'Laminas\\Validator\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/InvalidArgumentException.php', + 'Laminas\\Validator\\Exception\\InvalidMagicMimeFileException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/InvalidMagicMimeFileException.php', + 'Laminas\\Validator\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/RuntimeException.php', + 'Laminas\\Validator\\Explode' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Explode.php', + 'Laminas\\Validator\\File\\Count' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Count.php', + 'Laminas\\Validator\\File\\Crc32' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Crc32.php', + 'Laminas\\Validator\\File\\ExcludeExtension' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/ExcludeExtension.php', + 'Laminas\\Validator\\File\\ExcludeMimeType' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/ExcludeMimeType.php', + 'Laminas\\Validator\\File\\Exists' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Exists.php', + 'Laminas\\Validator\\File\\Extension' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Extension.php', + 'Laminas\\Validator\\File\\FileInformationTrait' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/FileInformationTrait.php', + 'Laminas\\Validator\\File\\FilesSize' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/FilesSize.php', + 'Laminas\\Validator\\File\\Hash' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Hash.php', + 'Laminas\\Validator\\File\\ImageSize' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/ImageSize.php', + 'Laminas\\Validator\\File\\IsCompressed' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/IsCompressed.php', + 'Laminas\\Validator\\File\\IsImage' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/IsImage.php', + 'Laminas\\Validator\\File\\Md5' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Md5.php', + 'Laminas\\Validator\\File\\MimeType' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/MimeType.php', + 'Laminas\\Validator\\File\\NotExists' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/NotExists.php', + 'Laminas\\Validator\\File\\Sha1' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Sha1.php', + 'Laminas\\Validator\\File\\Size' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Size.php', + 'Laminas\\Validator\\File\\Upload' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Upload.php', + 'Laminas\\Validator\\File\\UploadFile' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/UploadFile.php', + 'Laminas\\Validator\\File\\WordCount' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/WordCount.php', + 'Laminas\\Validator\\GpsPoint' => __DIR__ . '/..' . '/laminas/laminas-validator/src/GpsPoint.php', + 'Laminas\\Validator\\GreaterThan' => __DIR__ . '/..' . '/laminas/laminas-validator/src/GreaterThan.php', + 'Laminas\\Validator\\Hex' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Hex.php', + 'Laminas\\Validator\\Hostname' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Hostname.php', + 'Laminas\\Validator\\Iban' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Iban.php', + 'Laminas\\Validator\\Identical' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Identical.php', + 'Laminas\\Validator\\InArray' => __DIR__ . '/..' . '/laminas/laminas-validator/src/InArray.php', + 'Laminas\\Validator\\Ip' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Ip.php', + 'Laminas\\Validator\\IsCountable' => __DIR__ . '/..' . '/laminas/laminas-validator/src/IsCountable.php', + 'Laminas\\Validator\\IsInstanceOf' => __DIR__ . '/..' . '/laminas/laminas-validator/src/IsInstanceOf.php', + 'Laminas\\Validator\\Isbn' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Isbn.php', + 'Laminas\\Validator\\Isbn\\Isbn10' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Isbn/Isbn10.php', + 'Laminas\\Validator\\Isbn\\Isbn13' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Isbn/Isbn13.php', + 'Laminas\\Validator\\LessThan' => __DIR__ . '/..' . '/laminas/laminas-validator/src/LessThan.php', + 'Laminas\\Validator\\Module' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Module.php', + 'Laminas\\Validator\\NotEmpty' => __DIR__ . '/..' . '/laminas/laminas-validator/src/NotEmpty.php', + 'Laminas\\Validator\\Regex' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Regex.php', + 'Laminas\\Validator\\Sitemap\\Changefreq' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Sitemap/Changefreq.php', + 'Laminas\\Validator\\Sitemap\\Lastmod' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Sitemap/Lastmod.php', + 'Laminas\\Validator\\Sitemap\\Loc' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Sitemap/Loc.php', + 'Laminas\\Validator\\Sitemap\\Priority' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Sitemap/Priority.php', + 'Laminas\\Validator\\StaticValidator' => __DIR__ . '/..' . '/laminas/laminas-validator/src/StaticValidator.php', + 'Laminas\\Validator\\Step' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Step.php', + 'Laminas\\Validator\\StringLength' => __DIR__ . '/..' . '/laminas/laminas-validator/src/StringLength.php', + 'Laminas\\Validator\\Timezone' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Timezone.php', + 'Laminas\\Validator\\Translator\\TranslatorAwareInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php', + 'Laminas\\Validator\\Translator\\TranslatorInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Translator/TranslatorInterface.php', + 'Laminas\\Validator\\Uri' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Uri.php', + 'Laminas\\Validator\\Uuid' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Uuid.php', + 'Laminas\\Validator\\ValidatorChain' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorChain.php', + 'Laminas\\Validator\\ValidatorInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorInterface.php', + 'Laminas\\Validator\\ValidatorPluginManager' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorPluginManager.php', + 'Laminas\\Validator\\ValidatorPluginManagerAwareInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php', + 'Laminas\\Validator\\ValidatorPluginManagerFactory' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorPluginManagerFactory.php', + 'Laminas\\Validator\\ValidatorProviderInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorProviderInterface.php', + 'Laminas\\ZendFrameworkBridge\\Autoloader' => __DIR__ . '/..' . '/laminas/laminas-zendframework-bridge/src/Autoloader.php', + 'Laminas\\ZendFrameworkBridge\\ConfigPostProcessor' => __DIR__ . '/..' . '/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php', + 'Laminas\\ZendFrameworkBridge\\Module' => __DIR__ . '/..' . '/laminas/laminas-zendframework-bridge/src/Module.php', + 'Laminas\\ZendFrameworkBridge\\Replacements' => __DIR__ . '/..' . '/laminas/laminas-zendframework-bridge/src/Replacements.php', + 'Laminas\\ZendFrameworkBridge\\RewriteRules' => __DIR__ . '/..' . '/laminas/laminas-zendframework-bridge/src/RewriteRules.php', + 'League\\OAuth2\\Client\\Exception\\HostedDomainException' => __DIR__ . '/..' . '/league/oauth2-google/src/Exception/HostedDomainException.php', + 'League\\OAuth2\\Client\\Grant\\AbstractGrant' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/AbstractGrant.php', + 'League\\OAuth2\\Client\\Grant\\AuthorizationCode' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/AuthorizationCode.php', + 'League\\OAuth2\\Client\\Grant\\ClientCredentials' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/ClientCredentials.php', + 'League\\OAuth2\\Client\\Grant\\Exception\\InvalidGrantException' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php', + 'League\\OAuth2\\Client\\Grant\\GrantFactory' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/GrantFactory.php', + 'League\\OAuth2\\Client\\Grant\\Password' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/Password.php', + 'League\\OAuth2\\Client\\Grant\\RefreshToken' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/RefreshToken.php', + 'League\\OAuth2\\Client\\OptionProvider\\HttpBasicAuthOptionProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php', + 'League\\OAuth2\\Client\\OptionProvider\\OptionProviderInterface' => __DIR__ . '/..' . '/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php', + 'League\\OAuth2\\Client\\OptionProvider\\PostAuthOptionProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php', + 'League\\OAuth2\\Client\\Provider\\AbstractProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/AbstractProvider.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\IdentityProviderException' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php', + 'League\\OAuth2\\Client\\Provider\\GenericProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/GenericProvider.php', + 'League\\OAuth2\\Client\\Provider\\GenericResourceOwner' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/GenericResourceOwner.php', + 'League\\OAuth2\\Client\\Provider\\Google' => __DIR__ . '/..' . '/league/oauth2-google/src/Provider/Google.php', + 'League\\OAuth2\\Client\\Provider\\GoogleUser' => __DIR__ . '/..' . '/league/oauth2-google/src/Provider/GoogleUser.php', + 'League\\OAuth2\\Client\\Provider\\ResourceOwnerInterface' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/ResourceOwnerInterface.php', + 'League\\OAuth2\\Client\\Token\\AccessToken' => __DIR__ . '/..' . '/league/oauth2-client/src/Token/AccessToken.php', + 'League\\OAuth2\\Client\\Token\\AccessTokenInterface' => __DIR__ . '/..' . '/league/oauth2-client/src/Token/AccessTokenInterface.php', + 'League\\OAuth2\\Client\\Token\\ResourceOwnerAccessTokenInterface' => __DIR__ . '/..' . '/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php', + 'League\\OAuth2\\Client\\Tool\\ArrayAccessorTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/ArrayAccessorTrait.php', + 'League\\OAuth2\\Client\\Tool\\BearerAuthorizationTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\GuardedPropertyTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/GuardedPropertyTrait.php', + 'League\\OAuth2\\Client\\Tool\\MacAuthorizationTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/MacAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\ProviderRedirectTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/ProviderRedirectTrait.php', + 'League\\OAuth2\\Client\\Tool\\QueryBuilderTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/QueryBuilderTrait.php', + 'League\\OAuth2\\Client\\Tool\\RequestFactory' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/RequestFactory.php', + 'League\\OAuth2\\Client\\Tool\\RequiredParameterTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/RequiredParameterTrait.php', 'ListExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'ListOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'LogAPI' => __DIR__ . '/../..' . '/core/log.class.inc.php', @@ -1003,6 +1533,13 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php', 'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php', 'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => __DIR__ . '/..' . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => __DIR__ . '/..' . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriInterface' => __DIR__ . '/..' . '/psr/http-message/src/UriInterface.php', 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', @@ -2106,6 +2643,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'TemplateMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php', 'TemplateString' => __DIR__ . '/../..' . '/core/templatestring.class.inc.php', 'TemplateStringPlaceholder' => __DIR__ . '/../..' . '/core/templatestring.class.inc.php', + 'TheNetworg\\OAuth2\\Client\\Grant\\JwtBearer' => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src/Grant/JwtBearer.php', + 'TheNetworg\\OAuth2\\Client\\Provider\\Azure' => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src/Provider/Azure.php', + 'TheNetworg\\OAuth2\\Client\\Provider\\AzureResourceOwner' => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php', + 'TheNetworg\\OAuth2\\Client\\Token\\AccessToken' => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src/Token/AccessToken.php', 'ThemeHandler' => __DIR__ . '/../..' . '/application/themehandler.class.inc.php', 'ToolsLog' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'Trigger' => __DIR__ . '/../..' . '/core/trigger.class.inc.php', @@ -2118,6 +2659,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'TriggerOnStateEnter' => __DIR__ . '/../..' . '/core/trigger.class.inc.php', 'TriggerOnStateLeave' => __DIR__ . '/../..' . '/core/trigger.class.inc.php', 'TriggerOnThresholdReached' => __DIR__ . '/../..' . '/core/trigger.class.inc.php', + 'TrueBV\\Exception\\DomainOutOfBoundsException' => __DIR__ . '/..' . '/true/punycode/src/Exception/DomainOutOfBoundsException.php', + 'TrueBV\\Exception\\LabelOutOfBoundsException' => __DIR__ . '/..' . '/true/punycode/src/Exception/LabelOutOfBoundsException.php', + 'TrueBV\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/true/punycode/src/Exception/OutOfBoundsException.php', + 'TrueBV\\Punycode' => __DIR__ . '/..' . '/true/punycode/src/Punycode.php', 'TrueExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'Twig\\Cache\\CacheInterface' => __DIR__ . '/..' . '/twig/twig/src/Cache/CacheInterface.php', 'Twig\\Cache\\FilesystemCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/FilesystemCache.php', diff --git a/lib/composer/installed.json b/lib/composer/installed.json index 0cda41c11..1a13c97fc 100644 --- a/lib/composer/installed.json +++ b/lib/composer/installed.json @@ -81,6 +81,45 @@ ], "install-path": "../combodo/tcpdf" }, + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "version_normalized": "1.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "time": "2017-02-14T19:40:03+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "support": { + "issues": "https://github.com/container-interop/container-interop/issues", + "source": "https://github.com/container-interop/container-interop/tree/master" + }, + "abandoned": "psr/container", + "install-path": "../container-interop/container-interop" + }, { "name": "doctrine/lexer", "version": "1.0.2", @@ -219,6 +258,980 @@ ], "install-path": "../egulias/email-validator" }, + { + "name": "firebase/php-jwt", + "version": "v5.5.1", + "version_normalized": "5.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "83b609028194aa042ea33b5af2d41a7427de80e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6", + "reference": "83b609028194aa042ea33b5af2d41a7427de80e6", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <=9" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "time": "2021-11-08T20:18:51+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v5.5.1" + }, + "install-path": "../firebase/php-jwt" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.5", + "version_normalized": "6.5.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.17.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "time": "2020-06-16T21:01:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/6.5" + }, + "install-path": "../guzzlehttp/guzzle" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "version_normalized": "1.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "time": "2021-10-22T20:56:57+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "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/1.5.1" + }, + "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": "1.8.5", + "version_normalized": "1.8.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/337e3ad8e5716c15f9657bd214d16cc5e69df268", + "reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "time": "2022-03-20T21:51:18+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "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" + } + ], + "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/1.8.5" + }, + "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": "laminas/laminas-loader", + "version": "2.6.1", + "version_normalized": "2.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-loader.git", + "reference": "5d01c2c237ae9e68bec262f339947e2ea18979bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/5d01c2c237ae9e68bec262f339947e2ea18979bc", + "reference": "5d01c2c237ae9e68bec262f339947e2ea18979bc", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-loader": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" + }, + "time": "2019-12-31T17:18:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev", + "dev-develop": "2.7.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Autoloading and plugin loading strategies", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "loader" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-loader/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-loader/issues", + "rss": "https://github.com/laminas/laminas-loader/releases.atom", + "source": "https://github.com/laminas/laminas-loader" + }, + "install-path": "../laminas/laminas-loader" + }, + { + "name": "laminas/laminas-mail", + "version": "2.11.1", + "version_normalized": "2.11.1.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mail.git", + "reference": "7f674afeb38100b1869ce8e56bf2ec3cba3c679c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/7f674afeb38100b1869ce8e56bf2ec3cba3c679c", + "reference": "7f674afeb38100b1869ce8e56bf2ec3cba3c679c", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mime": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.10.2", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "true/punycode": "^2.1" + }, + "replace": { + "zendframework/zend-mail": "^2.10.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-crypt": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4" + }, + "suggest": { + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + }, + "time": "2020-07-28T21:17:48+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" + }, + "laminas": { + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Mail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mail" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mail/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mail/issues", + "rss": "https://github.com/laminas/laminas-mail/releases.atom", + "source": "https://github.com/laminas/laminas-mail" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "install-path": "../laminas/laminas-mail" + }, + { + "name": "laminas/laminas-mime", + "version": "2.7.4", + "version_normalized": "2.7.4.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mime.git", + "reference": "e45a7d856bf7b4a7b5bd00d6371f9961dc233add" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/e45a7d856bf7b4a7b5bd00d6371f9961dc233add", + "reference": "e45a7d856bf7b4a7b5bd00d6371f9961dc233add", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-mime": "^2.7.2" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-mail": "^2.6", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20" + }, + "suggest": { + "laminas/laminas-mail": "Laminas\\Mail component" + }, + "time": "2020-03-29T13:12:07+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Mime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Create and parse MIME messages and parts", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mime" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mime/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mime/issues", + "rss": "https://github.com/laminas/laminas-mime/releases.atom", + "source": "https://github.com/laminas/laminas-mime" + }, + "install-path": "../laminas/laminas-mime" + }, + { + "name": "laminas/laminas-servicemanager", + "version": "3.5.2", + "version_normalized": "3.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "0669e1eec8d9f61e35a5bc5012796d49f418b259" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/0669e1eec8d9f61e35a5bc5012796d49f418b259", + "reference": "0669e1eec8d9f61e35a5bc5012796d49f418b259", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "psr/container": "^1.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "zendframework/zend-servicemanager": "^3.4.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "mikey179/vfsstream": "^1.6.5", + "ocramius/proxy-manager": "^1.0 || ^2.0", + "phpbench/phpbench": "^0.13.0", + "phpunit/phpunit": "^5.7.25 || ^6.4.4" + }, + "suggest": { + "laminas/laminas-stdlib": "laminas-stdlib ^2.5 if you wish to use the MergeReplaceKey or MergeRemoveKey features in Config instances", + "ocramius/proxy-manager": "ProxyManager 1.* to handle lazy initialization of services" + }, + "time": "2021-01-17T16:54:43+00:00", + "bin": [ + "bin/generate-deps-for-config-factory", + "bin/generate-factory-for-class" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev", + "dev-develop": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Factory-Driven Dependency Injection Container", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "dependency-injection", + "di", + "dic", + "laminas", + "service-manager", + "servicemanager" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-servicemanager/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-servicemanager/issues", + "rss": "https://github.com/laminas/laminas-servicemanager/releases.atom", + "source": "https://github.com/laminas/laminas-servicemanager" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "install-path": "../laminas/laminas-servicemanager" + }, + { + "name": "laminas/laminas-stdlib", + "version": "3.2.1", + "version_normalized": "3.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "2b18347625a2f06a1a485acfbc870f699dbe51c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/2b18347625a2f06a1a485acfbc870f699dbe51c6", + "reference": "2b18347625a2f06a1a485acfbc870f699dbe51c6", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpbench/phpbench": "^0.13", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" + }, + "time": "2019-12-31T17:51:15+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "install-path": "../laminas/laminas-stdlib" + }, + { + "name": "laminas/laminas-validator", + "version": "2.12.2", + "version_normalized": "2.12.2.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-validator.git", + "reference": "0813f234812d9fa9058b6da39eb13dedc90227db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/0813f234812d9fa9058b6da39eb13dedc90227db", + "reference": "0813f234812d9fa9058b6da39eb13dedc90227db", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-validator": "self.version" + }, + "require-dev": { + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.5", + "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "psr/http-message": "^1.0" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" + }, + "time": "2019-12-31T17:57:44+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" + }, + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "validator" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-validator/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-validator/issues", + "rss": "https://github.com/laminas/laminas-validator/releases.atom", + "source": "https://github.com/laminas/laminas-validator" + }, + "install-path": "../laminas/laminas-validator" + }, + { + "name": "laminas/laminas-zendframework-bridge", + "version": "1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "6ede70583e101030bcace4dcddd648f760ddf642" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/6ede70583e101030bcace4dcddd648f760ddf642", + "reference": "6ede70583e101030bcace4dcddd648f760ddf642", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1 || ^9.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "time": "2020-09-14T14:23:00+00:00", + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ZendFrameworkBridge\\": "src//" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "keywords": [ + "ZendFramework", + "autoloading", + "laminas", + "zf" + ], + "support": { + "forum": "https://discourse.laminas.dev/", + "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", + "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", + "source": "https://github.com/laminas/laminas-zendframework-bridge" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "install-path": "../laminas/laminas-zendframework-bridge" + }, + { + "name": "league/oauth2-client", + "version": "2.6.1", + "version_normalized": "2.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "2334c249907190c132364f5dae0287ab8666aa19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/2334c249907190c132364f5dae0287ab8666aa19", + "reference": "2334c249907190c132364f5dae0287ab8666aa19", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99", + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", + "squizlabs/php_codesniffer": "^2.3 || ^3.0" + }, + "time": "2021-12-22T16:42:49+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.6.1" + }, + "install-path": "../league/oauth2-client" + }, + { + "name": "league/oauth2-google", + "version": "3.0.4", + "version_normalized": "3.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-google.git", + "reference": "6b79441f244040760bed5fdcd092a2bda7cf34c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-google/zipball/6b79441f244040760bed5fdcd092a2bda7cf34c6", + "reference": "6b79441f244040760bed5fdcd092a2bda7cf34c6", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^2.0", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^6.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "time": "2021-01-27T16:09:03+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me" + } + ], + "description": "Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "Authentication", + "authorization", + "client", + "google", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-google/issues", + "source": "https://github.com/thephpleague/oauth2-google/tree/3.0.4" + }, + "install-path": "../league/oauth2-google" + }, { "name": "nikic/php-parser", "version": "v4.13.2", @@ -750,6 +1763,62 @@ ], "install-path": "../psr/container" }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+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": "http://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/master" + }, + "install-path": "../psr/http-message" + }, { "name": "psr/log", "version": "1.1.2", @@ -851,6 +1920,53 @@ ], "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": "scssphp/scssphp", "version": "1.0.6", @@ -3546,6 +4662,117 @@ ], "install-path": "../symfony/yaml" }, + { + "name": "thenetworg/oauth2-azure", + "version": "v2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/TheNetworg/oauth2-azure.git", + "reference": "2649422a0dc74af32d21d9d738d37abcd5b03998" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/2649422a0dc74af32d21d9d738d37abcd5b03998", + "reference": "2649422a0dc74af32d21d9d738d37abcd5b03998", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "~3.0||~4.0||~5.0", + "league/oauth2-client": "~2.0", + "php": "^5.6|^7.0|^8.0" + }, + "time": "2021-01-11T12:20:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "TheNetworg\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Hajek", + "email": "jan.hajek@thenetw.org", + "homepage": "https://thenetw.org" + } + ], + "description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "SSO", + "aad", + "authorization", + "azure", + "azure active directory", + "client", + "microsoft", + "oauth", + "oauth2", + "windows azure" + ], + "support": { + "issues": "https://github.com/TheNetworg/oauth2-azure/issues", + "source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.0.1" + }, + "install-path": "../thenetworg/oauth2-azure" + }, + { + "name": "true/punycode", + "version": "v2.1.1", + "version_normalized": "2.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/true/php-punycode.git", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" + }, + "time": "2016-11-16T10:37:54+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "TrueBV\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "homepage": "https://github.com/true/php-punycode", + "keywords": [ + "idna", + "punycode" + ], + "support": { + "issues": "https://github.com/true/php-punycode/issues", + "source": "https://github.com/true/php-punycode/tree/master" + }, + "install-path": "../true/punycode" + }, { "name": "twig/twig", "version": "v1.42.5", diff --git a/lib/composer/installed.php b/lib/composer/installed.php index bd61b662c..37876bd7c 100644 --- a/lib/composer/installed.php +++ b/lib/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'c4ae94fd4ca204c8d94f19a0f58f24492d015d9b', + 'reference' => '134736dce5289b252d1017b3a2f3bbb03b6e63a8', 'name' => '__root__', 'dev' => true, ), @@ -16,7 +16,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'c4ae94fd4ca204c8d94f19a0f58f24492d015d9b', + 'reference' => '134736dce5289b252d1017b3a2f3bbb03b6e63a8', 'dev_requirement' => false, ), 'combodo/tcpdf' => array( @@ -28,6 +28,21 @@ 'reference' => '0e31c013ccd000aa6762e9186778aa6e259ac8e8', 'dev_requirement' => false, ), + 'container-interop/container-interop' => array( + 'pretty_version' => '1.2.0', + 'version' => '1.2.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../container-interop/container-interop', + 'aliases' => array(), + 'reference' => '79cbf1341c22ec75643d841642dd5d6acd83bdb8', + 'dev_requirement' => false, + ), + 'container-interop/container-interop-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '^1.2', + ), + ), 'doctrine/lexer' => array( 'pretty_version' => '1.0.2', 'version' => '1.0.2.0', @@ -46,6 +61,123 @@ 'reference' => '0dbf5d78455d4d6a41d186da50adc1122ec066f4', 'dev_requirement' => false, ), + 'firebase/php-jwt' => array( + 'pretty_version' => 'v5.5.1', + 'version' => '5.5.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../firebase/php-jwt', + 'aliases' => array(), + 'reference' => '83b609028194aa042ea33b5af2d41a7427de80e6', + 'dev_requirement' => false, + ), + 'guzzlehttp/guzzle' => array( + 'pretty_version' => '6.5.5', + 'version' => '6.5.5.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', + 'aliases' => array(), + 'reference' => '9d4290de1cfd701f38099ef7e183b64b4b7b0c5e', + 'dev_requirement' => false, + ), + 'guzzlehttp/promises' => array( + 'pretty_version' => '1.5.1', + 'version' => '1.5.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/promises', + 'aliases' => array(), + 'reference' => 'fe752aedc9fd8fcca3fe7ad05d419d32998a06da', + 'dev_requirement' => false, + ), + 'guzzlehttp/psr7' => array( + 'pretty_version' => '1.8.5', + 'version' => '1.8.5.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/psr7', + 'aliases' => array(), + 'reference' => '337e3ad8e5716c15f9657bd214d16cc5e69df268', + 'dev_requirement' => false, + ), + 'laminas/laminas-loader' => array( + 'pretty_version' => '2.6.1', + 'version' => '2.6.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-loader', + 'aliases' => array(), + 'reference' => '5d01c2c237ae9e68bec262f339947e2ea18979bc', + 'dev_requirement' => false, + ), + 'laminas/laminas-mail' => array( + 'pretty_version' => '2.11.1', + 'version' => '2.11.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-mail', + 'aliases' => array(), + 'reference' => '7f674afeb38100b1869ce8e56bf2ec3cba3c679c', + 'dev_requirement' => false, + ), + 'laminas/laminas-mime' => array( + 'pretty_version' => '2.7.4', + 'version' => '2.7.4.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-mime', + 'aliases' => array(), + 'reference' => 'e45a7d856bf7b4a7b5bd00d6371f9961dc233add', + 'dev_requirement' => false, + ), + 'laminas/laminas-servicemanager' => array( + 'pretty_version' => '3.5.2', + 'version' => '3.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-servicemanager', + 'aliases' => array(), + 'reference' => '0669e1eec8d9f61e35a5bc5012796d49f418b259', + 'dev_requirement' => false, + ), + 'laminas/laminas-stdlib' => array( + 'pretty_version' => '3.2.1', + 'version' => '3.2.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-stdlib', + 'aliases' => array(), + 'reference' => '2b18347625a2f06a1a485acfbc870f699dbe51c6', + 'dev_requirement' => false, + ), + 'laminas/laminas-validator' => array( + 'pretty_version' => '2.12.2', + 'version' => '2.12.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-validator', + 'aliases' => array(), + 'reference' => '0813f234812d9fa9058b6da39eb13dedc90227db', + 'dev_requirement' => false, + ), + 'laminas/laminas-zendframework-bridge' => array( + 'pretty_version' => '1.1.1', + 'version' => '1.1.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-zendframework-bridge', + 'aliases' => array(), + 'reference' => '6ede70583e101030bcace4dcddd648f760ddf642', + 'dev_requirement' => false, + ), + 'league/oauth2-client' => array( + 'pretty_version' => '2.6.1', + 'version' => '2.6.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/oauth2-client', + 'aliases' => array(), + 'reference' => '2334c249907190c132364f5dae0287ab8666aa19', + 'dev_requirement' => false, + ), + 'league/oauth2-google' => array( + 'pretty_version' => '3.0.4', + 'version' => '3.0.4.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/oauth2-google', + 'aliases' => array(), + 'reference' => '6b79441f244040760bed5fdcd092a2bda7cf34c6', + 'dev_requirement' => false, + ), 'nikic/php-parser' => array( 'pretty_version' => 'v4.13.2', 'version' => '4.13.2.0', @@ -134,6 +266,22 @@ 'dev_requirement' => false, ), 'psr/container-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + 1 => '^1.0', + ), + ), + 'psr/http-message' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', + 'dev_requirement' => false, + ), + 'psr/http-message-implementation' => array( 'dev_requirement' => false, 'provided' => array( 0 => '1.0', @@ -169,6 +317,15 @@ 0 => '1.0', ), ), + 'ralouphie/getallheaders' => array( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ralouphie/getallheaders', + 'aliases' => array(), + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', + 'dev_requirement' => false, + ), 'rsky/pear-core-min' => array( 'dev_requirement' => false, 'replaced' => array( @@ -478,6 +635,24 @@ 0 => '6.4.4', ), ), + 'thenetworg/oauth2-azure' => array( + 'pretty_version' => 'v2.0.1', + 'version' => '2.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../thenetworg/oauth2-azure', + 'aliases' => array(), + 'reference' => '2649422a0dc74af32d21d9d738d37abcd5b03998', + 'dev_requirement' => false, + ), + 'true/punycode' => array( + 'pretty_version' => 'v2.1.1', + 'version' => '2.1.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../true/punycode', + 'aliases' => array(), + 'reference' => 'a4d0c11a36dd7f4e7cd7096076cab6d3378a071e', + 'dev_requirement' => false, + ), 'twig/twig' => array( 'pretty_version' => 'v1.42.5', 'version' => '1.42.5.0', @@ -487,5 +662,41 @@ 'reference' => '87b2ea9d8f6fd014d0621ca089bb1b3769ea3f8e', 'dev_requirement' => false, ), + 'zendframework/zend-loader' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '2.6.1', + ), + ), + 'zendframework/zend-mail' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '^2.10.0', + ), + ), + 'zendframework/zend-mime' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '^2.7.2', + ), + ), + 'zendframework/zend-servicemanager' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '^3.4.0', + ), + ), + 'zendframework/zend-stdlib' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '3.2.1', + ), + ), + 'zendframework/zend-validator' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '2.12.2', + ), + ), ), ); diff --git a/sources/Controller/OAuth/OAuthAjaxController.php b/sources/Controller/OAuth/OAuthAjaxController.php index 73954e26a..908765c88 100644 --- a/sources/Controller/OAuth/OAuthAjaxController.php +++ b/sources/Controller/OAuth/OAuthAjaxController.php @@ -3,6 +3,7 @@ namespace Combodo\iTop\Controller\OAuth; use Combodo\iTop\Application\TwigBase\Controller\Controller; +use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderAbstract; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory; use utils; @@ -23,4 +24,35 @@ class OAuthAjaxController extends Controller $this->DisplayJSONPage($aResult); } + + public function OperationGetDisplayAuthenticationResults() + { + $aResult = ['status' => 'success', 'data' => []]; + $sProvider = utils::ReadParam('provider', '', false, 'raw'); + $sRedirectUrl = utils::ReadParam('redirect_url', '', false, 'raw'); + $sClientId = utils::ReadParam('client_id', '', false, 'raw'); + $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); + $sScope = utils::ReadParam('scope', '', false, 'raw'); + $sAdditional = utils::ReadParam('additional', '', false, 'raw'); + + $sRedirectUrlQuery = parse_url($sRedirectUrl)['query']; + $aOAuthResultDisplayClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); + $aAdditional = []; + parse_str($sAdditional, $aAdditional); + + $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProvider; + $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); + + $aQuery = []; + parse_str($sRedirectUrlQuery, $aQuery); + $sCode = $aQuery['code']; + $oProvider = OAuthClientProviderFactory::getVendorProvider($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); + $oAccessToken = OAuthClientProviderFactory::getAccessTokenFromCode($oProvider, $sCode); + + foreach ($aOAuthResultDisplayClasses as $sOAuthClass) { + $aResult['data'][] = $sOAuthClass::GetResultDisplayScript($sClientId, $sClientSecret, $sProvider, $oAccessToken); + } + + $this->DisplayJSONPage($aResult); + } } \ No newline at end of file From 8c217fdac9b6e05d876688cbc157af309b691526 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Fri, 13 May 2022 12:07:27 +0200 Subject: [PATCH 05/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=202.7=20migration=20(wip)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/composer/autoload_classmap.php | 1 + lib/composer/autoload_static.php | 1 + pages/ajax.oauth.wizard.php | 61 ------------------- pages/oauth.landing.php | 26 +++----- .../OAuth/OAuthLandingController.php | 13 ++++ .../pages/backoffice/oauth/Landing.html.twig | 3 + .../backoffice/oauth/Landing.ready.js.twig | 7 +++ 7 files changed, 32 insertions(+), 80 deletions(-) create mode 100644 sources/Controller/OAuth/OAuthLandingController.php create mode 100644 templates/pages/backoffice/oauth/Landing.html.twig create mode 100644 templates/pages/backoffice/oauth/Landing.ready.js.twig diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 872d9138e..cbc92b05b 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -151,6 +151,7 @@ return array( 'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthAjaxController' => $baseDir . '/sources/Controller/OAuth/OAuthAjaxController.php', + 'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => $baseDir . '/sources/Controller/OAuth/OAuthLandingController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthWizardController' => $baseDir . '/sources/Controller/OAuth/OAuthWizardController.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientResultDisplay' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 4d1e7a5bd..21bc64c73 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -519,6 +519,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthAjaxController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthAjaxController.php', + 'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthLandingController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthWizardController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthWizardController.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientResultDisplay' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php', diff --git a/pages/ajax.oauth.wizard.php b/pages/ajax.oauth.wizard.php index e0eded32a..7a753a320 100644 --- a/pages/ajax.oauth.wizard.php +++ b/pages/ajax.oauth.wizard.php @@ -11,64 +11,3 @@ $oUpdateController = new OAuthAjaxController($sTemplates, 'core'); $oUpdateController->AllowOnlyAdmin(); $oUpdateController->SetDefaultOperation('Default'); $oUpdateController->HandleOperation(); - - - - - -//require_once(APPROOT.'application/utils.inc.php'); -//require_once(APPROOT.'/application/application.inc.php'); -// -//require_once(APPROOT.'/application/loginwebpage.class.inc.php'); -// -//$oPage = new JsonPage(); -//$oPage->SetOutputDataOnly(true); -//$aResult = ['status' => 'success', 'data' => []]; -//try { -// $operation = utils::ReadParam('operation', ''); -// -// switch ($operation) { -// case 'get_authorization_url': -// $sProvider = utils::ReadParam('provider', '', false, 'raw'); -// $sClientId = utils::ReadParam('client_id', '', false, 'raw'); -// $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); -// $sScope = utils::ReadParam('scope', '', false, 'raw'); -// $sAdditional = utils::ReadParam('additional', '', false, 'raw'); -// $aAdditional = []; -// parse_str($sAdditional, $aAdditional); -// $sAuthorizationUrl = OAuthClientProviderFactory::getVendorProviderForAccessUrl($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); -// $aResult['data']['authorization_url'] = $sAuthorizationUrl; -// break; -// case 'get_display_authentication_results': -// $sProvider = utils::ReadParam('provider', '', false, 'raw'); -// $sRedirectUrl = utils::ReadParam('redirect_url', '', false, 'raw'); -// $sClientId = utils::ReadParam('client_id', '', false, 'raw'); -// $sClientSecret = utils::ReadParam('client_secret', '', false, 'raw'); -// $sScope = utils::ReadParam('scope', '', false, 'raw'); -// $sAdditional = utils::ReadParam('additional', '', false, 'raw'); -// -// $sRedirectUrlQuery = parse_url($sRedirectUrl)['query']; -// $aOAuthResultDisplayClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); -// $aAdditional = []; -// parse_str($sAdditional, $aAdditional); -// -// $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProvider; -// $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); -// -// $aQuery = []; -// parse_str($sRedirectUrlQuery, $aQuery); -// $sCode = $aQuery['code']; -// $oProvider = OAuthClientProviderFactory::getVendorProvider($sProvider, $sClientId, $sClientSecret, $sScope, $aAdditional); -// $oAccessToken = OAuthClientProviderFactory::getAccessTokenFromCode($oProvider, $sCode); -// -// foreach($aOAuthResultDisplayClasses as $sOAuthClass) { -// $aResult['data'][] = $sOAuthClass::GetResultDisplayScript($sClientId, $sClientSecret, $sProvider, $oAccessToken); -// } -// } -//} -//catch(Exception $e){ -// $aResult['status'] = 'error'; -// IssueLog::Error($e->getMessage()); -//} -//$oPage->SetData($aResult); -//$oPage->output(); \ No newline at end of file diff --git a/pages/oauth.landing.php b/pages/oauth.landing.php index d885ded25..27019f430 100644 --- a/pages/oauth.landing.php +++ b/pages/oauth.landing.php @@ -1,26 +1,14 @@ AddCSSClass('ibo-oauth-wizard--side-pane'); -$oPage = new WebPage(Dict::S('UI:Schema:Title')); - -$sJS = <<add_script($sJS); - - -$oPage->output(); +$oUpdateController = new OAuthLandingController($sTemplates, 'core'); +$oUpdateController->AllowOnlyAdmin(); +$oUpdateController->SetDefaultOperation('Landing'); +$oUpdateController->HandleOperation(); diff --git a/sources/Controller/OAuth/OAuthLandingController.php b/sources/Controller/OAuth/OAuthLandingController.php new file mode 100644 index 000000000..3c3fd7c77 --- /dev/null +++ b/sources/Controller/OAuth/OAuthLandingController.php @@ -0,0 +1,13 @@ +DisplayPage([]); + } +} \ No newline at end of file diff --git a/templates/pages/backoffice/oauth/Landing.html.twig b/templates/pages/backoffice/oauth/Landing.html.twig new file mode 100644 index 000000000..578bce7b0 --- /dev/null +++ b/templates/pages/backoffice/oauth/Landing.html.twig @@ -0,0 +1,3 @@ +{# @copyright Copyright (C) 2010-2022 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} + diff --git a/templates/pages/backoffice/oauth/Landing.ready.js.twig b/templates/pages/backoffice/oauth/Landing.ready.js.twig new file mode 100644 index 000000000..4c8717fe7 --- /dev/null +++ b/templates/pages/backoffice/oauth/Landing.ready.js.twig @@ -0,0 +1,7 @@ +{# @copyright Copyright (C) 2010-2022 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} + +window.addEventListener("message", function (event){ + event.source.postMessage(window.location.href, event.origin); + window.close(); +}, false); From caf939bf5835556317f457cd33e666e2de1d7815 Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Fri, 13 May 2022 14:06:11 +0200 Subject: [PATCH 06/41] =?UTF-8?q?N=C2=B02504=20N=C2=B03169=20N=C2=B05102?= =?UTF-8?q?=20Add=20dictionaries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dictionaries/en.dictionary.itop.ui.php | 16 ++++++++++++++++ .../OAuth/OAuthClientResultDisplayConf.php | 2 +- .../backoffice/oauth/DisplayConfig.html.twig | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index 6206470ee..11d926e88 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -1599,3 +1599,19 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Display messages from %1$s', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Display up to %1$s messages in the %2$s menu.', )); + + +// OAuth +Dict::Add('EN US', 'English', 'English', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails', +)); \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php b/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php index 36ae42a26..a2e3c6507 100644 --- a/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php @@ -12,7 +12,7 @@ class OAuthClientResultDisplayConf implements IOAuthClientResultDisplay $oConfResultPanel = new Panel(Dict::S('UI:OAuth:Wizard:ResultConf:Panel:Title'), [],Panel::DEFAULT_COLOR_SCHEME, 'ibo-oauth-wizard--conf--panel'); $oConfResultPanel->AddCSSClass('ibo-oauth-wizard--result--panel'); $oConfResultPanel->SetIsCollapsible(true); - $oConfResultPanel->AddHtml('

'.Dict::S('UI:OAuthEmailSynchro:Wizard:ResultConf:Panel:Description').'

'); + $oConfResultPanel->AddHtml('

'.Dict::S('UI:OAuth:Wizard:ResultConf:Panel:Description').'

'); $oConfResultPanel->AddHtml('
'); return $oConfResultPanel; } diff --git a/templates/pages/backoffice/oauth/DisplayConfig.html.twig b/templates/pages/backoffice/oauth/DisplayConfig.html.twig index ed018faee..185943da0 100644 --- a/templates/pages/backoffice/oauth/DisplayConfig.html.twig +++ b/templates/pages/backoffice/oauth/DisplayConfig.html.twig @@ -3,6 +3,6 @@
{{ 'UI:OAuth:Wizard:ResultConf:Panel:Title'|dict_s }} -

{{ 'UI:OAuthEmailSynchro:Wizard:ResultConf:Panel:Description'|dict_s }}

+

{{ 'UI:OAuth:Wizard:ResultConf:Panel:Description'|dict_s }}

\ No newline at end of file From 58b27a9daab850a4071f5a18216f36cd6e5b3b26 Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Fri, 13 May 2022 14:28:38 +0200 Subject: [PATCH 07/41] =?UTF-8?q?N=C2=B02504=20N=C2=B03169=20N=C2=B05102?= =?UTF-8?q?=20Handle=20result=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/composer/ClassLoader.php | 2 +- lib/composer/InstalledVersions.php | 13 +++++++++++++ lib/composer/autoload_real.php | 9 +++++++-- lib/composer/installed.php | 8 ++++---- .../bin/generate-deps-for-config-factory | 0 .../bin/generate-factory-for-class | 0 sources/Controller/OAuth/OAuthAjaxController.php | 3 ++- sources/Controller/OAuth/OAuthWizardController.php | 1 + templates/pages/backoffice/oauth/Wizard.html.twig | 2 +- .../pages/backoffice/oauth/Wizard.ready.js.twig | 2 +- 10 files changed, 30 insertions(+), 10 deletions(-) mode change 100644 => 100755 lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory mode change 100644 => 100755 lib/laminas/laminas-servicemanager/bin/generate-factory-for-class diff --git a/lib/composer/ClassLoader.php b/lib/composer/ClassLoader.php index 0cd6055d1..afef3fa2a 100644 --- a/lib/composer/ClassLoader.php +++ b/lib/composer/ClassLoader.php @@ -149,7 +149,7 @@ class ClassLoader /** * @return string[] Array of classname => path - * @psalm-var array + * @psalm-return array */ public function getClassMap() { diff --git a/lib/composer/InstalledVersions.php b/lib/composer/InstalledVersions.php index 7c5502ca4..d50e0c9fc 100644 --- a/lib/composer/InstalledVersions.php +++ b/lib/composer/InstalledVersions.php @@ -24,8 +24,21 @@ use Composer\Semver\VersionParser; */ class InstalledVersions { + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + */ private static $installed; + + /** + * @var bool|null + */ private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ private static $installedByVendor = array(); /** diff --git a/lib/composer/autoload_real.php b/lib/composer/autoload_real.php index 661cd2543..9a33b711c 100644 --- a/lib/composer/autoload_real.php +++ b/lib/composer/autoload_real.php @@ -60,11 +60,16 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b } } +/** + * @param string $fileIdentifier + * @param string $file + * @return void + */ function composerRequire0018331147de7601e7552f7da8e3bb8b($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - require $file; - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; } } diff --git a/lib/composer/installed.php b/lib/composer/installed.php index 37876bd7c..64f2ae5ec 100644 --- a/lib/composer/installed.php +++ b/lib/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '134736dce5289b252d1017b3a2f3bbb03b6e63a8', + 'reference' => 'caf939bf5835556317f457cd33e666e2de1d7815', 'name' => '__root__', 'dev' => true, ), @@ -16,7 +16,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '134736dce5289b252d1017b3a2f3bbb03b6e63a8', + 'reference' => 'caf939bf5835556317f457cd33e666e2de1d7815', 'dev_requirement' => false, ), 'combodo/tcpdf' => array( @@ -268,8 +268,8 @@ 'psr/container-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0', - 1 => '^1.0', + 0 => '^1.0', + 1 => '1.0', ), ), 'psr/http-message' => array( diff --git a/lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory b/lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory old mode 100644 new mode 100755 diff --git a/lib/laminas/laminas-servicemanager/bin/generate-factory-for-class b/lib/laminas/laminas-servicemanager/bin/generate-factory-for-class old mode 100644 new mode 100755 diff --git a/sources/Controller/OAuth/OAuthAjaxController.php b/sources/Controller/OAuth/OAuthAjaxController.php index 908765c88..8788c0d16 100644 --- a/sources/Controller/OAuth/OAuthAjaxController.php +++ b/sources/Controller/OAuth/OAuthAjaxController.php @@ -36,7 +36,8 @@ class OAuthAjaxController extends Controller $sAdditional = utils::ReadParam('additional', '', false, 'raw'); $sRedirectUrlQuery = parse_url($sRedirectUrl)['query']; - $aOAuthResultDisplayClasses = utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]')); + // TODO: Needs to handle mail to ticket part too + $aOAuthResultDisplayClasses = ['\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientResultDisplayConf']; $aAdditional = []; parse_str($sAdditional, $aAdditional); diff --git a/sources/Controller/OAuth/OAuthWizardController.php b/sources/Controller/OAuth/OAuthWizardController.php index b2e53641a..13866ca0a 100644 --- a/sources/Controller/OAuth/OAuthWizardController.php +++ b/sources/Controller/OAuth/OAuthWizardController.php @@ -43,6 +43,7 @@ class OAuthWizardController extends Controller 'redirect_uri' => ['type' => 'text', 'label' => Dict::S('UI:OAuth:Wizard:Form:Input:RedirectUri:Label'), 'read_only' => true, 'value' => OAuthClientProviderAbstract::GetRedirectUri()], ]; + // TODO: Needs to handle mail to ticket part too $aParams['aAdditionalBlocks'] = [ OAuthClientResultDisplayConf::GetResultDisplayTemplate(), ]; diff --git a/templates/pages/backoffice/oauth/Wizard.html.twig b/templates/pages/backoffice/oauth/Wizard.html.twig index 5d4a2555f..c707ea6d2 100644 --- a/templates/pages/backoffice/oauth/Wizard.html.twig +++ b/templates/pages/backoffice/oauth/Wizard.html.twig @@ -33,7 +33,7 @@ type="{{ aInput.type }}" id="wizard_input_{{ sName }}" name="{{ sName }}" - {% if aInput.real_only %} readonly {% endif %} + {% if aInput.read_only %} readonly {% endif %} value="{{ aInput.value }}" > diff --git a/templates/pages/backoffice/oauth/Wizard.ready.js.twig b/templates/pages/backoffice/oauth/Wizard.ready.js.twig index e33478887..b0362f346 100644 --- a/templates/pages/backoffice/oauth/Wizard.ready.js.twig +++ b/templates/pages/backoffice/oauth/Wizard.ready.js.twig @@ -8,7 +8,7 @@ const oOnOauthSuccess = function (event){ $.post( '{{ sAjaxUri }}', { - operation: 'get_display_authentication_results', + operation: 'GetDisplayAuthenticationResults', provider: $('[name="provider"]:checked').val(), client_id: $('[name="client_id"]').val(), client_secret: $('[name="client_secret"]').val(), From 11d2e62e67e4df549e2be432201843a2dc8b67de Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Fri, 13 May 2022 14:38:55 +0200 Subject: [PATCH 08/41] =?UTF-8?q?N=C2=B02504=20N=C2=B03169=20N=C2=B05102?= =?UTF-8?q?=20Correctly=20disable=20authentication=20button=20for=202.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/pages/backoffice/oauth/Wizard.ready.js.twig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/pages/backoffice/oauth/Wizard.ready.js.twig b/templates/pages/backoffice/oauth/Wizard.ready.js.twig index b0362f346..b6ccf248a 100644 --- a/templates/pages/backoffice/oauth/Wizard.ready.js.twig +++ b/templates/pages/backoffice/oauth/Wizard.ready.js.twig @@ -23,7 +23,7 @@ const oOnOauthSuccess = function (event){ new Function(item)(); }); } - $('.ibo-oauth-wizard--form--submit').trigger('leave_loading_state.button.itop'); + $('.ibo-oauth-wizard--form--submit').prop('disabled', false); } ); } @@ -71,7 +71,7 @@ const oOpenSignInWindow = function (url, name){ // Function used when the form is submitted const oOnFormSubmit = function(){ - $('.ibo-oauth-wizard--form--submit').trigger('enter_loading_state.button.itop'); + $('.ibo-oauth-wizard--form--submit').prop('disabled', true); $.post( '{{ sAjaxUri }}', { @@ -88,7 +88,7 @@ const oOnFormSubmit = function(){ oOpenSignInWindow(oData.data.authorization_url, 'OAuth authorization') } else{ - $('.ibo-oauth-wizard--form--submit').trigger('leave_loading_state.button.itop'); + $('.ibo-oauth-wizard--form--submit').prop('disabled', false); } } ); From 4c88dbd9acf44009efb948976f2df8db33898530 Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Fri, 13 May 2022 14:39:19 +0200 Subject: [PATCH 09/41] =?UTF-8?q?N=C2=B02504=20N=C2=B03169=20N=C2=B05102?= =?UTF-8?q?=20Add=20libraries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bin/generate-deps-for-config-factory | 117 +++++++ lib/bin/generate-factory-for-class | 117 +++++++ .../container-interop/.gitignore | 3 + .../container-interop/LICENSE | 20 ++ .../container-interop/README.md | 148 ++++++++ .../container-interop/composer.json | 15 + .../docs/ContainerInterface-meta.md | 114 +++++++ .../docs/ContainerInterface.md | 158 +++++++++ .../docs/Delegate-lookup-meta.md | 259 ++++++++++++++ .../container-interop/docs/Delegate-lookup.md | 60 ++++ .../docs/images/interoperating_containers.png | Bin 0 -> 25738 bytes .../docs/images/priority.png | Bin 0 -> 16252 bytes .../docs/images/side_by_side_containers.png | Bin 0 -> 16265 bytes .../Interop/Container/ContainerInterface.php | 15 + .../Exception/ContainerException.php | 15 + .../Container/Exception/NotFoundException.php | 15 + lib/psr/http-message/CHANGELOG.md | 36 ++ lib/psr/http-message/LICENSE | 19 ++ lib/psr/http-message/README.md | 13 + lib/psr/http-message/composer.json | 26 ++ lib/psr/http-message/src/MessageInterface.php | 187 ++++++++++ lib/psr/http-message/src/RequestInterface.php | 129 +++++++ .../http-message/src/ResponseInterface.php | 68 ++++ .../src/ServerRequestInterface.php | 261 ++++++++++++++ lib/psr/http-message/src/StreamInterface.php | 158 +++++++++ .../src/UploadedFileInterface.php | 123 +++++++ lib/psr/http-message/src/UriInterface.php | 323 ++++++++++++++++++ 27 files changed, 2399 insertions(+) create mode 100755 lib/bin/generate-deps-for-config-factory create mode 100755 lib/bin/generate-factory-for-class create mode 100644 lib/container-interop/container-interop/.gitignore create mode 100644 lib/container-interop/container-interop/LICENSE create mode 100644 lib/container-interop/container-interop/README.md create mode 100644 lib/container-interop/container-interop/composer.json create mode 100644 lib/container-interop/container-interop/docs/ContainerInterface-meta.md create mode 100644 lib/container-interop/container-interop/docs/ContainerInterface.md create mode 100644 lib/container-interop/container-interop/docs/Delegate-lookup-meta.md create mode 100644 lib/container-interop/container-interop/docs/Delegate-lookup.md create mode 100644 lib/container-interop/container-interop/docs/images/interoperating_containers.png create mode 100644 lib/container-interop/container-interop/docs/images/priority.png create mode 100644 lib/container-interop/container-interop/docs/images/side_by_side_containers.png create mode 100644 lib/container-interop/container-interop/src/Interop/Container/ContainerInterface.php create mode 100644 lib/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php create mode 100644 lib/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php create mode 100644 lib/psr/http-message/CHANGELOG.md create mode 100644 lib/psr/http-message/LICENSE create mode 100644 lib/psr/http-message/README.md create mode 100644 lib/psr/http-message/composer.json create mode 100644 lib/psr/http-message/src/MessageInterface.php create mode 100644 lib/psr/http-message/src/RequestInterface.php create mode 100644 lib/psr/http-message/src/ResponseInterface.php create mode 100644 lib/psr/http-message/src/ServerRequestInterface.php create mode 100644 lib/psr/http-message/src/StreamInterface.php create mode 100644 lib/psr/http-message/src/UploadedFileInterface.php create mode 100644 lib/psr/http-message/src/UriInterface.php diff --git a/lib/bin/generate-deps-for-config-factory b/lib/bin/generate-deps-for-config-factory new file mode 100755 index 000000000..b11a73d34 --- /dev/null +++ b/lib/bin/generate-deps-for-config-factory @@ -0,0 +1,117 @@ +#!/usr/bin/env php +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_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) { + include("phpvfscomposer://" . __DIR__ . '/..'.'/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory'); + exit(0); + } +} + +include __DIR__ . '/..'.'/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory'; diff --git a/lib/bin/generate-factory-for-class b/lib/bin/generate-factory-for-class new file mode 100755 index 000000000..4896b92fc --- /dev/null +++ b/lib/bin/generate-factory-for-class @@ -0,0 +1,117 @@ +#!/usr/bin/env php +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_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) { + include("phpvfscomposer://" . __DIR__ . '/..'.'/laminas/laminas-servicemanager/bin/generate-factory-for-class'); + exit(0); + } +} + +include __DIR__ . '/..'.'/laminas/laminas-servicemanager/bin/generate-factory-for-class'; diff --git a/lib/container-interop/container-interop/.gitignore b/lib/container-interop/container-interop/.gitignore new file mode 100644 index 000000000..b2395aa05 --- /dev/null +++ b/lib/container-interop/container-interop/.gitignore @@ -0,0 +1,3 @@ +composer.lock +composer.phar +/vendor/ diff --git a/lib/container-interop/container-interop/LICENSE b/lib/container-interop/container-interop/LICENSE new file mode 100644 index 000000000..7671d9020 --- /dev/null +++ b/lib/container-interop/container-interop/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 container-interop + +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/lib/container-interop/container-interop/README.md b/lib/container-interop/container-interop/README.md new file mode 100644 index 000000000..cdd7a44c8 --- /dev/null +++ b/lib/container-interop/container-interop/README.md @@ -0,0 +1,148 @@ +# Container Interoperability + +[![Latest Stable Version](https://poser.pugx.org/container-interop/container-interop/v/stable.png)](https://packagist.org/packages/container-interop/container-interop) +[![Total Downloads](https://poser.pugx.org/container-interop/container-interop/downloads.svg)](https://packagist.org/packages/container-interop/container-interop) + +## Deprecation warning! + +Starting Feb. 13th 2017, container-interop is officially deprecated in favor of [PSR-11](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md). +Container-interop has been the test-bed of PSR-11. From v1.2, container-interop directly extends PSR-11 interfaces. +Therefore, all containers implementing container-interop are now *de-facto* compatible with PSR-11. + +- Projects implementing container-interop interfaces are encouraged to directly implement PSR-11 interfaces instead. +- Projects consuming container-interop interfaces are very strongly encouraged to directly type-hint on PSR-11 interfaces, in order to be compatible with PSR-11 containers that are not compatible with container-interop. + +Regarding the delegate lookup feature, that is present in container-interop and not in PSR-11, the feature is actually a design pattern. It is therefore not deprecated. Documentation regarding this design pattern will be migrated from this repository into a separate website in the future. + +## About + +*container-interop* tries to identify and standardize features in *container* objects (service locators, +dependency injection containers, etc.) to achieve interoperability. + +Through discussions and trials, we try to create a standard, made of common interfaces but also recommendations. + +If PHP projects that provide container implementations begin to adopt these common standards, then PHP +applications and projects that use containers can depend on the common interfaces instead of specific +implementations. This facilitates a high-level of interoperability and flexibility that allows users to consume +*any* container implementation that can be adapted to these interfaces. + +The work done in this project is not officially endorsed by the [PHP-FIG](http://www.php-fig.org/), but it is being +worked on by members of PHP-FIG and other good developers. We adhere to the spirit and ideals of PHP-FIG, and hope +this project will pave the way for one or more future PSRs. + + +## Installation + +You can install this package through Composer: + +```json +composer require container-interop/container-interop +``` + +The packages adheres to the [SemVer](http://semver.org/) specification, and there will be full backward compatibility +between minor versions. + +## Standards + +### Available + +- [`ContainerInterface`](src/Interop/Container/ContainerInterface.php). +[Description](docs/ContainerInterface.md) [Meta Document](docs/ContainerInterface-meta.md). +Describes the interface of a container that exposes methods to read its entries. +- [*Delegate lookup feature*](docs/Delegate-lookup.md). +[Meta Document](docs/Delegate-lookup-meta.md). +Describes the ability for a container to delegate the lookup of its dependencies to a third-party container. This +feature lets several containers work together in a single application. + +### Proposed + +View open [request for comments](https://github.com/container-interop/container-interop/labels/RFC) + +## Compatible projects + +### Projects implementing `ContainerInterface` + +- [Acclimate](https://github.com/jeremeamia/acclimate-container): Adapters for + Aura.Di, Laravel, Nette DI, Pimple, Symfony DI, ZF2 Service manager, ZF2 + Dependency injection and any container using `ArrayAccess` +- [Aura.Di](https://github.com/auraphp/Aura.Di) +- [auryn-container-interop](https://github.com/elazar/auryn-container-interop) +- [Burlap](https://github.com/codeeverything/burlap) +- [Chernozem](https://github.com/pyrsmk/Chernozem) +- [Data Manager](https://github.com/chrismichaels84/data-manager) +- [Disco](https://github.com/bitexpert/disco) +- [InDI](https://github.com/idealogica/indi) +- [League/Container](http://container.thephpleague.com/) +- [Mouf](http://mouf-php.com) +- [Njasm Container](https://github.com/njasm/container) +- [PHP-DI](http://php-di.org) +- [Picotainer](https://github.com/thecodingmachine/picotainer) +- [PimpleInterop](https://github.com/moufmouf/pimple-interop) +- [Pimple3-ContainerInterop](https://github.com/Sam-Burns/pimple3-containerinterop) (using Pimple v3) +- [SitePoint Container](https://github.com/sitepoint/Container) +- [Thruster Container](https://github.com/ThrusterIO/container) (PHP7 only) +- [Ultra-Lite Container](https://github.com/ultra-lite/container) +- [Unbox](https://github.com/mindplay-dk/unbox) +- [XStatic](https://github.com/jeremeamia/xstatic) +- [Zend\ServiceManager](https://github.com/zendframework/zend-servicemanager) +- [Zit](https://github.com/inxilpro/Zit) + +### Projects implementing the *delegate lookup* feature + +- [Aura.Di](https://github.com/auraphp/Aura.Di) +- [Burlap](https://github.com/codeeverything/burlap) +- [Chernozem](https://github.com/pyrsmk/Chernozem) +- [InDI](https://github.com/idealogica/indi) +- [League/Container](http://container.thephpleague.com/) +- [Mouf](http://mouf-php.com) +- [Picotainer](https://github.com/thecodingmachine/picotainer) +- [PHP-DI](http://php-di.org) +- [PimpleInterop](https://github.com/moufmouf/pimple-interop) +- [Ultra-Lite Container](https://github.com/ultra-lite/container) + +### Middlewares implementing `ContainerInterface` + +- [Alias-Container](https://github.com/thecodingmachine/alias-container): add + aliases support to any container +- [Prefixer-Container](https://github.com/thecodingmachine/prefixer-container): + dynamically prefix identifiers +- [Lazy-Container](https://github.com/snapshotpl/lazy-container): lazy services + +### Projects using `ContainerInterface` + +The list below contains only a sample of all the projects consuming `ContainerInterface`. For a more complete list have a look [here](http://packanalyst.com/class?q=Interop%5CContainer%5CContainerInterface). + +| | Downloads | +| --- | --- | +| [Adroit](https://github.com/bitexpert/adroit) | ![](https://img.shields.io/packagist/dt/bitexpert/adroit.svg) | +| [Behat](https://github.com/Behat/Behat/pull/974) | ![](https://img.shields.io/packagist/dt/behat/behat.svg) | +| [blast-facades](https://github.com/phpthinktank/blast-facades): Minimize complexity and represent dependencies as facades. | ![](https://img.shields.io/packagist/dt/blast/facades.svg) | +| [interop.silex.di](https://github.com/thecodingmachine/interop.silex.di): an extension to [Silex](http://silex.sensiolabs.org/) that adds support for any *container-interop* compatible container | ![](https://img.shields.io/packagist/dt/mouf/interop.silex.di.svg) | +| [mindplay/walkway](https://github.com/mindplay-dk/walkway): a modular request router | ![](https://img.shields.io/packagist/dt/mindplay/walkway.svg) | +| [mindplay/middleman](https://github.com/mindplay-dk/middleman): minimalist PSR-7 middleware dispatcher | ![](https://img.shields.io/packagist/dt/mindplay/middleman.svg) | +| [PHP-DI/Invoker](https://github.com/PHP-DI/Invoker): extensible and configurable invoker/dispatcher | ![](https://img.shields.io/packagist/dt/php-di/invoker.svg) | +| [Prophiler](https://github.com/fabfuel/prophiler) | ![](https://img.shields.io/packagist/dt/fabfuel/prophiler.svg) | +| [Silly](https://github.com/mnapoli/silly): CLI micro-framework | ![](https://img.shields.io/packagist/dt/mnapoli/silly.svg) | +| [Slim v3](https://github.com/slimphp/Slim) | ![](https://img.shields.io/packagist/dt/slim/slim.svg) | +| [Splash](http://mouf-php.com/packages/mouf/mvc.splash-common/version/8.0-dev/README.md) | ![](https://img.shields.io/packagist/dt/mouf/mvc.splash-common.svg) | +| [Woohoo Labs. Harmony](https://github.com/woohoolabs/harmony): a flexible micro-framework | ![](https://img.shields.io/packagist/dt/woohoolabs/harmony.svg) | +| [zend-expressive](https://github.com/zendframework/zend-expressive) | ![](https://img.shields.io/packagist/dt/zendframework/zend-expressive.svg) | + + +## Workflow + +Everyone is welcome to join and contribute. + +The general workflow looks like this: + +1. Someone opens a discussion (GitHub issue) to suggest an interface +1. Feedback is gathered +1. The interface is added to a development branch +1. We release alpha versions so that the interface can be experimented with +1. Discussions and edits ensue until the interface is deemed stable by a general consensus +1. A new minor version of the package is released + +We try to not break BC by creating new interfaces instead of editing existing ones. + +While we currently work on interfaces, we are open to anything that might help towards interoperability, may that +be code, best practices, etc. diff --git a/lib/container-interop/container-interop/composer.json b/lib/container-interop/container-interop/composer.json new file mode 100644 index 000000000..855f76672 --- /dev/null +++ b/lib/container-interop/container-interop/composer.json @@ -0,0 +1,15 @@ +{ + "name": "container-interop/container-interop", + "type": "library", + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "license": "MIT", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "require": { + "psr/container": "^1.0" + } +} diff --git a/lib/container-interop/container-interop/docs/ContainerInterface-meta.md b/lib/container-interop/container-interop/docs/ContainerInterface-meta.md new file mode 100644 index 000000000..59f3d5599 --- /dev/null +++ b/lib/container-interop/container-interop/docs/ContainerInterface-meta.md @@ -0,0 +1,114 @@ +# ContainerInterface Meta Document + +## Introduction + +This document describes the process and discussions that lead to the `ContainerInterface`. +Its goal is to explain the reasons behind each decision. + +## Goal + +The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a +container to obtain objects and parameters. + +By standardizing such a behavior, frameworks and libraries using the `ContainerInterface` +could work with any compatible container. +That would allow end users to choose their own container based on their own preferences. + +It is important to distinguish the two usages of a container: + +- configuring entries +- fetching entries + +Most of the time, those two sides are not used by the same party. +While it is often end users who tend to configure entries, it is generally the framework that fetch +entries to build the application. + +This is why this interface focuses only on how entries can be fetched from a container. + +## Interface name + +The interface name has been thoroughly discussed and was decided by a vote. + +The list of options considered with their respective votes are: + +- `ContainerInterface`: +8 +- `ProviderInterface`: +2 +- `LocatorInterface`: 0 +- `ReadableContainerInterface`: -5 +- `ServiceLocatorInterface`: -6 +- `ObjectFactory`: -6 +- `ObjectStore`: -8 +- `ConsumerInterface`: -9 + +[Full results of the vote](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote) + +The complete discussion can be read in [the issue #1](https://github.com/container-interop/container-interop/issues/1). + +## Interface methods + +The choice of which methods the interface would contain was made after a statistical analysis of existing containers. +The results of this analysis are available [in this document](https://gist.github.com/mnapoli/6159681). + +The summary of the analysis showed that: + +- all containers offer a method to get an entry by its id +- a large majority name such method `get()` +- for all containers, the `get()` method has 1 mandatory parameter of type string +- some containers have an optional additional argument for `get()`, but it doesn't have the same purpose between containers +- a large majority of the containers offer a method to test if it can return an entry by its id +- a majority name such method `has()` +- for all containers offering `has()`, the method has exactly 1 parameter of type string +- a large majority of the containers throw an exception rather than returning null when an entry is not found in `get()` +- a large majority of the containers don't implement `ArrayAccess` + +The question of whether to include methods to define entries has been discussed in +[issue #1](https://github.com/container-interop/container-interop/issues/1). +It has been judged that such methods do not belong in the interface described here because it is out of its scope +(see the "Goal" section). + +As a result, the `ContainerInterface` contains two methods: + +- `get()`, returning anything, with one mandatory string parameter. Should throw an exception if the entry is not found. +- `has()`, returning a boolean, with one mandatory string parameter. + +### Number of parameters in `get()` method + +While `ContainerInterface` only defines one mandatory parameter in `get()`, it is not incompatible with +existing containers that have additional optional parameters. PHP allows an implementation to offer more parameters +as long as they are optional, because the implementation *does* satisfy the interface. + +This issue has been discussed in [issue #6](https://github.com/container-interop/container-interop/issues/6). + +### Type of the `$id` parameter + +The type of the `$id` parameter in `get()` and `has()` has been discussed in +[issue #6](https://github.com/container-interop/container-interop/issues/6). +While `string` is used in all the containers that were analyzed, it was suggested that allowing +anything (such as objects) could allow containers to offer a more advanced query API. + +An example given was to use the container as an object builder. The `$id` parameter would then be an +object that would describe how to create an instance. + +The conclusion of the discussion was that this was beyond the scope of getting entries from a container without +knowing how the container provided them, and it was more fit for a factory. + +## Contributors + +Are listed here all people that contributed in the discussions or votes, by alphabetical order: + +- [Amy Stephen](https://github.com/AmyStephen) +- [David Négrier](https://github.com/moufmouf) +- [Don Gilbert](https://github.com/dongilbert) +- [Jason Judge](https://github.com/judgej) +- [Jeremy Lindblom](https://github.com/jeremeamia) +- [Marco Pivetta](https://github.com/Ocramius) +- [Matthieu Napoli](https://github.com/mnapoli) +- [Paul M. Jones](https://github.com/pmjones) +- [Stephan Hochdörfer](https://github.com/shochdoerfer) +- [Taylor Otwell](https://github.com/taylorotwell) + +## Relevant links + +- [`ContainerInterface.php`](https://github.com/container-interop/container-interop/blob/master/src/Interop/Container/ContainerInterface.php) +- [List of all issues](https://github.com/container-interop/container-interop/issues?labels=ContainerInterface&milestone=&page=1&state=closed) +- [Vote for the interface name](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote) diff --git a/lib/container-interop/container-interop/docs/ContainerInterface.md b/lib/container-interop/container-interop/docs/ContainerInterface.md new file mode 100644 index 000000000..bda973d6f --- /dev/null +++ b/lib/container-interop/container-interop/docs/ContainerInterface.md @@ -0,0 +1,158 @@ +Container interface +=================== + +This document describes a common interface for dependency injection containers. + +The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a +container to obtain objects and parameters (called *entries* in the rest of this document). + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [RFC 2119][]. + +The word `implementor` in this document is to be interpreted as someone +implementing the `ContainerInterface` in a dependency injection-related library or framework. +Users of dependency injections containers (DIC) are referred to as `user`. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +1. Specification +----------------- + +### 1.1 Basics + +- The `Interop\Container\ContainerInterface` exposes two methods : `get` and `has`. + +- `get` takes one mandatory parameter: an entry identifier. It MUST be a string. + A call to `get` can return anything (a *mixed* value), or throws an exception if the identifier + is not known to the container. Two successive calls to `get` with the same + identifier SHOULD return the same value. However, depending on the `implementor` + design and/or `user` configuration, different values might be returned, so + `user` SHOULD NOT rely on getting the same value on 2 successive calls. + While `ContainerInterface` only defines one mandatory parameter in `get()`, implementations + MAY accept additional optional parameters. + +- `has` takes one unique parameter: an entry identifier. It MUST return `true` + if an entry identifier is known to the container and `false` if it is not. + `has($id)` returning true does not mean that `get($id)` will not throw an exception. + It does however mean that `get($id)` will not throw a `NotFoundException`. + +### 1.2 Exceptions + +Exceptions directly thrown by the container MUST implement the +[`Interop\Container\Exception\ContainerException`](../src/Interop/Container/Exception/ContainerException.php). + +A call to the `get` method with a non-existing id SHOULD throw a +[`Interop\Container\Exception\NotFoundException`](../src/Interop/Container/Exception/NotFoundException.php). + +### 1.3 Additional features + +This section describes additional features that MAY be added to a container. Containers are not +required to implement these features to respect the ContainerInterface. + +#### 1.3.1 Delegate lookup feature + +The goal of the *delegate lookup* feature is to allow several containers to share entries. +Containers implementing this feature can perform dependency lookups in other containers. + +Containers implementing this feature will offer a greater lever of interoperability +with other containers. Implementation of this feature is therefore RECOMMENDED. + +A container implementing this feature: + +- MUST implement the `ContainerInterface` +- MUST provide a way to register a delegate container (using a constructor parameter, or a setter, + or any possible way). The delegate container MUST implement the `ContainerInterface`. + +When a container is configured to use a delegate container for dependencies: + +- Calls to the `get` method should only return an entry if the entry is part of the container. + If the entry is not part of the container, an exception should be thrown + (as requested by the `ContainerInterface`). +- Calls to the `has` method should only return `true` if the entry is part of the container. + If the entry is not part of the container, `false` should be returned. +- If the fetched entry has dependencies, **instead** of performing + the dependency lookup in the container, the lookup is performed on the *delegate container*. + +Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself. + +It is however allowed for containers to provide exception cases for special entries, and a way to lookup +into the same container (or another container) instead of the delegate container. + +2. Package +---------- + +The interfaces and classes described as well as relevant exception are provided as part of the +[container-interop/container-interop](https://packagist.org/packages/container-interop/container-interop) package. + +3. `Interop\Container\ContainerInterface` +----------------------------------------- + +```php +setParentContainer($this); + } + } + ... + } +} + +``` + +**Cons:** + +Cons have been extensively discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51721777). +Basically, forcing a setter into an interface is a bad idea. Setters are similar to constructor arguments, +and it's a bad idea to standardize a constructor: how the delegate container is configured into a container is an implementation detail. This outweights the benefits of the interface. + +### 4.4 Alternative: no exception case for delegate lookups + +Originally, the proposed wording for delegate lookup calls was: + +> Important! The lookup MUST be performed on the delegate container **only**, not on the container itself. + +This was later replaced by: + +> Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself. +> +> It is however allowed for containers to provide exception cases for special entries, and a way to lookup +> into the same container (or another container) instead of the delegate container. + +Exception cases have been allowed to avoid breaking dependencies with some services that must be provided +by the container (on @njasm proposal). This was proposed here: https://github.com/container-interop/container-interop/pull/20#issuecomment-56597235 + +### 4.5 Alternative: having one of the containers act as the composite container + +In real-life scenarios, we usually have a big framework (Symfony 2, Zend Framework 2, etc...) and we want to +add another DI container to this container. Most of the time, the "big" framework will be responsible for +creating the controller's instances, using it's own DI container. Until *container-interop* is fully adopted, +the "big" framework will not be aware of the existence of a composite container that it should use instead +of its own container. + +For this real-life use cases, @mnapoli and @moufmouf proposed to extend the "big" framework's DI container +to make it act as a composite container. + +This has been discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-40367194) +and [here](http://mouf-php.com/container-interop-whats-next#solution4). + +This was implemented in Symfony 2 using: + +- [interop.symfony.di](https://github.com/thecodingmachine/interop.symfony.di/tree/v0.1.0) +- [framework interop](https://github.com/mnapoli/framework-interop/) + +This was implemented in Silex using: + +- [interop.silex.di](https://github.com/thecodingmachine/interop.silex.di) + +Having a container act as the composite container is not part of the delegate lookup standard because it is +simply a temporary design pattern used to make existing frameworks that do not support yet ContainerInterop +play nice with other DI containers. + + +5. Implementations +------------------ + +The following projects already implement the delegate lookup feature: + +- [Mouf](http://mouf-php.com), through the [`setDelegateLookupContainer` method](https://github.com/thecodingmachine/mouf/blob/2.0/src/Mouf/MoufManager.php#L2120) +- [PHP-DI](http://php-di.org/), through the [`$wrapperContainer` parameter of the constructor](https://github.com/mnapoli/PHP-DI/blob/master/src/DI/Container.php#L72) +- [pimple-interop](https://github.com/moufmouf/pimple-interop), through the [`$container` parameter of the constructor](https://github.com/moufmouf/pimple-interop/blob/master/src/Interop/Container/Pimple/PimpleInterop.php#L62) + +6. People +--------- + +Are listed here all people that contributed in the discussions, by alphabetical order: + +- [Alexandru Pătrănescu](https://github.com/drealecs) +- [Ben Peachey](https://github.com/potherca) +- [David Négrier](https://github.com/moufmouf) +- [Jeremy Lindblom](https://github.com/jeremeamia) +- [Marco Pivetta](https://github.com/Ocramius) +- [Matthieu Napoli](https://github.com/mnapoli) +- [Nelson J Morais](https://github.com/njasm) +- [Phil Sturgeon](https://github.com/philsturgeon) +- [Stephan Hochdörfer](https://github.com/shochdoerfer) + +7. Relevant Links +----------------- + +_**Note:** Order descending chronologically._ + +- [Pull request on the delegate lookup feature](https://github.com/container-interop/container-interop/pull/20) +- [Pull request on the interface idea](https://github.com/container-interop/container-interop/pull/8) +- [Original article exposing the delegate lookup idea along many others](http://mouf-php.com/container-interop-whats-next) + diff --git a/lib/container-interop/container-interop/docs/Delegate-lookup.md b/lib/container-interop/container-interop/docs/Delegate-lookup.md new file mode 100644 index 000000000..f64a8f785 --- /dev/null +++ b/lib/container-interop/container-interop/docs/Delegate-lookup.md @@ -0,0 +1,60 @@ +Delegate lookup feature +======================= + +This document describes a standard for dependency injection containers. + +The goal set by the *delegate lookup* feature is to allow several containers to share entries. +Containers implementing this feature can perform dependency lookups in other containers. + +Containers implementing this feature will offer a greater lever of interoperability +with other containers. Implementation of this feature is therefore RECOMMENDED. + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [RFC 2119][]. + +The word `implementor` in this document is to be interpreted as someone +implementing the delegate lookup feature in a dependency injection-related library or framework. +Users of dependency injections containers (DIC) are referred to as `user`. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +1. Vocabulary +------------- + +In a dependency injection container, the container is used to fetch entries. +Entries can have dependencies on other entries. Usually, these other entries are fetched by the container. + +The *delegate lookup* feature is the ability for a container to fetch dependencies in +another container. In the rest of the document, the word "container" will reference the container +implemented by the implementor. The word "delegate container" will reference the container we are +fetching the dependencies from. + +2. Specification +---------------- + +A container implementing the *delegate lookup* feature: + +- MUST implement the [`ContainerInterface`](ContainerInterface.md) +- MUST provide a way to register a delegate container (using a constructor parameter, or a setter, + or any possible way). The delegate container MUST implement the [`ContainerInterface`](ContainerInterface.md). + +When a container is configured to use a delegate container for dependencies: + +- Calls to the `get` method should only return an entry if the entry is part of the container. + If the entry is not part of the container, an exception should be thrown + (as requested by the [`ContainerInterface`](ContainerInterface.md)). +- Calls to the `has` method should only return `true` if the entry is part of the container. + If the entry is not part of the container, `false` should be returned. +- If the fetched entry has dependencies, **instead** of performing + the dependency lookup in the container, the lookup is performed on the *delegate container*. + +Important: By default, the dependency lookups SHOULD be performed on the delegate container **only**, not on the container itself. + +It is however allowed for containers to provide exception cases for special entries, and a way to lookup +into the same container (or another container) instead of the delegate container. + +3. Package / Interface +---------------------- + +This feature is not tied to any code, interface or package. diff --git a/lib/container-interop/container-interop/docs/images/interoperating_containers.png b/lib/container-interop/container-interop/docs/images/interoperating_containers.png new file mode 100644 index 0000000000000000000000000000000000000000..1d3fdd0ddbea28d77c08cfb65834ec11357be5fb GIT binary patch literal 25738 zcmb5V1yEd3(>6H410gWD1(G4b-CYNFm*50~6Wk@i1`EL*g1fuBTL|tJ++Bj~CGYp{ zU$y(!U%OP@B6IGMr@No-KHWot73C$+QHfANAP~BgBvcs$g8K+O?@{1@k=zM477*x@ zk`z=})%C@})K_2()b;bP%rWG{I2>pY1~Pzxm>?hmFenTKB#rU{3SU#Vl{Mh{T2Q?P8%}|GsTiYx75|G zy-Y9sP=)?`w{Y<+I3e)YHmY0GNjLmF!9oocFRvN=irVb%mZts9HBnK#;Rt6JnJ|>G zkWgzI3uTZP!b#DKm5=)A8lT7f8MFypPDMgy z;dNbJjL%c>a=R&SELZ$^j6el3u+3&;8MyuP>&26~O7&ySnGOk?P^WoCF?c9WfY(`d z{o);!WG+N*+@i8Fphe@e{L}b>P5baJx|0*?PnfSRjlA4L7EQm3zFN+KWJg~_`q5JO z=~=+{D5GoEpi0xk zFPDQQ==%)lgr5X#7-UT)kO6GTr~;mfZNgW#=I~jx z?9Tk@ijz#e=to6peIan_l?+r4zAkk(r$-APQ8i-I%jn#;k13BMz1=SI8}lVIyuNLZ zL~=$61O~EAPE+|p*2@ec{F|+)HxiEHIDqJH{_4kEilxCc1)X&d+cF z(=)u?PXD0OusCR$xi%DEx!F9#Cuk?@At&`Q;Bb)v4hJk7-g{6c~ZW$ z)3{G>^}WmSJUa|f+}mW@QC{A`USV#>rd~V|Sa&%Sci&}GU3zH*;gX_C7AOREwaRA` zoH>Hdc=IvZuZQu~K^ZRd;ceBl4fq8WWL7=@0RNA8Zy)m2ko1Wkf0MU2+-o85iw2@j zpF7pIg6L#!?ulg+afz9@SZYA;g0+e^>r)gHe@F!oK{#OdWb#-vyjG93Z-x+%7cRK@ zGuUUda$$^Per2w?AkxzO#ZCrHXsbv=kl$C%84vFRC2fxHx&+=uoj2c%jaKN6uxAG}(Iq?U7DXxuydropf!*Ov3Uju5fH&h{)(0Y8CNl^PK za?v4E>~y)d#QmNUAp`~Fgp1p8zLmfz(uRR*1q2)(V9=KeNj5!KY`i5oB<0{ z7=rKbFh|>})MC!w?*$usue>s0t9qhuZ>Qx5-&FoY2|=->+*Z&d=096I(k+|-DZD2A zp`oCqh#=;FNh2*4Ns=Uxt(VH5#e*^1&gzzFu3>qy$c;NUS4{B@qK$8#|3+=lt1T7% zmQ2w7dY@e1mHmr80aa!!d62z)!TQjZd~XQ*cBdbtvoR(&&nvzw{lcd9A6>qL6T=QS7dzmkO-x{Je6mETwqaVA2XC!I zS@aP=5LdS|f4A4<>U5UHC2EaufT{+RBDY6M-=d`OS7qdi@1lc3P~tF_8k^GoK7F{+ z5PwxfPp{_7KHzrF^Yq}6d&>Jp458As8``l=oK$6FzhGS}8 z0r?jkJEPMh^D=qQS!b2WT@hL>XYQUP;i7R%#l8LKi)B~iUM2)_Hl{jHVX3JpDP(7P zL@_Qh1Mpvt`WCwujtxh?$~WACji@Z*4ZTV(5ICu2<=4Hb6ruNt z3fD&#+%c4Z_r2hted-D-Y;NAlr2zc%p89E5r3NBiqB{aoiU16i&F^L;AVeoCt;~Y} zn6hrVjGgSQJ$xo9;2c!aQii_yAVuhE$C0k~>rGr0Bp45{$roMU=Im13)1j^)>7eJ= zx4|1&B3Ezs-T+R^jvtSNF&UCTK$`0aSOWn_bO2W-UN>137pZ`Rz!?hka&nuAiFWvm z&8^;YFRqGev;TN08gx$4mMno#Ls0rVBF?DVs3oxLki$6iNetd5I_|2!r&La!76~|4c2jRz@Yy%Y^YRRH&er$rI*4N8I4&6dZUnHDkkBe ziY?|}dK*SYnT}|mGR6J5FFRinamQ)>Vrq`{w40U+@?g7hfDKO;odlgA8adj|P6@=S`M;XNW^q9B1VGvl9f&5eOV=9UK({Wq7utbm&lIZo(lJ$Cg zxL7Ej^-|;1#KigTM4ouocu2_2)(COIs?S^#+AK#D)$5I_rd+Dm`t1jD%p^0+F>_H9 z8!?H%&-iAD(|bB&`})4&w^Xb%YK2BZoRzWsRP~r=H&3)dc7iQ#wMmhY9d;xyvEO1= zF55(hCScV3+#4h#%D++8j-IBXv-)a@6;*oFks6>xgKyn4s> zOWf9Z)=x|)C0zyt!NR|JX$1=pf5Y;rKNy>mhK9zX2FB}r@N3u6%<#s&=Vv@}xA!jO z1xWp8C^n@$pVJ=Bp;0)#Qq+5-Es=!C*mwBjX)%(sa3C&e-8vgxb8~ZayvXl?uc|R8 zNhrw@f2S*b6H0&wDXOYEZVe|64i4@yu4hX{&-TBY#aiDa866yegu#KnH0by|T;lT! zm()V;9=ywDhGbu7L`*^OD~ft#XAwaLY(#B0>!FpEm3!2Xk#}R~!#H5PEz=>fVHwG{ zAkabPDNCZP;QdJ#Hig)kY)}r)3p(%%P$z2$NAls#=?0@JG<~d36YU)(0|Q(<4CG7Y zd^mqO7)v{*)xExMbY+h+y|dGS{yTo*%nYC0A{Ye(TVHH&uw8Dx9TsbLIZl($BX5VK zVSe3+n%d*ai2@1v3Q2AxB#8JeTmGU&DVNF`_dFlr#9+jEIV$r4^v#iIWYF|BIR!B} z6*2V8o{dkzcbxe&Rzt7BarbvtDC`9oB-CN&7>lV>F|mTKq=YaVzWx;+nUv4XG=AQE89NJ|5H z6?wMFD_A5F=(ln8K57zTXgxs@bXqQ%W90Zch+(~0s|p1KQV(uts9W%QyubY=h9edU zT%qGi$qLJ>>`UY z#OPtsGGS!P?R@L4hd{v$4*U z>i)f!IG)k>p{Ju$rNYS;NMtitHaFir-RPsoLwotTj41q!=9E24SlCyKBAOZsW@TYP zDbF{Tl|6i-R16Ld?&;|(!UwI~SO`aec{Ul6fdKnsbz z{31xWMOIdUO;7lqit9}B#+vLm2XAg%u|`p&ux2z$u=+Txi`Ti}4we_$pkS?*eZ*=x zskqQtmEv%mQ@%E@zs~Nr0$z93U2B*KUokroe-&+e9Jf%8DG#s0Nyg^q=Z^_K-A|W% zEcsaHJ>C8*Z>8Doe5uK0EQMS2BXJ5!1IYdH{%XN?LS@iGP$0*){JoUpWY%Y~Z*Tdw zo5;4XRB7B|C7-_i-K_$wp|x}>L{=omVDz_IRr&nv{Cu}sICSUIovgK4z;jntN-87h ztJmGd?qb7vmqVa(!F_Uz2dI`#3XX}d*4kqpCCqvu&D_51?b z<1|^~;Zf34ziW3gL+=DNd`o2Kf6dwQGtU~_79A}$p&_(5__SP3J7~~Oe>4tfmldCmwQdwrOPg8a6hJkUGofoX}KU!5NuQB~S8)CG58JBKZ+HkhK*!s~E zt)xsd8teHydM!{!`{^`Ie%MDVWi8Xbm9?ly1+}kOS@_R~ zS;ZeSE*V;HjB=@_1lIhq^COM7e<^4*J)VPGE6M3v~UALZU| zzS}F>jpn>t8)hB*6mB1E+j_gT>~S1RGZs;isX?@*{p4l2#Fb$!_qs?7Q3;1weD#1J2E;E zG@Lwwk7bf;H#<5T`&^RIYSDEgT!yj5Fo$7FGqK;HQ*tuTk-yjqLAZkcoVH4U^gE96`2Ti$Vz3_)!|#F z`fpryO~cu8!*YvRfR@|KI-LYgD*mGp73X?;xIwhCy%6$X@0eF!Q!-#(gCC zL9Ll=L=*!}iIcCd%(fQzdr%QANg z9aqc!*-+`H1OljI77BS5|9RG%-QPJf^-C|{kqn|zGF+*qFjEOc(Ya;GN~p}vl|*L$ za2JOez2i21yOHjFw+lED?i(3$t_|pi67j@ke<3}+{`wg`k2%=SXo{~zTVvQD+d|+9 zXR0;sQ&#<38Y1F;wKp}?_uh!0;}0`kr(%VD!3nkkU&p8^)p^dkD%}`8wm++^)#utM z7>2-&ffSVXn|ojeGO6oZOHF1@8$Y^Y+kPx_)N#t5t0foxF;OX~N&!FdbgqR-EKMv- z&0Stl7YWuxU#Yn5nArMwx8Iz=qlFC#VS>aU$d{H|xxbn}tQ=w}r3ByZS4!4CJ^l@S zEoML{c{;yN42_Xj z14}88-VX~-^%o@j#}u8dzZ5mJT@nI;YH3u(Wz$Xu54=M2P8Otf<6bDpOS9l0?1yPw zG(f3kay3GcmwIDibmU#;<{nD?EeGjv16OF0Fr(p}@vM?4^8xVz--P+7@GZuvxXe$4 zgA?z9&4SM=gnU8{D#~7_Yro4hY&7NzkhH8yyEX2lfDT8z(@vShDWS@HlPe^V%QU8$ zi^+g27Y$1jgM2SmewHoyQ+xoPB;+bB5^ev>S5(q)g=@WbigBd)JQ+A+qGZ9TuAe)A zw+3WR2PeED=5aK0fP~P2CFPVHMbIUN3dVul83pryY_1A1W?1|5t>kpWwQMnVgoPeU zi99$66sJVs;6dH_wjA76nU-m)YM9&%C*GcCByC-`ELl@kBDH?{4UK{6BRM!)0d})j zR3?qXNtyX3ktRvsY;>#Q0YNVm3Y5q&^~q2B$!D~V)pBaf;wmIw7vrt_K%M{?H?Q^j zc=|&y*Z!Yqr_lkvH<h65EvjxKA_6qXmFdD2DB1%HvHEwh0?~D-}4Ud!9)lSR3&> zW;kzrb(hPg%L?pc8413bw=4^uO~PbumFDv243L`1>a^JK`3_SLE2LC`f$6v3b;VyY z$|(gulPEQ(wV7+dqB*T_VtR9nt7&Id!Dop;+WsyRVT58J?90fbW1>gZt2uE7{rt%9QyHC7#cRpj3 zcd#HE@sOb|DhbK0*DD+;jO%ClQ;!8c*-_szox^ld!S1)>1@Ah~=(@b{nHDC_)&+n` ze=v%@%}uF1Us(}HP;*(dL-r|AA&bKb@dgpJ#PC(7tDW80%&{g`R}x3-d+(RjRlcYR zhsLZ&Uk@>j&q04ir4B`J$rB}n5N$SXx>{T-lP7dodbnuYAdr%@l*iS1d@PEDF^Iqw z#(Ig)%@nq9{EnsG8Z#`;BV^Z`-9lWMmfqhLJ+1%wFx9iV+6HypGUWt?|3?2I;bdmq~ObFCnTzXRKcA@Ve^*oZEY$n1Af)*)RdG1 zUpv-YpM=oDOlDjVPBNocoY)#?nb7H%?#*V-vVq%L0KWEvABF%doDD0I>(C%@Hfb>D zTY4lCR1$SSp8u$NLcW~>8O>N*BMPA$9GB71OLkYTNAl%U7gwo-wNi0G3VF4i1uIJl zokiiIZYcEq@5wN1@h>O(eb`*oSxML~ZyF68@O&+hZx`gDxv(o;C!K;_rhwWv5-jW7 zS^WQFCAH7#B`U;n#CPx1s@9==WYXPivx==xvTt`jeQZVPB}k2n9HUeD+`eTc0Ex?Z zuQ1{gFD5X|8p~Du9x_A1s1SV1twUC&!Z!6~m`p}FoY}ayo`0%cck*RYFQ|L)ca|)9 zwiJ9Elr8Uk7iPkg$?J{jm-7WzdI-Pf=eI2U+qA0TBN2+4l7#7r@R#(2g*?1#?Js$= z=hH;aB8)#pVW+3uQH_;y4TDaQB;KY~A!dpg_Q)^HbZ*cYX!j2Mps!oR8kI|c#lG*O zXTOIQZ64A)&%uWk^HbXBn#cY*v$8Dau2Z!|MHE?mi@k zhtz~BaRUN`pF4Uk@yti>uLZUBTYC0J{JSCZgORL#-7>N<4xSWL?3%_s=n z;2NqtbuHF)IdJ6pZGm0j-jY^59Ed$;4E{Yzl+^wf#Y{fCR8}(m)a%M;N)qUBGOCcIY}{A+ZVmw zr`f^~pR`q_6VocGKnw73EDVS3tD2gWvS#Icg$sssM9&vckIF*rp|kY@2|cpqLhUJo zSF>7M^7y=qy>|~9PTmdWt>rzQjB`i??B7AZX=3+ zpS9Cun}3DWv&Ne&#sI4`wFOpKtrst>sz$${i}~vzmQ2eWPLFFlePpP5i-!yHR_wO% zC`6y(BSb%-2$2MuaV8Oe55vrDO=Ug!jpt9@(JE>jy0-eUfQ;eZ<*>(wL0ie35P+9Oy|4Vejsk^IJmUQt6F_qFrrLkWx^AP`k% zWm*_gB@Ay`>4k5l*r(|Ax_RHl33JgGpr5cohOu~kOxx=wIG~LftDD9c>RPFU4+4es zpWQ!fMuC7%Bs;xAw+Xmzv+{Y$R5g2Yq?k^mEYOY+Q=Mp4jzqTv0bd*Odu)S9X7GYe z=WQh3j8!4mSod-a&DL5^)wZ}!_e!-S_sx#3w=B5mAIlz3b0;qzc|YCa4w!B4KV0&1 zYlvQX--9P$>GEk|LQKc^225t%WvzFE6BDdVOeestfsZsI!mXvj;c!0ZfG(tJT#0iu zMfh!*U`9rUu&^+#Mk$GaCzl4BFC$4pQ4mJDj4T)Ks!wOv!&Nvj%BNLNVR8oDMmBPC zQ93+YoDd%-TXxij`a)pC4d!EF5r26_!2H8uO+R4^wbB@tm@L?~s6qkqF#JV6xw;A!@EI?T;shZQV` z^^!%Ygwd=UXeXSx^4Tho0m6XPyGD;2uwYm#WrT zb93`}mP8jmx}it=Www}10JkHz4$AB5Ph%m5SE!{FaT?C8*RI^QBBt4ogGj zpVzNnlS_Q}XCxBoCMv1U<1|P(9$CS1Bu@NsiWp9X6&!=#eEn$FQ;V@mq2Y5Ys&%%v zw@1io+K!Ljp)thaw5-zzeq)_ki?k?2fdCw2Gglcfom?{?u32CuUrsT zt2r!oc(1Fgd))RU;5m*!Vx1X{Pxq5}+$VJN>QUbY70{;Tbg`1&$(F8EzsNA_NhB{T#F+nmOM=DPxkM+2+j7{le-i2QeTyVSb0IPFk;wx)? z-E-{eTwfZsQU;lKmtWqYKyg*?r|EpIqFzu^Qf^h+-(>c3CEhg>ddx2LD!J%ep*IH$ z-EE=~jZaKO*R105TfHUVFRxNCAYyjKE-hAiiEJad7b{Q&23o8+K!2|j|B~`#tCn(c ze*P&LyoN?p@$;vziR;#;2SOI|_gZFI@;kFR@*iIt^j7oIz1^!+$WIX4@!8g=iZrwW zk;$kj2>53$@!Huk^S-jeDIrcvtvFvvYT5dERA~ft;e!B;$HvCebqy@5BkIUDCyHl3 zeiu@ON^t%)`YIJo(GkxH0(Q&Pq%$RyziJ?Emsp52?@m=;LxGCj&$W9G?)({uUK=6? zt)dYrK_biTmHd3H4uA0;xd}?7)1h-~TczF{u6h$ZS#!ve^UU`$t<=fgUDYd+I8-`% z^uO)PA?vuNxtXE}ez}(lRi0p={KVzC_B!QEj5B3h_r!92&Yp($=9~B*wqc(mO8@my z(t?9s-jM=9QjNM>QcUz|2~=HMK(?MVlC8hUwt_K_L(5vObrrOBH)Za_jwizT!^6WQ z?&x{IFInLQLsLfr5{h#_M)j5{^m^()j>jdQ6x&=MORAIaCT#gwe>X1C>xx!owQa_S zmG9q{EU7>0nCBd-bYkPI{gye+gr7iWoFK;{OW3m-CA-g9{=+^NCav5|B{o?w5+Y4g zd}!1_K=x78j(9A;DN%K3kofz>Yo4v19Qi;8U7ZXU*g1#JkHMD8 z(BY*zm5~(-_J#X;oC=SCw|p(bESJQ(!OGB{P{xTDjD2Y_JSj)h4EtWjO93-#b84yI zl7n*Zgx=gmN`rCNyx$$`ZF6jNXt`VOugUSW9_pxDC@m}|#FPn8=@%9zd04-m$(=~t zs0qH^SYZ=OX+1oGSw03<8Mc^!;;-%8;r)4+o|HoJUB}@W$>qqJt$5?#xRhz4WnwmX zrA_^De#$FaX=M^nJI%yVa;aTY<;h+Ehx0V4Bm<_uzsOUG?S0+!5>)AbM>HU4whxhWyb_9G*eFtPft@@U1%y|zks`;vl5 zvj&+=GIlh}(NB#aWL+9`o=!Y6Cg>s^tmR|%scPU;{=WXSUFDE`G(8-~)9a9{6}kknBD)MUJ@JL3p#}t{5@-As7hFbZu+wJ*&o6T! z`|bu^E;47BQhZAD$r!6WobM~Cg_SSJZ?x08b{j33$b7WLe@Iv@_Kd-2CH6;{k(0`l zICb*g&AGix_UW`{i>*E_`rGy-hQf{(^`7#^B5i4Ru(CAT~sU_BF<{e)-qDj@h zn(&0Qu&xK0hV-~cCrZH|4Pz5i@WxB2s#Qx4vDQn|cFo>7FrIa>Q;1%(n_1xmC1WKd z>YJ|Jd%0&F>x*Ohk-$TB6besuLpaDG6~!6Iu2f`PSJ`y;;vI)LtP7l9{>WRDINS7J zdftDoq$U|F*~{L{s&RzOZj0f()2kxI%yCp6;vS(~977fmkhPWEL)Gu$=t)d+Z zBs*_e7`fTom%NL`Vn3ADoRj;-QdDWY_Xs_Nb?v^XZalw1IlJ!k`70tSoEUI}(_qKRKf;;Ja4H{_rzxtRcA}^WWun zui2}hb_CBOT+`$w?(;u1FEU=huQ^^al$j)vYygDv0SL7-vQHy$g}rTJ+isLLG;%Hx z+^yQ`ZA@jA#HYv9{d;kK>}5OTdIuLY>$UiL+#H3~|VI>T>;^y;0Vx4w**+v3hz?9^<1$ym4_cg|Lf)BT~$>HtyX zg@c|GC7gJ_WINrf?NkS_{M`&n9~z#yPYz7b8UU7@S0(`#vw=J;HdJa4{W<-nE0e!v zL`3IzzJVTsp95=TwLcq4?edpcVmRRkS366~CXT8W!)T3W47 z9uPnkZEB$K6tKk*iH923s2p+=y)AZHX>WM5rr*YBCluq;7= zH;Vs{h`VN|L-_ji)|Wo969~(o+AU2U*4MSi(xd}nW9m?()Xlj<_|aq@fJNIWl(eN-~5-Ottyu z*vsvtt_?rL^*&yZk&)fUNiU9+1IuEowCuwq!miG1Kn0x{@fd90v{SRi=dt)#mCJFP z5=lF#AD{TlU!IIh$jV(y#x=K zoCddUDpF@cbmrf*EwdbScW?)D#*rmT&o})&^!(2I*9vU|9ZpbPeZ2FxanolueRq|f z*E4iSJ^Irm%fZ;+D@YQkvsz}{`wUFC^*P4hHvLfiB|_>ywph`Zqp2lYHv9IbhzimJ zmy`CIIy`Ik4L|SS%1*`KqgRA$oM)7hToW%syP`t0RYDfNEbNwiZkNyCYfJM{8*nT@ zd9pjrbba~Qx0uy-m!k;XC_%0w$8Lo%>d;gU#%i!#;w3kkG{tGYA;@(F5}=GI#}V<~ zUCA+L`D(!NEl*w|ENI$P*weyZDS2o@gssrxXvKjWxp|YHu05hmu2Qg{Lis{{31YF` z+|rX1$8mw;8+>2RNQAuIlg(rYY9RQAsnT5{QY$n}RV7qUf-lWjCmZ{n$**66iBuA; z@#f&H`dgh0B#740;}}jyF6S^5gJ`pqN{=k!OK$Mp)r+w?a{UiZOXSlO`ztX3$7?0g zn3hSnxh>o3tUjNImxTHqleAe7S9+5&E}9#8quFz9(bg3jYx_S3B)t&~v^U=6T}jyH zGOr0Ay0GqhExI5kXL!W&Cgm!S!#*8<-ucUo;_=}xcTu89?i)C6D2|~*n3KVe1?Rb4 zJDJRRb1;$MdR^@f^iR$XoF8<%UV0V=!i-eWu3!AJ0_+ml-6<7 z!?Gr(xj*yur{nn!sc`YExWD9J=Z?pUQ!1oY*w%x`8R1*c#J%7-jpSj(XUGT9Tf^G{ z@|&h!{KnlH2P-$Xbq^w&gufVpKM9V|I$>N9khu5aT_LN0DX&U5^KD1@JB;KX?`S;@ql%- zV*68fY}{*=qkS!SXG_kx&!dEPCbN=-R_t9cCdg_@zxXOa?>ZRnEBGrFUCni6+WH8) z;hX7gjIe57yYD~lR*zRqzW+gn<|^dMdhM!?RvJPHz~b+JAcdiHL#&TJBX{rlq|^kj zhPAr#tz=|7pTAxnBFG|ks=4p_#$eF@^rOT8n2%69>9IGKQ)`0&M-EqEK+ejli=-{| z>9g=tySLV+5gNfglmKI-grmkvjhHE6(R47T;(3`rctw}V2;&(XjH@@jgsskQyJ80I z&?}=`yxd1Z(uZXWLtJO>a*w6gGjA~Ff#!1!6C@19Q`BVurh-cQye=U|zN^A1Dc4Z- zuGrICPM<4j|MxH1ksrLaR3y4_|B;5SC=~@k9AQ0^F)fT+IT;<@Q8-d=j^GUW>&j<~ zV(eoR23!D&FkiEf_%J~hFf|4wd0JoWJn^u=F&86ez*BZUAuBhZ!lYN2gZ2-$N}Ttz zq|{7#f)zdbNtIfbR;GH{iA4HOq`cCDI`6|!$iFFR1cq_#NxuUuF6r;G^SBp4P5gUJ zHhwzhuQMTyKNwInvz$1|Nh}y83?)0cGB{|9lZdEbgKMB2Cys$30GRNDxu<}2V-4~7 zEGb5+<=Tkx4Mu{4Ni#XGdYsJ;z`*@UMw2OP-4xHN$R==Dp_L|243rFG3E7t6jm_Aq31xALxL3bO7;2gR49%K6^ROsn7 z=`a$x9v0YwyC<5aZw5nmO(_1UXW8qCJ@U{N^i*_IxPVrgiH~T~fs}cHXt>*=S*MHW zaSNe+JEdz-gxI5Kv|}ceg8)~IB~A4k?ww&9#`k26@@_g8`;k=#y)lfaPha%<5}Wxy zk9uwu%6T^wti)|V#Ij+pMhTbS%7Ry{9@Uvz>c!^xD^Ktje5SM~$>&Voj21ZW{+lEU zrgzwh@#JsOq|OYmLS%^@dh9v<_lDdbBJJ1ZL-{y9CCHEovr^${kr`CGIHWLtg-s0+ z`$Pp1lWkawadZ-0WXz0iujM|~xYvN^mjzR2B6KQ3L+ZJNWN*B@E))=CFSc+rDw)?f zFPH#aC|!!sm$HdQzV!^5KYW9iImGcZY*V93=N`JLpJ4+JwMhbtQy9H2aopb-G`X#V zPhJFBdQUpYHn3rdmCT?I<*k7bprr{sIy?4Z)Xpd)8L-UI9fHWvRxu0}Xh=U3&iJEP z^Vefb9jGJ9KrU=Ol)#(Ni#EK69VZgP0z9Zg76NDknZT-g0X}4}P|{o&PUIAkhLw5} z42OBIZ|q7p^LbUAUu(|4H;2@8vyxzIMu;ew8dAhwX+S>qC8Qq^v)>~8`3?2+i%d7o zl1M~iw?LJD;M6TYb%+CPQSdX;|9&6@21;K3+X0Xifbzdy0b0$56;Jnn3kQa6oNU6v zTa%+48QJGzdJ`lQhjKYRC2gI|<>e2Chj*G2#&L-0J0AfAX7B#Hh6aO-ZFG#0ARNt9 zPLTQC^;xy4gU}QKcFxHh?NNs(C%c<^5jzn&ng}bA#1V&v+d(zZ>yCs}YLlaJC__VL z&qlzmSq)m#IM`D-*wonqj_I++l}SQ^em3GN-ZG2KIa}&;U*zOeJY{nhrtiu`C{$C$ zvnpsQ7t{eMLi&ryn}tNc)JFSt`l8)w-u!Yktj@XaMIj2tIcV#`uUx+vd;Gg&aW~CxJ-#5Mz`=2 zw5(kQkA6Qzz9xor;nztE29}&K%iWH+Err!)v>tE&9-qd0MK$8n&{%Ge-5}Na`P$Ou zZ21w#Pft)&Ex0I+PY$50Cw#`U$Z+(EY;zOv=SZLGUJ%?Td*A2JJK!((B*iOj@>`XX zki88KZE`m!y7x)f!rA5&pQ^w@1?DDUzU3Vc1>&a0O z-pqv#wY%eGMB?M)t(LBaa~zY2g$4xm@Zi`Q$1VW&%tqkAyMls(WxX&&Fr9G$1_t|F zpbN8k!=G~`(a&3ALlC2)E7m;O`85o`DK!EX4yXjG;Ol`+0+(qd#8!*GJ8y;#;Ie*JaCAZNy{^oOMSl=pX_U zDFkg791-xY6=l^3ZTd%t?7SR2zh`|3m4@n^-`3x8X=;64BCxbBnLjcOg4E_q{6{~L zkka9O7#_5{<=(f1_quY}Z9)*M!hObh*vH7dcF?%buR4oMrvlVBxc|-fOQnQ$`>||D z-m^8OdH=!6F3Tp^cZ>;o{c2`rBs_w;xutkf>VTM|BdwZ504CUWI`RHdc?0ghZg;K- z4M>j=&Lb`@>hS`X;exMC3AVSyKgQhcnisEhNGEFCy7YNO+|p_$@RW=jJXnZoQhEP# z+0vA6;koRaR6nOM!Qx=*bz*$G1Meap!8+b!Egn_|)y5`ma>$m56A^k`dv!Yhg!A4f zIp1_du-&krxg~3+Va;C0L)m=J-cpm}{8|N`Nnd~ifnsece>k;@)ug_2)_N%Yvq0Uk zD&sCnwZHe3{0@8CUVkwvD6}zX|J^xGpDcWKumdwMduyYhB)nP6 zc*5|Hp_@2X-4B`(W%^w%zrw`*mP8SM3^LXN-g$#GhC{N(*n4pl`g$#PTRsHUMPsoX zcA6PbB0Kf@%g@K#p)rHw8jlaxXZdI^3s29Py%L^@QlE*+u}=H~Pe4{soeaw#jS0Zt z%CE5)(1c}2T|vzueXlS7%+?s44)Gfr9kj3Yw*Nws5d~g6Jw;Pfp>sVD&#An#U|96C zH+TT?s3Gc&YtSa23lF<8=)bt}LB0GRDPb_%ch6EN{YE72!G9ilj=LdLQ~%fPi%6FJ3;Q6}O{QpxhF5iPa0cO&__`MdXy%iY8(*|Zo=rtNsw zKX3NH5c_jmNo92AbkEDv8{kV5(_!hP7x}V!tyN?E<99Je5byd3C@2<{9+G{S}%_? z<)I|}Sqb@aJ!h)P@oH%y5SB1%6S)ZdG*a6B_MNc(j0GV2kr0KAaU@O=XEII$E?1v2 zuUhYh=M<}Mz4G)LRpp({K!4j}RPignBayZY_?ZKqvySEJ&Dej3qvEi=p}G;BmWu_x zEhTL%@nQ+h8mg9RElvi`oz9K20QAw%luJeWI+2Wu;N0+N@*6B5b}t~OPu)lz8?aKb z+=r<%Rh&pe+1owO!^P%t&jihJmJ=GV*YC|nZok`oc8s8u9FJB-U^hUyK;#a*&B-0R z*X)SIj#YY625(*?k08T-vfapr`;Xy=^v|o(DVUG~hw`?T5TvHC3hI>KU5J5I7&(dK zN!5N%YT0k2{YQC~n7}N;86Mf7Q1he7``B8+zK-l$VMOTC_x@~`|DK4J0ej__p%-tu z-q0|y9fo6MLFwb&&U-*nHrF3&A9W_oS~|}~s^RXyZUv?r`k!6L z(rn#vF*tJ!GwViz+t%BNb_|<<;Qb8P|AkTKv8yEe3+xu|p4e5pyd6Vx;Bo_Cu^+^N z?{)$Q0HsAOb0dmZs6+u8cHR{o;1C2M{nQgIvWYysai!=(5BmWW4$hSVVM=8_rSP24 ztt=qC?0ok4&k5PE`d^y_DhHTJqF^!nab^fUPzl8O{s(pZ)aqX%eDE8QN+4H20`!VeEO?JDk7tFU0@n}Zr~Q0&4|T0Rf(pP7a8ik>XUo)d*A*c$g= z5%4Ad|4{^V|F;Mj5;qMygtBFlbfyveM{>lE?F%PN4Pjq1g`^8@(A;7oi zryZwk)(%;nV;^Hv*;%=`1X;7Jt@60E%7v?CY)Ru4$0WKaPsOeZY^&C_n zK_d$s4HCoTvwmT;s@z%B^p3NXz5rj)2s8`$3f-?>a{`Fq@_1tH4k`&)~a z$`2%Of4Vn^?3CS!dL2t4^&hkL?Z2CVPYGo`7+OX)KzawX^c~0X9v&m#!@sj}sMcG1 z*~K5-93)Oz1Qiz1w9bAb12j}E=l=pSTDp^-A5Wj8qBGFzzw&DUh;Pz&Jek1#no-bT zf3$_pVfi8gTD)B2mD@%(QKd%8OPc)#QC`#c{C6A9;yPnbLgjTIZRqXp;AEDBYtECY zR^M$Eiv~Wx46$UE9*~_b^xb8p*aCsB|B;h)cQmgdM{;n}>sO75BB)1d&B+!VT8deGz;?x>b zX>Hs1*KXjxg_6->4P<@!)yCqYS*WrgfA}RYE}po@ETM`lt!1a^X_nbG9iY?t!T&}m z>vx&PPzGQtI{Ig_kTk6nj*cCUdc=)}p~W7RPQzdgimBp$>@0_$$Es6-KigjYEDY6Q z_xwF=3S}iIx9FWu{o@3*3yM@|PciOy0Vcmrygz-*X86@9K)=bz{BzeGp|_;T{rFA-{_@_+G znLc-P??WMw^>p1e?j1eH%I9o>qx5#WCt$-JuR+Jy0wRpWf;N0;m0I z_w9LnIy3%V#MQ5A8nrth#R-^&k7;MY{)FezZwVIB{Hq;8#w4;~d^OL3b9s3JkF|p8 z6YJcPT1{6fupmwQIqTUTn}5Jy1QyGc)b7KvHo*M{(^ewf+|$`D$W< zFfu2P9WXdVgHape^I~n-d!f{xS0Ns+h0KYpzVXPfAvOM3d_BZt2dS&IXpY94RZY>C+Ays zcJg9r_2;;mDn9PdU*+A;8$`$>Qju+rc%eA&qy>Tn5tTIJHK7}b+8;e?>#CJ9kRLfmm8C(R;Q1bBhk}dXa z)c&8FGN4PWO%LZbXUfxTP9T9qfB#;1XrqkvTDL?X+>ZezpxJC~>x5+ZXmXe2 zCI*xB%%8XLGDR(Xj^y){ht{#`Z?E4TQoScGE-m~Qf0eW`?igq+<3eC1A9}DK9(?F$ zFCXJ(TuRY~gjWDgn9+j&ug1PIDvl;-7a~A{F0KIze7&*5JZOMx> zlEnjN7H_#_Qz0F~|34spt^@$_)bM0uVv`!v6w@1s3Qc6b#-h#YP-^ zeJr`lDg0Q(O(`PWvTLN3ljC{RbKuNq{e4#vl#KIZ1`qV#da5F1Tv<`^9BIM^Qz#VJ zYnN24TQLw4_v+ok4jCGVNRaB#Jg`toMro&h^QS$wl2$2PZ+?*Z^7rv*&&qrLh}=D)%H8eY$Q45ZRm zE;W1(f=|a#9awb#)!Ke{wwPk@L1a2LtCDL|? z=F4C!x>FmJ{ovKNgJJ*V?N9mqNp9rik8d^>4XaIa=%TQR?DW`=UU4W4DJl$Dh5jRv z-SmC@_EonRcx^C;chTS2lRb>QJBB|bUmz*<)~*hA4L)cd2{pf*aqKBSL3s7KANe}$ zQaSueqEhljR@-lLxXhBr*e{+6T>n_wQ4aFcCQ*g`&r0xR>#yj~;*fb}jOj*a&i*vs zJ33H_yKUyqQTG{}9V=lurU`2FhR)xVEEKk_-YJg|L3=ACgtVXQh{bG9gK8;{|N8PkS`M3=c`gX2@OY|?<-Y*Rv5*a9}hO(!G}ke zt|a`=`j!;Wuc>etv&#Q9)({g%o9Mba29zjGIc@v@G{q-)?1%-7oQPkJpFf4;21$9x zSHv_1dT2b8o>i{9dL{35;S$=~2v zOJmX(a*&!+F}qAM^309hgbJRNbw(M!zDUjrN&3zH!24WryrX%Rt^PzsIfPf3g7x6 z5B~_l4{(+*MQgF~R-Uga(dgi>9+vZ^eq8NoY-*BUScn;KscF%^71|O*h zsD!9i9&e}Iir5se1ZK zt70jTqiPxV6}e3^Ce&PNia(8)XimR=y*KwDiWm$ATQSbWUtSIOYK1MOq_5rscEt9o zys2OVQ8Hox`nFs7hL7%b`J!gqJqu=qq6aBu_+MznbIx|;W8*U$!%YG6lu z&}uQqePCzzi9nNR`=Oq7$%QRllX90L+M8-iccq&uEX&{1I%JEkAZj`=INzb^C7WuC z-OT5Vx|j)p3fgQmDM&|K+{>dNwXL%3UU?76ZqePD#ECka&p5jj?mHB2yY$^So}uJ! zeZR<3kY=?UWK9<-=rJUU`rlXL#ifBrgr+`~{}l9FB=2|XQ#$ZlZ$-$G;r2zM{PTWOAf9GPRQR+niUM&=-a1W~h{Ih!&bohyay2AK1y+ zII*mIx1~ZT25@5M*D-YHq2S33JNmolITjnu>ioapxGtw{s=eDphf zG3e>6d(Hd_!#u$@N#1jFq6VVn=xodb#HjZ{Mx=H_MSAjL-B?wmJEDN2U!a&?n!$O( z=O&@b+1d*%d*b_DJZ>b8i!HrHbrVrNv_eN_27z>F*#6-n;3g%+*_eZ*Zk1mHnUyWA z>dnULWzFGv_#)~=l>?o(*GOv*`ME1$!p5|{?VjG#^iYsH(y8wo)(!(71Trc{ywv_& zz0%Wvda1I16^QxnQxH#B_G-4o+AM9((GLbE-NqcU%-BC@l6pq;I*Un9emqo~{OG{? z(4kRQ;Vkk!IS=;B{5o}h!RsCwv!cN^K|^TWSdNwNn7w{ih4&X-kOE6}WR8t30m60(r( zd3XRaczlK=dPBu_l&f=@X7z#AX%ee2mE*}q)FdQbJqYLw1m|&_M5-AsGuNclklQTy zpoi(m=LA=B=%VWd8{>W&IpFCO*18h#(0EiYS-}V?p-+6-#I6>3FpTgBO>avgL;*TF zm>Ldr(}BcbVLZ4qLejHE&Uj%zKIEVJ@Q+_DxkI~%L2X*F5A4M+#tOD7t_A7T`4@r( zXh4@tk&Y=Tm|)u^W>KD0G&1aAheXh^TaIb zv`g%!T+G81l1S_K?Rw4sKk!+vbzuQ>Pz-85NN)7P%`5gvqV4tvmG2zIAYdRW#jb`I z+5&Zn({mxXooRN?{K$1R3X0Q5tQ!-Dc<$5(2}YE%gtw%pDFy-ZlFGr~*wVVvf@XXC zX}};o)nvXCP*d zG6N3niiq8}n|+i~Ym7Y0kEk2PGf*5FW+>12G!T{=GkbGUZf;)W-sqr^Q4dV6fQwjZ z_#jG$uhuER9y0H;Uhz3<+oiAOrS5ScB~Yud!rZ-pRj9YS7f7&4;BX4jv=L(8kYjAFZktV;+&9s5@!l9loWZKC2@Pj z3z|N*;T}q|wY2;WrHjRG~fQn#PO~VsqD~k%paAt7+Z!AU$ zK#W-kFcyLyh2Q~+k-P$VtOt|9UMncr?c%q_`7edX>VHo*2zwBLS~!Y_w-0s9#qi)v zihvm@MO2aIHmY?LUL{SWco2Fybs#TvO%98x-;>%L&PBceJjY8q!XJX?&#W z-aWPCK&4}}w0d0c=`Ev&P~X@RLdB0QX!L!ZgRsO@lVX$%eibMNwEw_g>bpU+2Js@Zu9XZUz|K-w&_ zr{Q`-^BUyf6vzI=X}CnDgc|zA#pNvUIp%W2I)Brq|EWySmyLc~YmN{w!`j7jBFEtj zVWIZarVuz6cxyQz)MfQ-WbVk6T3q52Yx~bl%l++bKKDcYOR#O-?8C-om)_aVaa+KL z-F`Sm|0SSXzt$>VI)^JUZ7mWhY6iCKz+w)Fwnr$6V5BX2-|S!KShL*}-p}rF8Qkec zuMI3*ZKm3PmwW9Ef4?>PDi<4IH2JkD@y3Lk)<^{e<<^mqcyd(9TUkitLFW{G)a-4x zlwM0BS!s1*?ecZ8EoIBW%j@Q3qkko$W@h*J!n7Lg-!a0Az!nb(0+BV26`<$&O=9V66;yj#QZb6=$WGoN`|mlCYM$Ci7>$73 z?#<)^OzH_Ld@Wf!$3oi>>3jSe{pHsu2$>|;b;G0jMLK&dsUW^yL>9QujGLF2*Dz9h zXggCg=GhaI%^$-AE8=TF-vIcu040(fCv`Kv0XD338^<_+nzQFb@JQe|QC(f#2{_N< zmr)K2Tv{wG?dG)}--BdVD7CK)^%tN`Bsh~9m^CxLzDu7h-ofH_9vd||IXUhi;DYuZ zT>oNBTr60*iOPpOo0rp4SXj7M-1O2!)z$Ij$R-F#Bj?S5e@G;;oSpHn`QpQNj$h3n zSfFD4noyjTLY!!`%V2M>YLUh`)kD*+n(RmG(sh_k?w+0Hz48~v4Qbt#)91ARnZNS`_H`j<4a{mF~B3phR&4ktFr5Q=* zloS_O7&e3R_J1y>0X~h%+yVV|<0OL4jg${~`FohQ4eqjO>vlJOrlVQ(dkd}h_7@zH zMBs5ugbJq&M8e1A>SSZd=fdFL?jBu0U7ja~Gs^p_+PO+OlX!FE_}e{hcMNq*L z@V)_W95c)CT@vhkcfxd72?7aTuv@COi0O);TE`<96YXN!`F;-6)t=imfm&4i34ps zRC@%!(>g3)Bt6!7m+%NwKVF#N_nS2$Fd?iUKmTy4we>^Ht9RkKSF&GuoC--+2*0#= zrM$oYf-DUjzzl4ODg#{I-!h5#G>&Z?8OoAev~&%O`Aqu^>yYv5BA&{FN%`@dkVeE#}P}LXxtG8H^vdr zNlK9V-^Yi+wBt$r>}x)VmJqR;$I>%UCcU%#ChHo)$pq!jH0 zHitq#trCv!88(2aRg^t@dtEU7mB`BPB!fN^)QblLhh7aDU1a$|mPL*wnq{Q^0vmL6 zSJ&hM-~USHOQN84nRjc<(7Oa-c-!9tr#_kYzZ1YI1>LZ0!G#~XaYRaA9sfC12t~1q zXFBx#OH-~a%R+a^g=apLrlX=lL%|I%Vu6PER@V>&IV*xN^O?qnmW9HoX?%gw+EY>y`&;dK^N zYdwV)B%WNT46!2Uj-u#Zru{_G1xc(9@BDN2w2hp+@eCK+B=rOuVJwfx0%*Vrm*O5m zdgYj;D0r+o#HjWskJKt9W(sA3a;r^Wh-GfER`~s;;ZTmO4-VlL|AcFzt2-M-!Tahp z=!Jpel6f^33Xwz-2kH>biFMtG)@c;8O)jheLCauoG2HQ;k{JnMB3!w$YD={I?C-pQ-K8~wAL&1ROT6KnN)@pC# z0(m0nZf9p#etVQuI(xo11(2WZyEPU5z(AoHAtAtHke@2mk#v+%&-fY#Ti+Z^1vm^{ zcgIKMnE*QLgwaQuC*M{h%|Gj*5LEfp+fZEU@;g8SicL+{j3VNDek=jpz8B#3<9s*A z2E3swSwD9-rYt(LpI?d-M@?kAnPvXSoCmP1jVN&d6*yLep?!oEQ(jjmP~X+#7Ytzc zgV|c(%7! z@VNe4?F3v2@X){Ly+KiR+P;Dn|5v`}FGj^&iGe z*s-3M`^rLBWvAn9#Y=6Rw+V9&K!eHtRB#3NUMdaXnVY|TE2tUy$H3BaE~@@z37RY% zk@@y_ewXm|!N6jJGw)}FH}gsnqY1m}D7snqpXVtHryEEktIiV)aOy|Nb(ncQ|G>szJni%xSkFKIxaH;Azg_G$~HyGx-lN_2%T z8Sifs{cuNu9Kn9MWV%2IM2x?&sh%^*LWuzP;Vef73+!i`*SymwEa$UwWf&hzC)go52y1S{e>d5za`}Ric*)2(C#mGb&tZ|Uw*ackI1 z!n%c!9=v>>U~Ya^R};CoM%gwq<-Xoco$FTwhgq1u-=_UVO-&ER^Vf52%dzaVv`n>7 zNc`&gK{-Z(o(_cSa5SN-@^|5{*n?Uu3`INdA@zw21O?@Pm*94OH^#}*-SP@`5GG0( zh70d_fk=zQbvlF46(6JNWlVd<^vX+o(5JkTDyRNZGHDXIEB?EQM9V&JBc|cO1tFhV zxRIaR`8hKXk1xm9udOIwc@L`ITy*U_%+2$6@v|gXv`W+2$HFkE%L4);rwn#i~?+cbH`>oGmnd|&cWN$3K*b2QE7>l`RFM%1dD zsfVe5Xi}f!Iq;lu;rx7#WZ#fFn*mJf>m12%VF zUk@}$h{0;UbxWzjXs-(UF@t~Qn=JfW3kDzJg$|b4pksiXY{uAeu z>Blgk&B7f3dG_PW`}ZNcV?Ew(Ni}Aw`-6_#`$t+JOnNXzr?$Mt!AK6vH&r=F%9pV) z8p4=6WXYjh2`s>RV?)hLfie)%fgPN!k!h#*hV$mmou@!u3V&P4Vw0kxcSwT{2iNx^ zs=8HKiX#^0KQ%WsD@P{PfwY}IT|HG(r=wv1g$5=ExI!1AqKLds7jQzu9cVzPf9qFT zsjr>^K}Cs$Ef0Ls(@G3C1L7*?-Y{zD>3DP7km!CGId5}0J^;F}S{A2hK`S!6 zwoY0YLnSGNWlo_MUYvp|1Nmv#2<HEzc_{- zMFkzh@^VY7OB8(2IM`5jx&=Ck6(;H2A@$+F^9=s&>%x}JRc!0Te z@t&t><*5t=$jcV?K@V!b&qIVznG4Px+>Gn9@Q|^BkDtZHzG5;fbH=2S9(dhUd34nP zcXLJIK#m&cGElpAN87$tUg33iG;ulnKrO?|_ce8sbozTdSIp zwuVvwuDr{X8Yd{xN&$9bWK$$_CTwjU<5Tvq%?nt#&5OpjA=WPYM6Y#qg;0~oA3Jl? zLiz^uDhQWk&?Bs*LYel9P7-nz^Wf|A0FmBjG$3)Z;zK7_LZS_yvjBJmiMtK1!gKUt z7Xo5o%U8Ca7o)#JEt=5KE{Xji&jQWfkl^~>D7G=cm}F!Vc&y%Ha@s7IwA!7AK!)vr z3Ju#!pj&pWinDrvS}kDCeD3@*$DCMAF&v!}gu3ss^EE1tQI?l)X4$U=p@dB&ASXvD zykJKAst5Ay@5hg?t$lc3sxxZm((12f7vMacnM(O)9L(|%{hNJ>$#FCff7t0x8Cw}{ z)9Y!!tQmOcafi)v;SE^r}7qnI@j(PYn%_cdB%(ANA@Z2JGUV$H-Z#H z8UIvhg5D@WP~vYcpv}nQqmU@Qu@U^&aWct*eO1M01V^@Z(XoWh8`3L16{33_iBJCU zfKb1<8aNAh>FKU#WzXX-EeSbaQ)Q3LA5l}ezUHD8LgG)CO|G8RR8!m!_&l@-<}b6G z-UNEsH5s6O%7md%kO`+SjBMA<>2VkcwL+b*ODg6aSDDf^j#6QhyH*}TMG|^BoBhrAbX>JS6`)+CjMVW28&spcVU{(;S%(AS&u2KQzFKw^0?XZumafzHf7a6!N zZpAn7^N-Ai3w#?#v^SI0iyneE3+~YshRkP7?T;v)*g--L3uUZ54=oV`@{RE>$5y+tQzpl zB~?v$@RE(9MF#*t zP*hq>Sk?2{!91cZmCsxM)6!*2HII#6w@ovw^WRb!=0VN06<5;Otpc5y87j!Lpy%7L~W|#Bzs^dX#{yE zB0w{{y4pcre&lRt?DqC{L>b)N%p)i$m|ky)X7e5_7-*PMGwoMFx=i zt*@_-t@YyS1?*zTbs(3jkWc}X$LZ(p6>4MF{#HJJ(0}@ZU}&r`>WO1)at4#h2OjYKtyC@-%tr{#8(eE+P*$`MOm2) z3rENFGzT>`H5V5b6%`dbyXxiGH+$16*LL!=PaL66E z1m2vNwzsz%lI!d1Cs4xW;dis#;U=??2WMk~R~m+V z&s@Wg%VlstKaaVf^1O#X3#=#T)q072Y(b~pPUFYm+oK;t9~LcgeKC0ckRnUyc^$W1 z`B{8$Uh&##yO`05{j?D_ENsL7nWs+C> zxyr0r)XIighZysbAv*5!W|{Uscgg!mKtSo%i&+EE09C4OFFNRc-=HMZzIj}dWO>YQ z-)_IWs(CL6TOkdRI?XUZO(O>L|Y8)O@hs zPPceB=jCxH!%zvSGOAgDV>F?+t`(Z}b|o~4`bhrBI4$SZ((k3b{tWTOXU}eQt}OEm zD<0Zm0yO5w*JB>*EqVk|F=KbOuSo<3a{^yKs=M$%|SyntZFOHTM{eN5z?3OIBcB8n$ zkr8sM?q|dGN=~WFcS=c8iK1_fzc$OU3suLV&CTlhadE7XzbjlN(+dN;ls;#fCP;f< z9!Tf5j$_i~0$FX6WPvB0$XrW~fp0zhqHx*>J~{C17A7Ku(rw1@R=u8qn31Wdlgih? zHOL3WanmT2XuF(!y1W|9GjE!BpC^;7AP%NiYm^mdzGxN2;4W;NjVFm3B$*wP^*-@H z3p(*1O*bzfH8)&@ds7Dp$EVJAMU|v^sa)8G4^EGihY&&xX35UXC#mhpNzE_BDQ*(bpmRTG{|Dka+X7k3t za~(-ZN%Qz-zq*0<8n>igGj^f)WgS2EXC0=Kf>?q4dTtkwZ;THZ`^#|Aa{j>&*aa?m zZbnlqB_#wea;Zd~Y-URc<`cjVYJ~A`5EYJox+z@ga(BR5Kw~bSED1 z#X~0c4mxb)Xt^bn4Ji+9rAx^uw+TS|w;xT<&HYp<*oIj|8)qx)7hd{6Zhmowzg_U#NC_FDQ_<)5F?94I|MX!_zkV^@=NK<>*LAyZen&hw!;2VxS$sygv0 zkd{9F+l&IZ`1xFg!t^_+R*f8;j?!C8@jTPCjk5A8W0K%*Wso9M|Dgu8A`~@jT880_ zr&<-NR+^7+{tH`?G&d*s9W-yec1&RCYDD1nz)Y&MlMd^2+1cREGeOauG4K_F)N69F zxM)U)Akk7mLn$bku-4U&6jQpw@S0a>3YmfocHE1neIyQ?>D^(1=b2)q(jn^QNXsFq zKt>8SD=QzR<<>P2Lfa)8U%%1tT4!Ko!tSqPQ-4`IX_2#3YgwirueNsMAhlxQAYbwy zU@-N2f_G5-V%eU_5Lfp7bU|ucG6TO?o||{B69%h~iIS zbjk0fA1CG5k|7l5JHcwtT~LW0>w670o1InJD_`-#*j@ekhiG-1+TI$We+!e;QXKzS zR>)K>hSgDOsk~TDt9)q7oWR1jUr{?eLuufQ0?PqHrBPShq4V*ja(Hb_w#18=-_dzt zuIA*qI2?61#dj}}>{M{-ts+D*`KLVe1~iHis%12I=8LhvfFRPakrc2Pn2_+f4pG0y zUTFz}CTD+?GqjY6EEk#sEBzv|?Ez^;6xsMp#H{j0H5z; zZX}P)@2yz$aF}@dOxsS5Pu!0lZ(5PV?M@f@OvZxVUe-g14}4%TLncUM^_xpWx%J-{ z-~1P@!{Wyxl#)$fe$3tCle_-$P)$bdQ~6&`y%C<6)-V`h-uDm94az0)7ds{&+qX#O6wscLeNl1X2La{6*B{I4 z_g+xxk%3vWxgA>)(t)zAa9g6g;(Q610tfMY+Y}1t$><7x3gMS$k^~i7Ow%*g31i|_ zByEEa*#{KUqZw8bZ_Wr)W+w0Ond#5+t!;`f&I3Ief7|bE#PA+jQqbz>IH(XMAr!d- z%;yFqjcyi|=64bGl;nr|lXJ%XxDz0e4sqjm-5qD_@=E^zpT&L~UW?w*y~+)ymh!$m zY$*8227@F@*3j@=w-w6@4#C&#b<5OG{-zyVPzV~4|41a&+2{JtrRT>RU7tmL!UQnZ zP&w3f?CMUs{se4v(J`02=z&}7;$C$tRK~VYT43Yt7kpzI`pB2g5(KZwsLy#g<|=i# z0ExW83`~@}r|V>lqAuv4&W+kWk@kl;Ek!TnE30saT5L|4n`&!Ksn)a@(U8RJn`f~E zAVPU!g{0Cav#$485Ho85l)NVbP>4x{WvZG*vNDE>Wv$xj+r!a=QR2{z^gv6yELW#jGuatx8Bn9Y4kLlpC zKzke96%k)wo!qO{{4)w0cfYFTgKZ{@5`9y&BF37i-kC{N*t|Wn*3jF%uo-%JG9AP3 z{JpB9B5Cx{X|=zWz#=JKl0s`4$4*H6sUSw5oN>94J_7C=;~je^J~ zvH5ibw+tzx0#khzbRq_M4rjn%hA>MZ#gxB0`sO>bn9A|8ZZ~U`Rzg1Q9 zGn)mY;=TF;h$^;C6MlxBKliYQgl79oyMBs;|2X|RX7e-H$5982?hnlac3>(CBvscH z1O1_=xFRTYMQ2+m{?KP%fq?(XMN0zs|6OBwtWP}7IVSP*67Iu&VPGkcIpV(7@k~t(y}PlC#avE9V>Xn;?~~u%bwx!5tvQha z9xx9^&%m&3-@u2!CgICC1@iJMW~+PY-lu=zc!+p}$@GZ2}EimENG zaT6_&nSC{hP6a5CTeY9YxWm#LJrDN1Mbk9@+sgs|Ajd_NVqo$j_vznCYip~;Fb|mJ zTI8&#un>5EF&nF|x6hY-%rlLCJX-}^=084J4OD-8b<%}jJJ58ewx!*>@*~$}h7%T$ zkv8CCT2)o`;ll?%Xx1j$8rlerZ8hdhV=eQ~+uGimOue92az-P(({4Fc-|hK6o2L#X z=jO&T0~B}AM!3udlaQ_7Pn>i4@$eeA1+okP1RhrxstF|uhx=lPWaZ_Pf8zpx@jqdM zgM&Z;gTh`Wy=MyJ{#4a&fI+3c(j9oM;{2E9^%PJ z`=jLG($tiysw!nNGvJ!s>J0t;Cl=++lq&z91c>;dBx>4aCL8AqYMPEtAj1{S4>J zWk}z~JP<@ab)N?I#ggE%A041MS!AdIETTOxz{SPXY;2RR27nXsmF@jMe_qS2Tq^Oy zzQ1JISu=QuqpqkaDk?fXJ-tSthR=d4cf=f9VL6CB8)-&cs2ld-{z3NYYu}*3 z;rb2sNQ3LHve6f{(VvO0Y@Ss=x^fmSdm!u#F}KSpD)!-G!x`2N)A!oK>4XFtWK-g+ zhwfj39`8Nge$ZP(Ta%SzU*%c_Qlz!s99kzBF&S=DtpbJRuL-o?3W!I;r|@wc2!9n6 zFrW@(MExpAs{eCrV4(BgQB~D&#m{G~$R769y`LZvl&D1ben=nYFj2M2c%OS@bu?LE z#R^^<3cI-6s=jMDODsRgM)KMGgzXR7U}b|J?^4p!)9IEt&CI2mtOoI*$POE_>O}|o z)mPVnr)*T>DUhjZbJl`*G|%cF+;LzTo?y6Ojrw}m4Eb39wxpTLRTXdgqm^jeZ5!V= zt7tplT19R2_q}8a{}KE8iUE(b*I=p9Xv} zRDqq+TE;n1*~m|XWSoW5Mh*`^jX7JH5qSP8+n>swtdwQqVs_(vymxNdO=*T`KL;A%+WIiki za*FH+9I@^oKXdh-?M_w(A4)Xyukv$^a$c^)b{9iK6siRqcdZxx^xCdfXl(9w2Knw+ z1Vj8UF~@;jkteJHCpo5+t+Zi0;wO!Ma@DgxK|kGY-%&3F!zU3k5W=7k*5$qI!nN?46c! zG)AQ&9PF~~G<3w99?lGn6&TGXaUhI1-9w{u*|4Q>_ZFPmRW6zo{&AhOIk&lVy#phx zE>yr{eVC3fzTWy(+p30}F#WP(sKZ+i#GP!E<)w#*60wb~|043_ji~$TNEdAW7!3+j z^TZmlNL%~X`JCa~k^h%Dj&N*EPZ6hI24kHx#Q8*tIMqE5$ny_ECr#aMr@kLY@qN4L z!TMB9Cv$(?&imd=60UPC%zH5erm`R=qAlN*itA5{#Ki9nL|D&*1Q&5pij%!jG9_ix zqVy@%Caq#Mc9XF27dxzI>88MMGOZ5uJzz9nZoA~$WXk-^Jz!l0t_@eQrah5xpvGZv zpl4X*dQKn2pFl>f0ZCtUWIz5^#`oEsjWXHZSim49ivq3dovuqZDcXIgHYk9kx6yka zeF^2B>lWjs9iw+B+RjWO=+9dju<-IGQh)YaPqJ}zC+g<5c#zOw&^**?9XD6-nxX`2 z(S7!Cc;8?!4TD|YL|W68(o|^ER*u;w-Fxai(*-+Gb85z7 zCZ8`pv)L7uvy-Qq6RM&%tNCG@W}wE>6b0s;H%&$-B$IodJgJsoB@(!{+f`|Yzh=eVdc9L_^e$XiKi z#o-SNG)pjjf%upwTa>7rwY1&e645LmDIj=@qiV8;00h6}17lT)9NkV>vvI^mxsklP zZNl^$(-A5fN)y8$)Z*@qH$f zCcpNb!SY2uPg;`vl#q0;Yq3yIgK*PS%1Qj3pV+dq-d8EAseHr&7k7hFx3QlbR4_2= zl9-u-`?lD!OE~d_JY&p|tStmYnck!ln`T_0eCB#?(p@&x>`Df~u!=j8C9H35LUv*0 z957(r@5GCn(RF04CA~c|!?wla7;xobXmvy4n{Bw<<5&1sBS5C*iKU;CBi|)HtR~2!rogFr7!Id{Bh%zmBdWC}Cs2_AkkzN( z9$S3d@qgOO7TV}z|0pTu)#f5ed! zr}Sd%(~b11^aoq}<4dRZdUr2eY980&e!2XRlI~mb0k<6;ZeEVW=Y+rWUmWC;;*;5> zl;$&+T+%`BQFo8l z)|@xIJJ#7R(eyh_(_q4?DTAX)$fUMpY%J2BJFhd@^b8b6N;J3%iHiHOLMLuU5j0xp zF^6#yF;JQFVXtuPuTo?7jvfR#UDrBN?lR{6OUSh?g!7j(jxj=PNZ)UvFZ&#s2h=o| zAf}yul&U+P;|rgmC%CAyj-PpRAYMnr^Uc$C4l}CD%ZZa3g-JJJjezHpGM4EIP5DyLKil`U91;ABnZb1g5CZk-A8l)VLhX0@;O}J^3f`QszA0td3B# z`609w_OaV1FP}Kum7L*1pybm{thL278S?}`7Q2>YmFD^)^_Gk_-cn9n~vtH!vY8EEqEIV}9j4sInji%PFjIS)+sSk~X zGEXNRKG3WlZmas;SkIZ>8XI)wF4-C+G2_#c%;9b!!cP@}&+zj<-_{O%1%e1K#bH9Q z4lpeV{LF=ZfuD5$`BwhL*6r1I=0W5^_Tk3x5%iehQmhZj*O?IOE#oePotJm4&nsq2=hy_88ZN#HX%@b`6 zuBLWxQy;D4jE*ElnDvX95kg0xZ5r9wmhUE`Jw_t0hZ)osOMQ+`Z&<@mpccm zIx;@$NZ){i4V=p0aNofaU|a|k-Hz!{A3qIBXW;gjaUz$j2>beaFckWicX>c~o3B8) zG@c|Ot|#zUesH32ys>Mkf4?!|W$?*uF6F~;X?hjh>WhFXu%u%&UW?`I<~QP}ik{0w zNAAo29F89JpE$mX(|`I>)zFk@rLQ6Dvy5T$7%yi&nzQGSoz6xE_5C6xCP1ERxXpgQ z9nD0e$WFptX#4G!e_*HCjV)VlZ>AEqdU#U@Z$lCvD+Zx}`i2+F{mkMS5F<~QGxxtY zW%W&YC$4vK6;Z9#q^qufbC|c(;9#aqcYjkC#_if-L}VWh+{B5G?Uy8nV!^Vwsx2mV zgH~6a55j(c)$2^-&u;2eLG8CI%f}*bz2vE|BfSOvuG!t*#2@2E_De!2uwY@py)D^O zL87@t4GribRSyz$FXGrZ?^~rz-zHd*L{L6c4vq^8CRNCc9bdhB(&?{2QO@YH()bSZ zxpF*;Go8~qUuRTlOu5E}ABg__B^VT%@}B-^2_s#;zxi5Nx4{9)OJn}1GK_IY5Ahl80Kj;IQcE9qCYp|ZilROTL_0QEaG%c+Q)g{;|Z z9is-c{co1X4O%{#1fAOUN%?uBx9dc#UH;lsS^N|`ZpZdhlj&#Pj|wj+V>5vpU9iKk zoL~3Lc*IUEOxMlDiUT8OYkr^Tai@G%+RgIgD>j$3{$crfW@JBd+>%0xvQYzJ(3!92 zszogVy>QzH%im`@%|&l61;JYoC;t(r3sziVUEMeoq?%~t38DB)P8R9;&%7B0g+FO0 z_9R|^%|CqLZ8;GFt@r$KJXyZ44qTSAD$g7(#t~}F=grDg? zew@xnvUPf?`NqA8<=v-WsiYaaX!;$L8-dx}%pqTJ>Fs+(1*Ek;77zZ~JXFk`W6r|d zbwioHIr*~YQnedo!k6P>Yd^>{ntP@Oxy2wU;R2o|y0F5vfU8)Fl05hJD4S7(XJNia z`Zq#4x-3CGlGm$;CJJ2BNmea!tFr3qRU->F`uxR#X7&!voc+*|m3>UMmd_EWa9E2t%Uk7f>yY-nwIR zFr=8Z2%mVv5D+VAV$a%yygkWEi4uW_W4T@Zjb_+B3~2%!U)4*xjD!f7NZA9rv9>tV zC@oH9`}_P91RTiV+SU6mTnr+*USu>WQB|}Fn_Ooa%kh7gp*A$%JoupoMGK>)H*C?q z^zA@;emz0M1oRCxaikYtftg@F%%#R3|I9VQi^!4a%zLBrYoVn8LI6-|_tn~nA@W%j z5#lS87DoX=7>gYmrt?XJciTwt&QhXQe(X8I9m+4U_A}cAmz=?<-s+6yKL$ehijTPTd!-E>P zu9bc{o$@?VL)e-`;A*^jg%u7rWfL7wx(u!x?ix`&RL?+d%uB2Z3W(-fw6oEDnERSM zbvz2~+wT6*O5UihNvwgA)kfl|&hap2a+3T!9ih_q5(2jUooG6~uz^q!JZgey507=i zjb^XF#LYpf_f%rYPY$a8syYl#DKPG&sOGfZt=6DYWR!-9X=60a166>G!CAM7j~uhm ztVa$6%AKaiTcQqfjOIxaqori}a2*2)#I2`Xr^;eWJf1#wHgA)OfF@!(u*#YGrAM6Uc-G-VJ<10qGFpZ>cejBPzY(9za**aP+@``WxiHjfJ2bZW{h2z z*5qwQW|~;mqiVYs;yav{9&*O?m{8!4L1#)k+9|P(v`}DJ2)UxCl}7dZmvvT%iBcVC z;f8+Dyc{Z1cGAOcT-m>#?ZNHI3wqMfB#=SeEhTq1ziUJ`*)Icvgn~chb@-jO-Vd5( z#S1BBt#=z2SnasK4X5<+uCN$0L>%*jk-wA_mbr99V1EId+ft83KI!J2(2gNWo>h&O z66pVFLYS6u!Q;WXgM-m6+vd6Mslc%&E9xBH)|P_Z*CBY|N}hVbfb;FwlMTRUEvP0Y z@IDZcPAIQo0+T^+Kx+UP2Q&+N= zs+i!Dc(|a&RiE}4xA73(9||mDA&1=Vq$^&)^wTf)_3m=z;Dr!x_{WU^t{%&+>s5>w zB3r(w07O(kL;y0Goqoe3uP1`Y0{;!6xDo}wQ9rjM3Ig^^nhHWF{uYWnH53&f|BpYN zng8SO|4{8B0dGDuG~h%!I0PT}b_a_r_ck&Z*pEw$1tlYSy0FL$W|7A9LBojXR~qK( zoKP}@cz9XY%#H8t)KRnJR+S4DDT&SM?&5_H<3tWQ2^0qp3woFUM8B!5T((on9%XBI z-+aLCSAm^+)N`3rvhy)Pkv9_9uOTGXkDJ_BDg1ch=FM2SVkD8QzRAErr2jDc(^;@2 z>DIt`{*elHLs!Etxj`nQALa?VhY!Nnz8S$(>0(Z#R9N@`Vm?v>TX38Ls(b2U4*Qt#Vc z4&JfpA&)lSO8)6liO`x*xFCXn+vsD5(HmU*>&n$Qd#(ogw|p1P!m)WAydz2+JR=`Q z;xDB#i^hun7~3uD54iFPDOD-1YhQ7MPF^=FJd4AGg~c6{2>p>!jdTB7 zC3!w{g4mGjH0347R8%(c<@xZCa%R;dZd2T7kKiQ;u&tZ}ma_*Y3^dk!H8Ya6Nfy zFnvGcTj*_zo5q+m8cuSU)`)8i%lWG)$P_r=GJLEs(yJD(~+Z)S`@%;fKPR)73x zcK#BOVqQMtyT!ZAvFLZm09Q&AR`KvLKgnC{6m4|xSCZqh?4Kka_r*m9$6DgVX7Y&ivkPtd;en$ z-gk||qpvb71NTWO^79xzY~%QuL<$V}1LLWmdg`@a)eGHCq~yeQ{DXcX)hlGk0-Vn# zvL!0;Kr6VY`a+#$9y)$pKC|+zDCgU9lWmgfCbxE7m-=DFH}ML2NOT|C#ue$l8X=30FAJ&Vuwi^L z^3Y0}i{JCDkZ5yO-xPztMV+|uFmnK+*KhHI?f=&IqUs#;`Hr!ByZb7P-3YjaDs&kr zO2v;oRMgv)DP2Q=_-j!Z= z`1y52xWOu|U+q&p!&Cji7#cLa!Qh-O|yH={#NJL zLYxP6nl!>Y&tOUSS`;tPVF!b=7H*Xv&eA2uFd^jG&mnADxg78*V!}}1S`PpE`|)&d zMG5ysA_7-%VZmMbKVJQB3B-f|^`B;G5`Sx4r2eUY^7vr1KxGoUd0?w=Cg_1HJW^n? z;cEW+8R&<{yHUF>Ilv$v;)6JhJvUY~S|u}28;y{~7& zRZ(Rpk=DeX5jzXtzExBnUDCpf8p###d$>;}83^ir>b`!I{d?|Y&y4Z)2{#cwMW`bEdiJm{+JHT$%b_eR$S-;G8lHfX!a(mKhTESWAY4m|;%ghc2OIdlR*}Gt&D3UW%J>8mbQ+$oF4PsbtVkKIZ>@V%lb`*b$ z(7hn#jh%=<+qs8%8N3O%^l6#3?xg>9l|M~xu0VmaGgdUns1G=4R4=R2+r1^1%Xtm; z;vJMhH<7SrSJ(a22U@c#^|}^1ZhO|H>Cxb}-rCDmeZkJ@d($tb9Tl>-uR*xd8W17n z)6(0zf627OVX%{Mn%>0nV%s=}@6@K2MDR~JMOKA?M+!X?uUFQKdoM4Xol$nMZ}&HH zA|iQ`6_-XKCKHcn-lnLr9O~4Mv(lS&vBc)*04?8rYH_^}xOVlaZ*QHmdrwWo&d{T` zv1fuf#)0{;J%e`B#4WppuYTsQ9+!7qjjheb(vbj1Egs${HvQ>bnaT%~#aG937BJq1 z`wqm!3YdZ?F`I0B`VPpQk?R5xVDxkoLuSD(HaTq zY{en>59+l8EY_Zz`x@Q^m65m^G?&4DzN8yjc7m?57w=3zq#(IW>weA13Rva^8{FT` zdos$ng(Q8n{#1ZIHADZ*8Nj+QZMMrXu&>XDn09BUL#sUWr%oHIKV2|33ld8DF4En7 zL8H3RfQll+0p z4;cuoR1iZ7qn09MaR>0f@o>@+IhFCwO~9SqD~iwyj7@~%B9jrBx~MrQW5I*2=lrP& z-e$0m2Lk~tC^=U^T16CZ2YZC30@WrTCU#~tDc?OdnzLV4)jO(82RUVlOehG&iMFMn z0lz(bb-o?_9Zqy%H7`D?-GqU)E|z2*?I_YQbJd~&OHVN`UU?`Vb|8-9vr|64#nK>)hY!$W&7boe@8YU@X=hA)=l;Y-|Q&1O>b7^C^u8@ z3H>5>xp|9odBsCCUZrryW{nR!ei)KEE~1H1A|lvyZFAz+!z_YZ%m2n~Hj)!Ak85pb zRPyH6jPmsKkk%wMD7AMAm6ETSWS=pqljGKl->3%N< zHBKiiF>zT>1I_A4hl?%bzBnab6J@C1DqzYi>tpiKlOvSH{a2}nRj4I+7kCE;Pc}gs zYpVtEVxKekGunO2`>ex%HgpQ6-&;W}#vgqB#e_0dbfiPUFAG7k6v)q5?h)MT(p6|m zl|#^N0}E@adAk3U6RWeofps8GTC>jZHSP?IQUekjys%*ltP()|l<`j;bgWNyG!gvI zq972^tvl1X>!A1JPxW{jhYL?J3H9QRQ9Fhh1*>iGTl_bDKgi|{;z-b+pQIZ)z)Pzh zKie>Tv8fRJCh3cBc$B{myBe!21XekM}Ig+A{kyuT=#X3?@G9DIaMEJ(IDU_)3`8WVnkuDFHjH|Ie?dA*t`fW_ zjWEAo_Rb?}rfR}8Y<=JCC-BAp#EVUwLU%mCW!ZXRs>PE1nV<3DrJA4z^@L8grJ#|!f@f+j z?~obA2aPY}F6#?%?mFKbXD@|Jy19m~x9gmSrJ6T4dlYwWICh^56V;|G!&1PjElMu{ zF*_8~bDxt+ruro@%i$Sh7=@V_{gV2Q;nsMJCu(EvcrEwgg;OGLuDLjmC|Ts%5F%= zqh2j_mba0-QLgmUqd|gVo5q3JsLJ_Ld*$%$Na_)M*?;1!{F`?qicGF*8l@)MyIGz{ z5e<8&x3|tjjU^eZ0+I`NLD(m~h7Rzs{7CBdzg_g+jKTM^q?A}CJEd-ir)KFk*gGOM zw*CCI<2obYXQ|aH6s`O8T*EH&G;O$Y9S6Y#2`dE#9*h29ULJM55TZrNH|#qXbMcJA z!%#bM`%oP`@+%fdXKDkXFbEP8fDV8Jpz(?jirjul0h_n5ei~@}7=gF|G#m)2m57f8 zl9RwHl>V(|m-^du)kTlKeuvDU(RSo_e`+MK>VV?u9^N?Z=*+O z9H4E{yRx2}wn;2`B>Vs}4#do(YI1qAXf=tCyQrRZfG;1?Ik z$}oeG$HPVR;Ev5RNfq$gK=+M#ub@|ZJ`|?iGoHlk3;n)*#l)$iB4AK(9|;u}f=BK6 z(sTZ+aNXY^o)_y=JdZ|2S>WWqivgaQh7BP<FUx82Yy}yyC|8Sf0YB06?DO84e z_Y^7o8~ekA2c!PeEgwF-$ynkpJY@C%i6YVj!hw0QGHoG(l>WNEgX12;X*SKs@a40O@9E`NNX^ifZ9v8Cvww6)$t)~2Pkeni>`YjLy| ze{uAC33r_bf0LfK(rQ(fiUB@b;+;WuyHg8oA_>fxtGCM;Q2i}P-GLSO?8JOFPeMH@^b@#Kl1^XfxbSm8MKe8gayAto9}cw$N%Rr z1e->IlA5{;zFY1j_M)Vh}FN&vwBVJv%a~riHV7b2msMpL!;~~M znfw2YN8$AV+o4NTlLu|=x8Y$KhBr)^FP|ga!gGfDdV122{%M8%{7dr}(FyJi@K&_> ztCxmZcbCFi)8BTNo84^0#DaFg062a-ftXG39aMJDA#XQtf}aBKZ3ipKyKHvi{>t+5 z+IidUTLb{ms0k*OrLZ$K_4WccUTt~$cc{}dTj<^xa`cs*)RPa6KzauTT>r4jh=r7v z>aWhnD&1g*!9 zd6;+u(3Vc!W&QfrR=MoHIq|#xi6cchK?2MGjT&H5{gIWomxpYF6}x1h&y@>;?yJa$ zX_VNV+=V38-n~0_3HbyiuCA`<@O-5$FJf@k3jp{lOlp*8zhZses5^Hz*KCG{o>x%; zq`eUC(by>g4!!TK?kmHLk{`Ye{82`zuJNW}vCX6}Ci#6&F!NLAPcbcfW##Z}%UwbB zx$&Z}74gY44D>-qAIHE)^A`YrEsx*Wanhi$A7!tD=|a0l*WW}J6clK%o}M6e;IfmT zj-Bo9?&9F!^tNYwY$hhRuSN#oO>=BR1`<>C+_h9yr}k(_;5_RRJIjXWwOOQL#ccMY z2|BdjbGjL)mXb>O`GHAM%0cSS3|?P%eLgcYGfTgwGBz<8A$$`!Jota%o`ONWx3@9#&Sn@MOjV zR~`+gyLVvHli(&@$4;b2_SHycaDNx>hF8w@f#WCmp3POSo$SlYOE^b5txZit^xIdO zTuO$vn&F9N{pwOj3{@N1?AHMuxDuj3SP4wv<_|?N7=NCipNS}Q#JpiK@wKU_2z^l0 z``tI@BtzY)f(tEZuN(5I0w2vbFo^l^QpVWj5dnbZY(bC!qe0&1&&{D_ zt`7FOQjmdxU?TN)YzK${%i`i<2^1``E@*H!c(O#15CHgN47ag*JLCxZT=Jl>zRec^ zfOgipjf2A#pF<-w8TfG51mE-x02qGA6a;~-GWhxVg$GWy5|st~?mXAKg7>1S0D$eR zkdP3d07=p-1A)4=RD}{;CT+1h0KjFuQm5g4A8L}(V9EK_l}?J9l9KS<_B()KLTz*N zg427tEz6;jYw+XlWXa)T19p280$^^!8LlfLd9P^;7<0*sT=lN{<0*bQTp|K6STs{` z?5yy?z;0|2Q&#*JAh>GtPL)?y-ap*8z+YF>;&sw`=Ik^dW%GI%*Wle}@&o+KzcK5# zS}wYKcr<9wWqQKMeSLlBZ8?)=h`60iM>04l9g6{g&czlF2VdX&OfH9@*JWOb@L3$z zYBKV~OwP=t$Wdd5|AKptVpm4^BJW1XWxv$ywm*_cR)>|K1h6O&}Ssrb!UDc^=(3CPHsYMY^8ql z*Aq|&_2=$xso?g#tE;ONQSVvazOH^~;buZ6Ha7MI0udmI1!_W0lm!O`p|_@v&W=!l zeNgiR)jSmiQVSf=4|^ySN=8Nohl2Bpis-M9TeGmKo}S?OTcAv>^H_E*^q=tV~Q!_E#fsfd*(eMMWDE`EAk3 zCv6Yw`r@Ym3d?u|1YV~H>jM*Ingv*dpogzdp7ZdCdY#RIF5{reCcsK5DyjycijRl3%ehf$;!$~ zqbUx5;eZ0d!o037J;RaQ8API@ifPjGB-dKQZ^d__E8~j0kvCMdJjy(nvDO zGcsR3JmWHV5iWY^dUbKcoM4vLkl?WNU7{lr)p*rVKFeBEUy3a4A>irdy0AFAd^1th zc)3AX*L3yyxFJ`0SK|262WFlbpIKy*uV|cK(y`|vl7xHFqjzb4aX5~un)C9fu@CS` zE%ey$lsT#FOL_s9r{Lnh^%|LJL@mdQ}JQb zu8X0wG;nu9@Igd)K7Tu@(!t4tryk=vE()%*ZmWkMhGv{U9npCn3>t-Scf303&K)u7 zXV_T(#^U+(d_}`Fxn070uZ!8<@hu_P@!UbY`ZsG~^`o@3W=TVr6T3EI2@hmRr`Oq5 z74`%5pLL|%m*vB9XTvF%2kTiW7`X_J)i{oZgCS`qvNMq>NdrQruP-#{=hQf>^fJ0Z zM2OA2TIm=T9=J$Z^(}O)hDm%Tq4jC!ZU?pewhioXOv^BkDS^1PxRpS7t9&@@#iW@R zS_}0-&p1Zq$q)UnG+cndMfH2#g?N!dq#e@~#EAEdq2P2kS+bJsyOTQUn53ua=nKqZR#UunSCm>2%1W}!dK?EE5k{$HbM9=#&3kG* zeY{Z-{n86++IC_tPVMf}oUrno>^=(=bZ?_MjB2NB()&V>eryIrfHcg+=N?xN4f_8C>6r@^d^q$i4L4+|hOS++u=Y zEGh&dJFj%Ls)xWA)1+!Oj z$zXi=Kt`_8@`-gOjru}G^02+2aSOEz9PP)v~Hc_g_@3& zO3sEDxelpJJ9Utxv|-t(4*#g=6)X1;Nt2>9JDEIaF-&+nMoriJl?G~QSIHXJ;Mna? zAae+E#2rx6OQmQFdWT-_k>A|6T6}2R>9Ddm48P80vQYzFhp)IR&fxs_m?6_p>Z4Jf#jwQ<~!Chq3h)ujrGl#2TEJ@bJP$|DOM|<0XfBxIwEv_jrZqC}0c-@(?TKp$+cMZM z`VhV}3mHe*rLVY1@pW~dofSY%rnf4m!l?Y|+|9D>rtLbrE|*VKt*X(JmaB=j4HKp* zI*qvQb_cj7hXtQD@i?j4X{ zfpE7ij%Gw)V_i9Tpup#15*epU)PxJ0n+5p2$QUtPRdyY+S5W7;e| z^wB_cVEZKex-HWm27dP;^@1)mk9X!o z1k`?;+f+`NEJzZsjjHK6F<~^cL8;sCQdY&&5B)wEmZxuUO0=%LMfdFGciCKqel9;M z#jux}3dhUo=LKC3>XhMsY{Ypwu92$o>QWdhR{WDpfRww(bLv0`?V5Spsu|}p;sr+8 zuaaAY_)l$eSD_FNY0pc@pcpyX)S$zdhUp=93As3z+r8k=iX&Pq=f4@li_0jx4>AKt ziXD2eI_{BV#22*9!j5LO4B?^+J}Bg%UBQ7b9`oCnX@(+9DqkdI%i8=t!w%0JD2srf}M z!>~w7luRXMTo;0m=6zPKPZ%`ob!HuA3#=Kd5ALk2M!;h1-k&xS$bCafT_WBV!saiJ zKe{nK=sr$;V@PHV&Zqp|v{$#V^zo(VaT-FIb@6yi&%&5ygK@0Yo=u8rv+ zOG12qWo(Iw?WbMgZclPG(L-8=YpT!K9*y#qT#|&=aGU2gv14BAD2QhiV9Ex%HceNgtJ%Y*Hn%nZ-1d@%U_|h0v5sc%tn~80+Y)UIIA2u&&{b(9A99Ch!{g6A z3jtHvP)gIJk~X?jQS$t|KcvmJtN8k@g?g zdaEy()xzLV_Jj;;GPB!qm=y5oYLt7EevwqWZj3vD^5(78-=X#i>^cE7{9Jd+g!EB> zgGdx11J)goIn2$_Na#tgggm{tsa7^ZG2 z9G34w4d6ivfgkSf511`lynuNb>XdXpoxCnH^5a{8o3Lwc9*NX0$7%@}_iZDhmi94&)|8i=FY;?UUQ0Vmdc?rOl2F2^dXlYV zSSK_jiC5>gNB>Uh_m7%%bz}LqEEds4GK*m|(xD*Cab%2s4e`BC?#-IIqSggZ(ArB! zsd-k2u0n;rym*~^+};F6-~#9_-E-TYm8zQZm;t63eHk3U=G6ZJV_{kF1=;I(c?%q! z+A#3l;kmHxF}mrXyM`V42O%asx`vz$uf_KXG2z3!V(qh_sOz2k;o~swp9H3>KN5D< zfL$-dzW-yC>;r75+1Swo4tzGqRz)16_9IYasxGR8TTD8={MwD&g|DgDE!c{)LZJZw-W86Dp01@M-jhJuzTDzz8&f7BTL|HO_*Ht^N$3+0 z)QE9DQ_3IWiQJ~6Zh6i-ZugdcEZFC>6-DP{Hai_(%)avrIcxFE7%^G0x=kT;K>qVZ zU*e>%;}*WJq!L%K)K(uLlCXCWIp|rV@Mmd)s@5t`1Z7h7I&%D6e`F)5T@_2zGN2zn z7z8aY&@d4vgI=9zaxx^7=$~wm7#(nnpO*AGrpEbfFrl%&O>^VuJed95r~D}QKIGiC zp5yV#s)}dt%UeJP`ULaXUGTUZAX*Ag59y^qd4gJ_?Hghq-%^ zz8~{vbu*=6r&V+62lZP+=)-;ip14@MYQtowv1TihClbr$&0WtRNtRx{WVfcan9Sne zYz=5;JX^xO7Y0t~F2PX}OQ@B)hJ4IL?7s7yvV}j5Yk_8A%{-yee;5c47f=Zj^JN&( zF6LiRA5QlM+qKiI%$o(-(|;&n6y^9mh*3@#1R3@{yf<`Z{;JN;E_l9U%xrW)h!4xU zDbU%*F{9m`_o#?DmfycUQSXg)fJLq>GA}2wuDD*zKy+1XguBs}>t3P6h%dXeqor78 zQ;pzNU!f<}t9uwA(nF2gUNJk1vqTT=%#;&Waw<<3@l%|Mq+&1pVh{0u%J_4<5r((yALrM)??l z{Oz*_&Ynm`p%SeW=X)3i6djyfJULSjzpLMV#e<>s&;VCC0Y+?t86@H_6bF@wn1x;u zz)_wM7}Oo#{T7T1(tuiZ(^VO@shYw>4OChykSdUdiZdQQKEQA{K!1J6)!`ur`&{ut z@;ixv>S3K_Ko1LVIQ<01An`F>Z9_%B;QHr3aWb>BV@c$zCN@d7bu)F zaNFhP4`h3L`_lOZp4Hj*pNNSoZ-?huQe-Fn9xmIH$ti>dxPqgrP}3q9Q6uu5<64vd z0xR^~1@xrQyJ1N^I=N(6z6{zK$6Jvg+WqUo%gQRhbMfRNX%TN8uY!4XtSC&)$){w| zeeV}O=X;9kmhQ3=TtPmA%^nw7uiZcGO?S3y)bPiLWTy~QUq?Cn;RKfa;(nJ6szdFR}6O})-&{D&+6ha?MF^l zur^c^&Ig}Xkdu)u)95JSZH!D0fu=8%=Xq;>{rdPjg5mhj^Us_cn zm?0V$2TGdX1RM=aUAHDHzU$7+W9buy&dFD6=+qt zqmhA}Adb^Cvn^V%yRun5%9*7EG($MzeZ0eP-=B2{dq?bSvJP@oIN;hIuWoqDo!j_q zGgDPbM2GD@Va#8|IRynZ%zBq4elmu#?>+Tw^t#x=ZW7ZYEr21BR;k=B2_V{M`BwKO zx91u~8$i`-!D$t7QBjNy9>T{G65d8Aw(2QF4CtJsr*~S{qywn7@Klk%F%j+!@r&5v zN~m0|=bW`vV_@cVg1`I$jv5Zlk15~wO6Up^uVY=^?7_yNQ!PF_&NK?U>V0h^xLs{) zEqGjP*evHGQEF9mQkN%|7%nVug(9o+Uh6w+Q+FlSjYV>MdRMF=hFKa|7sN)OEVtqF zg!$~%1r=>mg(q1x4m+LH?N{b+VevB*!{uSn@1r;cw+W2KXG;5L6&ebs6PhAto-|)m zdeXD!xnjy8|5Ru@xSPdV&#mcEeZVA-qAG2%a`{1oYJ1sw_TGMf*;(v^=gLpl>BI7c z4ZZ12>|?TA1ZbEr59ah_RN5d_^pi8<#-0VDhQmje1Kj<2_#DX=kH41eJgQG}Qy~eo zNCv0NCqpgcL&ZX8g!th)vtr_u!D(o$#goR>kFn)O!PsYSQ*Om5UT(#_fn<1ZF=(A8 z)78m_Ze66lNA?6`J5ZN>QG)Ud`0k!QhvRf8Y=vfn7sOoS#gFBJ!bEbUYTte;ztliv zn%lbHZlw;N{6?+->G-`$zSo3Xcet9!RF>0-j&K^E8Df-Y@vhM*^_Y7n-`+e&j`7rI{1noteZ%gZOUjTU7I}KmuR=+yrj|NCl9%oT zmtzbqCF*vn=gwKY_d~(X)$oSGcII@HL_3@(i&`R7SHj!Cu9UdnY}MaV%)|HAS}uA& zFZMV(YLx?SC{}dtT*pfjN;ZNM7{x68BM>S8b`&C26k2}cY1K`$L}RHJ*y(uB%l(qw ztFa@|edR!;w61km;S?f~RW>KK)ptpx>r1Y(Mf_mKjz!ItxWe@Fyh9TTZsa{al;oZ| z9^|>AsYmZiR3*Bf>wE;;^MB#;G)-ae_iSW6`l!xWz2%+2Q%-Ya=1Q#suis3cV}LglTfggj#7anbNpY&> zx~@a%>0wO5-d=36VW?W=MQXi z^F-S!-dR$dr(hScdqC(Cr1inZUx<9P$6Hy;A z!JJ;5ysLX&W^>tJMKP=_XWsdIl!?g#HA4XK_|p6Mx3_8>8+*cul2Ur7f$IJJ3DJ_8(%5=D+c7wBtuF}4_)iBVUDyd!8>24 zx%s#$^gg*>DERL@vVc=CP|VJP{dT|jFXxya9fkQmdY8J->~SFRn$>l2!awS9hZz2h zh2s|lUt6UkK_S968NcJ9 zzo+ldh&Ts#V7h$n6RV2xk(a9dP~RploRG!_?d)2=VkU{vu1B5}B%++Q>&q{cIK_!7 z;&em4kuMl;N-{$Ywok)y+)5UNiVVxmVM1XBfAY*7uy<@M1S{WPrMc_zzNuWJ3#xu#A0@{ggHz5J6IsHaQFcRcU0 zcJlGV0>O{T#rwlwlYzAfzg8EKi`6dSyepIU+flb`F&2-^j&67Ma_e{0$R~v52@MES ztQc(3-LV$g7@%at45Vsq7$C_y6IW%_;)+_Q8l6ojEbF9T;E#1<7!W=Ey0n_@ddzX~h^{adUIrrC1iPx~jC`!(BC)l|C+#<8t zVv;LaL~jce9O8e|uTOs?|VLYK&|&%cZZOD`_WR)XDX> z#MMOU7fjaYW!1DeVx|3l>CToJq7RC`eB!Mny!0re@Eg^k)(;W=tlSd@`)*c{^HwiD z)@&;P)-j%?6qN3_)$ml?<#z`X)a6d@>Wcz_58Pd=o`vq`G2omb58HGjkY+BNu@ zhoNVl1CBFmGnsfc(>BHv9lkGpoyitT`BzW^qZJHWeKX{Sq(ns)e|$D81s-7ZE*2b{ zuU2K zeIW`;d#b*;`MZTp?yE-NC9)~P1qjY;!?FxI(1x5-bFI1`2?ttK+y|JdV$H|O=h|B*&q6K|9s>^B`AfXLYVvN0JalOevmV?DYh!54XNo79;Jw^#rRy^3)lzD>~O-m|%C)MkQ^ zKD@19YWt4N`Rj^g!&Cx#WHa3tFhcsgOZ3uTvP2IvW_@a;+VfNDWm&DQW(A^N#zB27%j&TOD zrn!~5TmT7%V$+xtwo}EiK-g~c`brrN!~jP{3$M=t;Fl4Qs>36e|Ld@fDIt3HZqEm6 z6$3yA^m^tIO|uk6oO?a|s@?`KA;shgsgo^q$daLl!T2pu&N2x=lj`OX?YGT4ivRf? z-~?#a50AO6VMT=Yhyep!bxQfLy|@GD;2#72d#CIEe^WAmS4~_ql+XLy&3hw*5~G72oJt=JhPo@V*dg9L7U>`-g?ss^_xm8{!R+uB}R zGh?s$v>Ep%H0-hlIF43(>r>XK_h6%3dxkuRBdd&qvG1ZE`}IE3y1=DKWw|e5_Xl(? z#Jnmvcx0;XJcV88JxM~Ko0g};_(IYt2(iz8V;zKO_k;u+vWrJm(XrD>f5>`5OsUJl zW*xS}_gUjMB|<-Lu>Ay;S8(eZD_`)5 z!CzXaN-vh=&v_p=ROOiegzLsZO+r3t27!&H_I1dRC-)(MsK65jr2CuWqj^fa-oGBR zwc-DOj#4ao7P$ynDiXSy$U}6dE^-oiFBVN+HwbxWC=hpqSKY-M^$}^bZ%-#ccBnBQ zV(jKr*`p86FE6UF_gB@UV5JFTfJEGX+52m`8m5ep={egxb?Qy8cWQ^`^(EW-ex~Sy zgrI7^BfV!Ox`eX=PKqeK>ZjQ#If#}<&TTHR9&PGc$b4VDHf=|;blje4U-3go4F1+% zEElh~uA)VXv+$-e)I3+3lRiI_Z_{m_)}SgJF|)X=pHOqQ8Shl#(W@6xW8SKzqE2#$ zAM;%gUBv|a+6TPc(M6K*&}gdq(z)x~Wtgy7@Yi~g_q2)5gWr{HCVzTgE%trvBRBTB zdh_?9=ogu-8)nUTowc~>Xzr+^9o>BL8XI9lD{Je6ZIrSKZlv~&Y?@r>#Ik2dbDJ^b zz|a8E1>n~>ke8K+pAgN;v2QH+gx|o3v(AY+hZDuth2GP*-kDE~y=QyZXN$8^r6CSw zD^TsJvbv37`Jgz?!zSpy*0CtoMEG(}{4JRrH# zK!L%5K1SWTS19smTiE@@3T|Z2{a;|7ESv8bg$Tja=tu+;X08zuPEJlun$~fGTXI@t2>h1E%o6Gu zP|=GMg*-vJU84nsw$+Z_?`w*gKXOuaW-q_{-=Y9k9zo3rK=gIH;Y-(Ri!i-tzugDG zyM8!;OwG^?a%x{NVxQ)^s}Fci4?t=WoWG60!@K15U+wzbi;pO~hU<^66@}5bf zR>j%yM)cMA@iMm)5yb1N038f>Qm);*0rG1&x0L}Tj28d4+1-c}CN)*z3At{)c{)}M zeHdFGUhix->p5cRbK>TGbp!WQMy_8Yns27xPrjL027$O977qM?Yu;Ej!swbm*nIiQ zSpVx^&uuqd?x}}givesjI9&^Ly{}9d!Z}ad zjw3>F^U&UL-d&_8h#+fxr52@5|6jpi6$HyQOT<9$t7H0eLpj+y;De1IPfm6zFN4ci zQ_~{)Bb%Y>_&|8C3`KW{tL+gi?Gz?AU)AQ6ti9dVO^b?7 zJ%C66*x3E9?7lB8+on4R_y1`hGJhOgz%F7eG(*SU|~EC8_lGnSmE&SO`Z;!7i2!$*f&-#dAc zId|T0z6}Y=ab%hQ^+V}Y`VZ7iG^YiajIHLtR?7(&ozJOP+40A3C-_YFs^GL+RotYbMQc(W9#K75rv8zJjT$GDj(;OK)>(ZYmbV z1i2Dab*t($a1l82(87ikA04sRwB<@GNmOjyM-u;$b3UQ>IeN(|B@ICxgs#%{Kzki( zFQ+r$3n4-u0FyfU%@pUzloN|b#cw&oqiHJ{|NZAVJj9jrtun``EVhTu_DbA?ZJL=} z>0*fh;k5pX*Mp)%{Uh!swv!zW1uka?>-H#nfh;;STOYt>$zO90j%u}vKHY$sXAfgS zB=)IpqpqylGh$ky852fbOn(V-qCap^AB~aDC_CcyUb{jXjn@RzrV}j(sJ%hyQ(ecwYKtWu^_(p;K+iSEuD*2QI3Qu z!ARIIAF{$TQI);tO<%XL(dzbrDscb=+>sR2*}AN*_80OORXo)4XQR1Hjg01bv8rAI zYr5e-a5lf+1a|O+mo7Q|>%jAd z4&Yrk2^IX`8m_abYkl7ARoA(TiN3AoD-D^E_O@fasQc5;g2R5}CK(dv-6ij>vYu5Z zt`hgZdm|B8MIT;i?qjL1Kd?+iQz0)WA%@H?j z!+E052e8CYe5CvZ*3F)1th*nN+*|v-w|0k-i6IY$+#)Md-zYzNk#cd;I3k=cDQky5 zJe)Hphh(HS1FXwWr3DBAyzXnDIjk=}Ao=J7d4;O_8ykB^$H(ort@@{wWciECCJZ53 zTGBTC1$uXrVX*6Qc{8MpffL@#%zIdN<9%4?`r+!)|A(-j_&s=5Lutlt+1~seVnDY` zxI9jG%mdx~8CF$MzSNxkY?#mN+8bB~%VhXZ*I5R-so+%9oNpQIGW-=UlOo;2Cy^cH-bbJH6aW#z1^4szN{ z5B@aV4KnU13)PT72_AKx#Pl4di$8aI@Zs~s3)>y8-#M@Ys=nZ7q4(l4r3NFvrInNw zj)hKVgje28og9hww@(yysC|dbE#ge#7e3m`xmLL`bemE8-#rj8%Y8vK&CJZqcu}5O zmgCr1wC}g-Tf=3Ih0^t?Lpf8~jazV}d68wEs8dv?_cYsYwbdeCmX_mPRfAObh*h;h z*EI#5sX7TA@RXmEUji^ux~=HtuBc4V1y#1pTxN%7dLy&)bvCU>yA-YUyyN%9MP*P$ z$+m=}N(zT-w(yUIcrZ*nJ^Fvj56rhi%oz#Sy#YNI zr9%Bw=feiD;4miO{ZM^hB^_Acf`mf%fwjGIti>k1_i5^4 z5+Gf1*0g}z8$KJp6* zPZXS?M)NwF+uDpRMO#9g)jUrnclR>r5X15Hbt)ve?`C;vUOsN>HcI!?lgpCu)BECb zxcE_a(9xX4Y1c9oHX`0V{x1C7X-I+O^%grjkQ%zeb$cdKaNOOczN!J?B-m{+Q0P+p z6BROk&b8&avX%-35ZhNFc(f@gkILLo4f{=-hXJ}fO~%f(#qwZLLI~wx1(C(eM$d~F zKaP~OJE4LpfD#^9icDAF0SF(v=~n$*R6gsd+vriv9367d6=1u=)vSg}V04IG=g{e- z%oOmme>T^q6sR87*IzI?on3eIS_B}Av3xoU^B)oU4ba|n|JQG^KrKV!x^MdOkp1Zt-`mL=AjwvBGX%l3R9X$PsQi&uL9_7icbf#m1JCHLSZqwRo|)ZS`uH?8w#PtVjP zj{w(z-C+>K+7)ot>%<2zb{FIE{;{A^z*sGYhuv*wM`)jE^+>^s+~-_XXNn3pLpS`_ zcl#4I!dT;+@K#b`kuGMOu>2Eb{4I#BrFmCqkz9pduJZV&<3xTMaGJ{HUGn-PgwF`L zHObS{B`!%9OvEHlwU73FJ)d6x0tnXN>a&)Qyo=zm6V_7X;pTl? z0i^8IOc1cFY*mogcP%Mt8@YeMUw@l-IrMyyIc1+QdMn+0{9ns$WsIBY%9www=2Z!b zWg`tucB-&h;_xh2!5GcV6!H4xi%AQDcKZNrf*glp(Huv2EWV@4vsD^dX0s;$wL}iO7|c9xh(_VDC|Ch&93K5g6HGG zTTa$TSvDC5gomReC#<1CTwe7m>e+NlCo!ya5MMRXS*ASR>I8m=THC&*>-;wSs`&71Z&ybqz!VD^5o7sIPf=?f6CerWZ+e*fOYBurH0w; z+k(rn(a-I@8w(7r^UsKNMzg}yW}1OOdYzBV&T_j^(GW&gZ@2Mlw(*m~hq+{QW$Dl~ z<**1nRPF)V#Ti{OJI1+JvUwfsMD`zdra(#04%-6qfnw@qcj*i#8)A3*ya3)mbB;`s zU5uWfhTT)BQ7&Iyv36rHJL{1ag?NsxlC*~ZRlj5mP&SX*`=dK zsLx|p#7v{!zUE?#{qP;DVY=tGy%U1*Et2UxJ3LnJpo_niK)OLK4h3=R%Hm|dJZ=}O z)7Adr!6^*GlT;l9(#fv8o}-2a78P2vP`bRz+#S;N2>?elLjR{ik_gR@%rN7Aby72n4nD5tK_GiJm&%Z z8?w6lp>=tHmmO7=Feg0_-a=22_gj81>nzP8mR9rjhGg^&4yOn5&}AB1kUE;3X3uai zf)jvD(1Z}ZU7_bGyos4fEa-H0;CZ3C#ElPO`8v=;ly1n9d{^MvO7vx9wdt(<-)8mS zN>Pm~;jXPvKaQ=wzKtlIf2~CIFU=I>+M5q+58!Xo!DP&SavTol3FQ^(h+Zxqa)wiy z_X?#w;{yEW<F;9H-4(do2*YxJ(yWG- z@Nx0h26G)?L>_W4F-2QNJ$wD`(C7rDCU)j0vC?B+IAPZxcL%Su^+vzRR_$-cIMd!sFo`H3XL8nvllq#G`6(e9(ov;kz;F zJW+4siEZ)+NLQQ7G1FBWFX#SDyKZEtu3xq`%cR48Wj#^(#d1aptYKpxh_ryL?3x=u z9~zHt9QNiIXYQUb+lj7D18SKmrJ?+Ik(_+X-OgYShCJYStRV)tF>?{e+}UZaveQpK z_nYvFr;p-VZ&0TWL3w%Ivx0{7QQ`QV*O|cE<5_8(+v93j0(XoYb`q(Bl(*zG4~qGp9LqLX zg|_1_kRQR$ht(?GS<>$)tSi5jB~=9X@!#Zm2p+L(%WX{UPq{;59dI}MIo13#UZb0W zoN7D#_myc;q1|Foi$GXxYgq%AN;CFHYy&@`VZR*U4XQ&E3mNXW#&`EU)r5vLntV`2 zbGjHOI>gxW=vV08HoI}UIZ+CJa6WmBSpN%y+|;mS@%)T?7+Z) zDNn;r^`PuK{}Xvjtt+n%^jFxs6xhbb23+@Yql|!`m|Si)S3_G@)U`uNNvYj%P#2>` z_kIFQG3Q_+1O=n~^uTVRdpI*A<}J_m=4Q|8KKecpI&uK11H2+vM(J%uYX@{#32s;( zeapka$q8S!|GPu7u*-HCWq2QlU~u(^Vd0X72D_aQ)LdhVT-FxTLnKq`#FPPwM*p!! z`qOC@BVO?Z<>E1(w30ujVzkU~P22j)zOV-sm3)=f)O^9tvgkr#mBEi6yqQ;*f^};2 z`q|HC6ciLx^G1h1YS3wUJf@FYVE>F7pQfj&=qg3#?2hYsZ*6Yk>3#NVA{Vy|U##v7 zr)PbDI0&M<=U)Y4JKCU?m5rj~naj3fctz z(v{QJP5H&R>P~4NE~@>2ihHNV8T)*#GLr`S9WVbd7^WR5C|RwcT13c!Gkq38T@<^{EMOqnBzr#duoUqt`_9 zc>4IqEoNH^Yt&=)^&%Br2eW_T`<%dyt@V7gM!y&YY#k3wbXJ>q>V`9*LufRbQ1m=v z)$JIA0_#Re@jRxLm6Z(*;v0PEoY8oViMmb2Lr1Sd{GG1DNhM?cyF#_MQzmFQG%Cs! zA4^I}OH&`YX#sC|;#mMn0q!;BS5}VgOFavFIzeNjX|i4zpS?xVm$#L27qk{ zrc?Frk-TKVLJL?)6P%r$ZFW%qHN0it!_o0zG*4|bPKH*4jl4Xgb3^{@O&|VZq74A(y4I zhEucJ8}EQbaF5A|M<9{Nkr8E>F5Hd=v$TaAt3ihM`}gmhmT;>VmJJ=Zad5m(mg63m zb>=~=zdnq7tW)c_euiq~kC!14Fv8O#I&RTkfu~%RqJitbnMMyq;8DHe>D(_~#9o^4 zfWff*d@J{8aiY^9As(J#!;s*eJaeX%PWAi(U0OSI z_h}}2!Ty?PgOEy)B@(yZ-*ko?>kH?c=g~~hq_Fp8{k9n72*jvHgro0aIBNYt+<)z> z=Aex-&kIYf!H(5=qWydXHI1%%ooB_|3j4~ruIWY&JjtUG(H|Fo0uR{&fdt(4ehp}5 z4V&)|))jz2$ECf)dcYgO(g-}zXR+W1VE2PH2%3eJ6&nOXPu97h{0ZqBOo7|UE=UIq z5Syy5?(FF3As8Z+)0fDPM|}%a3|y@SE+eyK_+BoI6=<;?=VO7YKP`MkooQO`_Uu>C zA8k!BBQZd)Y;3k${Rv|fpFrkn3VXi7pMyTPw7hwiaoPJ3&UPF|2WGLl1!@W+W7QrX z6}@vI{LJ|AePR$u-A=6t1_R&u>irS!ibiTUJ6Hbb2Z4SFI+z$48Oh6szqpJCXN*)> zj+?-t)|WB~&LGfJc6Qke#mQq+bT5ORv~*9BphKq72?$hezZ|sccFOtYO2XT_aZx}} zP;fZU0}%8D-rc(*a3v}HXAp4;KlzlGGu3vC%a|aUILg?Bgak*Sf~&V{x1MEq%*|EV z(k~~TfgnS{oOaD#2cAYjj9ML&LjAgVWRXwsQ@}J+ZQ?S0K=c%^7-&iHYe5 zH5aFtFdu4%?u{^g616K*R8YYFAXqGY5TE)*$R-OB=+&w!aCzJ7;uO#;KQC{+$I0^; lDg62tY5@ou6EsiaCMAN!E;xV!uGoVl#pK==5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/lib/psr/http-message/src/MessageInterface.php b/lib/psr/http-message/src/MessageInterface.php new file mode 100644 index 000000000..dd46e5ec8 --- /dev/null +++ b/lib/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/lib/psr/http-message/src/RequestInterface.php b/lib/psr/http-message/src/RequestInterface.php new file mode 100644 index 000000000..a96d4fd63 --- /dev/null +++ b/lib/psr/http-message/src/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name); +} diff --git a/lib/psr/http-message/src/StreamInterface.php b/lib/psr/http-message/src/StreamInterface.php new file mode 100644 index 000000000..f68f39126 --- /dev/null +++ b/lib/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} From 644e1ac4f6ff103389a2dcec6e2d2d1e8399f2fc Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Fri, 13 May 2022 16:27:35 +0200 Subject: [PATCH 10/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=202.7=20migration=20(wip)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bin/generate-deps-for-config-factory.bat | 4 ++++ lib/bin/generate-factory-for-class.bat | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 lib/bin/generate-deps-for-config-factory.bat create mode 100644 lib/bin/generate-factory-for-class.bat diff --git a/lib/bin/generate-deps-for-config-factory.bat b/lib/bin/generate-deps-for-config-factory.bat new file mode 100644 index 000000000..3eae0eb2c --- /dev/null +++ b/lib/bin/generate-deps-for-config-factory.bat @@ -0,0 +1,4 @@ +@ECHO OFF +setlocal DISABLEDELAYEDEXPANSION +SET BIN_TARGET=%~dp0/../laminas/laminas-servicemanager/bin/generate-deps-for-config-factory +php "%BIN_TARGET%" %* diff --git a/lib/bin/generate-factory-for-class.bat b/lib/bin/generate-factory-for-class.bat new file mode 100644 index 000000000..97f2fa32a --- /dev/null +++ b/lib/bin/generate-factory-for-class.bat @@ -0,0 +1,4 @@ +@ECHO OFF +setlocal DISABLEDELAYEDEXPANSION +SET BIN_TARGET=%~dp0/../laminas/laminas-servicemanager/bin/generate-factory-for-class +php "%BIN_TARGET%" %* From eb1d56f43942b3f49275005073919a701f13d109 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Mon, 16 May 2022 14:51:12 +0200 Subject: [PATCH 11/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=202.7=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/config.class.inc.php | 40 ++ core/email.class.inc.php | 459 ++---------------- lib/composer/ClassLoader.php | 2 +- lib/composer/autoload_classmap.php | 7 +- lib/composer/autoload_real.php | 9 +- lib/composer/autoload_static.php | 7 +- setup/email.test.php | 25 +- .../Controller/OAuth/OAuthAjaxController.php | 13 +- .../OAuth/OAuthWizardController.php | 21 +- .../OAuth/IOAuthClientResultDisplay.php | 2 +- .../Client/Smtp/SmtpOAuthLogin.php | 4 +- sources/Core/Email/EmailFactory.php | 14 + sources/Core/Email/EmailLaminas.php | 344 ++++++------- sources/Core/Email/EmailSwiftMailer.php | 15 +- .../TwigBase/Controller/Controller.php | 8 +- .../application/TwigBase/Twig/TwigHelper.php | 6 +- .../backoffice/oauth/DisplayConfig.html.twig | 2 +- 17 files changed, 309 insertions(+), 669 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index af1e05c36..7c3743388 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -555,6 +555,46 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'email_transport_smtp.oauth.provider' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], + 'email_transport_smtp.oauth.client_id' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], + 'email_transport_smtp.oauth.client_secret' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], + 'email_transport_smtp.oauth.access_token' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], + 'email_transport_smtp.oauth.refresh_token' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], 'email_css' => array( 'type' => 'string', 'description' => 'CSS that will override the standard stylesheet used for the notifications', diff --git a/core/email.class.inc.php b/core/email.class.inc.php index c303bf029..ee8a8dd41 100644 --- a/core/email.class.inc.php +++ b/core/email.class.inc.php @@ -24,11 +24,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ -use Pelago\Emogrifier\CssInliner; -use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; -use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; - -Swift_Preferences::getInstance()->setCharset('UTF-8'); +use Combodo\iTop\Core\Email\EmailFactory; define ('EMAIL_SEND_OK', 0); @@ -37,29 +33,16 @@ define ('EMAIL_SEND_ERROR', 2); class EMail { + protected $oMailer; + // Serialization formats const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. - // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string + // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) - - protected static $m_oConfig = null; - protected $m_aData; // For storing data to serialize - - public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE) - { - if (is_null(self::$m_oConfig)) - { - self::$m_oConfig = new Config($sConfigFile); - } - } - - protected $m_oMessage; public function __construct() { - $this->m_aData = array(); - $this->m_oMessage = new Swift_Message(); - $this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label')); + $this->oMailer = EmailFactory::GetMailer(); } /** @@ -70,481 +53,97 @@ class EMail */ public function SerializeV2() { - return serialize($this->m_aData); + return $this->oMailer->SerializeV2(); } - + /** * Custom de-serialization method + * * @param string $sSerializedMessage The serialized representation of the message + * + * @return \Email + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException */ static public function UnSerializeV2($sSerializedMessage) { - $aData = unserialize($sSerializedMessage); - $oMessage = new Email(); - - if (array_key_exists('body', $aData)) - { - $oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']); - } - if (array_key_exists('message_id', $aData)) - { - $oMessage->SetMessageId($aData['message_id']); - } - if (array_key_exists('bcc', $aData)) - { - $oMessage->SetRecipientBCC($aData['bcc']); - } - if (array_key_exists('cc', $aData)) - { - $oMessage->SetRecipientCC($aData['cc']); - } - if (array_key_exists('from', $aData)) - { - $oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']); - } - if (array_key_exists('reply_to', $aData)) - { - $oMessage->SetRecipientReplyTo($aData['reply_to']); - } - if (array_key_exists('to', $aData)) - { - $oMessage->SetRecipientTO($aData['to']); - } - if (array_key_exists('subject', $aData)) - { - $oMessage->SetSubject($aData['subject']); - } - - - if (array_key_exists('headers', $aData)) - { - foreach($aData['headers'] as $sKey => $sValue) - { - $oMessage->AddToHeader($sKey, $sValue); - } - } - if (array_key_exists('parts', $aData)) - { - foreach($aData['parts'] as $aPart) - { - $oMessage->AddPart($aPart['text'], $aPart['mimeType']); - } - } - if (array_key_exists('attachments', $aData)) - { - foreach($aData['attachments'] as $aAttachment) - { - $oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']); - } - } - return $oMessage; - } - - protected function SendAsynchronous(&$aIssues, $oLog = null) - { - try - { - AsyncSendEmail::AddToQueue($this, $oLog); - } - catch(Exception $e) - { - $aIssues = array($e->GetMessage()); - return EMAIL_SEND_ERROR; - } - $aIssues = array(); - return EMAIL_SEND_PENDING; - } - - protected function SendSynchronous(&$aIssues, $oLog = null) - { - // If the body of the message is in HTML, embed all images based on attachments - $this->EmbedInlineImages(); - - $this->LoadConfig(); - - $sTransport = self::$m_oConfig->Get('email_transport'); - switch ($sTransport) - { - case 'SMTP': - $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); - $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); - $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); - $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); - $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); - - $oTransport = new Swift_SmtpTransport($sHost, $sPort, $sEncryption); - if (strlen($sUserName) > 0) - { - $oTransport->setUsername($sUserName); - $oTransport->setPassword($sPassword); - } - break; - - case 'Null': - $oTransport = new Swift_NullTransport(); - break; - - case 'LogFile': - $oTransport = new Swift_LogFileTransport(); - $oTransport->setLogFile(APPROOT.'log/mail.log'); - break; - - case 'PHPMail': - default: - $oTransport = new Swift_SendmailTransport(); - } - - $oMailer = new Swift_Mailer($oTransport); - - $aFailedRecipients = array(); - $this->m_oMessage->setMaxLineLength(0); - $oKPI = new ExecutionKPI(); - try - { - $iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients); - if ($iSent === 0) - { - // Beware: it seems that $aFailedRecipients sometimes contains the recipients that actually received the message !!! - IssueLog::Warning('Email sending failed: Some recipients were invalid, aFailedRecipients contains: '.implode(', ', $aFailedRecipients)); - $aIssues = array('Some recipients were invalid.'); - $oKPI->ComputeStats('Email Sent', 'Error received'); - return EMAIL_SEND_ERROR; - } - else - { - $aIssues = array(); - $oKPI->ComputeStats('Email Sent', 'Succeded'); - return EMAIL_SEND_OK; - } - } - catch (Exception $e) - { - $oKPI->ComputeStats('Email Sent', 'Error received'); - throw $e; - } - } - - /** - * Reprocess the body of the message (if it is an HTML message) - * to replace the URL of images based on attachments by a link - * to an embedded image (i.e. cid:....) - */ - protected function EmbedInlineImages() - { - if ($this->m_aData['body']['mimeType'] == 'text/html') - { - $oDOMDoc = new DOMDocument(); - $oDOMDoc->preserveWhitespace = true; - @$oDOMDoc->loadHTML(''.$this->m_aData['body']['body']); // For loading HTML chunks where the character set is not specified - - $oXPath = new DOMXPath($oDOMDoc); - $sXPath = '//img[@'.InlineImage::DOM_ATTR_ID.']'; - $oImagesList = $oXPath->query($sXPath); - - if ($oImagesList->length != 0) - { - foreach($oImagesList as $oImg) - { - $iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID); - $oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */); - if ($oAttachment) - { - $sImageSecret = $oImg->getAttribute('data-img-secret'); - $sAttachmentSecret = $oAttachment->Get('secret'); - if ($sImageSecret !== $sAttachmentSecret) - { - // @see N°1921 - // If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret - continue; - } - - $oDoc = $oAttachment->Get('contents'); - $oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType()); - $sCid = $this->m_oMessage->embed($oSwiftImage); - $oImg->setAttribute('src', $sCid); - } - } - } - $sHtmlBody = $oDOMDoc->saveHTML(); - $this->m_oMessage->setBody($sHtmlBody, 'text/html', 'UTF-8'); - } + return EmailFactory::GetMailer()::UnSerializeV2($sSerializedMessage); } public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null) { - //select a default sender if none is provided. - if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){ - $this->SetRecipientFrom($this->m_aData['to']); - } - - if ($bForceSynchronous) - { - return $this->SendSynchronous($aIssues, $oLog); - } - else - { - $bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous'); - if ($bConfigASYNC) - { - return $this->SendAsynchronous($aIssues, $oLog); - } - else - { - return $this->SendSynchronous($aIssues, $oLog); - } - } + return $this->oMailer->Send($aIssues, $bForceSynchronous, $oLog); } public function AddToHeader($sKey, $sValue) { - if (!array_key_exists('headers', $this->m_aData)) - { - $this->m_aData['headers'] = array(); - } - $this->m_aData['headers'][$sKey] = $sValue; - - if (strlen($sValue) > 0) - { - $oHeaders = $this->m_oMessage->getHeaders(); - switch(strtolower($sKey)) - { - case 'return-path': - $this->m_oMessage->setReturnPath($sValue); - break; - - default: - $oHeaders->addTextHeader($sKey, $sValue); - } - } + $this->oMailer->AddToHeader($sKey, $sValue); } public function SetMessageId($sId) { - $this->m_aData['message_id'] = $sId; - - // Note: Swift will add the angle brackets for you - // so let's remove the angle brackets if present, for historical reasons - $sId = str_replace(array('<', '>'), '', $sId); - - $oMsgId = $this->m_oMessage->getHeaders()->get('Message-ID'); - $oMsgId->SetId($sId); + $this->oMailer->SetMessageId($sId); } public function SetReferences($sReferences) { - $this->AddToHeader('References', $sReferences); + $this->oMailer->SetReferences($sReferences); } public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null) { - if (($sMimeType === 'text/html') && ($sCustomStyles !== null)) - { - $oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument(); - HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone(); - $sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present - } - $this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType); - $this->m_oMessage->setBody($sBody, $sMimeType); + $this->oMailer->SetBody($sBody, $sMimeType, $sCustomStyles); } public function AddPart($sText, $sMimeType = 'text/html') { - if (!array_key_exists('parts', $this->m_aData)) - { - $this->m_aData['parts'] = array(); - } - $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType); - $this->m_oMessage->addPart($sText, $sMimeType); + $this->oMailer->AddPart($sText, $sMimeType); } public function AddAttachment($data, $sFileName, $sMimeType) { - if (!array_key_exists('attachments', $this->m_aData)) - { - $this->m_aData['attachments'] = array(); - } - $this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType); - $this->m_oMessage->attach(new Swift_Attachment($data, $sFileName, $sMimeType)); + $this->oMailer->AddAttachment($data, $sFileName, $sMimeType); } public function SetSubject($sSubject) { - $this->m_aData['subject'] = $sSubject; - $this->m_oMessage->setSubject($sSubject); + $this->oMailer->SetSubject($sSubject); } public function GetSubject() { - return $this->m_oMessage->getSubject(); + return $this->oMailer->GetSubject(); } - /** - * Helper to transform and sanitize addresses - * - get rid of empty addresses - */ - protected function AddressStringToArray($sAddressCSVList) - { - $aAddresses = array(); - foreach(explode(',', $sAddressCSVList) as $sAddress) - { - $sAddress = trim($sAddress); - if (strlen($sAddress) > 0) - { - $aAddresses[] = $sAddress; - } - } - return $aAddresses; - } - public function SetRecipientTO($sAddress) { - $this->m_aData['to'] = $sAddress; - if (!empty($sAddress)) - { - $aAddresses = $this->AddressStringToArray($sAddress); - $this->m_oMessage->setTo($aAddresses); - } + $this->oMailer->SetRecipientTO($sAddress); } public function GetRecipientTO($bAsString = false) { - $aRes = $this->m_oMessage->getTo(); - if ($aRes === null) - { - // There is no "To" header field - $aRes = array(); - } - if ($bAsString) - { - $aStrings = array(); - foreach ($aRes as $sEmail => $sName) - { - if (is_null($sName)) - { - $aStrings[] = $sEmail; - } - else - { - $sName = str_replace(array('<', '>'), '', $sName); - $aStrings[] = "$sName <$sEmail>"; - } - } - return implode(', ', $aStrings); - } - else - { - return $aRes; - } + return $this->oMailer->GetRecipientTO($bAsString); } public function SetRecipientCC($sAddress) { - $this->m_aData['cc'] = $sAddress; - if (!empty($sAddress)) - { - $aAddresses = $this->AddressStringToArray($sAddress); - $this->m_oMessage->setCc($aAddresses); - } + $this->oMailer->SetRecipientCC($sAddress); } public function SetRecipientBCC($sAddress) { - $this->m_aData['bcc'] = $sAddress; - if (!empty($sAddress)) - { - $aAddresses = $this->AddressStringToArray($sAddress); - $this->m_oMessage->setBcc($aAddresses); - } + $this->oMailer->SetRecipientBCC($sAddress); } public function SetRecipientFrom($sAddress, $sLabel = '') { - $this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel); - if ($sLabel != '') - { - $this->m_oMessage->setFrom(array($sAddress => $sLabel)); - } - else if (!empty($sAddress)) - { - $this->m_oMessage->setFrom($sAddress); - } + $this->oMailer->SetRecipientFrom($sAddress, $sLabel); } public function SetRecipientReplyTo($sAddress) { - $this->m_aData['reply_to'] = $sAddress; - if (!empty($sAddress)) - { - $this->m_oMessage->setReplyTo($sAddress); - } + $this->oMailer->SetRecipientReplyTo($sAddress); } -} - -///////////////////////////////////////////////////////////////////////////////////// - -/** - * Extension to SwiftMailer: "debug" transport that pretends messages have been sent, - * but just log them to a file. - * - * @package Swift - * @author Denis Flaven - */ -class Swift_Transport_LogFileTransport extends Swift_Transport_NullTransport -{ - protected $sLogFile; - - /** - * @inheritDoc - */ - public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) - { - $hFile = @fopen($this->sLogFile, 'a'); - if ($hFile) { - $sTxt = "================== ".date('Y-m-d H:i:s')." ==================\n"; - $sTxt .= $message->toString()."\n"; - - @fwrite($hFile, $sTxt); - @fclose($hFile); - } - - return parent::send($message, $failedRecipients); - } - - public function setLogFile($sFilename) - { - $this->sLogFile = $sFilename; - } -} - -/** - * Pretends messages have been sent, but just log them to a file. - * - * @package Swift - * @author Denis Flaven - */ -class Swift_LogFileTransport extends Swift_Transport_LogFileTransport -{ - /** - * Create a new LogFileTransport. - */ - public function __construct(Swift_Events_EventDispatcher $eventDispatcher) - { - parent::__construct($eventDispatcher); - call_user_func_array( - array($this, 'Swift_Transport_LogFileTransport::__construct'), - Swift_DependencyContainer::getInstance() - ->createDependenciesFor('transport.null') - ); - } - - /** - * Create a new LogFileTransport instance. - * - * @return Swift_LogFileTransport - */ - public static function newInstance() - { - return new self(); - } } \ No newline at end of file diff --git a/lib/composer/ClassLoader.php b/lib/composer/ClassLoader.php index afef3fa2a..0cd6055d1 100644 --- a/lib/composer/ClassLoader.php +++ b/lib/composer/ClassLoader.php @@ -149,7 +149,7 @@ class ClassLoader /** * @return string[] Array of classname => path - * @psalm-return array + * @psalm-var array */ public function getClassMap() { diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index cbc92b05b..598a39495 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -160,6 +160,7 @@ return array( 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientResultDisplayConf' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php', + 'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php', 'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php', @@ -246,6 +247,7 @@ return array( 'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', 'Doctrine\\Common\\Lexer\\AbstractLexer' => $vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php', 'EMail' => $baseDir . '/core/email.class.inc.php', + 'EMailLaminas' => $baseDir . '/sources/Core/Email/EmailLaminas.php', 'Egulias\\EmailValidator\\EmailLexer' => $vendorDir . '/egulias/email-validator/src/EmailLexer.php', 'Egulias\\EmailValidator\\EmailParser' => $vendorDir . '/egulias/email-validator/src/EmailParser.php', 'Egulias\\EmailValidator\\EmailValidator' => $vendorDir . '/egulias/email-validator/src/EmailValidator.php', @@ -311,7 +313,6 @@ return array( 'Egulias\\EmailValidator\\Warning\\QuotedString' => $vendorDir . '/egulias/email-validator/src/Warning/QuotedString.php', 'Egulias\\EmailValidator\\Warning\\TLD' => $vendorDir . '/egulias/email-validator/src/Warning/TLD.php', 'Egulias\\EmailValidator\\Warning\\Warning' => $vendorDir . '/egulias/email-validator/src/Warning/Warning.php', - 'EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php', 'EmailSwiftMailer' => $baseDir . '/sources/Core/Email/EmailSwiftMailer.php', 'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php', 'ErrorPage' => $baseDir . '/application/errorpage.class.inc.php', @@ -1265,8 +1266,8 @@ return array( 'StimulusInternal' => $baseDir . '/core/stimulus.class.inc.php', 'StimulusUserAction' => $baseDir . '/core/stimulus.class.inc.php', 'Str' => $baseDir . '/core/MyHelpers.class.inc.php', - 'Swift_LogFileTransport' => $baseDir . '/core/email.class.inc.php', - 'Swift_Transport_LogFileTransport' => $baseDir . '/core/email.class.inc.php', + 'Swift_LogFileTransport' => $baseDir . '/sources/Core/Email/EmailSwiftMailer.php', + 'Swift_Transport_LogFileTransport' => $baseDir . '/sources/Core/Email/EmailSwiftMailer.php', 'Symfony\\Bridge\\Twig\\AppVariable' => $vendorDir . '/symfony/twig-bridge/AppVariable.php', 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => $vendorDir . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => $vendorDir . '/symfony/twig-bridge/Command/LintCommand.php', diff --git a/lib/composer/autoload_real.php b/lib/composer/autoload_real.php index 9a33b711c..661cd2543 100644 --- a/lib/composer/autoload_real.php +++ b/lib/composer/autoload_real.php @@ -60,16 +60,11 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b } } -/** - * @param string $fileIdentifier - * @param string $file - * @return void - */ function composerRequire0018331147de7601e7552f7da8e3bb8b($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } } diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 21bc64c73..94b13646f 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -528,6 +528,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientResultDisplayConf' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php', + 'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php', 'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php', @@ -614,6 +615,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', 'Doctrine\\Common\\Lexer\\AbstractLexer' => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php', 'EMail' => __DIR__ . '/../..' . '/core/email.class.inc.php', + 'EMailLaminas' => __DIR__ . '/../..' . '/sources/Core/Email/EmailLaminas.php', 'Egulias\\EmailValidator\\EmailLexer' => __DIR__ . '/..' . '/egulias/email-validator/src/EmailLexer.php', 'Egulias\\EmailValidator\\EmailParser' => __DIR__ . '/..' . '/egulias/email-validator/src/EmailParser.php', 'Egulias\\EmailValidator\\EmailValidator' => __DIR__ . '/..' . '/egulias/email-validator/src/EmailValidator.php', @@ -679,7 +681,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Egulias\\EmailValidator\\Warning\\QuotedString' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/QuotedString.php', 'Egulias\\EmailValidator\\Warning\\TLD' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/TLD.php', 'Egulias\\EmailValidator\\Warning\\Warning' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/Warning.php', - 'EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php', 'EmailSwiftMailer' => __DIR__ . '/../..' . '/sources/Core/Email/EmailSwiftMailer.php', 'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php', 'ErrorPage' => __DIR__ . '/../..' . '/application/errorpage.class.inc.php', @@ -1633,8 +1634,8 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'StimulusInternal' => __DIR__ . '/../..' . '/core/stimulus.class.inc.php', 'StimulusUserAction' => __DIR__ . '/../..' . '/core/stimulus.class.inc.php', 'Str' => __DIR__ . '/../..' . '/core/MyHelpers.class.inc.php', - 'Swift_LogFileTransport' => __DIR__ . '/../..' . '/core/email.class.inc.php', - 'Swift_Transport_LogFileTransport' => __DIR__ . '/../..' . '/core/email.class.inc.php', + 'Swift_LogFileTransport' => __DIR__ . '/../..' . '/sources/Core/Email/EmailSwiftMailer.php', + 'Swift_Transport_LogFileTransport' => __DIR__ . '/../..' . '/sources/Core/Email/EmailSwiftMailer.php', 'Symfony\\Bridge\\Twig\\AppVariable' => __DIR__ . '/..' . '/symfony/twig-bridge/AppVariable.php', 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/LintCommand.php', diff --git a/setup/email.test.php b/setup/email.test.php index 07bec1b2c..d435fccea 100644 --- a/setup/email.test.php +++ b/setup/email.test.php @@ -107,9 +107,9 @@ function CheckEmailSetting($oP) } } break; - + case 'SMTP': - $oP->info("iTop is configured to use the SMTP transport."); + $oP->info("iTop is configured to use the $sTransport transport."); $sHost = MetaModel::GetConfig()->Get('email_transport_smtp.host'); $sPort = MetaModel::GetConfig()->Get('email_transport_smtp.port'); $sEncryption = MetaModel::GetConfig()->Get('email_transport_smtp.encryption'); @@ -124,7 +124,26 @@ function CheckEmailSetting($oP) $oP->warning("The default settings may not be suitable for your environment. You may want to adjust these values by editing iTop's configuration file (".utils::GetConfigFilePathRelative().")."); } break; - + + case 'SMTP_OAuth': + $oP->info("iTop is configured to use the $sTransport transport."); + $sHost = MetaModel::GetConfig()->Get('email_transport_smtp.host'); + $sPort = MetaModel::GetConfig()->Get('email_transport_smtp.port'); + $sEncryption = MetaModel::GetConfig()->Get('email_transport_smtp.encryption'); + $sDisplayEncryption = empty($sEncryption) ? 'no encryption ' : $sEncryption; + $sUserName = MetaModel::GetConfig()->Get('email_transport_smtp.username'); + $sDisplayUserName = empty($sUserName) ? 'no user ' : $sUserName; + $sProvider = MetaModel::GetConfig()->Get('email_transport_smtp.oauth.provider'); + $sDisplayProvider = empty($sProvider) ? 'no Provider ' : $sProvider; + $sClientID = MetaModel::GetConfig()->Get('email_transport_smtp.oauth.client_id'); + $sDisplayClientID = empty($sClientID) ? 'no password ' : $sClientID; + $oP->info("SMTP configuration (from config-itop.php): host: $sHost, port: $sPort, provider: $sDisplayProvider, user: $sDisplayUserName, client id: $sDisplayClientID, encryption: $sDisplayEncryption."); + if (($sHost == 'localhost') && ($sPort == '25') && ($sUserName == '') && ($sClientID == '') && ($sProvider == '')) { + $oP->warning("The default settings may not be suitable for your environment. You may want to adjust these values by editing iTop's configuration file (".utils::GetConfigFilePathRelative().').'); + } + break; + + case 'Null': $oP->warning("iTop is configured to use the Null transport: emails sending will have no effect."); $bRet = false; diff --git a/sources/Controller/OAuth/OAuthAjaxController.php b/sources/Controller/OAuth/OAuthAjaxController.php index 8788c0d16..155cdd9bc 100644 --- a/sources/Controller/OAuth/OAuthAjaxController.php +++ b/sources/Controller/OAuth/OAuthAjaxController.php @@ -3,7 +3,6 @@ namespace Combodo\iTop\Controller\OAuth; use Combodo\iTop\Application\TwigBase\Controller\Controller; -use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderAbstract; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory; use utils; @@ -36,13 +35,17 @@ class OAuthAjaxController extends Controller $sAdditional = utils::ReadParam('additional', '', false, 'raw'); $sRedirectUrlQuery = parse_url($sRedirectUrl)['query']; - // TODO: Needs to handle mail to ticket part too - $aOAuthResultDisplayClasses = ['\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientResultDisplayConf']; + + $aOAuthResultDisplayClasses[] = '\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientResultDisplayConf'; + if (class_exists('Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox')) { + $aOAuthResultDisplayClasses[] = 'Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox'; + } + $aAdditional = []; parse_str($sAdditional, $aAdditional); - $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProvider; - $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); + // $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProvider; + // $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); $aQuery = []; parse_str($sRedirectUrlQuery, $aQuery); diff --git a/sources/Controller/OAuth/OAuthWizardController.php b/sources/Controller/OAuth/OAuthWizardController.php index 13866ca0a..25265ea6b 100644 --- a/sources/Controller/OAuth/OAuthWizardController.php +++ b/sources/Controller/OAuth/OAuthWizardController.php @@ -9,11 +9,25 @@ namespace Combodo\iTop\Controller\OAuth; use Combodo\iTop\Application\TwigBase\Controller\Controller; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderAbstract; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientResultDisplayConf; +use Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox; use Dict; use utils; class OAuthWizardController extends Controller { + public function __construct($sViewPath, $sModuleName = 'core') + { + $aAdditionalPaths = []; + + // Add extensions' template path + // TODO Rewrite in 3.1 with utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', ...) + if (class_exists('Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox')) { + $aAdditionalPaths[] = utils::GetAbsoluteModulePath('combodo-oauth-email-synchro').'templates'; + } + + parent::__construct($sViewPath, $sModuleName, $aAdditionalPaths); + } + public function OperationWizard() { $aParams = []; @@ -44,9 +58,10 @@ class OAuthWizardController extends Controller ]; // TODO: Needs to handle mail to ticket part too - $aParams['aAdditionalBlocks'] = [ - OAuthClientResultDisplayConf::GetResultDisplayTemplate(), - ]; + $aParams['aAdditionalBlocks'][] = OAuthClientResultDisplayConf::GetResultDisplayTemplate(); + if (class_exists('Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox')) { + $aParams['aAdditionalBlocks'][] = OAuthClientResultDisplayMailbox::GetResultDisplayTemplate(); + } $this->DisplayPage($aParams); } diff --git a/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php b/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php index cf7701f39..e5111f38e 100644 --- a/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php +++ b/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php @@ -3,7 +3,7 @@ namespace Combodo\iTop\Core\Authentication\Client\OAuth; use League\OAuth2\Client\Token\AccessToken; interface IOAuthClientResultDisplay{ - public static function GetResultDisplayBlock(); + //public static function GetResultDisplayBlock(); public static function GetResultDisplayScript($sClientId, $sClientSecret, $sVendor, AccessToken $oAccessToken); public static function GetResultDisplayTemplate(); diff --git a/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php b/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php index e27ef7da8..1b0f7b721 100644 --- a/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php +++ b/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php @@ -90,7 +90,7 @@ class Oauth extends Login try { while (true) { - $sResponse = $this->_receive(60); + $sResponse = $this->_receive(30); IssueLog::Debug("SMTP Oauth receiving $sResponse", static::LOG_CHANNEL); @@ -100,7 +100,7 @@ class Oauth extends Login } else { if (preg_match('/Unauthorized/i', $sResponse) || preg_match('/Rejected/i', $sResponse) || - preg_match('/^(535|432|454|534|500|530|538)/', $sResponse)) { + preg_match('/^(535|432|454|534|500|530|538|334)/', $sResponse)) { IssueLog::Error('Unable to authenticate for outgoing mails for provider '.self::$oProvider::GetVendorName()." Error: $sResponse", static::LOG_CHANNEL); return false; diff --git a/sources/Core/Email/EmailFactory.php b/sources/Core/Email/EmailFactory.php index 56d84f21b..6d1354d9b 100644 --- a/sources/Core/Email/EmailFactory.php +++ b/sources/Core/Email/EmailFactory.php @@ -1,6 +1,20 @@ Get('email_transport'); + if ($sTransport == 'SMTP_OAuth') { + return EMailLaminas::GetMailer(); + } + return EmailSwiftMailer::GetMailer(); + } } \ No newline at end of file diff --git a/sources/Core/Email/EmailLaminas.php b/sources/Core/Email/EmailLaminas.php index 9ed1a1a70..f828113a8 100644 --- a/sources/Core/Email/EmailLaminas.php +++ b/sources/Core/Email/EmailLaminas.php @@ -38,24 +38,19 @@ use Pelago\Emogrifier\CssInliner; use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; -define ('EMAIL_SEND_OK', 0); -define ('EMAIL_SEND_PENDING', 1); -define ('EMAIL_SEND_ERROR', 2); - -class EMail +class EMailLaminas { // Serialization formats const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. - // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string + // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) - + protected static $m_oConfig = null; protected $m_aData; // For storing data to serialize - public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE) + public static function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE) { - if (is_null(self::$m_oConfig)) - { + if (is_null(self::$m_oConfig)) { self::$m_oConfig = new Config($sConfigFile); } } @@ -95,171 +90,160 @@ class EMail { $aData = unserialize($sSerializedMessage); $oMessage = new Email(); - - if (array_key_exists('body', $aData)) - { + + if (array_key_exists('body', $aData)) { $oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']); } - if (array_key_exists('message_id', $aData)) - { + if (array_key_exists('message_id', $aData)) { $oMessage->SetMessageId($aData['message_id']); } - if (array_key_exists('bcc', $aData)) - { + if (array_key_exists('bcc', $aData)) { $oMessage->SetRecipientBCC($aData['bcc']); } - if (array_key_exists('cc', $aData)) - { + if (array_key_exists('cc', $aData)) { $oMessage->SetRecipientCC($aData['cc']); } - if (array_key_exists('from', $aData)) - { + if (array_key_exists('from', $aData)) { $oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']); } - if (array_key_exists('reply_to', $aData)) - { + if (array_key_exists('reply_to', $aData)) { $oMessage->SetRecipientReplyTo($aData['reply_to']['address'], $aData['reply_to']['label']); } - if (array_key_exists('to', $aData)) - { + if (array_key_exists('to', $aData)) { $oMessage->SetRecipientTO($aData['to']); } - if (array_key_exists('subject', $aData)) - { + if (array_key_exists('subject', $aData)) { $oMessage->SetSubject($aData['subject']); } - - if (array_key_exists('headers', $aData)) - { - foreach($aData['headers'] as $sKey => $sValue) - { + + if (array_key_exists('headers', $aData)) { + foreach ($aData['headers'] as $sKey => $sValue) { $oMessage->AddToHeader($sKey, $sValue); } } - if (array_key_exists('parts', $aData)) - { - foreach($aData['parts'] as $aPart) - { + if (array_key_exists('parts', $aData)) { + foreach ($aData['parts'] as $aPart) { $oMessage->AddPart($aPart['text'], $aPart['mimeType']); } } - if (array_key_exists('attachments', $aData)) - { - foreach($aData['attachments'] as $aAttachment) - { + if (array_key_exists('attachments', $aData)) { + foreach ($aData['attachments'] as $aAttachment) { $oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']); } } + return $oMessage; } - - protected function SendAsynchronous(&$aIssues, $oLog = null) + + protected function SendAsynchronous(&$aIssues, $oLog = null) { - try - { + try { AsyncSendEmail::AddToQueue($this, $oLog); } - catch(Exception $e) - { + catch (Exception $e) { $aIssues = array($e->GetMessage()); + return EMAIL_SEND_ERROR; } $aIssues = array(); + return EMAIL_SEND_PENDING; } + public static function GetMailer() + { + return new EMailLaminas(); + } + /** * @throws \Exception */ protected function SendSynchronous(&$aIssues, $oLog = null) { - + $this->LoadConfig(); $sTransport = self::$m_oConfig->Get('email_transport'); - switch ($sTransport) - { - case 'SMTP': - $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); - $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); - $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); - $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); - $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); + switch ($sTransport) { + case 'SMTP': + $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); + $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); + $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); + $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); + $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); - $oTransport = new Smtp(); - $aOptions= [ - 'host' => $sHost, - 'port' => $sPort, - 'connection_class' => 'login', - 'connection_config' => [ - 'ssl' => $sEncryption, - ], - ]; - if (strlen($sUserName) > 0) - { - $aOptions['connection_config']['username'] = $sUserName; - $aOptions['connection_config']['password'] = $sPassword; - } - $oOptions = new SmtpOptions($aOptions); - $oTransport->setOptions($oOptions); - break; - case 'SMTP_OAuth': - $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); - $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); - $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); - $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); + $oTransport = new Smtp(); + $aOptions = [ + 'host' => $sHost, + 'port' => $sPort, + 'connection_class' => 'login', + 'connection_config' => [ + 'ssl' => $sEncryption, + ], + ]; + if (strlen($sUserName) > 0) { + $aOptions['connection_config']['username'] = $sUserName; + $aOptions['connection_config']['password'] = $sPassword; + } + $oOptions = new SmtpOptions($aOptions); + $oTransport->setOptions($oOptions); + break; + case 'SMTP_OAuth': + $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); + $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); + $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); + $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); - $oTransport = new Smtp(); - $aOptions= [ - 'host' => $sHost, - 'port' => $sPort, - 'connection_class' => 'Laminas\Mail\Protocol\Smtp\Auth\Oauth', - 'connection_config' => [ - 'ssl' => $sEncryption, - ], - ]; - if (strlen($sUserName) > 0) - { - $aOptions['connection_config']['username'] = $sUserName; - } - $oOptions = new SmtpOptions($aOptions); - $oTransport->setOptions($oOptions); + $oTransport = new Smtp(); + $aOptions = [ + 'host' => $sHost, + 'port' => $sPort, + 'connection_class' => 'Laminas\Mail\Protocol\Smtp\Auth\Oauth', + 'connection_config' => [ + 'ssl' => $sEncryption, + ], + ]; + if (strlen($sUserName) > 0) { + $aOptions['connection_config']['username'] = $sUserName; + } + $oOptions = new SmtpOptions($aOptions); + $oTransport->setOptions($oOptions); - \Laminas\Mail\Protocol\Smtp\Auth\Oauth::setProvider(OAuthClientProviderFactory::getProviderForSMTP()); - break; - case 'Null': - $oTransport = new Smtp(); - break; - - case 'LogFile': - $oTransport = new File(); - $aOptions = new FileOptions([ - 'path' => APPROOT.'log/mail.log', - ]); - $oTransport->setOptions($aOptions); - break; - - case 'PHPMail': - default: - $oTransport = new Smtp(); + \Laminas\Mail\Protocol\Smtp\Auth\Oauth::setProvider(OAuthClientProviderFactory::getProviderForSMTP()); + break; + case 'Null': + $oTransport = new Smtp(); + break; + + case 'LogFile': + $oTransport = new File(); + $aOptions = new FileOptions([ + 'path' => APPROOT.'log/mail.log', + ]); + $oTransport->setOptions($aOptions); + break; + + case 'PHPMail': + default: + $oTransport = new Smtp(); } - + $oKPI = new ExecutionKPI(); - try - { + try { $oTransport->send($this->m_oMessage); $aIssues = array(); $oKPI->ComputeStats('Email Sent', 'Succeded'); + return EMAIL_SEND_OK; } - catch(Laminas\Mail\Transport\Exception\RuntimeException $e){ + catch (Laminas\Mail\Transport\Exception\RuntimeException $e) { IssueLog::Warning('Email sending failed: Some recipients were invalid'); $aIssues = array('Some recipients were invalid.'); $oKPI->ComputeStats('Email Sent', 'Error received'); + return EMAIL_SEND_ERROR; } - catch (Exception $e) - { + catch (Exception $e) { $oKPI->ComputeStats('Email Sent', 'Error received'); throw $e; } @@ -287,18 +271,14 @@ class EMail $oImagesList = $oXPath->query($sXPath); $oImagesContent = new \Laminas\Mime\Message(); $aImagesParts = []; - if ($oImagesList->length != 0) - { - foreach($oImagesList as $oImg) - { + if ($oImagesList->length != 0) { + foreach ($oImagesList as $oImg) { $iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID); $oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */); - if ($oAttachment) - { + if ($oAttachment) { $sImageSecret = $oImg->getAttribute('data-img-secret'); $sAttachmentSecret = $oAttachment->Get('secret'); - if ($sImageSecret !== $sAttachmentSecret) - { + if ($sImageSecret !== $sAttachmentSecret) { // @see N°1921 // If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret continue; @@ -314,7 +294,7 @@ class EMail $oNewAttachment->filename = $oDoc->GetFileName(); $oNewAttachment->disposition = Mime::DISPOSITION_INLINE; $oNewAttachment->encoding = Mime::ENCODING_BASE64; - + $oImagesContent->addPart($oNewAttachment); $oImg->setAttribute('src', 'cid:'.$sCid); $aImagesParts[] = $oNewAttachment; @@ -322,29 +302,24 @@ class EMail } } $sBody = $oDOMDoc->saveHTML(); + return $aImagesParts; } public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null) { //select a default sender if none is provided. - if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){ + if (empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])) { $this->SetRecipientFrom($this->m_aData['to']); } - if ($bForceSynchronous) - { + if ($bForceSynchronous) { return $this->SendSynchronous($aIssues, $oLog); - } - else - { + } else { $bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous'); - if ($bConfigASYNC) - { + if ($bConfigASYNC) { return $this->SendAsynchronous($aIssues, $oLog); - } - else - { + } else { return $this->SendSynchronous($aIssues, $oLog); } } @@ -352,17 +327,14 @@ class EMail public function AddToHeader($sKey, $sValue) { - if (!array_key_exists('headers', $this->m_aData)) - { + if (!array_key_exists('headers', $this->m_aData)) { $this->m_aData['headers'] = array(); } $this->m_aData['headers'][$sKey] = $sValue; - - if (strlen($sValue) > 0) - { + + if (strlen($sValue) > 0) { $oHeaders = $this->m_oMessage->getHeaders(); - switch(strtolower($sKey)) - { + switch (strtolower($sKey)) { case 'return-path': $this->m_oMessage->setReturnPath($sValue); break; @@ -376,7 +348,7 @@ class EMail public function SetMessageId($sId) { $this->m_aData['message_id'] = $sId; - + // Note: Swift will add the angle brackets for you // so let's remove the angle brackets if present, for historical reasons $sId = str_replace(array('<', '>'), '', $sId); @@ -405,7 +377,7 @@ class EMail /** * Set current Email body and process inline images. - * + * * @param $sBody * @param string $sMimeType * @param $sCustomStyles @@ -419,7 +391,7 @@ class EMail { $oBody = new Laminas\Mime\Message(); $aAdditionalParts = []; - + if (($sMimeType === Mime::TYPE_HTML) && ($sCustomStyles !== null)) { $oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument(); HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone(); @@ -428,22 +400,22 @@ class EMail $this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType); // We don't want these modifications in m_aData['body'], otherwise it'll ruin asynchronous mail as they go through this method twice - if ($sMimeType === Mime::TYPE_HTML){ + if ($sMimeType === Mime::TYPE_HTML) { $aAdditionalParts = $this->EmbedInlineImages($sBody); - } - + } + // Add body content to as a new part $oNewPart = new Part($sBody); $oNewPart->encoding = Mime::ENCODING_8BIT; $oNewPart->type = $sMimeType; $oBody->addPart($oNewPart); - + // Add additional images as new body parts foreach ($aAdditionalParts as $oAdditionalPart) { $oBody->addPart($oAdditionalPart); } - if($oBody->isMultiPart()){ + if ($oBody->isMultiPart()) { $oContentTypeHeader = $this->m_oMessage->getHeaders(); foreach ($oContentTypeHeader as $oHeader) { if (!$oHeader instanceof ContentType) { @@ -455,12 +427,13 @@ class EMail break; } } - + $this->m_oMessage->setBody($oBody); } /** * Add a new part to the existing body + * * @param $sText * @param string $sMimeType * @@ -468,8 +441,7 @@ class EMail */ public function AddPart($sText, string $sMimeType = Mime::TYPE_HTML) { - if (!array_key_exists('parts', $this->m_aData)) - { + if (!array_key_exists('parts', $this->m_aData)) { $this->m_aData['parts'] = array(); } $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType); @@ -483,7 +455,7 @@ class EMail { $oBody = $this->m_oMessage->getBody(); - if(!$oBody->isMultiPart()){ + if (!$oBody->isMultiPart()) { $multipart_content = new Part($oBody->generateMessage()); $multipart_content->setType($oBody->getParts()[0]->getType()); $multipart_content->setBoundary($oBody->getMime()->boundary()); @@ -492,8 +464,7 @@ class EMail $oBody->addPart($multipart_content); } - if (!array_key_exists('attachments', $this->m_aData)) - { + if (!array_key_exists('attachments', $this->m_aData)) { $this->m_aData['attachments'] = array(); } $this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType); @@ -506,7 +477,7 @@ class EMail $oBody->addPart($oNewAttachment); - if($oBody->isMultiPart()){ + if ($oBody->isMultiPart()) { $oContentTypeHeader = $this->m_oMessage->getHeaders(); foreach ($oContentTypeHeader as $oHeader) { if (!$oHeader instanceof ContentType) { @@ -535,27 +506,25 @@ class EMail /** * Helper to transform and sanitize addresses - * - get rid of empty addresses - */ + * - get rid of empty addresses + */ protected function AddressStringToArray($sAddressCSVList) { $aAddresses = array(); - foreach(explode(',', $sAddressCSVList) as $sAddress) - { + foreach (explode(',', $sAddressCSVList) as $sAddress) { $sAddress = trim($sAddress); - if (strlen($sAddress) > 0) - { + if (strlen($sAddress) > 0) { $aAddresses[] = $sAddress; } } + return $aAddresses; } - + public function SetRecipientTO($sAddress) { $this->m_aData['to'] = $sAddress; - if (!empty($sAddress)) - { + if (!empty($sAddress)) { $aAddresses = $this->AddressStringToArray($sAddress); $this->m_oMessage->setTo($aAddresses); } @@ -564,32 +533,25 @@ class EMail public function GetRecipientTO($bAsString = false) { $aRes = $this->m_oMessage->getTo(); - if ($aRes === null || $aRes->count() === 0) - { + if ($aRes === null || $aRes->count() === 0) { // There is no "To" header field $aRes = array(); } - if ($bAsString) - { + if ($bAsString) { $aStrings = array(); - foreach ($aRes as $oEmail) - { + foreach ($aRes as $oEmail) { $sName = $oEmail->getName(); $sEmail = $oEmail->getEmail(); - if (is_null($sName)) - { + if (is_null($sName)) { $aStrings[] = $sEmail; - } - else - { + } else { $sName = str_replace(array('<', '>'), '', $sName); $aStrings[] = "$sName <$sEmail>"; } } + return implode(', ', $aStrings); - } - else - { + } else { return $aRes; } } @@ -597,8 +559,7 @@ class EMail public function SetRecipientCC($sAddress) { $this->m_aData['cc'] = $sAddress; - if (!empty($sAddress)) - { + if (!empty($sAddress)) { $aAddresses = $this->AddressStringToArray($sAddress); $this->m_oMessage->setCc($aAddresses); } @@ -607,8 +568,7 @@ class EMail public function SetRecipientBCC($sAddress) { $this->m_aData['bcc'] = $sAddress; - if (!empty($sAddress)) - { + if (!empty($sAddress)) { $aAddresses = $this->AddressStringToArray($sAddress); $this->m_oMessage->setBcc($aAddresses); } @@ -617,12 +577,9 @@ class EMail public function SetRecipientFrom($sAddress, $sLabel = '') { $this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel); - if ($sLabel != '') - { - $this->m_oMessage->setFrom(array($sAddress => $sLabel)); - } - else if (!empty($sAddress)) - { + if ($sLabel != '') { + $this->m_oMessage->setFrom(array($sAddress => $sLabel)); + } else if (!empty($sAddress)) { $this->m_oMessage->setFrom($sAddress); } } @@ -630,12 +587,9 @@ class EMail public function SetRecipientReplyTo($sAddress, $sLabel = '') { $this->m_aData['reply_to'] = array('address' => $sAddress, 'label' => $sLabel); - if ($sLabel != '') - { + if ($sLabel != '') { $this->m_oMessage->setReplyTo(array($sAddress => $sLabel)); - } - else if (!empty($sAddress)) - { + } else if (!empty($sAddress)) { $this->m_oMessage->setReplyTo($sAddress); } } diff --git a/sources/Core/Email/EmailSwiftMailer.php b/sources/Core/Email/EmailSwiftMailer.php index b0454a049..1fa7f6492 100644 --- a/sources/Core/Email/EmailSwiftMailer.php +++ b/sources/Core/Email/EmailSwiftMailer.php @@ -30,18 +30,8 @@ use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; Swift_Preferences::getInstance()->setCharset('UTF-8'); - -define ('EMAIL_SEND_OK', 0); -define ('EMAIL_SEND_PENDING', 1); -define ('EMAIL_SEND_ERROR', 2); - class EmailSwiftMailer { - // Serialization formats - const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. - // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string - const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) - protected static $m_oConfig = null; protected $m_aData; // For storing data to serialize @@ -155,6 +145,11 @@ class EmailSwiftMailer return EMAIL_SEND_PENDING; } + public static function GetMailer() + { + return new EmailSwiftMailer(); + } + protected function SendSynchronous(&$aIssues, $oLog = null) { // If the body of the message is in HTML, embed all images based on attachments diff --git a/sources/application/TwigBase/Controller/Controller.php b/sources/application/TwigBase/Controller/Controller.php index aea709b07..e8618699d 100644 --- a/sources/application/TwigBase/Controller/Controller.php +++ b/sources/application/TwigBase/Controller/Controller.php @@ -71,14 +71,14 @@ abstract class Controller * @param string $sViewPath Path of the twig files * @param string $sModuleName name of the module (or 'core' if not a module) */ - public function __construct($sViewPath, $sModuleName = 'core') + public function __construct($sViewPath, $sModuleName = 'core', $aAdditionalPaths = []) { $this->m_aLinkedScripts = array(); $this->m_aLinkedStylesheets = array(); $this->m_aSaas = array(); $this->m_aAjaxTabs = array(); $this->m_aDefaultParams = array(); - $this->SetViewPath($sViewPath); + $this->SetViewPath($sViewPath, $aAdditionalPaths); $this->SetModuleName($sModuleName); if ($sModuleName != 'core') { @@ -116,9 +116,9 @@ abstract class Controller * * @param string $sViewPath */ - public function SetViewPath($sViewPath) + public function SetViewPath($sViewPath, $aAdditionalPaths = []) { - $oTwig = TwigHelper::GetTwigEnvironment($sViewPath); + $oTwig = TwigHelper::GetTwigEnvironment($sViewPath, $aAdditionalPaths); $this->m_oTwig = $oTwig; } diff --git a/sources/application/TwigBase/Twig/TwigHelper.php b/sources/application/TwigBase/Twig/TwigHelper.php index 99253e701..efa3d5339 100644 --- a/sources/application/TwigBase/Twig/TwigHelper.php +++ b/sources/application/TwigBase/Twig/TwigHelper.php @@ -17,9 +17,13 @@ use WebPage; class TwigHelper { - public static function GetTwigEnvironment($sViewPath) + public static function GetTwigEnvironment($sViewPath, $aAdditionalPaths = []) { $oLoader = new Twig_Loader_Filesystem($sViewPath); + foreach ($aAdditionalPaths as $sAdditionalPath) { + $oLoader->addPath($sAdditionalPath); + } + $oTwig = new Twig_Environment($oLoader); Extension::RegisterTwigExtensions($oTwig); $sLocalPath = utils::LocalPath($sViewPath); diff --git a/templates/pages/backoffice/oauth/DisplayConfig.html.twig b/templates/pages/backoffice/oauth/DisplayConfig.html.twig index 185943da0..1fcf8b8b3 100644 --- a/templates/pages/backoffice/oauth/DisplayConfig.html.twig +++ b/templates/pages/backoffice/oauth/DisplayConfig.html.twig @@ -1,7 +1,7 @@ {# @copyright Copyright (C) 2010-2022 Combodo SARL #} {# @license http://opensource.org/licenses/AGPL-3.0 #} -
+
{{ 'UI:OAuth:Wizard:ResultConf:Panel:Title'|dict_s }}

{{ 'UI:OAuth:Wizard:ResultConf:Panel:Description'|dict_s }}

From 932ef780fd447579b6f0f7320c0d7b15ae1ff75d Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Tue, 17 May 2022 09:06:49 +0200 Subject: [PATCH 12/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=202.7=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/pages/backoffice/oauth/Wizard.html.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/pages/backoffice/oauth/Wizard.html.twig b/templates/pages/backoffice/oauth/Wizard.html.twig index c707ea6d2..0aee723b3 100644 --- a/templates/pages/backoffice/oauth/Wizard.html.twig +++ b/templates/pages/backoffice/oauth/Wizard.html.twig @@ -35,6 +35,7 @@ name="{{ sName }}" {% if aInput.read_only %} readonly {% endif %} value="{{ aInput.value }}" + size="100" > From 1d45eff9b02c4fcbba389032d1188587200f7b04 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Tue, 17 May 2022 10:11:15 +0200 Subject: [PATCH 13/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=202.7=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dictionaries/en.dictionary.itop.ui.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index 11d926e88..18420b2d8 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -1604,6 +1604,7 @@ Dict::Add('EN US', 'English', 'English', array( // OAuth Dict::Add('EN US', 'English', 'English', array( 'Menu:OAuthWizardMenu' => 'OAuth 2.0', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration', 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration', 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration', 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id', From 44eda676a379e1b1dcd304445d213d5880ad7234 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Tue, 17 May 2022 16:56:43 +0200 Subject: [PATCH 14/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=202.7=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/pages/backoffice/oauth/Wizard.ready.js.twig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/templates/pages/backoffice/oauth/Wizard.ready.js.twig b/templates/pages/backoffice/oauth/Wizard.ready.js.twig index b6ccf248a..8cc297840 100644 --- a/templates/pages/backoffice/oauth/Wizard.ready.js.twig +++ b/templates/pages/backoffice/oauth/Wizard.ready.js.twig @@ -58,6 +58,10 @@ const oOpenSignInWindow = function (url, name){ once we reach our landing page, it'll send us a reply */ oListener = window.setInterval(function(){ + if (oWindowObjectReference.closed) { + $('.ibo-oauth-wizard--form--submit').prop('disabled', false); + clearInterval(oListener); + } oWindowObjectReference.postMessage('anyone', '{{ sReturnUri }}'); }, 1000); /* Once we receive a response, transmit it to the server to get authenticate and display @@ -113,7 +117,6 @@ function oUpdateProviderImage(elem){ $('#b06d66ec-6c84-45dd-8c27-1263a6253192-107').attr('fill', oColor3); $('#f58f497e-6949-45c8-be5f-eee2aa0f6586').attr('fill', oColor3); $('#aff120b1-519b-4e96-ac87-836aa55663de').attr('fill', oColor3); - $('#aff120b1-519b-4e96-ac87-836aa55663de').attr('fill', oColor3); $('#ae7af94f-88d7-4204-9f07-e3651de85c05-111').attr('fill', oColor4); $('#a6768b0e-63d0-4b31-8462-9b2e0b00f0fd-112').attr('fill', oColor4); } From e1645f6903c6c574bcd7434648ce8bccef485df5 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Wed, 18 May 2022 08:41:58 +0200 Subject: [PATCH 15/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=20Config=20messages=20=20*=20Fix=20unit?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/config.class.inc.php | 10 +++++----- sources/Composer/iTopComposer.php | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 7c3743388..8aac1721b 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -557,7 +557,7 @@ class Config ), 'email_transport_smtp.oauth.provider' => [ 'type' => 'string', - 'description' => 'TODO', + 'description' => 'Email OAuth provider', 'default' => '', 'value' => '', 'source_of_value' => '', @@ -565,7 +565,7 @@ class Config ], 'email_transport_smtp.oauth.client_id' => [ 'type' => 'string', - 'description' => 'TODO', + 'description' => 'Email OAuth client id', 'default' => '', 'value' => '', 'source_of_value' => '', @@ -573,7 +573,7 @@ class Config ], 'email_transport_smtp.oauth.client_secret' => [ 'type' => 'string', - 'description' => 'TODO', + 'description' => 'Email OAuth client secret', 'default' => '', 'value' => '', 'source_of_value' => '', @@ -581,7 +581,7 @@ class Config ], 'email_transport_smtp.oauth.access_token' => [ 'type' => 'string', - 'description' => 'TODO', + 'description' => 'Email OAuth access token', 'default' => '', 'value' => '', 'source_of_value' => '', @@ -589,7 +589,7 @@ class Config ], 'email_transport_smtp.oauth.refresh_token' => [ 'type' => 'string', - 'description' => 'TODO', + 'description' => 'Email OAuth refresh token', 'default' => '', 'value' => '', 'source_of_value' => '', diff --git a/sources/Composer/iTopComposer.php b/sources/Composer/iTopComposer.php index c45d5f7d7..d9994e2ab 100644 --- a/sources/Composer/iTopComposer.php +++ b/sources/Composer/iTopComposer.php @@ -138,6 +138,8 @@ class iTopComposer $APPROOT_WITH_SLASHES.'lib/twig/twig/src/Test', $APPROOT_WITH_SLASHES.'lib/twig/twig/lib/Twig/Test', $APPROOT_WITH_SLASHES.'lib/twig/twig/doc/tests', + + $APPROOT_WITH_SLASHES.'lib/laminas/laminas-servicemanager/src/Test', ); } From 183c3c1baf19eee58b2872fe287415c3dfd02bea Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Thu, 19 May 2022 16:30:06 +0200 Subject: [PATCH 16/41] =?UTF-8?q?N=C2=B04666=20-=20Core=20Update=20:=20han?= =?UTF-8?q?dle=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/RunTimeEnvironmentCoreUpdater.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php index fea0abaef..6715878e0 100644 --- a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php +++ b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php @@ -11,6 +11,7 @@ require_once(APPROOT."setup/runtimeenv.class.inc.php"); use Config; use Exception; +use ModelFactory; use RunTimeEnvironment; use SetupUtils; @@ -126,4 +127,49 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment } throw new Exception('No configuration file available'); } + + protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir) + { + $aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir); + + // Add new mandatory modules + $sSourceDirFull = APPROOT.$sSourceDir; + if (!is_dir($sSourceDirFull)) + { + throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)"); + } + $aDirsToCompile = array($sSourceDirFull); + if (is_dir(APPROOT.'extensions')) + { + $aDirsToCompile[] = APPROOT.'extensions'; + } + $sExtraDir = APPROOT.'data/'.$this->sTargetEnv.'-modules/'; + if (is_dir($sExtraDir)) + { + $aDirsToCompile[] = $sExtraDir; + } + + $aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile); + $aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs); + + $oFactory = new ModelFactory($aDirsToCompile); + $aModules = $oFactory->FindModules(); + $aAvailableModules = []; + /** @var \MFModule $oModule */ + foreach ($aModules as $oModule) { + $aAvailableModules[$oModule->GetName()] = $oModule; + } + foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension) { + if ($oExtension->bMarkedAsChosen) { + foreach ($oExtension->aModules as $sModuleName) { + if (!isset($aRet[$sModuleName])) { + $aRet[$sModuleName] = $aAvailableModules[$sModuleName]; + } + } + } + } + + return $aRet; + } + } From ca3aae23a1ab901da44fd4f70b2b40d9fd97ea37 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Fri, 20 May 2022 09:33:41 +0200 Subject: [PATCH 17/41] =?UTF-8?q?N=C2=B04666=20-=20Core=20Update=20:=20han?= =?UTF-8?q?dle=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup/appupgradecopy.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 setup/appupgradecopy.php diff --git a/setup/appupgradecopy.php b/setup/appupgradecopy.php new file mode 100644 index 000000000..43fb18ada --- /dev/null +++ b/setup/appupgradecopy.php @@ -0,0 +1,21 @@ + Date: Fri, 20 May 2022 09:42:14 +0200 Subject: [PATCH 18/41] =?UTF-8?q?N=C2=B04666=20-=20Core=20Update=20:=20han?= =?UTF-8?q?dle=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup/appupgradecopy.php | 1 + 1 file changed, 1 insertion(+) diff --git a/setup/appupgradecopy.php b/setup/appupgradecopy.php index 43fb18ada..18fdd3e11 100644 --- a/setup/appupgradecopy.php +++ b/setup/appupgradecopy.php @@ -12,6 +12,7 @@ use Combodo\iTop\CoreUpdate\Service\CoreUpdater; function AppUpgradeCopyFiles($sSourceDir) { + CoreUpdater::CopyDir($sSourceDir, APPROOT); // Update Core update files $sSource = realpath($sSourceDir.'/datamodels/2.x/itop-core-update'); if ($sSource !== false) From a3f122184c2f47c57650b8beb1585ba207e46adf Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Fri, 20 May 2022 10:20:47 +0200 Subject: [PATCH 19/41] =?UTF-8?q?N=C2=B04642=20-=20Core=20Update=20:=20lim?= =?UTF-8?q?it=20the=20usage=20of=20this=20function=20-=20revert=20due=20to?= =?UTF-8?q?=20N=C2=B04666=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs.dict.itop-core-update.php | 1 - .../da.dict.itop-core-update.php | 1 - .../de.dict.itop-core-update.php | 1 - .../en.dict.itop-core-update.php | 1 - .../es_cr.dict.itop-core-update.php | 1 - .../fr.dict.itop-core-update.php | 1 - .../hu.dict.itop-core-update.php | 1 - .../it.dict.itop-core-update.php | 1 - .../ja.dict.itop-core-update.php | 1 - .../nl.dict.itop-core-update.php | 1 - .../pt_br.dict.itop-core-update.php | 1 - .../ru.dict.itop-core-update.php | 1 - .../sk.dict.itop-core-update.php | 1 - .../src/Service/CoreUpdater.php | 38 ------------------- .../tr.dict.itop-core-update.php | 1 - .../view/ConfirmUpdate.html.twig | 2 +- .../zh_cn.dict.itop-core-update.php | 1 - 17 files changed, 1 insertion(+), 54 deletions(-) diff --git a/datamodels/2.x/itop-core-update/cs.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/cs.dict.itop-core-update.php index 1daf5fa01..ee3034ecc 100644 --- a/datamodels/2.x/itop-core-update/cs.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/cs.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/da.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/da.dict.itop-core-update.php index 70ecb0aa4..ab703a9ab 100644 --- a/datamodels/2.x/itop-core-update/da.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/da.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/de.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/de.dict.itop-core-update.php index 72ad62598..7f801244b 100644 --- a/datamodels/2.x/itop-core-update/de.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/de.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Anwendungsupgrade nicht möglich: %1$s', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Einige angepasste Dateien wurden erkannt, eine Teil-Update kann nicht ausgeführt werden.
Befolgen Sie das Verfahren, um Ihr iTop manuell zu aktualisieren. Sie müssen das Setup benutzen, um Ihre Applikation zu aktualisieren.
', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Einige neue Module wurden erkannt, eine Teil-Update kann nicht ausgeführt werden.
Befolgen Sie das Verfahren, um Ihr iTop manuell zu aktualisieren. Sie müssen das Setup benutzen, um Ihre Applikation zu aktualisieren.
', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/en.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/en.dict.itop-core-update.php index d68e136a3..4cc013a27 100644 --- a/datamodels/2.x/itop-core-update/en.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/en.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('EN US', 'English', 'English', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/es_cr.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/es_cr.dict.itop-core-update.php index 546ef11ef..5ffe65d8d 100644 --- a/datamodels/2.x/itop-core-update/es_cr.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/es_cr.dict.itop-core-update.php @@ -77,7 +77,6 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'La aplicación no puede ser actualizada: %1$s', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Advertencia: la actualización de la aplicación puede fallar: %1$s', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/fr.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/fr.dict.itop-core-update.php index 6618a4537..fb80f2644 100644 --- a/datamodels/2.x/itop-core-update/fr.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/fr.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('FR FR', 'French', 'Français', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'L\'application ne peut pas être mise à jour : %1$s', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Attention : la mise à jour de l\'application peut échouer : %1$s', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Des fichiers modifiés ont été détectés, une mise à jour partielle ne peut pas être effectuée.
Suivez la procedure pour mettre à jour manuellement votre iTop. Vous devez utiliser la page d\'installation pour mettre à jour l\'application.', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'De nouveaux modules ont été détectés, une mise à jour partielle ne peut pas être effectuée.
Suivez la procedure pour mettre à jour manuellement votre iTop. Vous devez utiliser la page d\'installation pour mettre à jour l\'application.', 'iTopUpdate:UI:CheckInProgress'=>'Veuillez patienter pendant la vérification d\'intégrité', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/hu.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/hu.dict.itop-core-update.php index 67583ef4a..bf5ac0674 100644 --- a/datamodels/2.x/itop-core-update/hu.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/hu.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/it.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/it.dict.itop-core-update.php index ae86afa1e..e7fb3489d 100644 --- a/datamodels/2.x/itop-core-update/it.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/it.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/ja.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/ja.dict.itop-core-update.php index aad058424..865cfb513 100644 --- a/datamodels/2.x/itop-core-update/ja.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/ja.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/nl.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/nl.dict.itop-core-update.php index a5e396f95..4e15021e2 100644 --- a/datamodels/2.x/itop-core-update/nl.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/nl.dict.itop-core-update.php @@ -78,7 +78,6 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Updaten van de toepassing is niet mogelijk: %1$s', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/pt_br.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/pt_br.dict.itop-core-update.php index 5c1538739..1d6ead8f8 100644 --- a/datamodels/2.x/itop-core-update/pt_br.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/pt_br.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/ru.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/ru.dict.itop-core-update.php index 0909f2b95..c93bd7d25 100644 --- a/datamodels/2.x/itop-core-update/ru.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/ru.dict.itop-core-update.php @@ -64,7 +64,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Приложение не может быть обновлено: %1$s', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/sk.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/sk.dict.itop-core-update.php index e3ee182fe..25677f807 100644 --- a/datamodels/2.x/itop-core-update/sk.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/sk.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/src/Service/CoreUpdater.php b/datamodels/2.x/itop-core-update/src/Service/CoreUpdater.php index fdb98922b..32e6925c2 100644 --- a/datamodels/2.x/itop-core-update/src/Service/CoreUpdater.php +++ b/datamodels/2.x/itop-core-update/src/Service/CoreUpdater.php @@ -9,7 +9,6 @@ namespace Combodo\iTop\CoreUpdate\Service; -use Combodo\iTop\FilesInformation\Service\FileIntegrityException; use Combodo\iTop\FilesInformation\Service\FilesIntegrity; use DBBackup; use Dict; @@ -539,9 +538,6 @@ final class CoreUpdater $sRootPath = self::UPDATE_DIR.'web/'; FilesIntegrity::CheckInstallationIntegrity($sRootPath); - ///Check new modules - self::CheckNewModules($sRootPath); - SetupLog::Info('itop-core-update: Files integrity OK'); } catch (Exception $e) { @@ -609,38 +605,4 @@ final class CoreUpdater throw $e; } } - - /** - * Check if new modules (not already installed) are present, and throw an exception if that is the case as core update doesn't know how to install them automatically for know - * - * @param string $sRootPath - * - * @throws \ApplicationException - * @since 2.7.7 3.0.1 - */ - private static function CheckNewModules($sRootPath) - { - $aFilesInfo = FilesIntegrity::GetInstalledFiles($sRootPath.'manifest.xml'); - - if ($aFilesInfo === false) { - throw new FileIntegrityException(Dict::Format('FilesInformation:Error:MissingFile', 'manifest.xml')); - } - - @clearstatcache(); - $sSourceDir = MetaModel::GetConfig()->Get('source_dir'); - foreach ($aFilesInfo as $aFileInfo) { - if (strpos($aFileInfo['path'], $sSourceDir) === 0) { - $aFilePath = explode('/', $aFileInfo['path']); - $sFolderPath = $aFilePath[0].'/'.$aFilePath[1].'/'.$aFilePath[2]; - //if module don't already exist in itop and if module listed in manifest.xml is included in zip - if (!is_dir(APPROOT.'/'.$sFolderPath) && !is_file(APPROOT.'/'.$sFolderPath) - && is_dir($sRootPath.'/'.$sFolderPath)) { - $sLink = utils::GetAbsoluteUrlAppRoot().'setup/'; - $sLinkManualUpdate = 'https://www.itophub.io/wiki/page?id='.utils::GetItopVersionWikiSyntax().'%3Ainstall%3Aupgrading_itop#manually'; - throw new FileIntegrityException(Dict::Format('iTopUpdate:UI:CannotUpdateNewModules' , $sLink, $sLinkManualUpdate)); - } - } - // Packed with missing files... - } - } } diff --git a/datamodels/2.x/itop-core-update/tr.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/tr.dict.itop-core-update.php index a69c8f7df..041679a47 100644 --- a/datamodels/2.x/itop-core-update/tr.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/tr.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~', 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages diff --git a/datamodels/2.x/itop-core-update/view/ConfirmUpdate.html.twig b/datamodels/2.x/itop-core-update/view/ConfirmUpdate.html.twig index 1d1a458d6..1cf402bc7 100644 --- a/datamodels/2.x/itop-core-update/view/ConfirmUpdate.html.twig +++ b/datamodels/2.x/itop-core-update/view/ConfirmUpdate.html.twig @@ -64,7 +64,7 @@
- {{ sError|raw }} + {{ sError }}
diff --git a/datamodels/2.x/itop-core-update/zh_cn.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/zh_cn.dict.itop-core-update.php index f368a69a9..7553fd543 100644 --- a/datamodels/2.x/itop-core-update/zh_cn.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/zh_cn.dict.itop-core-update.php @@ -76,7 +76,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'iTopUpdate:UI:CanCoreUpdate:No' => '应用无法升级: %1$s', 'iTopUpdate:UI:CanCoreUpdate:Warning' => '警告: 应用升级可能会失败: %1$s', 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Some modified files were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', - 'iTopUpdate:UI:CannotUpdateNewModules' => 'Some new modules were detected, a partial update cannot be executed.
Follow the procedure in order to manually upgrade your iTop. You must use the setup to update the application.~~', 'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check~~', // Setup Messages From 754946bf62fcc9f62ddaf8120951b6c6127001e1 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Mon, 23 May 2022 12:09:40 +0200 Subject: [PATCH 20/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20=20*=20Fix=20legacy=20mailboxes?= =?UTF-8?q?=20compatibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/email.class.inc.php | 5 ++- lib/composer/autoload_classmap.php | 1 + lib/composer/autoload_static.php | 1 + sources/Core/Email/EmailFactory.php | 7 ++-- sources/Core/Email/EmailLaminas.php | 26 ++++++------- sources/Core/Email/EmailSwiftMailer.php | 13 ++++--- sources/Core/Email/iEMail.php | 51 +++++++++++++++++++++++++ 7 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 sources/Core/Email/iEMail.php diff --git a/core/email.class.inc.php b/core/email.class.inc.php index ee8a8dd41..4b3f0a052 100644 --- a/core/email.class.inc.php +++ b/core/email.class.inc.php @@ -25,13 +25,14 @@ */ use Combodo\iTop\Core\Email\EmailFactory; +use Combodo\iTop\Core\Email\iEMail; define ('EMAIL_SEND_OK', 0); define ('EMAIL_SEND_PENDING', 1); define ('EMAIL_SEND_ERROR', 2); -class EMail +class EMail implements iEMail { protected $oMailer; @@ -42,7 +43,7 @@ class EMail public function __construct() { - $this->oMailer = EmailFactory::GetMailer(); + $this->oMailer = EmailFactory::GetMailer($this); } /** diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 598a39495..75d1316ba 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -161,6 +161,7 @@ return array( 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientResultDisplayConf' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php', 'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php', + 'Combodo\\iTop\\Core\\Email\\iEMail' => $baseDir . '/sources/Core/Email/iEMail.php', 'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 94b13646f..a15cd2834 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -529,6 +529,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientResultDisplayConf' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php', 'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php', + 'Combodo\\iTop\\Core\\Email\\iEMail' => __DIR__ . '/../..' . '/sources/Core/Email/iEMail.php', 'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php', diff --git a/sources/Core/Email/EmailFactory.php b/sources/Core/Email/EmailFactory.php index 6d1354d9b..7ee132ee6 100644 --- a/sources/Core/Email/EmailFactory.php +++ b/sources/Core/Email/EmailFactory.php @@ -2,19 +2,20 @@ namespace Combodo\iTop\Core\Email; +use EMail; use EMailLaminas; use EmailSwiftMailer; use utils; class EmailFactory { - public static function GetMailer() + public static function GetMailer(EMail $oEMail) { $sTransport = utils::GetConfig()->Get('email_transport'); if ($sTransport == 'SMTP_OAuth') { - return EMailLaminas::GetMailer(); + return EMailLaminas::GetMailer($oEMail); } - return EmailSwiftMailer::GetMailer(); + return EmailSwiftMailer::GetMailer($oEMail); } } \ No newline at end of file diff --git a/sources/Core/Email/EmailLaminas.php b/sources/Core/Email/EmailLaminas.php index f828113a8..a13e8f674 100644 --- a/sources/Core/Email/EmailLaminas.php +++ b/sources/Core/Email/EmailLaminas.php @@ -26,6 +26,7 @@ @include APPROOT."/core/oauth.php"; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory; +use Combodo\iTop\Core\Email\iEMail; use Laminas\Mail\Header\ContentType; use Laminas\Mail\Message; use Laminas\Mail\Transport\File; @@ -38,7 +39,7 @@ use Pelago\Emogrifier\CssInliner; use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; -class EMailLaminas +class EMailLaminas implements iEMail { // Serialization formats const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. @@ -56,9 +57,11 @@ class EMailLaminas } protected $m_oMessage; + protected $oEMail; - public function __construct() + public function __construct(EMail $oEMail) { + $this->oEMail = $oEMail; $this->m_aData = array(); $this->m_oMessage = new Message(); $this->m_oMessage->setEncoding('UTF-8'); @@ -138,7 +141,7 @@ class EMailLaminas protected function SendAsynchronous(&$aIssues, $oLog = null) { try { - AsyncSendEmail::AddToQueue($this, $oLog); + AsyncSendEmail::AddToQueue($this->oEMail, $oLog); } catch (Exception $e) { $aIssues = array($e->GetMessage()); @@ -150,9 +153,9 @@ class EMailLaminas return EMAIL_SEND_PENDING; } - public static function GetMailer() + public static function GetMailer(EMail $oEMail) { - return new EMailLaminas(); + return new EMailLaminas($oEMail); } /** @@ -334,14 +337,7 @@ class EMailLaminas if (strlen($sValue) > 0) { $oHeaders = $this->m_oMessage->getHeaders(); - switch (strtolower($sKey)) { - case 'return-path': - $this->m_oMessage->setReturnPath($sValue); - break; - - default: - $oHeaders->addHeaderLine($sKey, $sValue); - } + $oHeaders->addHeaderLine($sKey, $sValue); } } @@ -387,7 +383,7 @@ class EMailLaminas * @throws \CoreException * @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException */ - public function SetBody($sBody, string $sMimeType = Mime::TYPE_HTML, $sCustomStyles = null) + public function SetBody($sBody, $sMimeType = Mime::TYPE_HTML, $sCustomStyles = null) { $oBody = new Laminas\Mime\Message(); $aAdditionalParts = []; @@ -439,7 +435,7 @@ class EMailLaminas * * @return void */ - public function AddPart($sText, string $sMimeType = Mime::TYPE_HTML) + public function AddPart($sText, $sMimeType = Mime::TYPE_HTML) { if (!array_key_exists('parts', $this->m_aData)) { $this->m_aData['parts'] = array(); diff --git a/sources/Core/Email/EmailSwiftMailer.php b/sources/Core/Email/EmailSwiftMailer.php index 1fa7f6492..d86624b7b 100644 --- a/sources/Core/Email/EmailSwiftMailer.php +++ b/sources/Core/Email/EmailSwiftMailer.php @@ -24,13 +24,14 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ +use Combodo\iTop\Core\Email\iEMail; use Pelago\Emogrifier\CssInliner; use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; Swift_Preferences::getInstance()->setCharset('UTF-8'); -class EmailSwiftMailer +class EmailSwiftMailer implements iEMail { protected static $m_oConfig = null; protected $m_aData; // For storing data to serialize @@ -44,9 +45,11 @@ class EmailSwiftMailer } protected $m_oMessage; + protected $oEMail; - public function __construct() + public function __construct(EMail $oEMail) { + $this->oEMail = $oEMail; $this->m_aData = array(); $this->m_oMessage = new Swift_Message(); $this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label')); @@ -134,7 +137,7 @@ class EmailSwiftMailer { try { - AsyncSendEmail::AddToQueue($this, $oLog); + AsyncSendEmail::AddToQueue($this->oEMail, $oLog); } catch(Exception $e) { @@ -145,9 +148,9 @@ class EmailSwiftMailer return EMAIL_SEND_PENDING; } - public static function GetMailer() + public static function GetMailer(EMail $oEMail) { - return new EmailSwiftMailer(); + return new EmailSwiftMailer($oEMail); } protected function SendSynchronous(&$aIssues, $oLog = null) diff --git a/sources/Core/Email/iEMail.php b/sources/Core/Email/iEMail.php new file mode 100644 index 000000000..9faaa4511 --- /dev/null +++ b/sources/Core/Email/iEMail.php @@ -0,0 +1,51 @@ + Date: Tue, 24 May 2022 18:20:18 +0200 Subject: [PATCH 21/41] =?UTF-8?q?N=C2=B04867=20-=20"Twig=20content=20not?= =?UTF-8?q?=20allowed"=20error=20when=20use=20the=20extkey=20widget=20sear?= =?UTF-8?q?ch=20icon=20in=20the=20user=20portal=20-=20Remove=20useless=20c?= =?UTF-8?q?ode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../portal/src/Form/ObjectFormManager.php | 34 +------------------ .../src/Helper/ObjectFormHandlerHelper.php | 7 ++-- .../portal/src/Twig/AppExtension.php | 3 +- .../itop-tickets/datamodel.itop-tickets.xml | 1 - 4 files changed, 5 insertions(+), 40 deletions(-) diff --git a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php index 9200b566a..5e8d99e16 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php @@ -114,9 +114,8 @@ class ObjectFormManager extends FormManager * @throws \Exception * @throws \SecurityException if twig content is present and $bTrustContent is false * - * @since 2.7.6 3.0.0 N°4384 new $bTrustContent parameter */ - public static function FromJSON($sJson, $bTrustContent = false) + public static function FromJSON($sJson) { $aJson = static::DecodeFormManagerData($sJson); @@ -172,37 +171,6 @@ class ObjectFormManager extends FormManager return $oFormManager; } - /** - * @param string $sPostedFormManagerData received data from the browser - * @param array $aOriginalFormProperties data generated server side - * - * @return bool true if the data are identical - * - * @since 2.7.6 3.0.0 N°4384 Check formmanager_data - */ - public static function CanTrustFormLayoutContent($sPostedFormManagerData, $aOriginalFormProperties) - { - $aPostedFormManagerData = static::DecodeFormManagerData($sPostedFormManagerData); - $sPostedFormLayoutType = (isset($aPostedFormManagerData['formproperties']['layout']['type'])) ? $aPostedFormManagerData['formproperties']['layout']['type'] : ''; - - if ($sPostedFormLayoutType === 'xhtml') { - return true; - } - - // We need to parse the content so that autoclose tags are returned correctly (`
` => `
`) - $oHtmlDocument = new \DOMDocument(); - - $sPostedFormLayoutContent = (isset($aPostedFormManagerData['formproperties']['layout']['content'])) ? $aPostedFormManagerData['formproperties']['layout']['content'] : ''; - $oHtmlDocument->loadXML(''.$sPostedFormLayoutContent.''); - $sPostedFormLayoutRendered = $oHtmlDocument->saveHTML(); - - $sOriginalFormLayoutContent = (isset($aOriginalFormProperties['layout']['content'])) ? $aOriginalFormProperties['layout']['content'] : ''; - $oHtmlDocument->loadXML(''.$sOriginalFormLayoutContent.''); - $sOriginalFormLayoutContentRendered = $oHtmlDocument->saveHTML(); - - return ($sPostedFormLayoutRendered === $sOriginalFormLayoutContentRendered); - } - /** * * @return \Symfony\Component\DependencyInjection\ContainerInterface diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php index 1a5e1936f..155ec5655 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php @@ -132,12 +132,10 @@ class ObjectFormHandlerHelper $bModal = ($oRequest->isXmlHttpRequest() && empty($sOperation)); // - Retrieve form properties - $aOriginalFormProperties = ApplicationHelper::GetLoadedFormFromClass($this->aCombodoPortalInstanceConf['forms'], $sObjectClass, $sMode); if ($aFormProperties === null) { - $aFormProperties = $aOriginalFormProperties; + $aFormProperties = ApplicationHelper::GetLoadedFormFromClass($this->aCombodoPortalInstanceConf['forms'], $sObjectClass, $sMode); } - // - Create and if (empty($sOperation)) { @@ -299,8 +297,7 @@ class ObjectFormHandlerHelper throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'Parameters formmanager_class and formmanager_data must be defined.'); } - $bTrustContent = $sFormManagerClass::CanTrustFormLayoutContent($sFormManagerData, $aOriginalFormProperties); - $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData, $bTrustContent); + $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData); $oFormManager->SetContainer($this->oContainer); // Applying action rules if present diff --git a/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php b/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php index 363e565df..88ad56440 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php @@ -99,7 +99,8 @@ class AppExtension extends AbstractExtension return $sUrl; }); - //$filters[] = new TwigFilter('filter', 'twig_array_filter'); + //since 2.7.7 3.0.2 3.1.0 N°4867 "Twig content not allowed" error when use the extkey widget search icon in the user portal + //overwrite native twig filter : disable use of 'system' filter $filters[] = new Twig_SimpleFilter('filter', function ($array, $arrow) { if ($arrow == 'system'){ return json_encode($array); diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index 9f2c0b0f4..9fba41021 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -1733,7 +1733,6 @@ 500 - true From 622f40c06cf9d3dbe170416498c4906dd1314318 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Wed, 25 May 2022 08:21:16 +0200 Subject: [PATCH 22/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20=20*=20Fix=20legacy=20mailboxes?= =?UTF-8?q?=20compatibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/email.class.inc.php | 2 +- sources/Core/Email/EmailFactory.php | 7 +++---- sources/Core/Email/EmailLaminas.php | 14 ++++++-------- sources/Core/Email/EmailSwiftMailer.php | 18 ++++++++---------- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/core/email.class.inc.php b/core/email.class.inc.php index 4b3f0a052..669e79bd5 100644 --- a/core/email.class.inc.php +++ b/core/email.class.inc.php @@ -43,7 +43,7 @@ class EMail implements iEMail public function __construct() { - $this->oMailer = EmailFactory::GetMailer($this); + $this->oMailer = EmailFactory::GetMailer(); } /** diff --git a/sources/Core/Email/EmailFactory.php b/sources/Core/Email/EmailFactory.php index 7ee132ee6..6d1354d9b 100644 --- a/sources/Core/Email/EmailFactory.php +++ b/sources/Core/Email/EmailFactory.php @@ -2,20 +2,19 @@ namespace Combodo\iTop\Core\Email; -use EMail; use EMailLaminas; use EmailSwiftMailer; use utils; class EmailFactory { - public static function GetMailer(EMail $oEMail) + public static function GetMailer() { $sTransport = utils::GetConfig()->Get('email_transport'); if ($sTransport == 'SMTP_OAuth') { - return EMailLaminas::GetMailer($oEMail); + return EMailLaminas::GetMailer(); } - return EmailSwiftMailer::GetMailer($oEMail); + return EmailSwiftMailer::GetMailer(); } } \ No newline at end of file diff --git a/sources/Core/Email/EmailLaminas.php b/sources/Core/Email/EmailLaminas.php index a13e8f674..3c2a48d6e 100644 --- a/sources/Core/Email/EmailLaminas.php +++ b/sources/Core/Email/EmailLaminas.php @@ -26,7 +26,6 @@ @include APPROOT."/core/oauth.php"; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory; -use Combodo\iTop\Core\Email\iEMail; use Laminas\Mail\Header\ContentType; use Laminas\Mail\Message; use Laminas\Mail\Transport\File; @@ -39,7 +38,7 @@ use Pelago\Emogrifier\CssInliner; use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; -class EMailLaminas implements iEMail +class EMailLaminas extends Email { // Serialization formats const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. @@ -57,11 +56,10 @@ class EMailLaminas implements iEMail } protected $m_oMessage; - protected $oEMail; - public function __construct(EMail $oEMail) + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct() { - $this->oEMail = $oEMail; $this->m_aData = array(); $this->m_oMessage = new Message(); $this->m_oMessage->setEncoding('UTF-8'); @@ -141,7 +139,7 @@ class EMailLaminas implements iEMail protected function SendAsynchronous(&$aIssues, $oLog = null) { try { - AsyncSendEmail::AddToQueue($this->oEMail, $oLog); + AsyncSendEmail::AddToQueue($this, $oLog); } catch (Exception $e) { $aIssues = array($e->GetMessage()); @@ -153,9 +151,9 @@ class EMailLaminas implements iEMail return EMAIL_SEND_PENDING; } - public static function GetMailer(EMail $oEMail) + public static function GetMailer() { - return new EMailLaminas($oEMail); + return new EMailLaminas(); } /** diff --git a/sources/Core/Email/EmailSwiftMailer.php b/sources/Core/Email/EmailSwiftMailer.php index d86624b7b..08edf28d7 100644 --- a/sources/Core/Email/EmailSwiftMailer.php +++ b/sources/Core/Email/EmailSwiftMailer.php @@ -24,14 +24,13 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ -use Combodo\iTop\Core\Email\iEMail; use Pelago\Emogrifier\CssInliner; use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; Swift_Preferences::getInstance()->setCharset('UTF-8'); -class EmailSwiftMailer implements iEMail +class EmailSwiftMailer extends EMail { protected static $m_oConfig = null; protected $m_aData; // For storing data to serialize @@ -45,11 +44,10 @@ class EmailSwiftMailer implements iEMail } protected $m_oMessage; - protected $oEMail; - public function __construct(EMail $oEMail) + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct() { - $this->oEMail = $oEMail; $this->m_aData = array(); $this->m_oMessage = new Swift_Message(); $this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label')); @@ -137,7 +135,7 @@ class EmailSwiftMailer implements iEMail { try { - AsyncSendEmail::AddToQueue($this->oEMail, $oLog); + AsyncSendEmail::AddToQueue($this, $oLog); } catch(Exception $e) { @@ -148,9 +146,9 @@ class EmailSwiftMailer implements iEMail return EMAIL_SEND_PENDING; } - public static function GetMailer(EMail $oEMail) + public static function GetMailer() { - return new EmailSwiftMailer($oEMail); + return new EmailSwiftMailer(); } protected function SendSynchronous(&$aIssues, $oLog = null) @@ -183,7 +181,7 @@ class EmailSwiftMailer implements iEMail break; case 'LogFile': - $oTransport = new Swift_LogFileTransport(); + $oTransport = new Swift_LogFileTransport(new Swift_Events_SimpleEventDispatcher()); $oTransport->setLogFile(APPROOT.'log/mail.log'); break; @@ -543,6 +541,6 @@ class Swift_LogFileTransport extends Swift_Transport_LogFileTransport */ public static function newInstance() { - return new self(); + return new self(new Swift_Events_SimpleEventDispatcher()); } } \ No newline at end of file From f6f9ee26e12213f5d128f2854785ec5894a281d7 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Fri, 27 May 2022 09:29:00 +0200 Subject: [PATCH 23/41] Removed laminas service manager test folder --- .../src/Test/CommonPluginManagerTrait.php | 115 ------------------ 1 file changed, 115 deletions(-) delete mode 100644 lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php diff --git a/lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php b/lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php deleted file mode 100644 index a736cb05c..000000000 --- a/lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php +++ /dev/null @@ -1,115 +0,0 @@ -getPluginManager(); - $reflection = new ReflectionProperty($manager, 'instanceOf'); - $reflection->setAccessible(true); - $this->assertEquals($this->getInstanceOf(), $reflection->getValue($manager), 'instanceOf does not match'); - } - - public function testShareByDefaultAndSharedByDefault() - { - $manager = $this->getPluginManager(); - $reflection = new ReflectionClass($manager); - $shareByDefault = $sharedByDefault = true; - - foreach ($reflection->getProperties() as $prop) { - if ($prop->getName() == 'shareByDefault') { - $prop->setAccessible(true); - $shareByDefault = $prop->getValue($manager); - } - if ($prop->getName() == 'sharedByDefault') { - $prop->setAccessible(true); - $sharedByDefault = $prop->getValue($manager); - } - } - - $this->assertTrue( - $shareByDefault == $sharedByDefault, - 'Values of shareByDefault and sharedByDefault do not match' - ); - } - - public function testRegisteringInvalidElementRaisesException() - { - $this->expectException($this->getServiceNotFoundException()); - $this->getPluginManager()->setService('test', $this); - } - - public function testLoadingInvalidElementRaisesException() - { - $manager = $this->getPluginManager(); - $manager->setInvokableClass('test', get_class($this)); - $this->expectException($this->getServiceNotFoundException()); - $manager->get('test'); - } - - /** - * @dataProvider aliasProvider - */ - public function testPluginAliasesResolve($alias, $expected) - { - $this->assertInstanceOf($expected, $this->getPluginManager()->get($alias), "Alias '$alias' does not resolve'"); - } - - public function aliasProvider() - { - $manager = $this->getPluginManager(); - $reflection = new ReflectionProperty($manager, 'aliases'); - $reflection->setAccessible(true); - $data = []; - foreach ($reflection->getValue($manager) as $alias => $expected) { - $data[] = [$alias, $expected]; - } - return $data; - } - - protected function getServiceNotFoundException() - { - $manager = $this->getPluginManager(); - if (method_exists($manager, 'configure')) { - return InvalidServiceException::class; - } - return $this->getV2InvalidPluginException(); - } - - /** - * Returns the plugin manager to test - * @return \Laminas\ServiceManager\AbstractPluginManager - */ - abstract protected function getPluginManager(); - - /** - * Returns the FQCN of the exception thrown under v2 by `validatePlugin()` - * @return mixed - */ - abstract protected function getV2InvalidPluginException(); - - /** - * Returns the value the instanceOf property has been set to - * @return string - */ - abstract protected function getInstanceOf(); -} From ddb95dc64ef7e4fd4430748d499ff50bae269252 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Fri, 27 May 2022 09:32:16 +0200 Subject: [PATCH 24/41] Removed laminas service manager test folder --- lib/composer/autoload_classmap.php | 1 - lib/composer/autoload_static.php | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 75d1316ba..c63cfe237 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -606,7 +606,6 @@ return array( 'Laminas\\ServiceManager\\PsrContainerDecorator' => $vendorDir . '/laminas/laminas-servicemanager/src/PsrContainerDecorator.php', 'Laminas\\ServiceManager\\ServiceLocatorInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php', 'Laminas\\ServiceManager\\ServiceManager' => $vendorDir . '/laminas/laminas-servicemanager/src/ServiceManager.php', - 'Laminas\\ServiceManager\\Test\\CommonPluginManagerTrait' => $vendorDir . '/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php', 'Laminas\\ServiceManager\\Tool\\ConfigDumper' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php', 'Laminas\\ServiceManager\\Tool\\ConfigDumperCommand' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php', 'Laminas\\ServiceManager\\Tool\\FactoryCreator' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index a15cd2834..2b50f81a9 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -974,7 +974,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Laminas\\ServiceManager\\PsrContainerDecorator' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/PsrContainerDecorator.php', 'Laminas\\ServiceManager\\ServiceLocatorInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php', 'Laminas\\ServiceManager\\ServiceManager' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ServiceManager.php', - 'Laminas\\ServiceManager\\Test\\CommonPluginManagerTrait' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php', 'Laminas\\ServiceManager\\Tool\\ConfigDumper' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php', 'Laminas\\ServiceManager\\Tool\\ConfigDumperCommand' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php', 'Laminas\\ServiceManager\\Tool\\FactoryCreator' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php', From fe1790793e717527ec8a7c5a5172b434d8cc41e9 Mon Sep 17 00:00:00 2001 From: acognet Date: Mon, 30 May 2022 15:06:16 +0200 Subject: [PATCH 25/41] =?UTF-8?q?N=C2=B04898=20-=20security=20hardening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/dbobject.class.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index a06816115..7167ae7da 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1880,7 +1880,7 @@ abstract class DBObject implements iDisplay $oTargetObj = MetaModel::GetObject($sTargetClass, $toCheck, false /*must be found*/, true /*allow all data*/); if (is_null($oTargetObj)) { - return "Target object not found ($sTargetClass::$toCheck)"; + return "Target object not found (".utils::HtmlEntities($sTargetClass).".::".utils::HtmlEntities($toCheck).")"; } } if ($oAtt->IsHierarchicalKey()) @@ -1889,7 +1889,7 @@ abstract class DBObject implements iDisplay $aValues = $oAtt->GetAllowedValues(array('this' => $this)); if (!array_key_exists($toCheck, $aValues)) { - return "Value not allowed [$toCheck]"; + return "Value not allowed [". utils::HtmlEntities($toCheck)."]"; } } } @@ -1903,7 +1903,7 @@ abstract class DBObject implements iDisplay $oTag->SetValues(explode(' ', $toCheck)); } catch (Exception $e) { - return "Tag value '$toCheck' is not a valid tag list"; + return "Tag value [". utils::HtmlEntities($toCheck)."] is not a valid tag list"; } return true; @@ -1931,7 +1931,7 @@ abstract class DBObject implements iDisplay $oTag->SetValues($aValues); } catch (Exception $e) { - return "Set value '$toCheck' is not a valid set"; + return "Set value[". utils::HtmlEntities($toCheck)."] is not a valid set"; } return true; @@ -1951,7 +1951,7 @@ abstract class DBObject implements iDisplay { if (!array_key_exists($toCheck, $aValues)) { - return "Value not allowed [$toCheck]"; + return "Value not allowed [". utils::HtmlEntities($toCheck)."]"; } } if (!is_null($iMaxSize = $oAtt->GetMaxSize())) @@ -1964,7 +1964,7 @@ abstract class DBObject implements iDisplay } if (!$oAtt->CheckFormat($toCheck)) { - return "Wrong format [$toCheck]"; + return "Wrong format [". utils::HtmlEntities($toCheck)."]"; } } else From 95dafc87c03f981fdc2034352f904b2874a04602 Mon Sep 17 00:00:00 2001 From: acognet Date: Mon, 30 May 2022 15:06:57 +0200 Subject: [PATCH 26/41] =?UTF-8?q?N=C2=B04867=20-=20"Twig=20content=20not?= =?UTF-8?q?=20allowed"=20error=20when=20use=20the=20extkey=20widget=20sear?= =?UTF-8?q?ch=20icon=20in=20the=20user=20portal=20-=20Add=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/twig/TwigTest.php | 35 +++++++++++++++++++++++++++++++++++ test/twig/test.html | 12 ++++++++++++ test/twig/test.html.twig | 13 +++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 test/twig/TwigTest.php create mode 100644 test/twig/test.html create mode 100644 test/twig/test.html.twig diff --git a/test/twig/TwigTest.php b/test/twig/TwigTest.php new file mode 100644 index 000000000..1457b30f6 --- /dev/null +++ b/test/twig/TwigTest.php @@ -0,0 +1,35 @@ +render($sFileName.'.twig'); + $this->assertSame($sHtml, $expected); + } + + public static function testTemplateProvider() + { + $aReturn = array(); + $aReturn['filter_system'] = [ + 'sFileName' => 'test.html', + 'expected' =>file_get_contents(dirname(__FILE__).'/test.html'), + ]; + + return $aReturn; + } +} \ No newline at end of file diff --git a/test/twig/test.html b/test/twig/test.html new file mode 100644 index 000000000..06cd232d3 --- /dev/null +++ b/test/twig/test.html @@ -0,0 +1,12 @@ +
+ User Name +
+
+ ["id"] +
+
+ ["touch+\/tmp\/test+"] +
+
+ 40, 42 +
\ No newline at end of file diff --git a/test/twig/test.html.twig b/test/twig/test.html.twig new file mode 100644 index 000000000..b2ae9dd5a --- /dev/null +++ b/test/twig/test.html.twig @@ -0,0 +1,13 @@ +
+ {{ 'UI:Login:UserNamePrompt'|dict_s }} +
+
+ {{['id']|filter('system')}} +
+
+ {{['touch+/tmp/test+']|filter('system')|join(',')}} +
+
+{% set sizes = [34, 36, 38, 40, 42] %} + {{ sizes|filter(v => v > 38)|join(', ') }} +
\ No newline at end of file From 9fd10bd73e6627c48b1e44f32b42f29e327f9fc0 Mon Sep 17 00:00:00 2001 From: Benjamin Dalsass Date: Tue, 31 May 2022 16:28:02 +0200 Subject: [PATCH 27/41] =?UTF-8?q?N=C2=B05168=20-=20Security=20hardening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Helper/ObjectFormHandlerHelper.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php index 155ec5655..b6361b417 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php @@ -297,6 +297,7 @@ class ObjectFormHandlerHelper throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'Parameters formmanager_class and formmanager_data must be defined.'); } + $this->CheckReadFormDataAllowed($sFormManagerData); $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData); $oFormManager->SetContainer($this->oContainer); @@ -435,6 +436,29 @@ class ObjectFormHandlerHelper return $oTwig->render($sId, $aData); } + /** + * Check if read object include in form data is allowed, throw an exception otherwise. + * + * @since 2.7.7 + * + * @param $sFormManagerData form data to check + * + * @return void + * @throws \CoreException + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + */ + public function CheckReadFormDataAllowed($sFormManagerData){ + $aJsonFromData = json_decode($sFormManagerData, true); + if(isset($aJsonFromData['formobject_class']) + && isset($aJsonFromData['formobject_id']) + && !$this->oSecurityHelper->IsActionAllowed(UR_ACTION_READ, $aJsonFromData['formobject_class'], $aJsonFromData['formobject_id'])){ + throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'Form data access denied.'); + } + } + /** * Return an array of the available modes for a form. * From 0abec767e34eb086491aa43958dce96068698e83 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Wed, 1 Jun 2022 10:45:53 +0200 Subject: [PATCH 28/41] Dictionaries --- dictionaries/cs.dictionary.itop.core.php | 10 + dictionaries/cs.dictionary.itop.ui.php | 21 + dictionaries/da.dictionary.itop.core.php | 10 + dictionaries/da.dictionary.itop.ui.php | 21 + dictionaries/de.dictionary.itop.core.php | 10 + dictionaries/de.dictionary.itop.ui.php | 21 + dictionaries/es_cr.dictionary.itop.core.php | 278 ++-- dictionaries/es_cr.dictionary.itop.ui.php | 1295 ++++++++++--------- dictionaries/fr.dictionary.itop.core.php | 2 - dictionaries/fr.dictionary.itop.ui.php | 17 + dictionaries/hu.dictionary.itop.core.php | 10 + dictionaries/hu.dictionary.itop.ui.php | 21 + dictionaries/it.dictionary.itop.core.php | 10 + dictionaries/it.dictionary.itop.ui.php | 21 + dictionaries/ja.dictionary.itop.core.php | 10 + dictionaries/ja.dictionary.itop.ui.php | 21 + dictionaries/nl.dictionary.itop.core.php | 10 + dictionaries/nl.dictionary.itop.ui.php | 113 +- dictionaries/pt_br.dictionary.itop.core.php | 10 + dictionaries/pt_br.dictionary.itop.ui.php | 21 + dictionaries/ru.dictionary.itop.core.php | 10 + dictionaries/ru.dictionary.itop.ui.php | 21 + dictionaries/sk.dictionary.itop.core.php | 10 + dictionaries/sk.dictionary.itop.ui.php | 21 + dictionaries/tr.dictionary.itop.core.php | 16 +- dictionaries/tr.dictionary.itop.ui.php | 77 +- dictionaries/zh_cn.dictionary.itop.core.php | 10 + dictionaries/zh_cn.dictionary.itop.ui.php | 21 + 28 files changed, 1260 insertions(+), 858 deletions(-) diff --git a/dictionaries/cs.dictionary.itop.core.php b/dictionaries/cs.dictionary.itop.core.php index a6cbc5787..c5a039262 100755 --- a/dictionaries/cs.dictionary.itop.core.php +++ b/dictionaries/cs.dictionary.itop.core.php @@ -1047,4 +1047,14 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array( 'Class:AsyncTask/Attribute:event_id+' => '~~', 'Class:AsyncTask/Attribute:finalclass' => 'Final class~~', 'Class:AsyncTask/Attribute:finalclass+' => '~~', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/cs.dictionary.itop.ui.php b/dictionaries/cs.dictionary.itop.ui.php index a4b55c9f2..5a9651419 100755 --- a/dictionaries/cs.dictionary.itop.ui.php +++ b/dictionaries/cs.dictionary.itop.ui.php @@ -455,6 +455,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Chyba: objekt byl již odstraněn!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Nemáte oprávnění k hromadnému odstranění objektů třídy %1$s', 'UI:Error:DeleteNotAllowedOn_Class' => 'Nemáte oprávnění k odstranění objektů třídy %1$s', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Nemáte oprávnění k hromadné aktualizaci objektů třídy %1$s', 'UI:Error:ObjectAlreadyCloned' => 'Chyba: objekt byl již naklonován!', 'UI:Error:ObjectAlreadyCreated' => 'Chyba: objekt byl již vytvořen!', @@ -463,6 +464,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array( 'UI:Error:InvalidDashboard' => 'Error: invalid dashboard~~', 'UI:Error:MaintenanceMode' => 'Application is currently in maintenance~~', 'UI:Error:MaintenanceTitle' => 'Maintenance~~', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Množství', 'UI:GroupBy:Count+' => 'Množství prvků', @@ -1553,6 +1555,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array( 'UI:Search:Criteria:Raw:Filtered' => 'Filtered~~', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s~~', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1589,3 +1593,20 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Display messages from %1$s~~', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Display up to %1$s messages in the %2$s menu.~~', )); + + +// OAuth +Dict::Add('CS CZ', 'Czech', 'Čeština', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/da.dictionary.itop.core.php b/dictionaries/da.dictionary.itop.core.php index 619451c68..99a95dc4a 100644 --- a/dictionaries/da.dictionary.itop.core.php +++ b/dictionaries/da.dictionary.itop.core.php @@ -1045,4 +1045,14 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array( 'Class:AsyncTask/Attribute:event_id+' => '~~', 'Class:AsyncTask/Attribute:finalclass' => 'Final class~~', 'Class:AsyncTask/Attribute:finalclass+' => '~~', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/da.dictionary.itop.ui.php b/dictionaries/da.dictionary.itop.ui.php index 156b64dec..aa2c546d5 100644 --- a/dictionaries/da.dictionary.itop.ui.php +++ b/dictionaries/da.dictionary.itop.ui.php @@ -442,6 +442,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Fejl: objekterne er allerede slettet!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Du har ikke tilladelse til at foretage en masse sletning af objekter i klassen %1$s', 'UI:Error:DeleteNotAllowedOn_Class' => 'Du har ikke tilladelse til at slette objekter af klassen %1$s', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Du har ikke tilladelse til at foretage en masse opdatering af objekter i klassen %1$s', 'UI:Error:ObjectAlreadyCloned' => 'Fejl: objektet er allerede klonet!', 'UI:Error:ObjectAlreadyCreated' => 'Fejl: objektet er allerede oprettet!', @@ -450,6 +451,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array( 'UI:Error:InvalidDashboard' => 'Error: invalid dashboard~~', 'UI:Error:MaintenanceMode' => 'Application is currently in maintenance~~', 'UI:Error:MaintenanceTitle' => 'Maintenance~~', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Antal', 'UI:GroupBy:Count+' => 'Antal af elementer', @@ -1542,6 +1544,8 @@ Ved tilknytningen til en trigger, bliver hver handling tildelt et "rækkefølge" 'UI:Search:Criteria:Raw:Filtered' => 'Filtered~~', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s~~', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1578,3 +1582,20 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Display messages from %1$s~~', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Display up to %1$s messages in the %2$s menu.~~', )); + + +// OAuth +Dict::Add('DA DA', 'Danish', 'Dansk', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/de.dictionary.itop.core.php b/dictionaries/de.dictionary.itop.core.php index 720df9d69..cd3093863 100644 --- a/dictionaries/de.dictionary.itop.core.php +++ b/dictionaries/de.dictionary.itop.core.php @@ -1044,4 +1044,14 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'Class:AsyncTask/Attribute:event_id+' => '', 'Class:AsyncTask/Attribute:finalclass' => 'Final Class', 'Class:AsyncTask/Attribute:finalclass+' => '', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/de.dictionary.itop.ui.php b/dictionaries/de.dictionary.itop.ui.php index e74b24327..1ef51b3b5 100644 --- a/dictionaries/de.dictionary.itop.ui.php +++ b/dictionaries/de.dictionary.itop.ui.php @@ -441,6 +441,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Fehler: die Objekte wurden bereits gelöscht!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Sie sind nicht berechtigt, mehrere Objekte der Klasse %1$s zu löschen', 'UI:Error:DeleteNotAllowedOn_Class' => 'Sie sind nicht berechtigt, Objekte der Klasse zu löschen %1$s', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Sie sind nicht berechtigt, diese Massenaktualisierung der Objekte der Klasse "%1$s" durchzuführen.', 'UI:Error:ObjectAlreadyCloned' => 'Fehler: das Objekt wurde bereits dupliziert!', 'UI:Error:ObjectAlreadyCreated' => 'Fehler: das Objekt wurde bereits erstellt!', @@ -449,6 +450,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:Error:InvalidDashboard' => 'Fehler: Ungültiges Dashboard', 'UI:Error:MaintenanceMode' => 'Die Anwendung befindet sich derzeit im Wartungsmodus.', 'UI:Error:MaintenanceTitle' => 'Wartung', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Anzahl', 'UI:GroupBy:Count+' => 'Anzahl der Elemente', @@ -1540,6 +1542,8 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm 'UI:Search:Criteria:Raw:Filtered' => 'Gefiltert', 'UI:Search:Criteria:Raw:FilteredOn' => 'Gefiltert über %1$s', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1576,3 +1580,20 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Nachrichten von %1$s anzeigen', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Zeigen Sie höchstens %1$s Beiträge im Menü (%2$s) an.', )); + + +// OAuth +Dict::Add('DE DE', 'German', 'Deutsch', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/es_cr.dictionary.itop.core.php b/dictionaries/es_cr.dictionary.itop.core.php index 1b8bd02f3..615184f62 100644 --- a/dictionaries/es_cr.dictionary.itop.core.php +++ b/dictionaries/es_cr.dictionary.itop.core.php @@ -22,62 +22,62 @@ * @license http://opensource.org/licenses/AGPL-3.0 * @traductor Miguel Turrubiates */ -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Core:DeletedObjectLabel' => '%1s (eliminado)', - 'Core:DeletedObjectTip' => 'Elemento ha sido Eliminado en %1$s (%2$s)', + 'Core:DeletedObjectTip' => 'Elemento ha sido Eliminado en %1$s (%2$s)', 'Core:UnknownObjectLabel' => 'Elemento No Encontrado (Clase: %1$s, Identificador: %2$d)', 'Core:UnknownObjectTip' => 'El Elemento no pudo ser encontrado. Pudo haber sido eliminado hace tiempo y purgado de la Bitácora.', 'Core:UniquenessDefaultError' => 'Regla de unicidad \'%1$s\' en error', - 'Core:AttributeLinkedSet' => 'Arreglo de objetos', + 'Core:AttributeLinkedSet' => 'Arreglo de objetos', 'Core:AttributeLinkedSet+' => 'Cualquier tipo de objetos [subclass] de la misma clase', - 'Core:AttributeDashboard' => 'Panel de Control', + 'Core:AttributeDashboard' => 'Panel de Control', 'Core:AttributeDashboard+' => 'Panel de control y supervisión', - 'Core:AttributePhoneNumber' => 'Número telefónico', + 'Core:AttributePhoneNumber' => 'Número telefónico', 'Core:AttributePhoneNumber+' => '', - 'Core:AttributeObsolescenceDate' => 'Fecha de Obsolescencia', + 'Core:AttributeObsolescenceDate' => 'Fecha de Obsolescencia', 'Core:AttributeObsolescenceDate+' => '', - 'Core:AttributeTagSet' => 'Lista de etiquetas', - 'Core:AttributeTagSet+' => '', - 'Core:AttributeSet:placeholder' => 'Click to agregar', - 'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)', - 'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s de %3$s)', + 'Core:AttributeTagSet' => 'Lista de etiquetas', + 'Core:AttributeTagSet+' => '', + 'Core:AttributeSet:placeholder' => 'Click to agregar', + 'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)', + 'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s de %3$s)', 'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s de clases hijas)', - 'Core:AttributeCaseLog' => 'Bitácora', + 'Core:AttributeCaseLog' => 'Bitácora', 'Core:AttributeCaseLog+' => '', - 'Core:AttributeMetaEnum' => 'Enumeración Calculada', + 'Core:AttributeMetaEnum' => 'Enumeración Calculada', 'Core:AttributeMetaEnum+' => '', - 'Core:AttributeLinkedSetIndirect' => 'Arreglo de objetos (N-N)', + 'Core:AttributeLinkedSetIndirect' => 'Arreglo de objetos (N-N)', 'Core:AttributeLinkedSetIndirect+' => 'Cualquier tipo de objetos [subclass] de la misma clase', - 'Core:AttributeInteger' => 'Entero', + 'Core:AttributeInteger' => 'Entero', 'Core:AttributeInteger+' => 'Valor numérico (puede ser negativo)', - 'Core:AttributeDecimal' => 'Decimal', + 'Core:AttributeDecimal' => 'Decimal', 'Core:AttributeDecimal+' => 'Valor decimal (puede ser negativo)', - 'Core:AttributeBoolean' => 'Booleano', - 'Core:AttributeBoolean+' => 'Booleano', + 'Core:AttributeBoolean' => 'Booleano', + 'Core:AttributeBoolean+' => 'Booleano', 'Core:AttributeBoolean/Value:null' => 'Nulo', - 'Core:AttributeBoolean/Value:yes' => 'Si', - 'Core:AttributeBoolean/Value:no' => 'No', + 'Core:AttributeBoolean/Value:yes' => 'Si', + 'Core:AttributeBoolean/Value:no' => 'No', - 'Core:AttributeArchiveFlag' => 'Bandera de Archivado', - 'Core:AttributeArchiveFlag/Value:yes' => 'Si', + 'Core:AttributeArchiveFlag' => 'Bandera de Archivado', + 'Core:AttributeArchiveFlag/Value:yes' => 'Si', 'Core:AttributeArchiveFlag/Value:yes+' => 'Este objeto es solo visible en modo Archivado', - 'Core:AttributeArchiveFlag/Value:no' => 'No', - 'Core:AttributeArchiveFlag/Label' => 'Archivado', - 'Core:AttributeArchiveFlag/Label+' => '', - 'Core:AttributeArchiveDate/Label' => 'Fecha de Archivado', + 'Core:AttributeArchiveFlag/Value:no' => 'No', + 'Core:AttributeArchiveFlag/Label' => 'Archivado', + 'Core:AttributeArchiveFlag/Label+' => '', + 'Core:AttributeArchiveDate/Label' => 'Fecha de Archivado', 'Core:AttributeArchiveDate/Label+' => '', 'Core:AttributeObsolescenceFlag' => 'Bandera de Obsolescencia', @@ -193,24 +193,24 @@ Operadores:
'Core:AttributeTable' => 'Tabla', 'Core:AttributeTable+' => 'Arreglo indexado con dos dimensiones', - 'Core:AttributePropertySet' => 'Propiedades', + 'Core:AttributePropertySet' => 'Propiedades', 'Core:AttributePropertySet+' => 'Lista de propiedades sin tipo (nombre y valor)', - 'Core:AttributeFriendlyName' => 'Nombre común', + 'Core:AttributeFriendlyName' => 'Nombre común', 'Core:AttributeFriendlyName+' => 'Atributo creado automáticamente; el nombre común es obtenido de varios atributos', - 'Core:FriendlyName-Label' => 'Nombre común', + 'Core:FriendlyName-Label' => 'Nombre común', 'Core:FriendlyName-Description' => 'Nombre común', - 'Core:AttributeTag' => 'Etiquetas', + 'Core:AttributeTag' => 'Etiquetas', 'Core:AttributeTag+' => '', - - 'Core:Context=REST/JSON' => 'REST', - 'Core:Context=Synchro' => 'Synchro', - 'Core:Context=Setup' => 'Configuración', + + 'Core:Context=REST/JSON' => 'REST', + 'Core:Context=Synchro' => 'Synchro', + 'Core:Context=Setup' => 'Configuración', 'Core:Context=GUI:Console' => 'Consola', - 'Core:Context=CRON' => 'cron', - 'Core:Context=GUI:Portal' => 'Portal', + 'Core:Context=CRON' => 'cron', + 'Core:Context=GUI:Portal' => 'Portal', )); @@ -223,7 +223,7 @@ Operadores:
// Class: CMDBChange // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:CMDBChange' => 'Cambio', 'Class:CMDBChange+' => 'Cambios', 'Class:CMDBChange/Attribute:date' => 'Fecha', @@ -236,7 +236,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: CMDBChangeOp // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:CMDBChangeOp' => 'Operación de Cambios', 'Class:CMDBChangeOp+' => 'Operación de Cambios', 'Class:CMDBChangeOp/Attribute:change' => 'Cambio', @@ -257,7 +257,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: CMDBChangeOpCreate // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:CMDBChangeOpCreate' => 'Creación de Objeto', 'Class:CMDBChangeOpCreate+' => 'Creación de Objeto', )); @@ -266,7 +266,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: CMDBChangeOpDelete // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:CMDBChangeOpDelete' => 'Borrado de Objeto', 'Class:CMDBChangeOpDelete+' => 'Borrado de Objeto', )); @@ -275,7 +275,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: CMDBChangeOpSetAttribute // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:CMDBChangeOpSetAttribute' => 'Cambio en Objeto', 'Class:CMDBChangeOpSetAttribute+' => 'Cambio en Objeto', 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Atributo', @@ -286,7 +286,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: CMDBChangeOpSetAttributeScalar // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:CMDBChangeOpSetAttributeScalar' => 'Cambio de Propiedad', 'Class:CMDBChangeOpSetAttributeScalar+' => 'Cambio de Propiedades escalares del Objeto', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Valor Anterior', @@ -295,7 +295,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'Nuevo Valor del Atributo', )); // Used by CMDBChangeOp... & derived classes -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Change:ObjectCreated' => 'Objeto Creado', 'Change:ObjectDeleted' => 'Objeto Eliminado', 'Change:ObjectModified' => 'Objeto Modificado', @@ -314,7 +314,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: CMDBChangeOpSetAttributeBlob // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:CMDBChangeOpSetAttributeBlob' => 'Cambio de Datos', 'Class:CMDBChangeOpSetAttributeBlob+' => 'Cambio de Datos', 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Valor Anterior', @@ -325,7 +325,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: CMDBChangeOpSetAttributeText // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:CMDBChangeOpSetAttributeText' => 'Cambio de Texto', 'Class:CMDBChangeOpSetAttributeText+' => 'Cambio de Texto', 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Valor Anterior', @@ -336,7 +336,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: Event // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:Event' => 'Bitácora de Eventos', 'Class:Event+' => 'Evento interno de aplicación', 'Class:Event/Attribute:message' => 'Mensaje', @@ -353,7 +353,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: EventNotification // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:EventNotification' => 'Notificación de Evento', 'Class:EventNotification+' => 'Notificación de Evento', 'Class:EventNotification/Attribute:trigger_id' => 'Disparador', @@ -368,7 +368,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: EventNotificationEmail // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:EventNotificationEmail' => 'Correo Electrónico de Notificación de Evento', 'Class:EventNotificationEmail+' => 'Correo Electrónico de Notificación de Evento', 'Class:EventNotificationEmail/Attribute:to' => 'Para', @@ -391,7 +391,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: EventIssue // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:EventIssue' => 'Registro de Evento', 'Class:EventIssue+' => 'Evidencia de un evento (warning, error, etc.)', 'Class:EventIssue/Attribute:issue' => 'Evento', @@ -414,7 +414,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: EventWebService // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:EventWebService' => 'Evento de WebService', 'Class:EventWebService+' => 'Evidencia de una llamada de servicio Web', 'Class:EventWebService/Attribute:verb' => 'Verbo', @@ -431,7 +431,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Class:EventWebService/Attribute:data+' => 'Datos de Resultado', )); -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:EventRestService' => 'Llamada REST/JSON', 'Class:EventRestService+' => 'Traza de llamada a servicio REST/JSON', 'Class:EventRestService/Attribute:operation' => 'Operación', @@ -452,7 +452,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: EventLoginUsage // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:EventLoginUsage' => 'Uso de la Cuenta', 'Class:EventLoginUsage+' => 'Uso de la Cuenta', 'Class:EventLoginUsage/Attribute:user_id' => 'Usuario', @@ -467,7 +467,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: Action // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:Action' => 'Acción Personalizada', 'Class:Action+' => 'Acción definida por el usuario', 'Class:Action/Attribute:name' => 'Nombre', @@ -492,7 +492,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: ActionNotification // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:ActionNotification' => 'Notificación', 'Class:ActionNotification+' => 'Notificación (resúmen)', )); @@ -501,7 +501,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: ActionEmail // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:ActionEmail' => 'Notificación por Correo Electrónico', 'Class:ActionEmail+' => 'Notificación por Correo Electrónico', 'Class:ActionEmail/Attribute:test_recipient' => 'Destinatario de Prueba', @@ -534,24 +534,24 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: Trigger // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'Class:Trigger' => 'Disparador', - 'Class:Trigger+' => 'Disparador', - 'Class:Trigger/Attribute:description' => 'Descripción', +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( + 'Class:Trigger' => 'Disparador', + 'Class:Trigger+' => 'Disparador', + 'Class:Trigger/Attribute:description' => 'Descripción', 'Class:Trigger/Attribute:description+' => 'Descripción', - 'Class:Trigger/Attribute:action_list' => 'Acciones', + 'Class:Trigger/Attribute:action_list' => 'Acciones', 'Class:Trigger/Attribute:action_list+' => 'Acciones realizadas cuando se activó el disparador', - 'Class:Trigger/Attribute:finalclass' => 'Clase', - 'Class:Trigger/Attribute:finalclass+' => 'Clase', - 'Class:Trigger/Attribute:context' => 'Contexto', - 'Class:Trigger/Attribute:context+' => 'Contexto para permitir el inicio del disparador', + 'Class:Trigger/Attribute:finalclass' => 'Clase', + 'Class:Trigger/Attribute:finalclass+' => 'Clase', + 'Class:Trigger/Attribute:context' => 'Contexto', + 'Class:Trigger/Attribute:context+' => 'Contexto para permitir el inicio del disparador', )); // // Class: TriggerOnObject // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:TriggerOnObject' => 'Disparador (Depende de la clase)', 'Class:TriggerOnObject+' => 'Disparador en una clase de objeto dada', 'Class:TriggerOnObject/Attribute:target_class' => 'Clase destino', @@ -566,7 +566,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: TriggerOnPortalUpdate // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:TriggerOnPortalUpdate' => 'Disparador (cuando se actualiza desde el portal)', 'Class:TriggerOnPortalUpdate+' => 'Disparador cuando un usuario actualiza desde el portal', )); @@ -575,7 +575,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: TriggerOnStateChange // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:TriggerOnStateChange' => 'Disparador (en cambio de estado)', 'Class:TriggerOnStateChange+' => 'Disparador en cambio de estado de objeto', 'Class:TriggerOnStateChange/Attribute:state' => 'Estado', @@ -586,7 +586,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: TriggerOnStateEnter // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:TriggerOnStateEnter' => 'Disparador (entrando a un estado)', 'Class:TriggerOnStateEnter+' => 'Disparador en cambio de estado de objeto - entrando', )); @@ -595,7 +595,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: TriggerOnStateLeave // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:TriggerOnStateLeave' => 'Disparador (saliendo de un estado)', 'Class:TriggerOnStateLeave+' => 'Disparador en cambio de estado de objeto - saliendo', )); @@ -604,7 +604,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: TriggerOnObjectCreate // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:TriggerOnObjectCreate' => 'Disparador (creación de objeto)', 'Class:TriggerOnObjectCreate+' => 'Disparador en la creación de objeto (hija de clase) de una clase dada', )); @@ -613,7 +613,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: TriggerOnObjectDelete // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:TriggerOnObjectDelete' => 'Disparador (eliminando un objecto)', 'Class:TriggerOnObjectDelete+' => 'Disparador al eliminar un objecto de la clase dada [o una clase hija] ', )); @@ -622,7 +622,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: TriggerOnObjectUpdate // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:TriggerOnObjectUpdate' => 'Disparador (actualizando un objecto)', 'Class:TriggerOnObjectUpdate+' => 'Disparador al actualizar un objeto de la clase dada [o una clase hija]', 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Campos objetivo', @@ -633,7 +633,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: TriggerOnThresholdReached // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:TriggerOnThresholdReached' => 'Disparador (en umbral)', 'Class:TriggerOnThresholdReached+' => 'Disparador en umbral Stop-Watch alcanzado', 'Class:TriggerOnThresholdReached/Attribute:stop_watch_code' => 'Detener watch', @@ -646,7 +646,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: lnkTriggerAction // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:lnkTriggerAction' => 'Relación Acción y Disparador', 'Class:lnkTriggerAction+' => 'Relación Acción y Disparador', 'Class:lnkTriggerAction/Attribute:action_id' => 'Acción', @@ -664,7 +664,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // // Synchro Data Source // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:SynchroDataSource/Attribute:name' => 'Nombre', 'Class:SynchroDataSource/Attribute:name+' => 'Nombre de la Fuente de Datos', 'Class:SynchroDataSource/Attribute:description' => 'Descripción', @@ -912,80 +912,80 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Core:BulkExport:PDFPageOrientation' => 'Orientación de la Página:', 'Core:BulkExport:PageOrientation-L' => 'Horizontal', 'Core:BulkExport:PageOrientation-P' => 'Vertical', - 'Core:BulkExport:XMLFormat' => 'Archivo XML (*.xml)', - 'Core:BulkExport:XMLOptions' => 'Opciones XML', - 'Core:BulkExport:SpreadsheetFormat' => 'Formato Tabla HTML (*.html)', - 'Core:BulkExport:SpreadsheetOptions' => 'Opciones de Tabla', - 'Core:BulkExport:OptionNoLocalize' => 'Código de exportación en lugar de etiqueta', - 'Core:BulkExport:OptionLinkSets' => 'Incluir objetos ligados', - 'Core:BulkExport:OptionFormattedText' => 'Conservar formato de texto', - 'Core:BulkExport:ScopeDefinition' => 'Definición de los objetos a exportar', - 'Core:BulkExportLabelOQLExpression' => 'Consulta OQL:', - 'Core:BulkExportLabelPhrasebookEntry' => 'Entrada Consulta de Libreta de Consultas:', - 'Core:BulkExportMessageEmptyOQL' => 'Por favor ingrese una consulta OQL válida.', - 'Core:BulkExportMessageEmptyPhrasebookEntry' => 'Por favor seleccione un entrada válida de la libreta de consultas.', - 'Core:BulkExportQueryPlaceholder' => 'Escriba una consulta OQL aquí...', - 'Core:BulkExportCanRunNonInteractive' => 'Click aquí para ejecutar la exportación en modo no interactivo.', - 'Core:BulkExportLegacyExport' => 'Click aquí para acceder a la exportación tradicional.', - 'Core:BulkExport:XLSXOptions' => 'Opciones Excel', - 'Core:BulkExport:TextFormat' => 'Compos texto conteniendo algunas marcas HTML', - 'Core:BulkExport:DateTimeFormat' => 'Formato de fecha y hora', + 'Core:BulkExport:XMLFormat' => 'Archivo XML (*.xml)', + 'Core:BulkExport:XMLOptions' => 'Opciones XML', + 'Core:BulkExport:SpreadsheetFormat' => 'Formato Tabla HTML (*.html)', + 'Core:BulkExport:SpreadsheetOptions' => 'Opciones de Tabla', + 'Core:BulkExport:OptionNoLocalize' => 'Código de exportación en lugar de etiqueta', + 'Core:BulkExport:OptionLinkSets' => 'Incluir objetos ligados', + 'Core:BulkExport:OptionFormattedText' => 'Conservar formato de texto', + 'Core:BulkExport:ScopeDefinition' => 'Definición de los objetos a exportar', + 'Core:BulkExportLabelOQLExpression' => 'Consulta OQL:', + 'Core:BulkExportLabelPhrasebookEntry' => 'Entrada Consulta de Libreta de Consultas:', + 'Core:BulkExportMessageEmptyOQL' => 'Por favor ingrese una consulta OQL válida.', + 'Core:BulkExportMessageEmptyPhrasebookEntry' => 'Por favor seleccione un entrada válida de la libreta de consultas.', + 'Core:BulkExportQueryPlaceholder' => 'Escriba una consulta OQL aquí...', + 'Core:BulkExportCanRunNonInteractive' => 'Click aquí para ejecutar la exportación en modo no interactivo.', + 'Core:BulkExportLegacyExport' => 'Click aquí para acceder a la exportación tradicional.', + 'Core:BulkExport:XLSXOptions' => 'Opciones Excel', + 'Core:BulkExport:TextFormat' => 'Compos texto conteniendo algunas marcas HTML', + 'Core:BulkExport:DateTimeFormat' => 'Formato de fecha y hora', 'Core:BulkExport:DateTimeFormatDefault_Example' => 'Formato por omisión (%1$s), ej. %2$s', - 'Core:BulkExport:DateTimeFormatCustom_Format' => 'Formato personalizado: %1$s', - 'Core:BulkExport:PDF:PageNumber' => 'Página %1$s', - 'Core:DateTime:Placeholder_d' => 'DD', // Day of the month: 2 digits (with leading zero) - 'Core:DateTime:Placeholder_j' => 'D', // Day of the month: 1 or 2 digits (without leading zero) - 'Core:DateTime:Placeholder_m' => 'MM', // Month on 2 digits i.e. 01-12 - 'Core:DateTime:Placeholder_n' => 'M', // Month on 1 or 2 digits 1-12 - 'Core:DateTime:Placeholder_Y' => 'AAAA', // Year on 4 digits - 'Core:DateTime:Placeholder_y' => 'AA', // Year on 2 digits - 'Core:DateTime:Placeholder_H' => 'hh', // Hour 00..23 - 'Core:DateTime:Placeholder_h' => 'h', // Hour 01..12 - 'Core:DateTime:Placeholder_G' => 'hh', // Hour 0..23 - 'Core:DateTime:Placeholder_g' => 'h', // Hour 1..12 - 'Core:DateTime:Placeholder_a' => 'am/pm', // am/pm (lowercase) - 'Core:DateTime:Placeholder_A' => 'AM/PM', // AM/PM (uppercase) - 'Core:DateTime:Placeholder_i' => 'mm', // minutes, 2 digits: 00..59 - 'Core:DateTime:Placeholder_s' => 'ss', // seconds, 2 digits 00..59 - 'Core:Validator:Default' => 'Formato incorrecto', - 'Core:Validator:Mandatory' => 'Por favor, ingrese este campo', - 'Core:Validator:MustBeInteger' => 'Debe ser un entero', - 'Core:Validator:MustSelectOne' => 'Por favor, seleccione uno', + 'Core:BulkExport:DateTimeFormatCustom_Format' => 'Formato personalizado: %1$s', + 'Core:BulkExport:PDF:PageNumber' => 'Página %1$s', + 'Core:DateTime:Placeholder_d' => 'DD', // Day of the month: 2 digits (with leading zero) + 'Core:DateTime:Placeholder_j' => 'D', // Day of the month: 1 or 2 digits (without leading zero) + 'Core:DateTime:Placeholder_m' => 'MM', // Month on 2 digits i.e. 01-12 + 'Core:DateTime:Placeholder_n' => 'M', // Month on 1 or 2 digits 1-12 + 'Core:DateTime:Placeholder_Y' => 'AAAA', // Year on 4 digits + 'Core:DateTime:Placeholder_y' => 'AA', // Year on 2 digits + 'Core:DateTime:Placeholder_H' => 'hh', // Hour 00..23 + 'Core:DateTime:Placeholder_h' => 'h', // Hour 01..12 + 'Core:DateTime:Placeholder_G' => 'hh', // Hour 0..23 + 'Core:DateTime:Placeholder_g' => 'h', // Hour 1..12 + 'Core:DateTime:Placeholder_a' => 'am/pm', // am/pm (lowercase) + 'Core:DateTime:Placeholder_A' => 'AM/PM', // AM/PM (uppercase) + 'Core:DateTime:Placeholder_i' => 'mm', // minutes, 2 digits: 00..59 + 'Core:DateTime:Placeholder_s' => 'ss', // seconds, 2 digits 00..59 + 'Core:Validator:Default' => 'Formato incorrecto', + 'Core:Validator:Mandatory' => 'Por favor, ingrese este campo', + 'Core:Validator:MustBeInteger' => 'Debe ser un entero', + 'Core:Validator:MustSelectOne' => 'Por favor, seleccione uno', )); // // Class: TagSetFieldData // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'Class:TagSetFieldData' => '%2$s para la clase %1$s', +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( + 'Class:TagSetFieldData' => '%2$s para la clase %1$s', 'Class:TagSetFieldData+' => 'Datos de campo', - 'Class:TagSetFieldData/Attribute:code' => 'Código', - 'Class:TagSetFieldData/Attribute:code+' => 'Código interno. Debe contener al menos tres caracteres alfanuméricos', - 'Class:TagSetFieldData/Attribute:label' => 'Etiqueta', - 'Class:TagSetFieldData/Attribute:label+' => 'Etiqueta mostrada', - 'Class:TagSetFieldData/Attribute:description' => 'Descripción', + 'Class:TagSetFieldData/Attribute:code' => 'Código', + 'Class:TagSetFieldData/Attribute:code+' => 'Código interno. Debe contener al menos tres caracteres alfanuméricos', + 'Class:TagSetFieldData/Attribute:label' => 'Etiqueta', + 'Class:TagSetFieldData/Attribute:label+' => 'Etiqueta mostrada', + 'Class:TagSetFieldData/Attribute:description' => 'Descripción', 'Class:TagSetFieldData/Attribute:description+' => 'Descripción de la etiqueta', - 'Class:TagSetFieldData/Attribute:finalclass' => 'Clase', - 'Class:TagSetFieldData/Attribute:obj_class' => 'Clase de objeto', - 'Class:TagSetFieldData/Attribute:obj_attcode' => 'Código de campo', + 'Class:TagSetFieldData/Attribute:finalclass' => 'Clase', + 'Class:TagSetFieldData/Attribute:obj_class' => 'Clase de objeto', + 'Class:TagSetFieldData/Attribute:obj_attcode' => 'Código de campo', - 'Core:TagSetFieldData:ErrorDeleteUsedTag' => 'Etiquetas es uso no pueden ser borradas', + 'Core:TagSetFieldData:ErrorDeleteUsedTag' => 'Etiquetas es uso no pueden ser borradas', 'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Los códigos o las etiquetas deben ser únicos', - 'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'El código de la etiqueta debe contener entre 3 y %1$d caracteres alfanuméricos', - 'Core:TagSetFieldData:ErrorTagCodeReservedWord' => 'El código elegido es una palabra reservada', - 'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'La etiqueta no odebe contener \'%1$s\' y no puede estar vacía', - 'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Códigos de etiqueta en uso no pueden ser borrados', - 'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'Etiquetas "Object Class" no pueden ser cambiadas', + 'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'El código de la etiqueta debe contener entre 3 y %1$d caracteres alfanuméricos', + 'Core:TagSetFieldData:ErrorTagCodeReservedWord' => 'El código elegido es una palabra reservada', + 'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'La etiqueta no odebe contener \'%1$s\' y no puede estar vacía', + 'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Códigos de etiqueta en uso no pueden ser borrados', + 'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'Etiquetas "Object Class" no pueden ser cambiadas', 'Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed' => 'Etiquetas "Attribute Code" no pueden ser cambiadas', - 'Core:TagSetFieldData:WhereIsThisTagTab' => 'Uso de la etiqueta (%1$d)', - 'Core:TagSetFieldData:NoEntryFound' => 'No hay entradas para esta etiqueta', + 'Core:TagSetFieldData:WhereIsThisTagTab' => 'Uso de la etiqueta (%1$d)', + 'Core:TagSetFieldData:NoEntryFound' => 'No hay entradas para esta etiqueta', )); // // Class: DBProperty // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:DBProperty' => 'Propiedad BD', 'Class:DBProperty+' => 'Propiedad de Base de Datos', 'Class:DBProperty/Attribute:name' => 'Nombre', @@ -1003,7 +1003,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // // Class: BackgroundTask // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:BackgroundTask' => 'Tarea en Segundo Plano', 'Class:BackgroundTask+' => 'Tarea en Segundo Plano', 'Class:BackgroundTask/Attribute:class_name' => 'Nombre de Clase', @@ -1033,7 +1033,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // // Class: AsyncTask // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:AsyncTask' => 'Tarea Asíncrona', 'Class:AsyncTask+' => 'Tarea Asíncrona', 'Class:AsyncTask/Attribute:created' => 'Creado', @@ -1046,4 +1046,14 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Class:AsyncTask/Attribute:event_id+' => 'Evento', 'Class:AsyncTask/Attribute:finalclass' => 'Clase', 'Class:AsyncTask/Attribute:finalclass+' => 'Clase', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index caac26bee..b24fd05fb 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -33,7 +33,7 @@ // // Class: AuditCategory // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:AuditCategory' => 'Auditoría de Categorías', 'Class:AuditCategory+' => 'Auditoría de Categorías', 'Class:AuditCategory/Attribute:name' => 'Nombre de Categoría', @@ -50,35 +50,35 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: AuditRule // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'Class:AuditRule' => 'Regla de Auditoría', - 'Class:AuditRule+' => 'Regla a revisar para una categoría de auditoría específica', - 'Class:AuditRule/Attribute:name' => 'Nombre de la Regla', - 'Class:AuditRule/Attribute:name+' => 'Nombre corto para esta regla', - 'Class:AuditRule/Attribute:description' => 'Descripción de regla de auditoría', - 'Class:AuditRule/Attribute:description+' => 'Descripción larga para esta regla de auditoría', - 'Class:TagSetFieldData/Attribute:finalclass' => 'Clase Etiqueta', - 'Class:TagSetFieldData/Attribute:obj_class' => 'Clase Objeto', - 'Class:TagSetFieldData/Attribute:obj_attcode' => 'Código de Campo', - 'Class:AuditRule/Attribute:query' => 'Consulta a Ejecutar', - 'Class:AuditRule/Attribute:query+' => 'Expresión OQL a ejecutar', - 'Class:AuditRule/Attribute:valid_flag' => '¿Objetos Válidos?', - 'Class:AuditRule/Attribute:valid_flag+' => 'Verdadero si la regla retorna los objetos válidos, falso cualquier otra cosa', - 'Class:AuditRule/Attribute:valid_flag/Value:true' => 'Verdadero', - 'Class:AuditRule/Attribute:valid_flag/Value:true+' => '', - 'Class:AuditRule/Attribute:valid_flag/Value:false' => 'Falso', +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( + 'Class:AuditRule' => 'Regla de Auditoría', + 'Class:AuditRule+' => 'Regla a revisar para una categoría de auditoría específica', + 'Class:AuditRule/Attribute:name' => 'Nombre de la Regla', + 'Class:AuditRule/Attribute:name+' => 'Nombre corto para esta regla', + 'Class:AuditRule/Attribute:description' => 'Descripción de regla de auditoría', + 'Class:AuditRule/Attribute:description+' => 'Descripción larga para esta regla de auditoría', + 'Class:TagSetFieldData/Attribute:finalclass' => 'Clase Etiqueta', + 'Class:TagSetFieldData/Attribute:obj_class' => 'Clase Objeto', + 'Class:TagSetFieldData/Attribute:obj_attcode' => 'Código de Campo', + 'Class:AuditRule/Attribute:query' => 'Consulta a Ejecutar', + 'Class:AuditRule/Attribute:query+' => 'Expresión OQL a ejecutar', + 'Class:AuditRule/Attribute:valid_flag' => '¿Objetos Válidos?', + 'Class:AuditRule/Attribute:valid_flag+' => 'Verdadero si la regla retorna los objetos válidos, falso cualquier otra cosa', + 'Class:AuditRule/Attribute:valid_flag/Value:true' => 'Verdadero', + 'Class:AuditRule/Attribute:valid_flag/Value:true+' => '', + 'Class:AuditRule/Attribute:valid_flag/Value:false' => 'Falso', 'Class:AuditRule/Attribute:valid_flag/Value:false+' => '', - 'Class:AuditRule/Attribute:category_id' => 'Categoría', - 'Class:AuditRule/Attribute:category_id+' => 'La categoría para esta regla', - 'Class:AuditRule/Attribute:category_name' => 'Categoría', - 'Class:AuditRule/Attribute:category_name+' => 'Nombre de la categoría para esta regla', + 'Class:AuditRule/Attribute:category_id' => 'Categoría', + 'Class:AuditRule/Attribute:category_id+' => 'La categoría para esta regla', + 'Class:AuditRule/Attribute:category_name' => 'Categoría', + 'Class:AuditRule/Attribute:category_name+' => 'Nombre de la categoría para esta regla', )); // // Class: QueryOQL // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:Query' => 'Consulta', 'Class:Query+' => 'Un query es un set de datos definidos de manera dinámica', 'Class:Query/Attribute:name' => 'Nombre', @@ -102,53 +102,53 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: User // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'Class:User' => 'Usuario', - 'Class:User+' => 'Credencial de usuario', - 'Class:User/Attribute:finalclass' => 'Tipo de Cuenta', - 'Class:User/Attribute:finalclass+' => 'Tipo de Cuenta', - 'Class:User/Attribute:contactid' => 'Contacto (persona)', - 'Class:User/Attribute:contactid+' => 'Detalles personales de la información de negocio', - 'Class:User/Attribute:org_id' => 'Organización', - 'Class:User/Attribute:org_id+' => 'Organización de la persona asociada', - 'Class:User/Attribute:last_name' => 'Apellidos', - 'Class:User/Attribute:last_name+' => 'Apellidos', - 'Class:User/Attribute:first_name' => 'Nombre', - 'Class:User/Attribute:first_name+' => 'Nombre', - 'Class:User/Attribute:email' => 'Correo Electrónico', - 'Class:User/Attribute:email+' => 'Correo Electrónico del contacto correspondiente', - 'Class:User/Attribute:login' => 'Usuario', - 'Class:User/Attribute:login+' => 'cadena de identificacion de usuario', - 'Class:User/Attribute:language' => 'Idioma', - 'Class:User/Attribute:language+' => 'idioma del usuario', - 'Class:User/Attribute:language/Value:EN US' => 'English', +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( + 'Class:User' => 'Usuario', + 'Class:User+' => 'Credencial de usuario', + 'Class:User/Attribute:finalclass' => 'Tipo de Cuenta', + 'Class:User/Attribute:finalclass+' => 'Tipo de Cuenta', + 'Class:User/Attribute:contactid' => 'Contacto (persona)', + 'Class:User/Attribute:contactid+' => 'Detalles personales de la información de negocio', + 'Class:User/Attribute:org_id' => 'Organización', + 'Class:User/Attribute:org_id+' => 'Organización de la persona asociada', + 'Class:User/Attribute:last_name' => 'Apellidos', + 'Class:User/Attribute:last_name+' => 'Apellidos', + 'Class:User/Attribute:first_name' => 'Nombre', + 'Class:User/Attribute:first_name+' => 'Nombre', + 'Class:User/Attribute:email' => 'Correo Electrónico', + 'Class:User/Attribute:email+' => 'Correo Electrónico del contacto correspondiente', + 'Class:User/Attribute:login' => 'Usuario', + 'Class:User/Attribute:login+' => 'cadena de identificacion de usuario', + 'Class:User/Attribute:language' => 'Idioma', + 'Class:User/Attribute:language+' => 'idioma del usuario', + 'Class:User/Attribute:language/Value:EN US' => 'English', 'Class:User/Attribute:language/Value:EN US+' => 'English (U.S.)', - 'Class:User/Attribute:language/Value:FR FR' => 'Frances', + 'Class:User/Attribute:language/Value:FR FR' => 'Frances', 'Class:User/Attribute:language/Value:FR FR+' => 'Frances (Francia)', - 'Class:User/Attribute:profile_list' => 'Perfiles', - 'Class:User/Attribute:profile_list+' => 'Roles, y permisos otorgados a esa persona', - 'Class:User/Attribute:allowed_org_list' => 'Organizaciones Permitidas', - 'Class:User/Attribute:allowed_org_list+' => 'El usuario tiene permitido ver la información perteneciente a las siguientes Organizaciones. Sino se especificó una Organización, esto no es una restricción.', - 'Class:User/Attribute:status' => 'Estatus', - 'Class:User/Attribute:status+' => 'Cuando el usuario se encuentra habilitado o deshabilitado.', - 'Class:User/Attribute:status/Value:enabled' => 'Habilitado', + 'Class:User/Attribute:profile_list' => 'Perfiles', + 'Class:User/Attribute:profile_list+' => 'Roles, y permisos otorgados a esa persona', + 'Class:User/Attribute:allowed_org_list' => 'Organizaciones Permitidas', + 'Class:User/Attribute:allowed_org_list+' => 'El usuario tiene permitido ver la información perteneciente a las siguientes Organizaciones. Sino se especificó una Organización, esto no es una restricción.', + 'Class:User/Attribute:status' => 'Estatus', + 'Class:User/Attribute:status+' => 'Cuando el usuario se encuentra habilitado o deshabilitado.', + 'Class:User/Attribute:status/Value:enabled' => 'Habilitado', 'Class:User/Attribute:status/Value:disabled' => 'Deshabilitado', - 'Class:User/Error:LoginMustBeUnique' => 'Usuario debe ser único - "%1s" ya se encuentra en uso.', - 'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Al menos un Perfil debe ser asignado a este usuario.', + 'Class:User/Error:LoginMustBeUnique' => 'Usuario debe ser único - "%1s" ya se encuentra en uso.', + 'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Al menos un Perfil debe ser asignado a este usuario.', 'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Al menos una organización debe ser asignada a este usuario.', - 'Class:User/Error:OrganizationNotAllowed' => 'Organización no permitida.', - 'Class:User/Error:UserOrganizationNotAllowed' => 'El usuario no pertenece a las oganizaciones permitidas.', - 'Class:User/Error:PersonIsMandatory' => 'El Contacto es obligatorio.', - 'Class:UserInternal' => 'Usuario Interno', - 'Class:UserInternal+' => 'Usuario definido en iTop', + 'Class:User/Error:OrganizationNotAllowed' => 'Organización no permitida.', + 'Class:User/Error:UserOrganizationNotAllowed' => 'El usuario no pertenece a las oganizaciones permitidas.', + 'Class:User/Error:PersonIsMandatory' => 'El Contacto es obligatorio.', + 'Class:UserInternal' => 'Usuario Interno', + 'Class:UserInternal+' => 'Usuario definido en iTop', )); // // Class: URP_Profiles // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:URP_Profiles' => 'Perfil', 'Class:URP_Profiles+' => 'Perfil de usuario', 'Class:URP_Profiles/Attribute:name' => 'Nombre', @@ -163,7 +163,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: URP_Dimensions // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:URP_Dimensions' => 'Dimensión', 'Class:URP_Dimensions+' => 'Dimensión de Aplicación (definiendo silos)', 'Class:URP_Dimensions/Attribute:name' => 'Nombre', @@ -178,7 +178,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: URP_UserProfile // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:URP_UserProfile' => 'Asignación de Perfiles', 'Class:URP_UserProfile+' => 'Perfiles de Usuarios', 'Class:URP_UserProfile/Attribute:userid' => 'Usuario', @@ -198,26 +198,26 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'Class:URP_UserOrg' => 'Organizaciones de Usuario', - 'Class:URP_UserOrg+' => 'Organizaciones Permitidas', - 'Class:URP_UserOrg/Attribute:userid' => 'Usuario', - 'Class:URP_UserOrg/Attribute:userid+' => 'Cuenta de usuario', - 'Class:URP_UserOrg/Attribute:userlogin' => 'Login', - 'Class:URP_UserOrg/Attribute:userlogin+' => 'Login del usuario', - 'Class:URP_UserOrg/Attribute:allowed_org_id' => 'Organización', - 'Class:URP_UserOrg/Attribute:allowed_org_id+' => 'Organización Permitida', - 'Class:URP_UserOrg/Attribute:allowed_org_name' => 'Organización', +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( + 'Class:URP_UserOrg' => 'Organizaciones de Usuario', + 'Class:URP_UserOrg+' => 'Organizaciones Permitidas', + 'Class:URP_UserOrg/Attribute:userid' => 'Usuario', + 'Class:URP_UserOrg/Attribute:userid+' => 'Cuenta de usuario', + 'Class:URP_UserOrg/Attribute:userlogin' => 'Login', + 'Class:URP_UserOrg/Attribute:userlogin+' => 'Login del usuario', + 'Class:URP_UserOrg/Attribute:allowed_org_id' => 'Organización', + 'Class:URP_UserOrg/Attribute:allowed_org_id+' => 'Organización Permitida', + 'Class:URP_UserOrg/Attribute:allowed_org_name' => 'Organización', 'Class:URP_UserOrg/Attribute:allowed_org_name+' => 'Organización Permitida', - 'Class:URP_UserOrg/Attribute:reason' => 'Motivo', - 'Class:URP_UserOrg/Attribute:reason+' => 'Explicar porqué esta persona tiene permitido ver la información de esta Organización', + 'Class:URP_UserOrg/Attribute:reason' => 'Motivo', + 'Class:URP_UserOrg/Attribute:reason+' => 'Explicar porqué esta persona tiene permitido ver la información de esta Organización', )); // // Class: URP_ProfileProjection // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:URP_ProfileProjection' => 'Proyecciones de Perfil', 'Class:URP_ProfileProjection+' => 'Proyecciones de Perfil', 'Class:URP_ProfileProjection/Attribute:dimensionid' => 'Dimensión', @@ -238,7 +238,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: URP_ClassProjection // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:URP_ClassProjection' => 'Proyecciones de Clase', 'Class:URP_ClassProjection+' => 'Proyecciones de Clase', 'Class:URP_ClassProjection/Attribute:dimensionid' => 'Dimensión', @@ -257,7 +257,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: URP_ActionGrant // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:URP_ActionGrant' => 'Permisos sobre Acciones', 'Class:URP_ActionGrant+' => 'Permisos sobre Acciones', 'Class:URP_ActionGrant/Attribute:profileid' => 'Perfil', @@ -280,30 +280,30 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Class: URP_StimulusGrant // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'Class:URP_StimulusGrant' => 'Permisos de Cambio de Estado', - 'Class:URP_StimulusGrant+' => 'Permisos de Cambio de Estado en el Ciclo de Vida del Objeto', - 'Class:URP_StimulusGrant/Attribute:profileid' => 'Perfil', - 'Class:URP_StimulusGrant/Attribute:profileid+' => 'Uso del perfil', - 'Class:URP_StimulusGrant/Attribute:profile' => 'Perfil', - 'Class:URP_StimulusGrant/Attribute:profile+' => 'Uso del perfil', - 'Class:URP_StimulusGrant/Attribute:class' => 'Clase', - 'Class:URP_StimulusGrant/Attribute:class+' => 'Clase destino', - 'Class:URP_StimulusGrant/Attribute:permission' => 'Permiso', - 'Class:URP_StimulusGrant/Attribute:permission+' => '¿Permitido o No Permitido?', - 'Class:URP_StimulusGrant/Attribute:permission/Value:yes' => 'Si', +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( + 'Class:URP_StimulusGrant' => 'Permisos de Cambio de Estado', + 'Class:URP_StimulusGrant+' => 'Permisos de Cambio de Estado en el Ciclo de Vida del Objeto', + 'Class:URP_StimulusGrant/Attribute:profileid' => 'Perfil', + 'Class:URP_StimulusGrant/Attribute:profileid+' => 'Uso del perfil', + 'Class:URP_StimulusGrant/Attribute:profile' => 'Perfil', + 'Class:URP_StimulusGrant/Attribute:profile+' => 'Uso del perfil', + 'Class:URP_StimulusGrant/Attribute:class' => 'Clase', + 'Class:URP_StimulusGrant/Attribute:class+' => 'Clase destino', + 'Class:URP_StimulusGrant/Attribute:permission' => 'Permiso', + 'Class:URP_StimulusGrant/Attribute:permission+' => '¿Permitido o No Permitido?', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes' => 'Si', 'Class:URP_StimulusGrant/Attribute:permission/Value:yes+' => '', - 'Class:URP_StimulusGrant/Attribute:permission/Value:no' => 'No', - 'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => '', - 'Class:URP_StimulusGrant/Attribute:stimulus' => 'Cambio de Estado', - 'Class:URP_StimulusGrant/Attribute:stimulus+' => 'Código de Cambio de Estado', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no' => 'No', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => '', + 'Class:URP_StimulusGrant/Attribute:stimulus' => 'Cambio de Estado', + 'Class:URP_StimulusGrant/Attribute:stimulus+' => 'Código de Cambio de Estado', )); // // Class: URP_AttributeGrant // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:URP_AttributeGrant' => 'Permisos en Atributos', 'Class:URP_AttributeGrant+' => 'Permisos en Atributos', 'Class:URP_AttributeGrant/Attribute:actiongrantid' => 'Concesión de Acción', @@ -315,7 +315,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // // Class: UserDashboard // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Class:UserDashboard' => 'Tablero de Usuario', 'Class:UserDashboard+' => 'Tablero de Usuario', 'Class:UserDashboard/Attribute:user_id' => 'Usuario', @@ -329,7 +329,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // // Expression to Natural language // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Expression:Unit:Short:DAY' => 'd', 'Expression:Unit:Short:WEEK' => 's', 'Expression:Unit:Short:MONTH' => 'm', @@ -341,14 +341,14 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // String from the User Interface: menu, messages, buttons, etc... // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'BooleanLabel:yes' => 'Si', 'BooleanLabel:no' => 'No', 'UI:Login:Title' => 'Inicio de Sesión', - 'Menu:WelcomeMenu' => 'Bienvenido',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:WelcomeMenu+' => 'Bienvenido a iTop',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:WelcomeMenuPage' => 'Bienvenido',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:WelcomeMenuPage+' => 'Bienvenido a iTop',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:WelcomeMenu' => 'Bienvenido', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:WelcomeMenu+' => 'Bienvenido a iTop', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:WelcomeMenuPage' => 'Bienvenido', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:WelcomeMenuPage+' => 'Bienvenido a iTop', // Duplicated into itop-welcome-itil (will be removed from here...) 'UI:WelcomeMenu:Title' => 'Bienvenido a iTop', 'UI:WelcomeMenu:LeftBlock' => '

iTop es un completo portal de administración de servicios de TI basado en código abierto.

@@ -362,7 +362,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(

Todos los módulos pueden ser configurados, paso a paso, individual e independientemente de los otros.

', - 'UI:WelcomeMenu:RightBlock' => '

iTop está orientado a los proveedores de servicios, le permite al personal de TI administrar fácilmente múltiples Organizaciones. + 'UI:WelcomeMenu:RightBlock' => '

iTop está orientado a los proveedores de servicios, le permite al personal de TI administrar fácilmente múltiples Organizaciones.

iTop, provee un conjunto de funciones de procesos de negocio que:

  • Mejora la efectividad de la adminitración de TI
  • Dirige el desempeño de la operaciones de TI
  • @@ -378,60 +378,60 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(

', 'UI:WelcomeMenu:AllOpenRequests' => 'Requerimientos Abiertos: %1$d', - 'UI:WelcomeMenu:MyCalls' => 'Mis Requerimientos', - 'UI:WelcomeMenu:OpenIncidents' => 'Incidentes Abiertos: %1$d', - 'UI:WelcomeMenu:AllConfigItems' => 'Elementos de Configuración: %1$d', - 'UI:WelcomeMenu:MyIncidents' => 'Incidentes Asignados a Mí', - 'UI:AllOrganizations' => ' Todas las Organizaciones', - 'UI:YourSearch' => 'Su búsqueda', - 'UI:LoggedAsMessage' => 'Conectado como %1$s', - 'UI:LoggedAsMessage+Admin' => 'Conectado como %1$s (Administrator)', - 'UI:Button:Logoff' => 'Cerrar Sesión', - 'UI:Button:GlobalSearch' => 'Buscar', - 'UI:Button:Search' => 'Buscar', - 'UI:Button:Query' => 'Consultar', - 'UI:Button:Ok' => 'Aceptar', - 'UI:Button:Save' => 'Guardar', - 'UI:Button:Cancel' => 'Cancelar', - 'UI:Button:Close' => 'Cerrar', - 'UI:Button:Apply' => 'Aplicar', - 'UI:Button:Back' => '<< Anterior', - 'UI:Button:Restart' => '|<< Reiniciar', - 'UI:Button:Next' => 'Siguiente >>', - 'UI:Button:Finish' => 'Finalizar', - 'UI:Button:DoImport' => '¡Importar los datos!', - 'UI:Button:Done' => 'Listo', - 'UI:Button:SimulateImport' => 'Simular la Importación', - 'UI:Button:Test' => 'Probar', - 'UI:Button:Evaluate' => 'Evaluar', - 'UI:Button:Evaluate:Title' => 'Evaluar (Ctrl+Enter)', - 'UI:Button:AddObject' => 'Agregar', - 'UI:Button:BrowseObjects' => 'Examinar', - 'UI:Button:Add' => 'Agregar ', - 'UI:Button:AddToList' => '<< Agregar', - 'UI:Button:RemoveFromList' => 'Remover >>', - 'UI:Button:FilterList' => 'Filtrar', - 'UI:Button:Create' => 'Crear', - 'UI:Button:Delete' => 'Borrar', - 'UI:Button:Rename' => 'Renombrar', - 'UI:Button:ChangePassword' => 'Cambiar Contraseña', - 'UI:Button:ResetPassword' => 'Restablecer Contraseña', - 'UI:Button:Insert' => 'Insertar', - 'UI:Button:More' => 'Más', - 'UI:Button:Less' => 'Menos', - 'UI:Button:Wait' => 'Por favor espere mientras se actualizan los campos', - 'UI:Treeview:CollapseAll' => 'Contraer Todo', - 'UI:Treeview:ExpandAll' => 'Expandir Todo', + 'UI:WelcomeMenu:MyCalls' => 'Mis Requerimientos', + 'UI:WelcomeMenu:OpenIncidents' => 'Incidentes Abiertos: %1$d', + 'UI:WelcomeMenu:AllConfigItems' => 'Elementos de Configuración: %1$d', + 'UI:WelcomeMenu:MyIncidents' => 'Incidentes Asignados a Mí', + 'UI:AllOrganizations' => ' Todas las Organizaciones', + 'UI:YourSearch' => 'Su búsqueda', + 'UI:LoggedAsMessage' => 'Conectado como %1$s', + 'UI:LoggedAsMessage+Admin' => 'Conectado como %1$s (Administrator)', + 'UI:Button:Logoff' => 'Cerrar Sesión', + 'UI:Button:GlobalSearch' => 'Buscar', + 'UI:Button:Search' => 'Buscar', + 'UI:Button:Query' => 'Consultar', + 'UI:Button:Ok' => 'Aceptar', + 'UI:Button:Save' => 'Guardar', + 'UI:Button:Cancel' => 'Cancelar', + 'UI:Button:Close' => 'Cerrar', + 'UI:Button:Apply' => 'Aplicar', + 'UI:Button:Back' => '<< Anterior', + 'UI:Button:Restart' => '|<< Reiniciar', + 'UI:Button:Next' => 'Siguiente >>', + 'UI:Button:Finish' => 'Finalizar', + 'UI:Button:DoImport' => '¡Importar los datos!', + 'UI:Button:Done' => 'Listo', + 'UI:Button:SimulateImport' => 'Simular la Importación', + 'UI:Button:Test' => 'Probar', + 'UI:Button:Evaluate' => 'Evaluar', + 'UI:Button:Evaluate:Title' => 'Evaluar (Ctrl+Enter)', + 'UI:Button:AddObject' => 'Agregar', + 'UI:Button:BrowseObjects' => 'Examinar', + 'UI:Button:Add' => 'Agregar ', + 'UI:Button:AddToList' => '<< Agregar', + 'UI:Button:RemoveFromList' => 'Remover >>', + 'UI:Button:FilterList' => 'Filtrar', + 'UI:Button:Create' => 'Crear', + 'UI:Button:Delete' => 'Borrar', + 'UI:Button:Rename' => 'Renombrar', + 'UI:Button:ChangePassword' => 'Cambiar Contraseña', + 'UI:Button:ResetPassword' => 'Restablecer Contraseña', + 'UI:Button:Insert' => 'Insertar', + 'UI:Button:More' => 'Más', + 'UI:Button:Less' => 'Menos', + 'UI:Button:Wait' => 'Por favor espere mientras se actualizan los campos', + 'UI:Treeview:CollapseAll' => 'Contraer Todo', + 'UI:Treeview:ExpandAll' => 'Expandir Todo', - 'UI:SearchToggle' => 'Buscar', - 'UI:ClickToCreateNew' => 'Crear %1$s', - 'UI:SearchFor_Class' => 'Buscar %1$s', - 'UI:NoObjectToDisplay' => 'Ninguna Información por Visualizar.', - 'UI:Error:SaveFailed' => 'El objeto no puede ser guardado :', - 'UI:Error:MandatoryTemplateParameter_object_id' => 'El parámetro object_id es obligatorio cuando link_attr es especificado. Verifique la definición de la plantilla de visualización.', - 'UI:Error:MandatoryTemplateParameter_target_attr' => 'El parámetro target_attr es obligatorio cuando link_attr es especificado. Verifique la definición de la plantilla de visualización.', - 'UI:Error:MandatoryTemplateParameter_group_by' => 'El parámetro group_by es obligatorio. Verifique la definición de la plantilla de visualización.', - 'UI:Error:InvalidGroupByFields' => 'La lista de campos para agrupar por: "%1$s" es invalida.', + 'UI:SearchToggle' => 'Buscar', + 'UI:ClickToCreateNew' => 'Crear %1$s', + 'UI:SearchFor_Class' => 'Buscar %1$s', + 'UI:NoObjectToDisplay' => 'Ninguna Información por Visualizar.', + 'UI:Error:SaveFailed' => 'El objeto no puede ser guardado :', + 'UI:Error:MandatoryTemplateParameter_object_id' => 'El parámetro object_id es obligatorio cuando link_attr es especificado. Verifique la definición de la plantilla de visualización.', + 'UI:Error:MandatoryTemplateParameter_target_attr' => 'El parámetro target_attr es obligatorio cuando link_attr es especificado. Verifique la definición de la plantilla de visualización.', + 'UI:Error:MandatoryTemplateParameter_group_by' => 'El parámetro group_by es obligatorio. Verifique la definición de la plantilla de visualización.', + 'UI:Error:InvalidGroupByFields' => 'La lista de campos para agrupar por: "%1$s" es invalida.', 'UI:Error:UnsupportedStyleOfBlock' => 'Error: Estilo de bloque no soportado: "%1$s".', 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Definición de vínculo incorrecto: la clase de objeto a administrar : %1$s no fue encontrada como clave externa en la clase %2$s', 'UI:Error:Object_Class_Id_NotFound' => 'No se encontro el objeto: %1$s:%2$d.', @@ -443,154 +443,156 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:Error:UploadStoppedByExtension_FileName' => 'Carga de archivo interrumpida por la extension. (Nombre de archivo original = "%1$s").', 'UI:Error:UploadFailedUnknownCause_Code' => 'Carga de archivo fallida, causa desconocida. (Codigo de error = "%1$s").', - 'UI:Error:1ParametersMissing' => 'Error: El siguiente parámetro debe ser especificado para esta operacion: %1$s.', - 'UI:Error:2ParametersMissing' => 'Error: Los siguientes parámetros deben ser especificados para esta operacion: %1$s y %2$s.', - 'UI:Error:3ParametersMissing' => 'Error: Los siguientes parámetros deben ser especificados para esta operacion: %1$s, %2$s y %3$s.', - 'UI:Error:4ParametersMissing' => 'Error: Los siguientes parámetros deben ser especificados para esta operacion: %1$s, %2$s, %3$s y %4$s.', - 'UI:Error:IncorrectOQLQuery_Message' => 'Error: Consulta OQL incorrecta: %1$s', + 'UI:Error:1ParametersMissing' => 'Error: El siguiente parámetro debe ser especificado para esta operacion: %1$s.', + 'UI:Error:2ParametersMissing' => 'Error: Los siguientes parámetros deben ser especificados para esta operacion: %1$s y %2$s.', + 'UI:Error:3ParametersMissing' => 'Error: Los siguientes parámetros deben ser especificados para esta operacion: %1$s, %2$s y %3$s.', + 'UI:Error:4ParametersMissing' => 'Error: Los siguientes parámetros deben ser especificados para esta operacion: %1$s, %2$s, %3$s y %4$s.', + 'UI:Error:IncorrectOQLQuery_Message' => 'Error: Consulta OQL incorrecta: %1$s', 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Se ha producido un error al ejecutar la consulta: %1$s', - 'UI:Error:ObjectAlreadyUpdated' => 'Error: el objeta ha sido previamente actualizado.', - 'UI:Error:ObjectCannotBeUpdated' => 'Error: el objeto no puede ser actualizado.', - 'UI:Error:ObjectsAlreadyDeleted' => 'Error: los objetos ya han sido borrados!', - 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'No esta autorizado a borrar un lote de de objetos de la clase %1$s', - 'UI:Error:DeleteNotAllowedOn_Class' => 'No esta autorizado a borrar objetos del la clase %1$s', - 'UI:Error:BulkModifyNotAllowedOn_Class' => 'No esta autorizado a actualizar un lote de de objetos de la clase %1$s', - 'UI:Error:ObjectAlreadyCloned' => 'Error: el objeto ha sido previamente duplicado!', - 'UI:Error:ObjectAlreadyCreated' => 'Error: el objeto ha sido previamente creado!', - 'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'Error: estimulo invalido "%1$s" en objeto %2$s en estado "%3$s".', - 'UI:Error:InvalidDashboardFile' => 'Error: archivo de dashboard inválido', - 'UI:Error:InvalidDashboard' => 'Error: Dashboard inválido', - 'UI:Error:MaintenanceMode' => 'La aplicación se encuentra actualmente en mantenimiento', - 'UI:Error:MaintenanceTitle' => 'Mantenimiento', + 'UI:Error:ObjectAlreadyUpdated' => 'Error: el objeta ha sido previamente actualizado.', + 'UI:Error:ObjectCannotBeUpdated' => 'Error: el objeto no puede ser actualizado.', + 'UI:Error:ObjectsAlreadyDeleted' => 'Error: los objetos ya han sido borrados!', + 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'No esta autorizado a borrar un lote de de objetos de la clase %1$s', + 'UI:Error:DeleteNotAllowedOn_Class' => 'No esta autorizado a borrar objetos del la clase %1$s', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', + 'UI:Error:BulkModifyNotAllowedOn_Class' => 'No esta autorizado a actualizar un lote de de objetos de la clase %1$s', + 'UI:Error:ObjectAlreadyCloned' => 'Error: el objeto ha sido previamente duplicado!', + 'UI:Error:ObjectAlreadyCreated' => 'Error: el objeto ha sido previamente creado!', + 'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'Error: estimulo invalido "%1$s" en objeto %2$s en estado "%3$s".', + 'UI:Error:InvalidDashboardFile' => 'Error: archivo de dashboard inválido', + 'UI:Error:InvalidDashboard' => 'Error: Dashboard inválido', + 'UI:Error:MaintenanceMode' => 'La aplicación se encuentra actualmente en mantenimiento', + 'UI:Error:MaintenanceTitle' => 'Mantenimiento', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', - 'UI:GroupBy:Count' => 'Cuenta', - 'UI:GroupBy:Count+' => 'Número de Elementos', - 'UI:CountOfObjects' => '%1$d Elementos cumplen Criterio.', - 'UI_CountOfObjectsShort' => '%1$d Elemento(s)', - 'UI:NoObject_Class_ToDisplay' => 'No hay %1$s para Mostrar', - 'UI:History:LastModified_On_By' => 'Última Modificación el %1$s por %2$s.', - 'UI:HistoryTab' => 'Historia', - 'UI:NotificationsTab' => 'Notificaciones', - 'UI:History:BulkImports' => 'Historia', - 'UI:History:BulkImports+' => 'Lista de importaciones CSV (últimas importaciones primero)', - 'UI:History:BulkImportDetails' => 'Cambios resultantes de la importación CVS realizada en %1$s (por %2$s)', - 'UI:History:Date' => 'Fecha', - 'UI:History:Date+' => 'Fecha del Cambio', - 'UI:History:User' => 'Usuario', - 'UI:History:User+' => 'Usuario que hizo el Cambio', - 'UI:History:Changes' => 'Cambios', - 'UI:History:Changes+' => 'Cambios hechos al objeto', - 'UI:History:StatsCreations' => 'Creado', - 'UI:History:StatsCreations+' => 'Cuenta de objetos creados', + 'UI:GroupBy:Count' => 'Cuenta', + 'UI:GroupBy:Count+' => 'Número de Elementos', + 'UI:CountOfObjects' => '%1$d Elementos cumplen Criterio.', + 'UI_CountOfObjectsShort' => '%1$d Elemento(s)', + 'UI:NoObject_Class_ToDisplay' => 'No hay %1$s para Mostrar', + 'UI:History:LastModified_On_By' => 'Última Modificación el %1$s por %2$s.', + 'UI:HistoryTab' => 'Historia', + 'UI:NotificationsTab' => 'Notificaciones', + 'UI:History:BulkImports' => 'Historia', + 'UI:History:BulkImports+' => 'Lista de importaciones CSV (últimas importaciones primero)', + 'UI:History:BulkImportDetails' => 'Cambios resultantes de la importación CVS realizada en %1$s (por %2$s)', + 'UI:History:Date' => 'Fecha', + 'UI:History:Date+' => 'Fecha del Cambio', + 'UI:History:User' => 'Usuario', + 'UI:History:User+' => 'Usuario que hizo el Cambio', + 'UI:History:Changes' => 'Cambios', + 'UI:History:Changes+' => 'Cambios hechos al objeto', + 'UI:History:StatsCreations' => 'Creado', + 'UI:History:StatsCreations+' => 'Cuenta de objetos creados', 'UI:History:StatsModifs' => 'Modificado', - 'UI:History:StatsModifs+' => 'Cuenta de objetos modificados', - 'UI:History:StatsDeletes' => 'Borrados', - 'UI:History:StatsDeletes+' => 'Cuenta de objetos borrados', - 'UI:Loading' => 'Cargando', - 'UI:Menu:Actions' => 'Acciones', - 'UI:Menu:OtherActions' => 'Otras Acciones', - 'UI:Menu:New' => 'Nuevo', - 'UI:Menu:Add' => 'Agregar', - 'UI:Menu:Manage' => 'Administrar', - 'UI:Menu:EMail' => 'Enviar por Correo Electrónico', - 'UI:Menu:CSVExport' => 'Exportar a CSV...', - 'UI:Menu:Modify' => 'Modificar', - 'UI:Menu:Delete' => 'Borrar', - 'UI:Menu:BulkDelete' => 'Borrar', - 'UI:UndefinedObject' => 'No Definido', + 'UI:History:StatsModifs+' => 'Cuenta de objetos modificados', + 'UI:History:StatsDeletes' => 'Borrados', + 'UI:History:StatsDeletes+' => 'Cuenta de objetos borrados', + 'UI:Loading' => 'Cargando', + 'UI:Menu:Actions' => 'Acciones', + 'UI:Menu:OtherActions' => 'Otras Acciones', + 'UI:Menu:New' => 'Nuevo', + 'UI:Menu:Add' => 'Agregar', + 'UI:Menu:Manage' => 'Administrar', + 'UI:Menu:EMail' => 'Enviar por Correo Electrónico', + 'UI:Menu:CSVExport' => 'Exportar a CSV...', + 'UI:Menu:Modify' => 'Modificar', + 'UI:Menu:Delete' => 'Borrar', + 'UI:Menu:BulkDelete' => 'Borrar', + 'UI:UndefinedObject' => 'No Definido', 'UI:Document:OpenInNewWindow:Download' => 'abrir en nueva ventana: %1$s, Descargar: %2$s', - 'UI:SplitDateTime-Date' => 'fecha', - 'UI:SplitDateTime-Time' => 'hora', - 'UI:TruncatedResults' => 'Mostrando %1$d objetos de %2$d', - 'UI:DisplayAll' => 'Mostrar todo', - 'UI:CollapseList' => 'Contraer', - 'UI:CountOfResults' => '%1$d objeto(s)', - 'UI:ChangesLogTitle' => 'Registro de cambios (%1$d):', - 'UI:EmptyChangesLogTitle' => 'Registro de cambios esta vacio', - 'UI:SearchFor_Class_Objects' => 'Buscar %1$s', - 'UI:OQLQueryBuilderTitle' => 'Constructor de consultas OQL', - 'UI:OQLQueryTab' => 'Consulta OQL', - 'UI:SimpleSearchTab' => 'Búsqueda simple', - 'UI:Details+' => 'Detalles', - 'UI:SearchValue:Any' => '* Cualquiera *', - 'UI:SearchValue:Mixed' => '* mezclado *', - 'UI:SearchValue:NbSelected' => '# seleccionado', - 'UI:SearchValue:CheckAll' => 'Seleccionar Todo', - 'UI:SearchValue:UncheckAll' => 'Deseleccionar Todo', - 'UI:SelectOne' => '-- Seleccione uno --', - 'UI:Login:Welcome' => 'Bienvenido a iTop', - 'UI:Login:IncorrectLoginPassword' => 'Usuario/Contraseña incorrecto, por favor intente otra vez.', - 'UI:Login:IdentifyYourself' => 'Identifiquese antes de continuar', - 'UI:Login:UserNamePrompt' => 'Usuario ', - 'UI:Login:PasswordPrompt' => 'Contraseña', - 'UI:Login:ForgotPwd' => '¿Olvidó su contraseña?', - 'UI:Login:ForgotPwdForm' => 'Olvido de Contraseña', - 'UI:Login:ForgotPwdForm+' => 'iTop puede enviarle un correo en el cual encontrará las instrucciones a seguir para restablecer su contraseña.', - 'UI:Login:ResetPassword' => 'Enviar Ahora', - 'UI:Login:ResetPwdFailed' => 'Error al enviar correo-e: %1$s', - 'UI:Login:SeparatorOr' => 'O', + 'UI:SplitDateTime-Date' => 'fecha', + 'UI:SplitDateTime-Time' => 'hora', + 'UI:TruncatedResults' => 'Mostrando %1$d objetos de %2$d', + 'UI:DisplayAll' => 'Mostrar todo', + 'UI:CollapseList' => 'Contraer', + 'UI:CountOfResults' => '%1$d objeto(s)', + 'UI:ChangesLogTitle' => 'Registro de cambios (%1$d):', + 'UI:EmptyChangesLogTitle' => 'Registro de cambios esta vacio', + 'UI:SearchFor_Class_Objects' => 'Buscar %1$s', + 'UI:OQLQueryBuilderTitle' => 'Constructor de consultas OQL', + 'UI:OQLQueryTab' => 'Consulta OQL', + 'UI:SimpleSearchTab' => 'Búsqueda simple', + 'UI:Details+' => 'Detalles', + 'UI:SearchValue:Any' => '* Cualquiera *', + 'UI:SearchValue:Mixed' => '* mezclado *', + 'UI:SearchValue:NbSelected' => '# seleccionado', + 'UI:SearchValue:CheckAll' => 'Seleccionar Todo', + 'UI:SearchValue:UncheckAll' => 'Deseleccionar Todo', + 'UI:SelectOne' => '-- Seleccione uno --', + 'UI:Login:Welcome' => 'Bienvenido a iTop', + 'UI:Login:IncorrectLoginPassword' => 'Usuario/Contraseña incorrecto, por favor intente otra vez.', + 'UI:Login:IdentifyYourself' => 'Identifiquese antes de continuar', + 'UI:Login:UserNamePrompt' => 'Usuario ', + 'UI:Login:PasswordPrompt' => 'Contraseña', + 'UI:Login:ForgotPwd' => '¿Olvidó su contraseña?', + 'UI:Login:ForgotPwdForm' => 'Olvido de Contraseña', + 'UI:Login:ForgotPwdForm+' => 'iTop puede enviarle un correo en el cual encontrará las instrucciones a seguir para restablecer su contraseña.', + 'UI:Login:ResetPassword' => 'Enviar Ahora', + 'UI:Login:ResetPwdFailed' => 'Error al enviar correo-e: %1$s', + 'UI:Login:SeparatorOr' => 'O', - 'UI:ResetPwd-Error-WrongLogin' => '\'%1$s\' no es un usuario válido', + 'UI:ResetPwd-Error-WrongLogin' => '\'%1$s\' no es un usuario válido', 'UI:ResetPwd-Error-NotPossible' => 'Cuentas externas no permiten restablecimiento de contraseña.', - 'UI:ResetPwd-Error-FixedPwd' => 'La cuenta no permite restablecimiento de contraseña.', - 'UI:ResetPwd-Error-NoContact' => 'La cuenta no está asociada a una persona.', - 'UI:ResetPwd-Error-NoEmailAtt' => 'La cuenta no está asociada a una persona con correo electrónico. Por favor contacte al administrador.', - 'UI:ResetPwd-Error-NoEmail' => 'Falta dirección de correo electrónico. Por favor contacte al administrador.', - 'UI:ResetPwd-Error-Send' => 'Falla al envar un correo. Por favor contacte al administrador.', - 'UI:ResetPwd-EmailSent' => 'Por favor verifique su buzón de correo y siga las instrucciones. Si no recibe el mensaje, por favor verifique la cuenta proporcionada.', - 'UI:ResetPwd-EmailSubject' => 'Restablecer contraseña de iTop', - 'UI:ResetPwd-EmailBody' => '

Ha solicitado restablecer su contraseña en iTop.

Por favor de click en la siguiente liga: proporcione una nueva contraseña

.', + 'UI:ResetPwd-Error-FixedPwd' => 'La cuenta no permite restablecimiento de contraseña.', + 'UI:ResetPwd-Error-NoContact' => 'La cuenta no está asociada a una persona.', + 'UI:ResetPwd-Error-NoEmailAtt' => 'La cuenta no está asociada a una persona con correo electrónico. Por favor contacte al administrador.', + 'UI:ResetPwd-Error-NoEmail' => 'Falta dirección de correo electrónico. Por favor contacte al administrador.', + 'UI:ResetPwd-Error-Send' => 'Falla al envar un correo. Por favor contacte al administrador.', + 'UI:ResetPwd-EmailSent' => 'Por favor verifique su buzón de correo y siga las instrucciones. Si no recibe el mensaje, por favor verifique la cuenta proporcionada.', + 'UI:ResetPwd-EmailSubject' => 'Restablecer contraseña de iTop', + 'UI:ResetPwd-EmailBody' => '

Ha solicitado restablecer su contraseña en iTop.

Por favor de click en la siguiente liga: proporcione una nueva contraseña

.', - 'UI:ResetPwd-Title' => 'Restablecer Contraseña', - 'UI:ResetPwd-Error-InvalidToken' => 'Lo siento, tal vez su contraseña ya ha sido cambiada, o ha recibido varios correos electrónicos. Por favor asegurese de haber dado click a la liga del último correo recibido.', + 'UI:ResetPwd-Title' => 'Restablecer Contraseña', + 'UI:ResetPwd-Error-InvalidToken' => 'Lo siento, tal vez su contraseña ya ha sido cambiada, o ha recibido varios correos electrónicos. Por favor asegurese de haber dado click a la liga del último correo recibido.', 'UI:ResetPwd-Error-EnterPassword' => 'Contraseña Nueva para \'%1$s\'.', - 'UI:ResetPwd-Ready' => 'La contraseña ha sido cambiada.', - 'UI:ResetPwd-Login' => 'Click aquí para conectarse ', + 'UI:ResetPwd-Ready' => 'La contraseña ha sido cambiada.', + 'UI:ResetPwd-Login' => 'Click aquí para conectarse ', - 'UI:Login:About' => 'Acerca de', - 'UI:Login:ChangeYourPassword' => 'Cambie su Contraseña', - 'UI:Login:OldPasswordPrompt' => 'Contraseña Actual', - 'UI:Login:NewPasswordPrompt' => 'Contraseña Nueva', - 'UI:Login:RetypeNewPasswordPrompt' => 'Confirme Contraseña Nueva', - 'UI:Login:IncorrectOldPassword' => 'Error: la Contraseña Anterior es Incorrecta', - 'UI:LogOffMenu' => 'Cerrar Sesión', - 'UI:LogOff:ThankYou' => 'Gracias por usar iTop', - 'UI:LogOff:ClickHereToLoginAgain' => 'Click aquí para conectarse nuevamente', - 'UI:ChangePwdMenu' => 'Cambiar Contraseña', - 'UI:Login:PasswordChanged' => '¡Contraseña Exitosamente Cambiada!', - 'UI:AccessRO-All' => 'iTop está en modo de sólo lectura', - 'UI:AccessRO-Users' => 'iTop está en modo de sólo lectura para usuarios', - 'UI:ApplicationEnvironment' => 'Ambiente: %1$s', - 'UI:Login:RetypePwdDoesNotMatch' => '¡La Nueva Contraseña y su Confirmación No Coinciden!', - 'UI:Button:Login' => 'Entrar', - 'UI:Login:Error:AccessRestricted' => 'El acceso a iTop está restringido. Por favor contacte al Administrador de iTop.', - 'UI:Login:Error:AccessAdmin' => 'Acceso restringido a usuarios con privilegio de administrador. Por favor contacte al Administrador de iTop.', - 'UI:Login:Error:WrongOrganizationName' => 'Organización desconocida', + 'UI:Login:About' => 'Acerca de', + 'UI:Login:ChangeYourPassword' => 'Cambie su Contraseña', + 'UI:Login:OldPasswordPrompt' => 'Contraseña Actual', + 'UI:Login:NewPasswordPrompt' => 'Contraseña Nueva', + 'UI:Login:RetypeNewPasswordPrompt' => 'Confirme Contraseña Nueva', + 'UI:Login:IncorrectOldPassword' => 'Error: la Contraseña Anterior es Incorrecta', + 'UI:LogOffMenu' => 'Cerrar Sesión', + 'UI:LogOff:ThankYou' => 'Gracias por usar iTop', + 'UI:LogOff:ClickHereToLoginAgain' => 'Click aquí para conectarse nuevamente', + 'UI:ChangePwdMenu' => 'Cambiar Contraseña', + 'UI:Login:PasswordChanged' => '¡Contraseña Exitosamente Cambiada!', + 'UI:AccessRO-All' => 'iTop está en modo de sólo lectura', + 'UI:AccessRO-Users' => 'iTop está en modo de sólo lectura para usuarios', + 'UI:ApplicationEnvironment' => 'Ambiente: %1$s', + 'UI:Login:RetypePwdDoesNotMatch' => '¡La Nueva Contraseña y su Confirmación No Coinciden!', + 'UI:Button:Login' => 'Entrar', + 'UI:Login:Error:AccessRestricted' => 'El acceso a iTop está restringido. Por favor contacte al Administrador de iTop.', + 'UI:Login:Error:AccessAdmin' => 'Acceso restringido a usuarios con privilegio de administrador. Por favor contacte al Administrador de iTop.', + 'UI:Login:Error:WrongOrganizationName' => 'Organización desconocida', 'UI:Login:Error:MultipleContactsHaveSameEmail' => 'Varios contactos tienen la misma dirección de correo electrónico', - 'UI:Login:Error:NoValidProfiles' => 'Perfil inválido', - 'UI:CSVImport:MappingSelectOne' => '-- seleccione uno --', - 'UI:CSVImport:MappingNotApplicable' => '-- ignore este campo --', - 'UI:CSVImport:NoData' => 'Conjunto de datos vacío..., por favor provea algun dato.', - 'UI:Title:DataPreview' => 'Vista previa de datos', - 'UI:CSVImport:ErrorOnlyOneColumn' => 'Error: Los datos sólo contienen una columna. ¿Seleccionó el separador de campos adecuado?', - 'UI:CSVImport:FieldName' => 'Campo %1$d', - 'UI:CSVImport:DataLine1' => 'Linea de datos 1', - 'UI:CSVImport:DataLine2' => 'Linea de datos 2', - 'UI:CSVImport:idField' => 'Id (Clave Primaria)', - 'UI:Title:BulkImport' => 'iTop - Importación por Lotes', - 'UI:Title:BulkImport+' => 'Asistente de Importación Archivos CSV', - 'UI:Title:BulkSynchro_nbItem_ofClass_class' => 'Sincronización de %1$d objetos de la clase %2$s', - 'UI:CSVImport:ClassesSelectOne' => '-- Seleccione uno --', - 'UI:CSVImport:ErrorExtendedAttCode' => 'Error Interno: "%1$s" es un código incorrecto debido a que "%2$s" NO es una clave externa de la clase "%3$s"', - 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d objeto(s) permanecerá sin cambio.', - 'UI:CSVImport:ObjectsWillBeModified' => '%1$d objeto(s) será modificado.', - 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d objeto(s) será agregado.', - 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d objeto(s) tendrá error.', - 'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d objeto(s) permanencen sin cambio.', - 'UI:CSVImport:ObjectsWereModified' => '%1$d objeto(s) será modificado.', - 'UI:CSVImport:ObjectsWereAdded' => '%1$d objeto(s) fué agregado.', - 'UI:CSVImport:ObjectsHadErrors' => '%1$d objeto(s) tuvo errores.', - 'UI:Title:CSVImportStep2' => 'Paso 2 de 5: Opciones de Datos CSV', + 'UI:Login:Error:NoValidProfiles' => 'Perfil inválido', + 'UI:CSVImport:MappingSelectOne' => '-- seleccione uno --', + 'UI:CSVImport:MappingNotApplicable' => '-- ignore este campo --', + 'UI:CSVImport:NoData' => 'Conjunto de datos vacío..., por favor provea algun dato.', + 'UI:Title:DataPreview' => 'Vista previa de datos', + 'UI:CSVImport:ErrorOnlyOneColumn' => 'Error: Los datos sólo contienen una columna. ¿Seleccionó el separador de campos adecuado?', + 'UI:CSVImport:FieldName' => 'Campo %1$d', + 'UI:CSVImport:DataLine1' => 'Linea de datos 1', + 'UI:CSVImport:DataLine2' => 'Linea de datos 2', + 'UI:CSVImport:idField' => 'Id (Clave Primaria)', + 'UI:Title:BulkImport' => 'iTop - Importación por Lotes', + 'UI:Title:BulkImport+' => 'Asistente de Importación Archivos CSV', + 'UI:Title:BulkSynchro_nbItem_ofClass_class' => 'Sincronización de %1$d objetos de la clase %2$s', + 'UI:CSVImport:ClassesSelectOne' => '-- Seleccione uno --', + 'UI:CSVImport:ErrorExtendedAttCode' => 'Error Interno: "%1$s" es un código incorrecto debido a que "%2$s" NO es una clave externa de la clase "%3$s"', + 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d objeto(s) permanecerá sin cambio.', + 'UI:CSVImport:ObjectsWillBeModified' => '%1$d objeto(s) será modificado.', + 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d objeto(s) será agregado.', + 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d objeto(s) tendrá error.', + 'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d objeto(s) permanencen sin cambio.', + 'UI:CSVImport:ObjectsWereModified' => '%1$d objeto(s) será modificado.', + 'UI:CSVImport:ObjectsWereAdded' => '%1$d objeto(s) fué agregado.', + 'UI:CSVImport:ObjectsHadErrors' => '%1$d objeto(s) tuvo errores.', + 'UI:Title:CSVImportStep2' => 'Paso 2 de 5: Opciones de Datos CSV', 'UI:Title:CSVImportStep3' => 'Paso 3 de 5: Mapeo de Datos', 'UI:Title:CSVImportStep4' => 'Paso 4 de 5: Simular Importación', 'UI:Title:CSVImportStep5' => 'Paso 5 de 5: Importación Completada', @@ -685,40 +687,40 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:Audit:ErrorIn_Rule_Reason' => 'Error de OQL en la Regla %1$s: %2$s.', 'UI:Audit:ErrorIn_Category_Reason' => 'Error de OQL en la Categoría %1$s: %2$s.', - 'UI:RunQuery:Title' => 'iTop - Evaluación de consultas OQL', - 'UI:RunQuery:QueryExamples' => 'Explorador de Consultas', - 'UI:RunQuery:HeaderPurpose' => 'Propósito', - 'UI:RunQuery:HeaderPurpose+' => 'Explicación acerca de la consulta', - 'UI:RunQuery:HeaderOQLExpression' => 'Expresión OQL', - 'UI:RunQuery:HeaderOQLExpression+' => 'La consulta en sintáxis OQL', - 'UI:RunQuery:ExpressionToEvaluate' => 'Expresión a evaluar: ', - 'UI:RunQuery:MoreInfo' => 'Más información acerca de la consulta: ', - 'UI:RunQuery:DevelopedQuery' => 'Expresión de consulta rediseñada: ', - 'UI:RunQuery:SerializedFilter' => 'Filtro de serialización: ', - 'UI:RunQuery:DevelopedOQL' => 'OQL Desarrollado', - 'UI:RunQuery:DevelopedOQLCount' => 'OQL Desarrollado para cuenta', - 'UI:RunQuery:ResultSQLCount' => 'SQL resultante para cuenta', - 'UI:RunQuery:ResultSQL' => 'SQL Resultante', - 'UI:RunQuery:Error' => 'Ha ocurrido un error al ejecutar la consulta: %1$s', - 'UI:Query:UrlForExcel' => 'URL para usarse en consultas web de MS-Excel', - 'UI:Query:UrlV1' => 'La lista de campos se ha dejado sin especificación. La página export-V2.php no puede ser invocada sin está información. Por lo tanto, el URL sugerido abajo apunta a la página legada: export.php. Esta versión legada de exportación tiene la siguiente limitación: la lista de campos exportados puede variar, dependiendo del formato de salida y el modelo de datos de iTop. Desea garantizar que la lista de columnas exportadas permanenzcan estables durante la ejecución, entonces debe especificar un valor para el atributo "Campos" y utilice la página export-V2.php.', - 'UI:Schema:Title' => 'Esquema de Objetos en iTop', - 'UI:Schema:CategoryMenuItem' => 'Categoria %1$s', - 'UI:Schema:Relationships' => 'Relaciones', - 'UI:Schema:AbstractClass' => 'Clase Abstracta: Ningún objeto de esta clase puede ser representado.', - 'UI:Schema:NonAbstractClass' => 'Clase NoAbstracta: Objetos de esta clase pueden ser representados.', - 'UI:Schema:ClassHierarchyTitle' => 'Jerarquia de Clases', - 'UI:Schema:AllClasses' => 'Todas las Clases', - 'UI:Schema:ExternalKey_To' => 'Clave Externa a %1$s', - 'UI:Schema:Columns_Description' => 'Columnas: %1$s', - 'UI:Schema:Default_Description' => 'Predeterminar: "%1$s"', - 'UI:Schema:NullAllowed' => 'Permite Nulos', - 'UI:Schema:NullNotAllowed' => 'NO permite Nulos', - 'UI:Schema:Attributes' => 'Atributos', - 'UI:Schema:AttributeCode' => 'Código de Atributo', - 'UI:Schema:AttributeCode+' => 'Código Interno del Atributo', - 'UI:Schema:Label' => 'Etiqueta', - 'UI:Schema:Label+' => 'Etiqueta del Atributo', + 'UI:RunQuery:Title' => 'iTop - Evaluación de consultas OQL', + 'UI:RunQuery:QueryExamples' => 'Explorador de Consultas', + 'UI:RunQuery:HeaderPurpose' => 'Propósito', + 'UI:RunQuery:HeaderPurpose+' => 'Explicación acerca de la consulta', + 'UI:RunQuery:HeaderOQLExpression' => 'Expresión OQL', + 'UI:RunQuery:HeaderOQLExpression+' => 'La consulta en sintáxis OQL', + 'UI:RunQuery:ExpressionToEvaluate' => 'Expresión a evaluar: ', + 'UI:RunQuery:MoreInfo' => 'Más información acerca de la consulta: ', + 'UI:RunQuery:DevelopedQuery' => 'Expresión de consulta rediseñada: ', + 'UI:RunQuery:SerializedFilter' => 'Filtro de serialización: ', + 'UI:RunQuery:DevelopedOQL' => 'OQL Desarrollado', + 'UI:RunQuery:DevelopedOQLCount' => 'OQL Desarrollado para cuenta', + 'UI:RunQuery:ResultSQLCount' => 'SQL resultante para cuenta', + 'UI:RunQuery:ResultSQL' => 'SQL Resultante', + 'UI:RunQuery:Error' => 'Ha ocurrido un error al ejecutar la consulta: %1$s', + 'UI:Query:UrlForExcel' => 'URL para usarse en consultas web de MS-Excel', + 'UI:Query:UrlV1' => 'La lista de campos se ha dejado sin especificación. La página export-V2.php no puede ser invocada sin está información. Por lo tanto, el URL sugerido abajo apunta a la página legada: export.php. Esta versión legada de exportación tiene la siguiente limitación: la lista de campos exportados puede variar, dependiendo del formato de salida y el modelo de datos de iTop. Desea garantizar que la lista de columnas exportadas permanenzcan estables durante la ejecución, entonces debe especificar un valor para el atributo "Campos" y utilice la página export-V2.php.', + 'UI:Schema:Title' => 'Esquema de Objetos en iTop', + 'UI:Schema:CategoryMenuItem' => 'Categoria %1$s', + 'UI:Schema:Relationships' => 'Relaciones', + 'UI:Schema:AbstractClass' => 'Clase Abstracta: Ningún objeto de esta clase puede ser representado.', + 'UI:Schema:NonAbstractClass' => 'Clase NoAbstracta: Objetos de esta clase pueden ser representados.', + 'UI:Schema:ClassHierarchyTitle' => 'Jerarquia de Clases', + 'UI:Schema:AllClasses' => 'Todas las Clases', + 'UI:Schema:ExternalKey_To' => 'Clave Externa a %1$s', + 'UI:Schema:Columns_Description' => 'Columnas: %1$s', + 'UI:Schema:Default_Description' => 'Predeterminar: "%1$s"', + 'UI:Schema:NullAllowed' => 'Permite Nulos', + 'UI:Schema:NullNotAllowed' => 'NO permite Nulos', + 'UI:Schema:Attributes' => 'Atributos', + 'UI:Schema:AttributeCode' => 'Código de Atributo', + 'UI:Schema:AttributeCode+' => 'Código Interno del Atributo', + 'UI:Schema:Label' => 'Etiqueta', + 'UI:Schema:Label+' => 'Etiqueta del Atributo', 'UI:Schema:Type' => 'Tipo', 'UI:Schema:Type+' => 'Tipo de dato del Atributo', @@ -750,64 +752,64 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s esta referenciado por la clase %2$s a travez de el campo %3$s', 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s está vinculado a %2$s a travez de %3$s::%4$s', 'UI:Schema:Links:1-n' => 'Clases apuntando a %1$s (1:n enlaces):', - 'UI:Schema:Links:n-n' => 'Clases apuntando a %1$s (n:n enlaces):', - 'UI:Schema:Links:All' => 'Gráfico de todos los Casos Relacionados', - 'UI:Schema:NoLifeCyle' => 'No hay Ciclo de Vida definido para esta Clase.', - 'UI:Schema:LifeCycleTransitions' => 'Transiciones', - 'UI:Schema:LifeCyleAttributeOptions' => 'Opciones del Atributo', - 'UI:Schema:LifeCycleHiddenAttribute' => 'Oculto', - 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Solo-lectrura', - 'UI:Schema:LifeCycleMandatoryAttribute' => 'Mandatorio', - 'UI:Schema:LifeCycleAttributeMustChange' => 'Debe cambiar', - 'UI:Schema:LifeCycleAttributeMustPrompt' => 'Se le pedira al usuario que cambie el valor', - 'UI:Schema:LifeCycleEmptyList' => 'Lista Vacía', - 'UI:Schema:ClassFilter' => 'Clase:', - 'UI:Schema:DisplayLabel' => 'Visualización:', - 'UI:Schema:DisplaySelector/LabelAndCode' => 'Etiqueta y código', - 'UI:Schema:DisplaySelector/Label' => 'Etiqueta', - 'UI:Schema:DisplaySelector/Code' => 'Código', - 'UI:Schema:Attribute/Filter' => 'Filtro', - 'UI:Schema:DefaultNullValue' => 'Nulo por Omisión : "%1$s"', - 'UI:LinksWidget:Autocomplete+' => 'Escriba los primeros 3 caracteres...', - 'UI:Edit:TestQuery' => 'Consulta de Prueba', - 'UI:Combo:SelectValue' => '--- Seleccione un valor ---', - 'UI:Label:SelectedObjects' => 'Objetos seleccionados: ', - 'UI:Label:AvailableObjects' => 'Objetos disponibles: ', - 'UI:Link_Class_Attributes' => '%1$s atributos', - 'UI:SelectAllToggle+' => 'Seleccionar / Deseleccionar todo', - 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Agregar %1$s objetos vinculados con %2$s: %3$s', - 'UI:AddObjectsOf_Class_LinkedWith_Class' => 'Agregar %1$s objetos a vincular con %2$s', - 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Administrar %1$s objetos vinculados con %2$s: %3$s', - 'UI:AddLinkedObjectsOf_Class' => 'Agregar %1$s', - 'UI:RemoveLinkedObjectsOf_Class' => 'Eliminar Seleccionados', - 'UI:Message:EmptyList:UseAdd' => 'La lista esta vacía, use el botón "Agregar" para añadir elementos.', - 'UI:Message:EmptyList:UseSearchForm' => 'Use la forma arriba para buscar objetos a ser agregados.', - 'UI:Wizard:FinalStepTitle' => 'Paso Final: Confirmación', - 'UI:Title:DeletionOf_Object' => 'Borrado de %1$s', - 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Borrado por lote de %1$d objetos de la clase %2$s', - 'UI:Delete:NotAllowedToDelete' => 'No esta autorizado para borrar este objeto', - 'UI:Delete:NotAllowedToUpdate_Fields' => 'No esta autorizado para actualizar el siguiente campo(s): %1$s', - 'UI:Error:ActionNotAllowed' => 'No está autorizado a realizar esta acción', - 'UI:Error:NotEnoughRightsToDelete' => 'Este objeto no pudo ser borrado porque el usuario actual no posee suficientes permisos', - 'UI:Error:CannotDeleteBecause' => 'Esto objeto no puede ser borrado debido a: %1$s', - 'UI:Error:CannotDeleteBecauseOfDepencies' => 'Este objeto no pudo ser borrado porque algunas operaciones manuales deben ser ejecutadas antes de eso', - 'UI:Error:CannotDeleteBecauseManualOpNeeded' => 'Este objeto no puede ser borrado debido a que algunas operaciones manuales manuales deben ser realizadas antes', - 'UI:Archive_User_OnBehalfOf_User' => '%1$s en nombre de %2$s', - 'UI:Delete:Deleted' => 'Borrado', - 'UI:Delete:AutomaticallyDeleted' => 'Borrado automaticamente', - 'UI:Delete:AutomaticResetOf_Fields' => 'Reinicio automático de campo(s): %1$s', - 'UI:Delete:CleaningUpRefencesTo_Object' => 'Limpiando todas las referencias a %1$s', + 'UI:Schema:Links:n-n' => 'Clases apuntando a %1$s (n:n enlaces):', + 'UI:Schema:Links:All' => 'Gráfico de todos los Casos Relacionados', + 'UI:Schema:NoLifeCyle' => 'No hay Ciclo de Vida definido para esta Clase.', + 'UI:Schema:LifeCycleTransitions' => 'Transiciones', + 'UI:Schema:LifeCyleAttributeOptions' => 'Opciones del Atributo', + 'UI:Schema:LifeCycleHiddenAttribute' => 'Oculto', + 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Solo-lectrura', + 'UI:Schema:LifeCycleMandatoryAttribute' => 'Mandatorio', + 'UI:Schema:LifeCycleAttributeMustChange' => 'Debe cambiar', + 'UI:Schema:LifeCycleAttributeMustPrompt' => 'Se le pedira al usuario que cambie el valor', + 'UI:Schema:LifeCycleEmptyList' => 'Lista Vacía', + 'UI:Schema:ClassFilter' => 'Clase:', + 'UI:Schema:DisplayLabel' => 'Visualización:', + 'UI:Schema:DisplaySelector/LabelAndCode' => 'Etiqueta y código', + 'UI:Schema:DisplaySelector/Label' => 'Etiqueta', + 'UI:Schema:DisplaySelector/Code' => 'Código', + 'UI:Schema:Attribute/Filter' => 'Filtro', + 'UI:Schema:DefaultNullValue' => 'Nulo por Omisión : "%1$s"', + 'UI:LinksWidget:Autocomplete+' => 'Escriba los primeros 3 caracteres...', + 'UI:Edit:TestQuery' => 'Consulta de Prueba', + 'UI:Combo:SelectValue' => '--- Seleccione un valor ---', + 'UI:Label:SelectedObjects' => 'Objetos seleccionados: ', + 'UI:Label:AvailableObjects' => 'Objetos disponibles: ', + 'UI:Link_Class_Attributes' => '%1$s atributos', + 'UI:SelectAllToggle+' => 'Seleccionar / Deseleccionar todo', + 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Agregar %1$s objetos vinculados con %2$s: %3$s', + 'UI:AddObjectsOf_Class_LinkedWith_Class' => 'Agregar %1$s objetos a vincular con %2$s', + 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Administrar %1$s objetos vinculados con %2$s: %3$s', + 'UI:AddLinkedObjectsOf_Class' => 'Agregar %1$s', + 'UI:RemoveLinkedObjectsOf_Class' => 'Eliminar Seleccionados', + 'UI:Message:EmptyList:UseAdd' => 'La lista esta vacía, use el botón "Agregar" para añadir elementos.', + 'UI:Message:EmptyList:UseSearchForm' => 'Use la forma arriba para buscar objetos a ser agregados.', + 'UI:Wizard:FinalStepTitle' => 'Paso Final: Confirmación', + 'UI:Title:DeletionOf_Object' => 'Borrado de %1$s', + 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Borrado por lote de %1$d objetos de la clase %2$s', + 'UI:Delete:NotAllowedToDelete' => 'No esta autorizado para borrar este objeto', + 'UI:Delete:NotAllowedToUpdate_Fields' => 'No esta autorizado para actualizar el siguiente campo(s): %1$s', + 'UI:Error:ActionNotAllowed' => 'No está autorizado a realizar esta acción', + 'UI:Error:NotEnoughRightsToDelete' => 'Este objeto no pudo ser borrado porque el usuario actual no posee suficientes permisos', + 'UI:Error:CannotDeleteBecause' => 'Esto objeto no puede ser borrado debido a: %1$s', + 'UI:Error:CannotDeleteBecauseOfDepencies' => 'Este objeto no pudo ser borrado porque algunas operaciones manuales deben ser ejecutadas antes de eso', + 'UI:Error:CannotDeleteBecauseManualOpNeeded' => 'Este objeto no puede ser borrado debido a que algunas operaciones manuales manuales deben ser realizadas antes', + 'UI:Archive_User_OnBehalfOf_User' => '%1$s en nombre de %2$s', + 'UI:Delete:Deleted' => 'Borrado', + 'UI:Delete:AutomaticallyDeleted' => 'Borrado automaticamente', + 'UI:Delete:AutomaticResetOf_Fields' => 'Reinicio automático de campo(s): %1$s', + 'UI:Delete:CleaningUpRefencesTo_Object' => 'Limpiando todas las referencias a %1$s', 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => 'Limpiando todas las referencias a %1$d objetos de la clase %2$s', - 'UI:Delete:Done+' => 'Realizado', - 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s borrado.', - 'UI:Delete:ConfirmDeletionOf_Name' => 'Borrado de %1$s', - 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => 'Borrado de %1$d objetos de la clase %2$s', - 'UI:Delete:CannotDeleteBecause' => 'No puede ser borrado: %1$s', - 'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => 'Deberia ser borrado automaticamente, pero usted no esta autorizado para hacerlo', - 'UI:Delete:MustBeDeletedManuallyButNotPossible' => 'Debe ser borrado manualmente - pero usted no está autorizado para borrar este objeto, por favor contacte al administrador de la aplicación', - 'UI:Delete:WillBeDeletedAutomatically' => 'Será borrado automaticamente', - 'UI:Delete:MustBeDeletedManually' => 'Debe ser borrado manualmente', - 'UI:Delete:CannotUpdateBecause_Issue' => 'Debe ser actualizado automaticamente, pero: %1$s', + 'UI:Delete:Done+' => 'Realizado', + 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s borrado.', + 'UI:Delete:ConfirmDeletionOf_Name' => 'Borrado de %1$s', + 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => 'Borrado de %1$d objetos de la clase %2$s', + 'UI:Delete:CannotDeleteBecause' => 'No puede ser borrado: %1$s', + 'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => 'Deberia ser borrado automaticamente, pero usted no esta autorizado para hacerlo', + 'UI:Delete:MustBeDeletedManuallyButNotPossible' => 'Debe ser borrado manualmente - pero usted no está autorizado para borrar este objeto, por favor contacte al administrador de la aplicación', + 'UI:Delete:WillBeDeletedAutomatically' => 'Será borrado automaticamente', + 'UI:Delete:MustBeDeletedManually' => 'Debe ser borrado manualmente', + 'UI:Delete:CannotUpdateBecause_Issue' => 'Debe ser actualizado automaticamente, pero: %1$s', 'UI:Delete:WillAutomaticallyUpdate_Fields' => 'Será actualizado automaticamente (reset: %1$s)', 'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d objetos/vinculos están referenciando %2$s', 'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d objetos/vinculos están referenciando algunos de los objetos a ser borrados', @@ -882,40 +884,37 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:UserManagement:Action:BulkModify+' => 'Crear/Editar masivamente (importar CSV)', 'UI:UserManagement:Action:BulkDelete' => 'eliminación masiva', 'UI:UserManagement:Action:BulkDelete+' => 'eliminación masiva de objetos', - 'UI:UserManagement:Action:Stimuli' => 'Stimuli', - 'UI:UserManagement:Action:Stimuli+' => 'Acciones (compound) permitidas', - 'UI:UserManagement:Action' => 'Acción', - 'UI:UserManagement:Action+' => 'Acción ejecutada por el usuario', - 'UI:UserManagement:TitleActions' => 'Acciones', - 'UI:UserManagement:Permission' => 'Permisos', - 'UI:UserManagement:Permission+' => 'Permisos de usuario', - 'UI:UserManagement:Attributes' => 'Atributos', - 'UI:UserManagement:ActionAllowed:Yes' => 'Si', - 'UI:UserManagement:ActionAllowed:No' => 'No', - 'UI:UserManagement:AdminProfile+' => 'Los administradores tienen acceso total de lectura/escritura para todos los objetos en la base de datos.', - 'UI:UserManagement:NoLifeCycleApplicable' => 'N/A', - 'UI:UserManagement:NoLifeCycleApplicable+' => 'No se ha definido ciclo de vida para esta clase', - 'UI:UserManagement:GrantMatrix' => 'Matriz de Acceso', - 'UI:UserManagement:LinkBetween_User_And_Profile' => 'Vinculo entre %1$s y %2$s', - 'UI:UserManagement:LinkBetween_User_And_Org' => 'Vínculo entre %1$s y %2$s', + 'UI:UserManagement:Action:Stimuli' => 'Stimuli', + 'UI:UserManagement:Action:Stimuli+' => 'Acciones (compound) permitidas', + 'UI:UserManagement:Action' => 'Acción', + 'UI:UserManagement:Action+' => 'Acción ejecutada por el usuario', + 'UI:UserManagement:TitleActions' => 'Acciones', + 'UI:UserManagement:Permission' => 'Permisos', + 'UI:UserManagement:Permission+' => 'Permisos de usuario', + 'UI:UserManagement:Attributes' => 'Atributos', + 'UI:UserManagement:ActionAllowed:Yes' => 'Si', + 'UI:UserManagement:ActionAllowed:No' => 'No', + 'UI:UserManagement:AdminProfile+' => 'Los administradores tienen acceso total de lectura/escritura para todos los objetos en la base de datos.', + 'UI:UserManagement:NoLifeCycleApplicable' => 'N/A', + 'UI:UserManagement:NoLifeCycleApplicable+' => 'No se ha definido ciclo de vida para esta clase', + 'UI:UserManagement:GrantMatrix' => 'Matriz de Acceso', + 'UI:UserManagement:LinkBetween_User_And_Profile' => 'Vinculo entre %1$s y %2$s', + 'UI:UserManagement:LinkBetween_User_And_Org' => 'Vínculo entre %1$s y %2$s', - 'Menu:AdminTools' => 'Herramientas Administrativas', - // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:AdminTools+' => 'Herramientas Administrativas', - // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:AdminTools?' => 'Herramientas accesibles sólo a usuarios con Perfil de administrador', - // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:AdminTools' => 'Herramientas Administrativas', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:AdminTools+' => 'Herramientas Administrativas', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:AdminTools?' => 'Herramientas accesibles sólo a usuarios con Perfil de administrador', // Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:SystemTools' => 'Sistema', - 'UI:ChangeManagementMenu' => 'Control de Cambios', - 'UI:ChangeManagementMenu+' => 'Control de Cambios', - 'UI:ChangeManagementMenu:Title' => 'Resumen de Cambios', - 'UI-ChangeManagementMenu-ChangesByType' => 'Cambios por Tipo', - 'UI-ChangeManagementMenu-ChangesByStatus' => 'Cambios por Estatus', - 'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'Cambios por Grupo de Trabajo', + 'UI:ChangeManagementMenu' => 'Control de Cambios', + 'UI:ChangeManagementMenu+' => 'Control de Cambios', + 'UI:ChangeManagementMenu:Title' => 'Resumen de Cambios', + 'UI-ChangeManagementMenu-ChangesByType' => 'Cambios por Tipo', + 'UI-ChangeManagementMenu-ChangesByStatus' => 'Cambios por Estatus', + 'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'Cambios por Grupo de Trabajo', 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Cambios No Asignados Aún', - 'UI:ConfigurationManagementMenu' => 'Administración de la Configuración', + 'UI:ConfigurationManagementMenu' => 'Administración de la Configuración', 'UI:ConfigurationManagementMenu+' => 'Administración de la Configuración', 'UI:ConfigurationManagementMenu:Title' => 'Resumen de Infrastructura', 'UI-ConfigurationManagementMenu-InfraByType' => 'Objetos de Infraestructura por Tipo', @@ -951,17 +950,17 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI-ContactsMenu-ContactsByType' => 'Contactos por Tipo', 'UI-ContactsMenu-ContactsByStatus' => 'Contactos por Estatus', - 'Menu:CSVImportMenu' => 'Importar CSV',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:CSVImportMenu+' => 'Creación o Actualización Másiva',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:CSVImportMenu' => 'Importar CSV', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:CSVImportMenu+' => 'Creación o Actualización Másiva', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:DataModelMenu' => 'Modelo de Datos',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:DataModelMenu+' => 'Resumen del Modelo de Datos',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:DataModelMenu' => 'Modelo de Datos', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:DataModelMenu+' => 'Resumen del Modelo de Datos', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:ExportMenu' => 'Exportar',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:ExportMenu+' => 'Exportar los Resultados de Cualquier Consulta en HTML, CSV o XML',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:ExportMenu' => 'Exportar', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:ExportMenu+' => 'Exportar los Resultados de Cualquier Consulta en HTML, CSV o XML', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:NotificationsMenu' => 'Notificaciones',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:NotificationsMenu+' => 'Configuración de las Notificaciones',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:NotificationsMenu' => 'Notificaciones', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:NotificationsMenu+' => 'Configuración de las Notificaciones', // Duplicated into itop-welcome-itil (will be removed from here...) 'UI:NotificationsMenu:Title' => 'Configuración de las Notificaciones', 'UI:NotificationsMenu:Help' => 'Ayuda', 'UI:NotificationsMenu:HelpContent' => '

En iTop las notificaciones son completamente personalizables. Están basadas en dos conjuntos de objetos: Disparadores y Acciones.

@@ -981,7 +980,7 @@ Tales acciones tambien definen la plantilla a ser usada para enviar el correo as

Una página especial: email.test.php está disponible para probar y diagnosticar su configuración de correo de PHP.

Para ser ejecutadas, las acciones deben estar asociadas con los disparadores. Cuando se asocien con un disparador, cada acción recibe un número de "orden", esto especifica en que orden se ejecutaran las acciones.

', - 'UI:NotificationsMenu:Triggers' => 'Disparadores', + 'UI:NotificationsMenu:Triggers' => 'Disparadores', 'UI:NotificationsMenu:AvailableTriggers' => 'Disparadores disponibles', 'UI:NotificationsMenu:OnCreate' => 'Cuando un objeto es creado', 'UI:NotificationsMenu:OnStateEnter' => 'Cuando un objeto entra a un estado específico', @@ -989,45 +988,38 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'UI:NotificationsMenu:Actions' => 'Acciones', 'UI:NotificationsMenu:AvailableActions' => 'Acciones Disponibles', - 'Menu:TagAdminMenu' => 'Configuración de Etiquetas', - 'Menu:TagAdminMenu+' => 'Gestión de valores de Etiquetas', - 'UI:TagAdminMenu:Title' => 'Configuración de Etiquetas', - 'UI:TagAdminMenu:NoTags' => 'No hay campos Etiquetas configurados', + 'Menu:TagAdminMenu' => 'Configuración de Etiquetas', + 'Menu:TagAdminMenu+' => 'Gestión de valores de Etiquetas', + 'UI:TagAdminMenu:Title' => 'Configuración de Etiquetas', + 'UI:TagAdminMenu:NoTags' => 'No hay campos Etiquetas configurados', 'UI:TagSetFieldData:Error' => 'Error: %1$s', - 'Menu:AuditCategories' => 'Auditar Categorías', - // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:AuditCategories+' => 'Auditar Categorías', - // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:Notifications:Title' => 'Auditar Categorías', - // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:AuditCategories' => 'Auditar Categorías', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:AuditCategories+' => 'Auditar Categorías', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:Notifications:Title' => 'Auditar Categorías', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:RunQueriesMenu' => 'Ejecutar Consultas', - // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:RunQueriesMenu+' => 'Ejecutar Cualquier Consulta', - // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:RunQueriesMenu' => 'Ejecutar Consultas', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:RunQueriesMenu+' => 'Ejecutar Cualquier Consulta', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:QueryMenu' => 'Libreta de Consultas', - // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:QueryMenu+' => 'Libreta de Consultas', - // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:QueryMenu' => 'Libreta de Consultas', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:QueryMenu+' => 'Libreta de Consultas', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:DataAdministration' => 'Administración de Datos',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:DataAdministration+' => 'Administración de Datos',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:DataAdministration' => 'Administración de Datos', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:DataAdministration+' => 'Administración de Datos', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UniversalSearchMenu' => 'Búsqueda Universal',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UniversalSearchMenu+' => 'Buscar cualquier cosa',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UniversalSearchMenu' => 'Búsqueda Universal', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UniversalSearchMenu+' => 'Buscar cualquier cosa', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UserManagementMenu' => 'Administración de Usuarios',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UserManagementMenu+' => 'Administración de Usuarios',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UserManagementMenu' => 'Administración de Usuarios', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UserManagementMenu+' => 'Administración de Usuarios', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:ProfilesMenu' => 'Perfiles',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:ProfilesMenu+' => 'Perfiles',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:ProfilesMenu:Title' => 'Perfiles',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:ProfilesMenu' => 'Perfiles', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:ProfilesMenu+' => 'Perfiles', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:ProfilesMenu:Title' => 'Perfiles', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UserAccountsMenu' => 'Cuentas de Usuario',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UserAccountsMenu+' => 'Cuentas de Usuario',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UserAccountsMenu:Title' => 'Cuentas de Usuario',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UserAccountsMenu' => 'Cuentas de Usuario', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UserAccountsMenu+' => 'Cuentas de Usuario', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UserAccountsMenu:Title' => 'Cuentas de Usuario', // Duplicated into itop-welcome-itil (will be removed from here...) 'UI:iTopVersion:Short' => '%1$s versión %2$s', 'UI:iTopVersion:Long' => '%1$s versión %2$s-%3$s compilada en %4$s', @@ -1038,46 +1030,46 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'UI:Document:NoPreview' => 'No hay prevista disponible para este tipo de archivo', 'UI:Download-CSV' => 'Descargar %1$s', - 'UI:DeadlineMissedBy_duration' => 'No se cumplió por %1$s', - 'UI:Deadline_LessThan1Min' => '< 1 min', - 'UI:Deadline_Minutes' => '%1$d min', - 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', - 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', - 'UI:Help' => 'Ayuda', - 'UI:PasswordConfirm' => '(Confirmar)', + 'UI:DeadlineMissedBy_duration' => 'No se cumplió por %1$s', + 'UI:Deadline_LessThan1Min' => '< 1 min', + 'UI:Deadline_Minutes' => '%1$d min', + 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', + 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', + 'UI:Help' => 'Ayuda', + 'UI:PasswordConfirm' => '(Confirmar)', 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Antes de Agregar un(a) %1$s, Guarde los Cambios Realizados.', - 'UI:DisplayThisMessageAtStartup' => 'Desplegar este Mensaje al Inicio', - 'UI:RelationshipGraph' => 'Vista Gráfica', - 'UI:RelationshipList' => 'Lista', - 'UI:RelationGroups' => 'Grupos', - 'UI:OperationCancelled' => 'Operación Cancelada', - 'UI:ElementsDisplayed' => 'Despliegue', - 'UI:RelationGroupNumber_N' => 'Grupo #%1$d', - 'UI:Relation:ExportAsPDF' => 'Exportar como PDF...', - 'UI:RelationOption:GroupingThreshold' => 'Umbral de Agrupamiento', - 'UI:Relation:AdditionalContextInfo' => 'Información Contextual Adicional', - 'UI:Relation:NoneSelected' => 'Ninguno', - 'UI:Relation:Zoom' => 'Zoom', - 'UI:Relation:ExportAsAttachment' => 'Exportar como Anexo...', - 'UI:Relation:DrillDown' => 'Detalles...', - 'UI:Relation:PDFExportOptions' => 'Opciones de exportación PDF', - 'UI:Relation:AttachmentExportOptions_Name' => 'Opciones para anexo a %1$s', - 'UI:RelationOption:Untitled' => 'Sin Título', - 'UI:Relation:Key' => 'Llave', - 'UI:Relation:Comments' => 'Comentarios', - 'UI:RelationOption:Title' => 'Título', - 'UI:RelationOption:IncludeList' => 'Incluír lista de objetos', - 'UI:RelationOption:Comments' => 'Comentarios', - 'UI:Button:Export' => 'Exportar', - 'UI:Relation:PDFExportPageFormat' => 'Formato de Página', - 'UI:PageFormat_A3' => 'A3', - 'UI:PageFormat_A4' => 'A4', - 'UI:PageFormat_Letter' => 'Carta', - 'UI:Relation:PDFExportPageOrientation' => 'Orientación de Página', - 'UI:PageOrientation_Portrait' => 'Vertical', - 'UI:PageOrientation_Landscape' => 'Horizontal', - 'UI:RelationTooltip:Redundancy' => 'Redundancia', - 'UI:RelationTooltip:ImpactedItems_N_of_M' => '# de elementos impactados: %1$d / %2$d', + 'UI:DisplayThisMessageAtStartup' => 'Desplegar este Mensaje al Inicio', + 'UI:RelationshipGraph' => 'Vista Gráfica', + 'UI:RelationshipList' => 'Lista', + 'UI:RelationGroups' => 'Grupos', + 'UI:OperationCancelled' => 'Operación Cancelada', + 'UI:ElementsDisplayed' => 'Despliegue', + 'UI:RelationGroupNumber_N' => 'Grupo #%1$d', + 'UI:Relation:ExportAsPDF' => 'Exportar como PDF...', + 'UI:RelationOption:GroupingThreshold' => 'Umbral de Agrupamiento', + 'UI:Relation:AdditionalContextInfo' => 'Información Contextual Adicional', + 'UI:Relation:NoneSelected' => 'Ninguno', + 'UI:Relation:Zoom' => 'Zoom', + 'UI:Relation:ExportAsAttachment' => 'Exportar como Anexo...', + 'UI:Relation:DrillDown' => 'Detalles...', + 'UI:Relation:PDFExportOptions' => 'Opciones de exportación PDF', + 'UI:Relation:AttachmentExportOptions_Name' => 'Opciones para anexo a %1$s', + 'UI:RelationOption:Untitled' => 'Sin Título', + 'UI:Relation:Key' => 'Llave', + 'UI:Relation:Comments' => 'Comentarios', + 'UI:RelationOption:Title' => 'Título', + 'UI:RelationOption:IncludeList' => 'Incluír lista de objetos', + 'UI:RelationOption:Comments' => 'Comentarios', + 'UI:Button:Export' => 'Exportar', + 'UI:Relation:PDFExportPageFormat' => 'Formato de Página', + 'UI:PageFormat_A3' => 'A3', + 'UI:PageFormat_A4' => 'A4', + 'UI:PageFormat_Letter' => 'Carta', + 'UI:Relation:PDFExportPageOrientation' => 'Orientación de Página', + 'UI:PageOrientation_Portrait' => 'Vertical', + 'UI:PageOrientation_Landscape' => 'Horizontal', + 'UI:RelationTooltip:Redundancy' => 'Redundancia', + 'UI:RelationTooltip:ImpactedItems_N_of_M' => '# de elementos impactados: %1$d / %2$d', 'UI:RelationTooltip:CriticalThreshold_N_of_M' => 'Umbral Crítico: %1$d / %2$d', 'Portal:Title' => 'Portal de Usuario', 'Portal:NoRequestMgmt' => 'Estimado(a) %1$s, ha sido redirigido a esta página porque su cuenta está configurada con el Perfil \'Portal user\'. Desafortunadamente, iTop no fue instalado con el módulo \'Request Management\'. Por favor contacte a su Administrador.', @@ -1151,43 +1143,43 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'UI:HierarchyOf_Class' => 'Jerarquía de %1$s', 'UI:Preferences' => 'Preferencias', 'UI:ArchiveModeOn' => 'Activar modo Archivado', - 'UI:ArchiveModeOff' => 'Deactivar modo Archivado', - 'UI:ArchiveMode:Banner' => 'Modo Archivado', - 'UI:ArchiveMode:Banner+' => 'Objetos archivados son visibles, y ninguna modificación es permitida', - 'UI:FavoriteOrganizations' => 'Mi Organización Favorita', - 'UI:FavoriteOrganizations+' => 'Verifique en la siguiente lista de Organizaciones, la que necesite ver en los menues para un rápido acceso. Nota, esto no es una configuración de seguridad, elementos de cualquier Organización son visibles y pueden ser accesados mediante la selección de "Todas las Organizaciones" en la lista del menú.', - 'UI:FavoriteLanguage' => 'Idioma de la Interfaz de Usuario', - 'UI:Favorites:SelectYourLanguage' => 'Seleccione su Idioma Predeterminado', - 'UI:FavoriteOtherSettings' => 'Otras Configuraciones', - 'UI:Favorites:Default_X_ItemsPerPage' => 'Tamaño Predeterminado de Listas: %1$s elementos por página', - 'UI:Favorites:ShowObsoleteData' => 'Mostrar datos Obsoletos', - 'UI:Favorites:ShowObsoleteData+' => 'Mostrar datos obsoletos en resultados de búsqueda y listas de elementos seleccionables', - 'UI:NavigateAwayConfirmationMessage' => 'Cualquier modificación será descartada.', - 'UI:CancelConfirmationMessage' => 'Perderá los cambios realizados. ¿Desea Continuar?', - 'UI:AutoApplyConfirmationMessage' => 'Algunos cambios no han sido aplicados todavía. ¿Quiere que iTop los tome en cuenta?', - 'UI:Create_Class_InState' => 'Crear %1$s en el estado: ', - 'UI:OrderByHint_Values' => 'Ordenamiento: %1$s', - 'UI:Menu:AddToDashboard' => 'Agregar a Panel de Control', - 'UI:Button:Refresh' => 'Refrescar', - 'UI:Button:GoPrint' => 'Imprimir...', - 'UI:ExplainPrintable' => 'Click en el icono %1$s para ocultar elementos de la impresión.
Use la funcionalidad "vista preliminar" de su navegador para visualizar antes de imprimir.
Nota: Este encabezado y controles de ajuste no serán impresos.', - 'UI:PrintResolution:FullSize' => 'Tamaño Completo', - 'UI:PrintResolution:A4Portrait' => 'A4 Vertical', - 'UI:PrintResolution:A4Landscape' => 'A4 Horizontal', - 'UI:PrintResolution:LetterPortrait' => 'Carta Vertical', - 'UI:PrintResolution:LetterLandscape' => 'Carta Horizontal', - 'UI:Toggle:StandardDashboard' => 'Estandar', - 'UI:Toggle:CustomDashboard' => 'Personalizado', + 'UI:ArchiveModeOff' => 'Deactivar modo Archivado', + 'UI:ArchiveMode:Banner' => 'Modo Archivado', + 'UI:ArchiveMode:Banner+' => 'Objetos archivados son visibles, y ninguna modificación es permitida', + 'UI:FavoriteOrganizations' => 'Mi Organización Favorita', + 'UI:FavoriteOrganizations+' => 'Verifique en la siguiente lista de Organizaciones, la que necesite ver en los menues para un rápido acceso. Nota, esto no es una configuración de seguridad, elementos de cualquier Organización son visibles y pueden ser accesados mediante la selección de "Todas las Organizaciones" en la lista del menú.', + 'UI:FavoriteLanguage' => 'Idioma de la Interfaz de Usuario', + 'UI:Favorites:SelectYourLanguage' => 'Seleccione su Idioma Predeterminado', + 'UI:FavoriteOtherSettings' => 'Otras Configuraciones', + 'UI:Favorites:Default_X_ItemsPerPage' => 'Tamaño Predeterminado de Listas: %1$s elementos por página', + 'UI:Favorites:ShowObsoleteData' => 'Mostrar datos Obsoletos', + 'UI:Favorites:ShowObsoleteData+' => 'Mostrar datos obsoletos en resultados de búsqueda y listas de elementos seleccionables', + 'UI:NavigateAwayConfirmationMessage' => 'Cualquier modificación será descartada.', + 'UI:CancelConfirmationMessage' => 'Perderá los cambios realizados. ¿Desea Continuar?', + 'UI:AutoApplyConfirmationMessage' => 'Algunos cambios no han sido aplicados todavía. ¿Quiere que iTop los tome en cuenta?', + 'UI:Create_Class_InState' => 'Crear %1$s en el estado: ', + 'UI:OrderByHint_Values' => 'Ordenamiento: %1$s', + 'UI:Menu:AddToDashboard' => 'Agregar a Panel de Control', + 'UI:Button:Refresh' => 'Refrescar', + 'UI:Button:GoPrint' => 'Imprimir...', + 'UI:ExplainPrintable' => 'Click en el icono %1$s para ocultar elementos de la impresión.
Use la funcionalidad "vista preliminar" de su navegador para visualizar antes de imprimir.
Nota: Este encabezado y controles de ajuste no serán impresos.', + 'UI:PrintResolution:FullSize' => 'Tamaño Completo', + 'UI:PrintResolution:A4Portrait' => 'A4 Vertical', + 'UI:PrintResolution:A4Landscape' => 'A4 Horizontal', + 'UI:PrintResolution:LetterPortrait' => 'Carta Vertical', + 'UI:PrintResolution:LetterLandscape' => 'Carta Horizontal', + 'UI:Toggle:StandardDashboard' => 'Estandar', + 'UI:Toggle:CustomDashboard' => 'Personalizado', - 'UI:ConfigureThisList' => 'Configurar Lista', + 'UI:ConfigureThisList' => 'Configurar Lista', 'UI:ListConfigurationTitle' => 'Configuración de Lista', - 'UI:ColumnsAndSortOrder' => 'Columnas y Ordenamiento:', - 'UI:UseDefaultSettings' => 'Usar Configuración por Omisión', - 'UI:UseSpecificSettings' => 'Usar la Siguiente Configuración:', + 'UI:ColumnsAndSortOrder' => 'Columnas y Ordenamiento:', + 'UI:UseDefaultSettings' => 'Usar Configuración por Omisión', + 'UI:UseSpecificSettings' => 'Usar la Siguiente Configuración:', 'UI:Display_X_ItemsPerPage' => 'Desplegar %1$s elementos por página', - 'UI:UseSavetheSettings' => 'Guardar Configuraciones', - 'UI:OnlyForThisList' => 'Sólo esta Lista', - 'UI:ForAllLists' => 'Defecto en todas las listas', + 'UI:UseSavetheSettings' => 'Guardar Configuraciones', + 'UI:OnlyForThisList' => 'Sólo esta Lista', + 'UI:ForAllLists' => 'Defecto en todas las listas', 'UI:ExtKey_AsLink' => '%1$s (Liga)', 'UI:ExtKey_AsFriendlyName' => '%1$s (Nombre Común)', 'UI:ExtField_AsRemoteField' => '%1$s (%2$s)', @@ -1223,29 +1215,29 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'UI:DashboardEdit:DashletProperties' => 'Propiedades de Dashlet', 'UI:Form:Property' => 'Propiedad', - 'UI:Form:Value' => 'Valor', + 'UI:Form:Value' => 'Valor', - 'UI:DashletUnknown:Label' => 'Desconocido', - 'UI:DashletUnknown:Description' => 'Dashlet desconocido (puede haber sido desinstalado)', - 'UI:DashletUnknown:RenderText:View' => 'No es posible desplegar este dashlet.', - 'UI:DashletUnknown:RenderText:Edit' => 'No es posible desplegar este dashlet (clase "%1$s"). Verifique con su administrador si está todavia disponible.', + 'UI:DashletUnknown:Label' => 'Desconocido', + 'UI:DashletUnknown:Description' => 'Dashlet desconocido (puede haber sido desinstalado)', + 'UI:DashletUnknown:RenderText:View' => 'No es posible desplegar este dashlet.', + 'UI:DashletUnknown:RenderText:Edit' => 'No es posible desplegar este dashlet (clase "%1$s"). Verifique con su administrador si está todavia disponible.', 'UI:DashletUnknown:RenderNoDataText:Edit' => 'No hay vista previa para este dashlet (clase "%1$s").', 'UI:DashletUnknown:Prop-XMLConfiguration' => 'Configuración (mostrado como código XML)', - 'UI:DashletProxy:Label' => 'Proxy', - 'UI:DashletProxy:Description' => 'Dashlet Proxy', + 'UI:DashletProxy:Label' => 'Proxy', + 'UI:DashletProxy:Description' => 'Dashlet Proxy', 'UI:DashletProxy:RenderNoDataText:Edit' => 'No hay vista previa para este dashlet de terceros (clase "%1$s").', 'UI:DashletProxy:Prop-XMLConfiguration' => 'Configuración (mostrado como XML sin formato)', - 'UI:DashletPlainText:Label' => 'Texto', - 'UI:DashletPlainText:Description' => 'Texto Plano (sin formato)', - 'UI:DashletPlainText:Prop-Text' => 'Texto', + 'UI:DashletPlainText:Label' => 'Texto', + 'UI:DashletPlainText:Description' => 'Texto Plano (sin formato)', + 'UI:DashletPlainText:Prop-Text' => 'Texto', 'UI:DashletPlainText:Prop-Text:Default' => 'Escriba texto aquí...', - 'UI:DashletObjectList:Label' => 'Lista de Objetos', + 'UI:DashletObjectList:Label' => 'Lista de Objetos', 'UI:DashletObjectList:Description' => 'Lista de Objetos en dashlet', - 'UI:DashletObjectList:Prop-Title' => 'Título', - 'UI:DashletObjectList:Prop-Query' => 'Consulta', + 'UI:DashletObjectList:Prop-Title' => 'Título', + 'UI:DashletObjectList:Prop-Query' => 'Consulta', 'UI:DashletObjectList:Prop-Menu' => 'Menú', 'UI:DashletGroupBy:Prop-Title' => 'Título', @@ -1352,13 +1344,13 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'Month-10-Short' => 'Oct', 'Month-11-Short' => 'Nov', 'Month-12-Short' => 'Dic', - 'Calendar-FirstDayOfWeek' => '0',// 0 = Sunday, 1 = Monday, etc... + 'Calendar-FirstDayOfWeek' => '0', // 0 = Sunday, 1 = Monday, etc... 'UI:Menu:ShortcutList' => 'Crear Acceso Rápido', 'UI:ShortcutRenameDlg:Title' => 'Renombrar Acceso Rápido', 'UI:ShortcutListDlg:Title' => 'Crear Acceso Rápido para la Lista', 'UI:ShortcutDelete:Confirm' => 'Por favor conforme que desea Eliminar el/los Acceso(s) Rápido(s)', - 'Menu:MyShortcuts' => 'Mis Accesos Rápidos',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:MyShortcuts' => 'Mis Accesos Rápidos', // Duplicated into itop-welcome-itil (will be removed from here...) 'Class:Shortcut' => 'Acceso Rápido', 'Class:Shortcut+' => 'Acceso Rápido', 'Class:Shortcut/Attribute:name' => 'Nombre', @@ -1374,20 +1366,20 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'Class:ShortcutOQL/Attribute:auto_reload_sec/tip' => 'El interválo mínimo es de %1$d segundos', 'UI:FillAllMandatoryFields' => 'Por favor llenar los campos obligatorios.', - 'UI:ValueMustBeSet' => 'Por favor, ingrese un valor', - 'UI:ValueMustBeChanged' => 'Por favor cambie el valor', - 'UI:ValueInvalidFormat' => 'Formato inválido', + 'UI:ValueMustBeSet' => 'Por favor, ingrese un valor', + 'UI:ValueMustBeChanged' => 'Por favor cambie el valor', + 'UI:ValueInvalidFormat' => 'Formato inválido', - 'UI:CSVImportConfirmTitle' => 'Por favor confirme la operación', - 'UI:CSVImportConfirmMessage' => '¿Está seguro?', - 'UI:CSVImportError_items' => 'Errores: %1$d', - 'UI:CSVImportCreated_items' => 'Creados: %1$d', - 'UI:CSVImportModified_items' => 'Modificados: %1$d', - 'UI:CSVImportUnchanged_items' => 'Sin cambios: %1$d', - 'UI:CSVImport:DateAndTimeFormats' => 'Formato de Fecha y Hora', + 'UI:CSVImportConfirmTitle' => 'Por favor confirme la operación', + 'UI:CSVImportConfirmMessage' => '¿Está seguro?', + 'UI:CSVImportError_items' => 'Errores: %1$d', + 'UI:CSVImportCreated_items' => 'Creados: %1$d', + 'UI:CSVImportModified_items' => 'Modificados: %1$d', + 'UI:CSVImportUnchanged_items' => 'Sin cambios: %1$d', + 'UI:CSVImport:DateAndTimeFormats' => 'Formato de Fecha y Hora', 'UI:CSVImport:DefaultDateTimeFormat_Format_Example' => 'Formato Predeterminado: %1$s (ejem.: %2$s)', - 'UI:CSVImport:CustomDateTimeFormat' => 'Formato Personalizado: %1$s', - 'UI:CSVImport:CustomDateTimeFormatTooltip' => 'Marcadores disponibles: + 'UI:CSVImport:CustomDateTimeFormat' => 'Formato Personalizado: %1$s', + 'UI:CSVImport:CustomDateTimeFormatTooltip' => 'Marcadores disponibles:
@@ -1404,29 +1396,29 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden",
Yaño (4 digitos, ejem. 2016)
yaño (2 digitos, ejem. 16 for 2016)
mmes (2 digitos, ejem. 01..12)
ssegundos (2 digitos, ejem. 00..59)
', - 'UI:Button:Remove' => 'Remover', + 'UI:Button:Remove' => 'Remover', 'UI:AddAnExisting_Class' => 'Agregar objetos del tipo %1$s...', - 'UI:SelectionOf_Class' => 'Selección de objetos del tipo %1$s', + 'UI:SelectionOf_Class' => 'Selección de objetos del tipo %1$s', - 'UI:AboutBox' => 'Acerca de iTop...', - 'UI:About:Title' => 'Acerca de iTop', - 'UI:About:DataModel' => 'Modelo de Datos', - 'UI:About:Support' => 'Información de Soporte', - 'UI:About:Licenses' => 'Licencias', - 'UI:About:InstallationOptions' => 'Opciones de Instalación', + 'UI:AboutBox' => 'Acerca de iTop...', + 'UI:About:Title' => 'Acerca de iTop', + 'UI:About:DataModel' => 'Modelo de Datos', + 'UI:About:Support' => 'Información de Soporte', + 'UI:About:Licenses' => 'Licencias', + 'UI:About:InstallationOptions' => 'Opciones de Instalación', 'UI:About:ManualExtensionSource' => 'Extensión', - 'UI:About:Extension_Version' => 'Versión: %1$s', + 'UI:About:Extension_Version' => 'Versión: %1$s', 'UI:About:RemoteExtensionSource' => 'Fuente', 'UI:DisconnectedDlgMessage' => 'Está desconectado. Debe identificarse para continuar usando la aplicación.', - 'UI:DisconnectedDlgTitle' => 'Advertencia', - 'UI:LoginAgain' => 'Conectarse nuevamente', - 'UI:StayOnThePage' => 'Mantenerse en esta página', + 'UI:DisconnectedDlgTitle' => 'Advertencia', + 'UI:LoginAgain' => 'Conectarse nuevamente', + 'UI:StayOnThePage' => 'Mantenerse en esta página', - 'ExcelExporter:ExportMenu' => 'Exportar a Excel...', + 'ExcelExporter:ExportMenu' => 'Exportar a Excel...', 'ExcelExporter:ExportDialogTitle' => 'Exportar a Excel', - 'ExcelExporter:ExportButton' => 'Exportar', - 'ExcelExporter:DownloadButton' => 'Descargar %1$s', + 'ExcelExporter:ExportButton' => 'Exportar', + 'ExcelExporter:DownloadButton' => 'Descargar %1$s', 'ExcelExporter:RetrievingData' => 'Recuperando datos...', 'ExcelExporter:BuildingExcelFile' => 'Construyendo el archivo de Excel...', 'ExcelExporter:Done' => 'Hecho.', @@ -1446,44 +1438,44 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'UI:Menu:ExportPDF' => 'Exportar como PDF...', 'UI:Menu:PrintableVersion' => 'Versión imprimible', - 'UI:BrowseInlineImages' => 'Ver imágenes...', - 'UI:UploadInlineImageLegend' => 'Subir nueva imágen', - 'UI:SelectInlineImageToUpload' => 'Seleccione la imágen a subir', + 'UI:BrowseInlineImages' => 'Ver imágenes...', + 'UI:UploadInlineImageLegend' => 'Subir nueva imágen', + 'UI:SelectInlineImageToUpload' => 'Seleccione la imágen a subir', 'UI:AvailableInlineImagesLegend' => 'Imágenes disponibles', - 'UI:NoInlineImage' => 'No hay imágenes disponibles en el servidor. Use el botón "Seleccionar archivo" para seleccionar una imágen de su equipo local y subirla al servidor.', + 'UI:NoInlineImage' => 'No hay imágenes disponibles en el servidor. Use el botón "Seleccionar archivo" para seleccionar una imágen de su equipo local y subirla al servidor.', - 'UI:ToggleFullScreen' => 'Cambiar Maximizar / Minimizar', - 'UI:Button:ResetImage' => 'Recuperar imágen previa', - 'UI:Button:RemoveImage' => 'Remover imágen', + 'UI:ToggleFullScreen' => 'Cambiar Maximizar / Minimizar', + 'UI:Button:ResetImage' => 'Recuperar imágen previa', + 'UI:Button:RemoveImage' => 'Remover imágen', 'UI:UploadNotSupportedInThisMode' => 'La modificación de imágenes o archivos no está soportado en este modo.', - 'UI:Button:RemoveDocument' => 'Remover documento', + 'UI:Button:RemoveDocument' => 'Remover documento', // Search form - 'UI:Search:Toggle' => 'Minimizar/ Expandir', - 'UI:Search:AutoSubmit:DisabledHint' => 'Auto enviar ha sido deshabilitado para esta clase', - 'UI:Search:Obsolescence:DisabledHint' => ' Basado en sus preferencias, datos obsoletos están ocultos', - 'UI:Search:NoAutoSubmit:ExplainText' => 'Agregue algún criterio en el cuadro de búsqueda o haga click en el botón de búsqueda para ver los objetos.', - 'UI:Search:Criterion:MoreMenu:AddCriteria' => 'Agregar nuevo criterio', + 'UI:Search:Toggle' => 'Minimizar/ Expandir', + 'UI:Search:AutoSubmit:DisabledHint' => 'Auto enviar ha sido deshabilitado para esta clase', + 'UI:Search:Obsolescence:DisabledHint' => ' Basado en sus preferencias, datos obsoletos están ocultos', + 'UI:Search:NoAutoSubmit:ExplainText' => 'Agregue algún criterio en el cuadro de búsqueda o haga click en el botón de búsqueda para ver los objetos.', + 'UI:Search:Criterion:MoreMenu:AddCriteria' => 'Agregar nuevo criterio', // - Add new criteria button - 'UI:Search:AddCriteria:List:RecentlyUsed:Title' => 'Recientemente usado', - 'UI:Search:AddCriteria:List:MostPopular:Title' => 'Más popular', - 'UI:Search:AddCriteria:List:Others:Title' => 'Otros', - 'UI:Search:AddCriteria:List:RecentlyUsed:Placeholder' => 'Ninguno todavía', + 'UI:Search:AddCriteria:List:RecentlyUsed:Title' => 'Recientemente usado', + 'UI:Search:AddCriteria:List:MostPopular:Title' => 'Más popular', + 'UI:Search:AddCriteria:List:Others:Title' => 'Otros', + 'UI:Search:AddCriteria:List:RecentlyUsed:Placeholder' => 'Ninguno todavía', // - Criteria titles // - Default widget - 'UI:Search:Criteria:Title:Default:Any' => '%1$s: Cualquier', - 'UI:Search:Criteria:Title:Default:Empty' => '%1$s está vacío', - 'UI:Search:Criteria:Title:Default:NotEmpty' => '%1$s no está vacío', - 'UI:Search:Criteria:Title:Default:Equals' => '%1$s igual a %2$s', - 'UI:Search:Criteria:Title:Default:Contains' => '%1$s contiene %2$s', - 'UI:Search:Criteria:Title:Default:StartsWith' => '%1$s comienza con %2$s', - 'UI:Search:Criteria:Title:Default:EndsWith' => '%1$s termina con %2$s', - 'UI:Search:Criteria:Title:Default:RegExp' => '%1$s coincide con %2$s', - 'UI:Search:Criteria:Title:Default:GreaterThan' => '%1$s > %2$s', - 'UI:Search:Criteria:Title:Default:GreaterThanOrEquals' => '%1$s >= %2$s', - 'UI:Search:Criteria:Title:Default:LessThan' => '%1$s < %2$s', + 'UI:Search:Criteria:Title:Default:Any' => '%1$s: Cualquier', + 'UI:Search:Criteria:Title:Default:Empty' => '%1$s está vacío', + 'UI:Search:Criteria:Title:Default:NotEmpty' => '%1$s no está vacío', + 'UI:Search:Criteria:Title:Default:Equals' => '%1$s igual a %2$s', + 'UI:Search:Criteria:Title:Default:Contains' => '%1$s contiene %2$s', + 'UI:Search:Criteria:Title:Default:StartsWith' => '%1$s comienza con %2$s', + 'UI:Search:Criteria:Title:Default:EndsWith' => '%1$s termina con %2$s', + 'UI:Search:Criteria:Title:Default:RegExp' => '%1$s coincide con %2$s', + 'UI:Search:Criteria:Title:Default:GreaterThan' => '%1$s > %2$s', + 'UI:Search:Criteria:Title:Default:GreaterThanOrEquals' => '%1$s >= %2$s', + 'UI:Search:Criteria:Title:Default:LessThan' => '%1$s < %2$s', 'UI:Search:Criteria:Title:Default:LessThanOrEquals' => '%1$s <= %2$s', 'UI:Search:Criteria:Title:Default:Different' => '%1$s ≠ %2$s', 'UI:Search:Criteria:Title:Default:Between' => '%1$s entre [%2$s]', @@ -1521,49 +1513,43 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", // - Criteria operators // - Default widget - 'UI:Search:Criteria:Operator:Default:Empty' => 'Está vacío', - 'UI:Search:Criteria:Operator:Default:NotEmpty' => 'No está vacío', - 'UI:Search:Criteria:Operator:Default:Equals' => 'Igual', - 'UI:Search:Criteria:Operator:Default:Between' => 'Entre', + 'UI:Search:Criteria:Operator:Default:Empty' => 'Está vacío', + 'UI:Search:Criteria:Operator:Default:NotEmpty' => 'No está vacío', + 'UI:Search:Criteria:Operator:Default:Equals' => 'Igual', + 'UI:Search:Criteria:Operator:Default:Between' => 'Entre', // - String widget - 'UI:Search:Criteria:Operator:String:Contains' => 'Contiene', - 'UI:Search:Criteria:Operator:String:StartsWith' => 'Comienza con', - 'UI:Search:Criteria:Operator:String:EndsWith' => 'Termina con', - 'UI:Search:Criteria:Operator:String:RegExp' => 'Exp. Regular', + 'UI:Search:Criteria:Operator:String:Contains' => 'Contiene', + 'UI:Search:Criteria:Operator:String:StartsWith' => 'Comienza con', + 'UI:Search:Criteria:Operator:String:EndsWith' => 'Termina con', + 'UI:Search:Criteria:Operator:String:RegExp' => 'Exp. Regular', // - Numeric widget - 'UI:Search:Criteria:Operator:Numeric:Equals' => 'Igual', - // => '=', - 'UI:Search:Criteria:Operator:Numeric:GreaterThan' => 'Mayor', - // => '>', - 'UI:Search:Criteria:Operator:Numeric:GreaterThanOrEquals' => 'Mayor / igual', - // > '>=', - 'UI:Search:Criteria:Operator:Numeric:LessThan' => 'Menor', - // => '<', - 'UI:Search:Criteria:Operator:Numeric:LessThanOrEquals' => 'Menor / igual', - // > '<=', - 'UI:Search:Criteria:Operator:Numeric:Different' => 'Diferente', - // => '≠', + 'UI:Search:Criteria:Operator:Numeric:Equals' => 'Igual', // => '=', + 'UI:Search:Criteria:Operator:Numeric:GreaterThan' => 'Mayor', // => '>', + 'UI:Search:Criteria:Operator:Numeric:GreaterThanOrEquals' => 'Mayor / igual', // > '>=', + 'UI:Search:Criteria:Operator:Numeric:LessThan' => 'Menor', // => '<', + 'UI:Search:Criteria:Operator:Numeric:LessThanOrEquals' => 'Menor / igual', // > '<=', + 'UI:Search:Criteria:Operator:Numeric:Different' => 'Diferente', // => '≠', // - Tag Set Widget - 'UI:Search:Criteria:Operator:TagSet:Matches' => 'Coincidencias', + 'UI:Search:Criteria:Operator:TagSet:Matches' => 'Coincidencias', // - Other translations - 'UI:Search:Value:Filter:Placeholder' => 'Filtro...', - 'UI:Search:Value:Search:Placeholder' => 'Búsqueda...', - 'UI:Search:Value:Autocomplete:StartTyping' => 'Inicie escribiento posibles valores.', - 'UI:Search:Value:Autocomplete:Wait' => 'Por favor espere...', - 'UI:Search:Value:Autocomplete:NoResult' => 'Sin Resultados.', - 'UI:Search:Value:Toggler:CheckAllNone' => 'Marcar todos / ninguno', - 'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Marcar todos / ninguno visible', + 'UI:Search:Value:Filter:Placeholder' => 'Filtro...', + 'UI:Search:Value:Search:Placeholder' => 'Búsqueda...', + 'UI:Search:Value:Autocomplete:StartTyping' => 'Inicie escribiento posibles valores.', + 'UI:Search:Value:Autocomplete:Wait' => 'Por favor espere...', + 'UI:Search:Value:Autocomplete:NoResult' => 'Sin Resultados.', + 'UI:Search:Value:Toggler:CheckAllNone' => 'Marcar todos / ninguno', + 'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Marcar todos / ninguno visible', // - Widget other translations - 'UI:Search:Criteria:Numeric:From' => 'De', - 'UI:Search:Criteria:Numeric:Until' => 'Para', - 'UI:Search:Criteria:Numeric:PlaceholderFrom' => 'Cualquier', - 'UI:Search:Criteria:Numeric:PlaceholderUntil' => 'Cualquier', - 'UI:Search:Criteria:DateTime:From' => 'De', - 'UI:Search:Criteria:DateTime:FromTime' => 'De', - 'UI:Search:Criteria:DateTime:Until' => 'hasta', - 'UI:Search:Criteria:DateTime:UntilTime' => 'hasta', + 'UI:Search:Criteria:Numeric:From' => 'De', + 'UI:Search:Criteria:Numeric:Until' => 'Para', + 'UI:Search:Criteria:Numeric:PlaceholderFrom' => 'Cualquier', + 'UI:Search:Criteria:Numeric:PlaceholderUntil' => 'Cualquier', + 'UI:Search:Criteria:DateTime:From' => 'De', + 'UI:Search:Criteria:DateTime:FromTime' => 'De', + 'UI:Search:Criteria:DateTime:Until' => 'hasta', + 'UI:Search:Criteria:DateTime:UntilTime' => 'hasta', 'UI:Search:Criteria:DateTime:PlaceholderFrom' => 'Cualquier fecha', 'UI:Search:Criteria:DateTime:PlaceholderFromTime' => 'Cualquier fecha', 'UI:Search:Criteria:DateTime:PlaceholderUntil' => 'Cualquier fecha', @@ -1572,39 +1558,58 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'UI:Search:Criteria:Raw:Filtered' => 'Filtrado', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtrado en %1$s', + + 'UI:StateChanged' => 'State changed~~', )); // // Expression to Natural language // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( 'Expression:Operator:AND' => ' Y ', 'Expression:Operator:OR' => ' O ', 'Expression:Operator:=' => ': ', - 'Expression:Unit:Short:DAY' => 'd', - 'Expression:Unit:Short:WEEK' => 's', + 'Expression:Unit:Short:DAY' => 'd', + 'Expression:Unit:Short:WEEK' => 's', 'Expression:Unit:Short:MONTH' => 'm', - 'Expression:Unit:Short:YEAR' => 'a', + 'Expression:Unit:Short:YEAR' => 'a', - 'Expression:Unit:Long:DAY' => 'día(s)', - 'Expression:Unit:Long:HOUR' => 'hora(s)', + 'Expression:Unit:Long:DAY' => 'día(s)', + 'Expression:Unit:Long:HOUR' => 'hora(s)', 'Expression:Unit:Long:MINUTE' => 'minuto(s)', - 'Expression:Verb:NOW' => 'Ahora', + 'Expression:Verb:NOW' => 'Ahora', 'Expression:Verb:ISNULL' => ': Nulo', )); // // iTop Newsroom menu // -Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Newsroom:NoNewMessage' => 'Sin Mensajes', - 'UI:Newsroom:MarkAllAsRead' => 'Marcar todos los mensajes como leídos', - 'UI:Newsroom:ViewAllMessages' => 'Ver todos los mensajes', - 'UI:Newsroom:Preferences' => 'Preferencia de Notificaciones', - 'UI:Newsroom:ConfigurationLink' => 'Configuración', - 'UI:Newsroom:ResetCache' => 'Borrar caché', +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( + 'UI:Newsroom:NoNewMessage' => 'Sin Mensajes', + 'UI:Newsroom:MarkAllAsRead' => 'Marcar todos los mensajes como leídos', + 'UI:Newsroom:ViewAllMessages' => 'Ver todos los mensajes', + 'UI:Newsroom:Preferences' => 'Preferencia de Notificaciones', + 'UI:Newsroom:ConfigurationLink' => 'Configuración', + 'UI:Newsroom:ResetCache' => 'Borrar caché', 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Desplegar mensajes de %1$s', - 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Desplegar hasta %1$s mensajes en el menú %2$s.', + 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Desplegar hasta %1$s mensajes en el menú %2$s.', )); + + +// OAuth +Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index 0703c5e28..2a9f66b3a 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -1053,8 +1053,6 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:AsyncTask/Attribute:last_error+' => '', 'Class:AsyncTask/Attribute:last_attempt' => 'Dernière tentative', 'Class:AsyncTask/Attribute:last_attempt+' => '', - - )); // Additional language entries not present in English dict diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 5f2f0082e..344c75b98 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -1579,3 +1579,20 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Afficher les messages de %1$s', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Afficher au plus %1$s messages dans le menu %2$s.', )); + + +// OAuth +Dict::Add('FR FR', 'French', 'Français', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'Configuration OAuth 2.0', + 'UI:OAuth:Wizard:Page:Title' => 'Configuration OAuth 2.0', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'Configuration OAuth 2.0', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Id client', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Secret client', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Paramètres additionnels', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'URI de redirection', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentification', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration pour SMTP', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Copier ces lignes dans la configuration pour utiliser cette connexion OAyth 2.0 pour les mails sortants', +)); \ No newline at end of file diff --git a/dictionaries/hu.dictionary.itop.core.php b/dictionaries/hu.dictionary.itop.core.php index bc42eae22..cf2cbe090 100755 --- a/dictionaries/hu.dictionary.itop.core.php +++ b/dictionaries/hu.dictionary.itop.core.php @@ -1043,4 +1043,14 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'Class:AsyncTask/Attribute:event_id+' => '~~', 'Class:AsyncTask/Attribute:finalclass' => 'Final class~~', 'Class:AsyncTask/Attribute:finalclass+' => '~~', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/hu.dictionary.itop.ui.php b/dictionaries/hu.dictionary.itop.ui.php index 0af1f5d71..b7b048515 100755 --- a/dictionaries/hu.dictionary.itop.ui.php +++ b/dictionaries/hu.dictionary.itop.ui.php @@ -440,6 +440,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Hiba: az objektum már korában törlésre került!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Az osztály objektumainak tömeges törlése nem engedélyezett %1$s', 'UI:Error:DeleteNotAllowedOn_Class' => 'Az osztály objektumainak törlése nem engedélyezett %1$s', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Az osztály objektumainak tömeges frissítése nem engedélyezett %1$s', 'UI:Error:ObjectAlreadyCloned' => 'Hiba: az objektum már klónozott!', 'UI:Error:ObjectAlreadyCreated' => 'Hiba: az objekltum már létrehozva!', @@ -448,6 +449,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'UI:Error:InvalidDashboard' => 'Error: invalid dashboard~~', 'UI:Error:MaintenanceMode' => 'Application is currently in maintenance~~', 'UI:Error:MaintenanceTitle' => 'Maintenance~~', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Számossága', 'UI:GroupBy:Count+' => '', @@ -1539,6 +1541,8 @@ Akció kiváltó okhoz rendelésekor kap egy sorszámot , amely meghatározza az 'UI:Search:Criteria:Raw:Filtered' => 'Filtered~~', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s~~', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1575,3 +1579,20 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Display messages from %1$s~~', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Display up to %1$s messages in the %2$s menu.~~', )); + + +// OAuth +Dict::Add('HU HU', 'Hungarian', 'Magyar', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/it.dictionary.itop.core.php b/dictionaries/it.dictionary.itop.core.php index ea4d21ed0..b0e3721ad 100644 --- a/dictionaries/it.dictionary.itop.core.php +++ b/dictionaries/it.dictionary.itop.core.php @@ -1045,4 +1045,14 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'Class:AsyncTask/Attribute:event_id+' => '~~', 'Class:AsyncTask/Attribute:finalclass' => 'Final class~~', 'Class:AsyncTask/Attribute:finalclass+' => '~~', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/it.dictionary.itop.ui.php b/dictionaries/it.dictionary.itop.ui.php index 9fd19faf6..f1a4f99c0 100644 --- a/dictionaries/it.dictionary.itop.ui.php +++ b/dictionaries/it.dictionary.itop.ui.php @@ -453,6 +453,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Errore: gli oggetti sono già stati eliminati!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Non hai i permessi per eseguire una eliminazione collettiva degli oggetti della classe %1$s', 'UI:Error:DeleteNotAllowedOn_Class' => 'Non ti è permesso di eliminare gli oggetti della classe %1$s', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Non hai i permessi per eseguire un aggiornamento collettivo degli oggetti della classe %1$s', 'UI:Error:ObjectAlreadyCloned' => 'Errore: l\'oggetto è già stato clonato!', 'UI:Error:ObjectAlreadyCreated' => 'Errore: l\'oggetto è già stato creato!', @@ -461,6 +462,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'UI:Error:InvalidDashboard' => 'Error: invalid dashboard~~', 'UI:Error:MaintenanceMode' => 'L\'applicazione è attualmente in manutenzione', 'UI:Error:MaintenanceTitle' => 'Maintenance~~', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Conteggio', 'UI:GroupBy:Count+' => '', @@ -1554,6 +1556,8 @@ Quando è associata a un trigger, ad ogni azione è assegnato un numero "ordine" 'UI:Search:Criteria:Raw:Filtered' => 'Filtered~~', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s~~', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1590,3 +1594,20 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Display messages from %1$s~~', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Display up to %1$s messages in the %2$s menu.~~', )); + + +// OAuth +Dict::Add('IT IT', 'Italian', 'Italiano', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/ja.dictionary.itop.core.php b/dictionaries/ja.dictionary.itop.core.php index ee45b3014..39bd9618b 100644 --- a/dictionaries/ja.dictionary.itop.core.php +++ b/dictionaries/ja.dictionary.itop.core.php @@ -1043,4 +1043,14 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'Class:AsyncTask/Attribute:event_id+' => '~~', 'Class:AsyncTask/Attribute:finalclass' => 'Final class~~', 'Class:AsyncTask/Attribute:finalclass+' => '~~', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/ja.dictionary.itop.ui.php b/dictionaries/ja.dictionary.itop.ui.php index 29c859ceb..e30db1132 100644 --- a/dictionaries/ja.dictionary.itop.ui.php +++ b/dictionaries/ja.dictionary.itop.ui.php @@ -440,6 +440,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'UI:Error:ObjectsAlreadyDeleted' => 'エラー:オブジェクトは既に削除されています。', 'UI:Error:BulkDeleteNotAllowedOn_Class' => '%1$s クラスのオブジェクトに対するバルク削除の実行は許可されていません。', 'UI:Error:DeleteNotAllowedOn_Class' => '%1$s クラスのオブジェクトの削除は許可されていません。', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => '%1$s クラスのオブジェクトに対するバルクアップデートの実行は許可されていません。', 'UI:Error:ObjectAlreadyCloned' => 'エラー:このオブジェクトはすでに、クローンされています。', 'UI:Error:ObjectAlreadyCreated' => 'エラー:このオブジェクトは既に作成済みです。', @@ -448,6 +449,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'UI:Error:InvalidDashboard' => 'Error: invalid dashboard~~', 'UI:Error:MaintenanceMode' => 'Application is currently in maintenance~~', 'UI:Error:MaintenanceTitle' => 'Maintenance~~', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'カウント', 'UI:GroupBy:Count+' => '要素数', @@ -1540,6 +1542,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'UI:Search:Criteria:Raw:Filtered' => 'Filtered~~', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s~~', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1576,3 +1580,20 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Display messages from %1$s~~', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Display up to %1$s messages in the %2$s menu.~~', )); + + +// OAuth +Dict::Add('JA JP', 'Japanese', '日本語', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/nl.dictionary.itop.core.php b/dictionaries/nl.dictionary.itop.core.php index b1bacd34d..eb0252ac8 100644 --- a/dictionaries/nl.dictionary.itop.core.php +++ b/dictionaries/nl.dictionary.itop.core.php @@ -1051,4 +1051,14 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Class:AsyncTask/Attribute:event_id+' => '', 'Class:AsyncTask/Attribute:finalclass' => 'Uiteindelijke klasse', 'Class:AsyncTask/Attribute:finalclass+' => '', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/nl.dictionary.itop.ui.php b/dictionaries/nl.dictionary.itop.ui.php index 25b17bc37..3c269cb75 100644 --- a/dictionaries/nl.dictionary.itop.ui.php +++ b/dictionaries/nl.dictionary.itop.ui.php @@ -349,14 +349,14 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'BooleanLabel:yes' => 'Ja', 'BooleanLabel:no' => 'Nee', - 'UI:Login:Title' => 'Aanmelden in '.ITOP_APPLICATION_SHORT, + 'UI:Login:Title' => 'Aanmelden in ITOP_APPLICATION_SHORT', 'Menu:WelcomeMenu' => 'Welkom', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:WelcomeMenu+' => 'Welkom in '.ITOP_APPLICATION_SHORT, // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:WelcomeMenu+' => 'Welkom in ITOP_APPLICATION_SHORT', // Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:WelcomeMenuPage' => 'Welkom', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:WelcomeMenuPage+' => 'Welkom in '.ITOP_APPLICATION_SHORT, // Duplicated into itop-welcome-itil (will be removed from here...) - 'UI:WelcomeMenu:Title' => 'Welkom in '.ITOP_APPLICATION_SHORT, + 'Menu:WelcomeMenuPage+' => 'Welkom in ITOP_APPLICATION_SHORT', // Duplicated into itop-welcome-itil (will be removed from here...) + 'UI:WelcomeMenu:Title' => 'Welkom in ITOP_APPLICATION_SHORT', - 'UI:WelcomeMenu:LeftBlock' => '

'.ITOP_APPLICATION_SHORT.' is een compleet en open source portaal voor IT-operaties.

+ 'UI:WelcomeMenu:LeftBlock' => '

ITOP_APPLICATION_SHORT is een compleet en open source portaal voor IT-operaties.

    Op maat van jouw IT-omgeving:
  • Complete CMDB (Configuration Management Database) voor het documenteren en beheren van de IT-inventaris.
  • Incident Management-module voor het vinden van en communiceren over alle problemen die optreden .
  • @@ -367,14 +367,14 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(

Alle modules kunnen volledig onafhankelijk van elkaar worden opgezet, stap voor stap.

', - 'UI:WelcomeMenu:RightBlock' => '

'.ITOP_APPLICATION_SHORT.' is gericht op serviceproviders. Het zorgt ervoor dat IT-engineers gemakkelijk meerdere klanten of organisaties kunnen beheren. -

    '.ITOP_APPLICATION_SHORT.' zorgt dankzij een uitgebreide set van bedrijfsprocessen voor een reeks voordelen: + 'UI:WelcomeMenu:RightBlock' => '

    ITOP_APPLICATION_SHORT is gericht op serviceproviders. Het zorgt ervoor dat IT-engineers gemakkelijk meerdere klanten of organisaties kunnen beheren. +

      ITOP_APPLICATION_SHORT zorgt dankzij een uitgebreide set van bedrijfsprocessen voor een reeks voordelen:
    • De efficientië van het IT-management versterkt.
    • De prestaties van IT-operaties verbetert.
    • De klanttevredenheid verhoogt en leidinggevenden inzicht biedt in hun bedrijfsperformantie.

    -

    '.ITOP_APPLICATION_SHORT.' is klaar om geïntegreerd te worden met jouw huidige infrastructuur rond IT-management.

    +

    ITOP_APPLICATION_SHORT is klaar om geïntegreerd te worden met jouw huidige infrastructuur rond IT-management.

      De adoptie van dit IT-operationele portaal zal je helpen met:
    • Het beter beheren van een steeds complexere IT-omgeving.
    • @@ -441,7 +441,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Incorrecte linkdefinitie: de klasse %1$s om objecten te beheren werd niet gevonden als externe sleutel (key) in de klasse %2$s', 'UI:Error:Object_Class_Id_NotFound' => 'Object: %1$s:%2$d niet gevonden', 'UI:Error:WizardCircularReferenceInDependencies' => 'Fout: cirkelverwijzing in de afhankelijke variabelen tussen de velden. Controleer het datamodel.', - 'UI:Error:UploadedFileTooBig' => 'Het geüploade bestand is te groot. De maximale grootte is %1$s. Contacteer jouw '.ITOP_APPLICATION_SHORT.'-beheerder om deze limiet aan te passen. (Controleer de PHP-configuratie voor "upload_max_filesize" en "post_max_size" op de server).', + 'UI:Error:UploadedFileTooBig' => 'Het geüploade bestand is te groot. De maximale grootte is %1$s. Contacteer jouw ITOP_APPLICATION_SHORT-beheerder om deze limiet aan te passen. (Controleer de PHP-configuratie voor "upload_max_filesize" en "post_max_size" op de server).', 'UI:Error:UploadedFileTruncated.' => 'Het geüploade bestand is ingekort!', 'UI:Error:NoTmpDir' => 'De tijdelijke opslagruimte is niet gedefinieerd.', 'UI:Error:CannotWriteToTmp_Dir' => 'Niet mogelijk om het tijdelijke bestand naar een tijdelijke map weg te schrijven. upload_tmp_dir = "%1$s".', @@ -459,6 +459,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Fout: objecten zijn al verwijderd', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Je bent niet gemachtigd om meerdere objecten in klasse "%1$s") in één keer te verwijderen.', 'UI:Error:DeleteNotAllowedOn_Class' => 'Je bent niet gemachtigd om objecten van de klasse "%1$s" te verwijderen', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Je bent niet gemachtigd om meerdere objecten (klasse %1$s) in één keer aan te passen', 'UI:Error:ObjectAlreadyCloned' => 'Fout: het object is al gekloond!', 'UI:Error:ObjectAlreadyCreated' => 'Fout: het object is al aangemaakt!', @@ -467,6 +468,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Error:InvalidDashboard' => 'Fout: ongeldig dashboard', 'UI:Error:MaintenanceMode' => 'Toepassing is momenteel in onderhoud', 'UI:Error:MaintenanceTitle' => 'Onderhoud', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Aantal', 'UI:GroupBy:Count+' => 'Aantal objecten', @@ -523,14 +525,14 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:SearchValue:CheckAll' => 'Vink alles aan', 'UI:SearchValue:UncheckAll' => 'Vink alles uit', 'UI:SelectOne' => '-- selecteer --', - 'UI:Login:Welcome' => 'Welkom in '.ITOP_APPLICATION_SHORT.'!', + 'UI:Login:Welcome' => 'Welkom in ITOP_APPLICATION_SHORT!', 'UI:Login:IncorrectLoginPassword' => 'Ongeldige gebruikersnaam of wachtwoord, probeer opnieuw.', 'UI:Login:IdentifyYourself' => 'Identificeer jezelf voordat je verder gaat', 'UI:Login:UserNamePrompt' => 'Gebruikersnaam', 'UI:Login:PasswordPrompt' => 'Wachtwoord', 'UI:Login:ForgotPwd' => 'Wachtwoord vergeten?', 'UI:Login:ForgotPwdForm' => 'Wachtwoord vergeten', - 'UI:Login:ForgotPwdForm+' => ITOP_APPLICATION_SHORT.' kan je een e-mail sturen waarin de instructies voor het resetten van jouw account staan.', + 'UI:Login:ForgotPwdForm+' => 'ITOP_APPLICATION_SHORT kan je een e-mail sturen waarin de instructies voor het resetten van jouw account staan.', 'UI:Login:ResetPassword' => 'Stuur nu!', 'UI:Login:ResetPwdFailed' => 'E-mail sturen mislukt: %1$s', 'UI:Login:SeparatorOr' => 'Of', @@ -543,8 +545,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:ResetPwd-Error-NoEmail' => 'Er ontbreekt een e-mailadres. Neem contact op met jouw beheerder.', 'UI:ResetPwd-Error-Send' => 'Er is een technisch probleem bij het verzenden van de e-mail. Neem contact op met jouw beheerder.', 'UI:ResetPwd-EmailSent' => 'Kijk in jouw mailbox (eventueel bij ongewenste mail) en volg de instructies...', - 'UI:ResetPwd-EmailSubject' => 'Reset jouw '.ITOP_APPLICATION_SHORT.'-wachtwoord', - 'UI:ResetPwd-EmailBody' => '

      Je hebt een reset van jouw '.ITOP_APPLICATION_SHORT.'-wachtwoord aangevraagd.

      Klik op deze link (eenmalig te gebruiken) om een nieuw wachtwoord in te voeren

      .', + 'UI:ResetPwd-EmailSubject' => 'Reset jouw ITOP_APPLICATION_SHORT-wachtwoord', + 'UI:ResetPwd-EmailBody' => '

      Je hebt een reset van jouw ITOP_APPLICATION_SHORT-wachtwoord aangevraagd.

      Klik op deze link (eenmalig te gebruiken) om een nieuw wachtwoord in te voeren

      .', 'UI:ResetPwd-Title' => 'Reset wachtwoord', 'UI:ResetPwd-Error-InvalidToken' => 'Sorry. Jouw wachtwoord is al gereset, of je hebt al meerdere e-mails ontvangen. Zorg ervoor dat je de link in de laatst ontvangen e-mail gebruikt.', @@ -552,24 +554,24 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:ResetPwd-Ready' => 'Het wachtwoord is veranderd', 'UI:ResetPwd-Login' => 'Klik hier om in te loggen', - 'UI:Login:About' => ITOP_APPLICATION, + 'UI:Login:About' => 'ITOP_APPLICATION', 'UI:Login:ChangeYourPassword' => 'Verander jouw wachtwoord', 'UI:Login:OldPasswordPrompt' => 'Oud wachtwoord', 'UI:Login:NewPasswordPrompt' => 'Nieuw wachtwoord', 'UI:Login:RetypeNewPasswordPrompt' => 'Herhaal nieuwe wachtwoord', 'UI:Login:IncorrectOldPassword' => 'Fout: het oude wachtwoord is incorrect', 'UI:LogOffMenu' => 'Log uit', - 'UI:LogOff:ThankYou' => 'Bedankt voor het gebruiken van '.ITOP_APPLICATION, + 'UI:LogOff:ThankYou' => 'Bedankt voor het gebruiken van ITOP_APPLICATION', 'UI:LogOff:ClickHereToLoginAgain' => 'Klik hier om in te loggen', 'UI:ChangePwdMenu' => 'Verander wachtwoord', 'UI:Login:PasswordChanged' => 'Wachtwoord met succes aangepast', - 'UI:AccessRO-All' => ITOP_APPLICATION.' is alleen-lezen', - 'UI:AccessRO-Users' => ITOP_APPLICATION.' is alleen-lezen voor eindgebruikers', + 'UI:AccessRO-All' => 'ITOP_APPLICATION is alleen-lezen', + 'UI:AccessRO-Users' => 'ITOP_APPLICATION is alleen-lezen voor eindgebruikers', 'UI:ApplicationEnvironment' => 'Omgeving van de applicatie: %1$s', 'UI:Login:RetypePwdDoesNotMatch' => 'Het nieuwe wachtwoord en de herhaling van het nieuwe wachtwoord komen niet overeen', - 'UI:Button:Login' => 'Ga naar '.ITOP_APPLICATION, - 'UI:Login:Error:AccessRestricted' => 'Geen toegang tot '.ITOP_APPLICATION_SHORT.'. Neem contact op met een '.ITOP_APPLICATION_SHORT.'-beheerder.', - 'UI:Login:Error:AccessAdmin' => 'Alleen toegankelijk voor mensen met beheerdersrechten. Neem contact op met een '.ITOP_APPLICATION_SHORT.'-beheerder', + 'UI:Button:Login' => 'Ga naar ITOP_APPLICATION', + 'UI:Login:Error:AccessRestricted' => 'Geen toegang tot ITOP_APPLICATION_SHORT. Neem contact op met een ITOP_APPLICATION_SHORT-beheerder.', + 'UI:Login:Error:AccessAdmin' => 'Alleen toegankelijk voor mensen met beheerdersrechten. Neem contact op met een ITOP_APPLICATION_SHORT-beheerder', 'UI:Login:Error:WrongOrganizationName' => 'Onbekende organisatie', 'UI:Login:Error:MultipleContactsHaveSameEmail' => 'Meerdere contacten hebben hetzelfde e-mailadres', 'UI:Login:Error:NoValidProfiles' => 'Geen geldig profiel opgegeven', @@ -582,7 +584,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:CSVImport:DataLine1' => 'Dataregel 1', 'UI:CSVImport:DataLine2' => 'Dataregel 2', 'UI:CSVImport:idField' => 'id (Primaire sleutel (key))', - 'UI:Title:BulkImport' => ITOP_APPLICATION_SHORT.' - Bulk import', + 'UI:Title:BulkImport' => 'ITOP_APPLICATION_SHORT - Bulk import', 'UI:Title:BulkImport+' => 'CSV Import Wizard', 'UI:Title:BulkSynchro_nbItem_ofClass_class' => 'Synchronisatie van %1$d objecten van klasse "%2$s"', 'UI:CSVImport:ClassesSelectOne' => '-- selecteer een --', @@ -679,9 +681,9 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:CSVExport:AdvancedMode' => 'Geavanceerde mode', 'UI:CSVExport:AdvancedMode+' => 'In geavanceerde mode worden verscheidene kolommen toegevoegd aan de export: id van het object, id van de externe codes en hun reconciliation-attributen.', 'UI:CSVExport:LostChars' => 'Tekstcoderingsprobleem', - 'UI:CSVExport:LostChars+' => 'Het gedownloade bestand zal worden gecodeerd in %1$s. '.ITOP_APPLICATION_SHORT.' heeft een aantal karakters gedetecteerd die niet compatibel zijn met dit formaat. Deze karakters zullen worden vervangen door een ander karakter (bijvoorbeeld karakters met accent kunnen het accent verliezen), of ze zullen worden verwijderd. Je kan data kopiëren en plakken van jouw webbrowser. Ook kan je de beheerder contacteren om de codes te veranderen (Zie parameter \'csv_file_default_charset\').', + 'UI:CSVExport:LostChars+' => 'Het gedownloade bestand zal worden gecodeerd in %1$s. ITOP_APPLICATION_SHORT heeft een aantal karakters gedetecteerd die niet compatibel zijn met dit formaat. Deze karakters zullen worden vervangen door een ander karakter (bijvoorbeeld karakters met accent kunnen het accent verliezen), of ze zullen worden verwijderd. Je kan data kopiëren en plakken van jouw webbrowser. Ook kan je de beheerder contacteren om de codes te veranderen (Zie parameter \'csv_file_default_charset\').', - 'UI:Audit:Title' => ITOP_APPLICATION_SHORT.' - CMDB Audit', + 'UI:Audit:Title' => 'ITOP_APPLICATION_SHORT - CMDB Audit', 'UI:Audit:InteractiveAudit' => 'Interactieve Audit', 'UI:Audit:HeaderAuditRule' => 'Auditregel', 'UI:Audit:HeaderNbObjects' => '# objecten', @@ -690,7 +692,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Audit:ErrorIn_Rule_Reason' => 'OQL-fout in de regel %1$s: %2$s.', 'UI:Audit:ErrorIn_Category_Reason' => 'OQL-fout in de categorie %1$s: %2$s.', - 'UI:RunQuery:Title' => ITOP_APPLICATION_SHORT.' - Evaluatie van OQL-query', + 'UI:RunQuery:Title' => 'ITOP_APPLICATION_SHORT - Evaluatie van OQL-query', 'UI:RunQuery:QueryExamples' => 'Voorbeelden van query\'s', 'UI:RunQuery:HeaderPurpose' => 'Doel', 'UI:RunQuery:HeaderPurpose+' => 'Uitleg over de query', @@ -707,7 +709,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:RunQuery:Error' => 'Er trad een fout op tijdens het uitvoeren van deze query: %1$s', 'UI:Query:UrlForExcel' => 'URL om te gebruiken voor MS Excel-webquery\'s', 'UI:Query:UrlV1' => 'De lijst van velden is leeg gelaten. De pagina export-V2.php kan niet aangeroepen worden zonder deze informatie.Daarom verwijst de onderstaande link naar de oude export-pagina: export.php. Deze verouderde versie heeft enkele beperkingen: de lijst van geëxporteerde velden kan verschillen afhankelijk van het gekozen export-formaat en het datamodel van iTop. Als je wil dat de lijst van geëxporteerde kolommen hetzelfde blijft over lange tijd, dan moet je een waarde opgeven voor het attribuut "Velden" en de pagina export-V2.php gebruiken.', - 'UI:Schema:Title' => ITOP_APPLICATION_SHORT.' objecten-schema', + 'UI:Schema:Title' => 'ITOP_APPLICATION_SHORT objecten-schema', 'UI:Schema:CategoryMenuItem' => 'Categorie %1$s', 'UI:Schema:Relationships' => 'Relaties', 'UI:Schema:AbstractClass' => 'Abstracte klasse: objecten van deze klasse kunnen niet worden geïnstantieerd.', @@ -822,9 +824,9 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Delete:PleaseDoTheManualOperations' => 'Verricht eerst de handmatige handelingen die hierboven staan voordat je dit object verwijdert', 'UI:Delect:Confirm_Object' => 'Bevestig dat je %1$s wil verwijderen.', 'UI:Delect:Confirm_Count_ObjectsOf_Class' => 'Bevestig dat je de volgende %1$d objecten van klasse %2$s wilt verwijderen.', - 'UI:WelcomeToITop' => 'Welkom in '.ITOP_APPLICATION, - 'UI:DetailsPageTitle' => ITOP_APPLICATION_SHORT.' - %1$s - %2$s details', - 'UI:ErrorPageTitle' => ITOP_APPLICATION_SHORT.' - Fout', + 'UI:WelcomeToITop' => 'Welkom in ITOP_APPLICATION', + 'UI:DetailsPageTitle' => 'ITOP_APPLICATION_SHORT - %1$s - %2$s details', + 'UI:ErrorPageTitle' => 'ITOP_APPLICATION_SHORT - Fout', 'UI:ObjectDoesNotExist' => 'Sorry, dit object bestaat niet (of je bent niet gemachtigd het te bekijken).', 'UI:ObjectArchived' => 'Dit object werd gearchiveerd. Gelieve de Archief-mode in te schakelen of je beheerder te contacteren.', 'Tag:Archived' => 'Gearchiveerd', @@ -834,7 +836,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Tag:Synchronized' => 'Gesynchroniseerd', 'ObjectRef:Archived' => 'Gearchiveerd', 'ObjectRef:Obsolete' => 'Buiten dienst', - 'UI:SearchResultsPageTitle' => ITOP_APPLICATION_SHORT.' - Zoekresultaten', + 'UI:SearchResultsPageTitle' => 'ITOP_APPLICATION_SHORT - Zoekresultaten', 'UI:SearchResultsTitle' => 'Zoekresultaten', 'UI:SearchResultsTitle+' => 'Volledige tekst - zoekresultaten', 'UI:Search:NoSearch' => 'Geen zoekopdracht', @@ -844,28 +846,28 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:FullTextSearchTitle_Text' => 'Resultaten voor "%1$s":', 'UI:Search:Count_ObjectsOf_Class_Found' => '%1$d object(en) van klasse %2$s gevonden.', 'UI:Search:NoObjectFound' => 'Geen object gevonden.', - 'UI:ModificationPageTitle_Object_Class' => ITOP_APPLICATION_SHORT.' - %1$s - %2$s aanpassing', + 'UI:ModificationPageTitle_Object_Class' => 'ITOP_APPLICATION_SHORT - %1$s - %2$s aanpassing', 'UI:ModificationTitle_Class_Object' => 'Aanpassen van %1$s: %2$s', - 'UI:ClonePageTitle_Object_Class' => ITOP_APPLICATION_SHORT.' - Kloon %1$s - %2$s aanpassing', + 'UI:ClonePageTitle_Object_Class' => 'ITOP_APPLICATION_SHORT - Kloon %1$s - %2$s aanpassing', 'UI:CloneTitle_Class_Object' => 'Klonen van %1$s: %2$s', - 'UI:CreationPageTitle_Class' => ITOP_APPLICATION_SHORT.' - %1$s aanmaken', + 'UI:CreationPageTitle_Class' => 'ITOP_APPLICATION_SHORT - %1$s aanmaken', 'UI:CreationTitle_Class' => '%1$s aanmaken', 'UI:SelectTheTypeOf_Class_ToCreate' => 'Selecteer het type %1$s dat moet worden aangemaakt:', 'UI:Class_Object_NotUpdated' => 'Geen verandering waargenomen, %1$s (%2$s) is niet aangepast.', 'UI:Class_Object_Updated' => '%1$s (%2$s) aangepast.', - 'UI:BulkDeletePageTitle' => ITOP_APPLICATION_SHORT.' - Meerdere objecten verwijderen', + 'UI:BulkDeletePageTitle' => 'ITOP_APPLICATION_SHORT - Meerdere objecten verwijderen', 'UI:BulkDeleteTitle' => 'Selecteer de objecten die je wilt verwijderen:', 'UI:PageTitle:ObjectCreated' => 'Object Aangemaakt.', 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s aangemaakt.', 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Bezig met het toepassen van %1$s op object: %2$s in fase %3$s tot doelfase: %4$s.', 'UI:ObjectCouldNotBeWritten' => 'Het object kon niet geschreven worden: %1$s', - 'UI:PageTitle:FatalError' => ITOP_APPLICATION_SHORT.' - Fatale Fout', + 'UI:PageTitle:FatalError' => 'ITOP_APPLICATION_SHORT - Fatale Fout', 'UI:SystemIntrusion' => 'Toegang geweigerd. Je hebt een actie aangevraagd waarvoor je niet gemachtigd bent.', - 'UI:FatalErrorMessage' => 'Fatale fout, '.ITOP_APPLICATION_SHORT.' kan niet doorgaan.', + 'UI:FatalErrorMessage' => 'Fatale fout, ITOP_APPLICATION_SHORT kan niet doorgaan.', 'UI:Error_Details' => 'Fout: %1$s.', - 'UI:PageTitle:ClassProjections' => ITOP_APPLICATION_SHORT.' gebruikersbeheer - klasse-projecties', - 'UI:PageTitle:ProfileProjections' => ITOP_APPLICATION_SHORT.' gebruikersbeheer - profiel-projecties', + 'UI:PageTitle:ClassProjections' => 'ITOP_APPLICATION_SHORT gebruikersbeheer - klasse-projecties', + 'UI:PageTitle:ProfileProjections' => 'ITOP_APPLICATION_SHORT gebruikersbeheer - profiel-projecties', 'UI:UserManagement:Class' => 'Klasse', 'UI:UserManagement:Class+' => 'Klasse van objecten', 'UI:UserManagement:ProjectedObject' => 'Object', @@ -966,8 +968,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Menu:NotificationsMenu+' => 'Configuratie van de meldingen', // Duplicated into itop-welcome-itil (will be removed from here...) 'UI:NotificationsMenu:Title' => 'Configuratie van Meldingen', 'UI:NotificationsMenu:Help' => 'Help', - 'UI:NotificationsMenu:HelpContent' => '

      In '.ITOP_APPLICATION_SHORT.' zijn de meldingen volledig aan te passen. Ze zijn gebaseerd op twee sets van objecten: triggers and actions.

      -

      Triggers bepalen wanneer er een melding is. Er zijn verschillende triggers als onderdeel van '.ITOP_APPLICATION_SHORT.' core, maar andere kunnen door middel van uitbreidingen worden toegevoegd. + 'UI:NotificationsMenu:HelpContent' => '

      In ITOP_APPLICATION_SHORT zijn de meldingen volledig aan te passen. Ze zijn gebaseerd op twee sets van objecten: triggers and actions.

      +

      Triggers bepalen wanneer er een melding is. Er zijn verschillende triggers als onderdeel van ITOP_APPLICATION_SHORT core, maar andere kunnen door middel van uitbreidingen worden toegevoegd.

      Sommige triggers worden uitgevoerd:

      @@ -1075,8 +1077,8 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:RelationTooltip:Redundancy' => 'Redundantie', 'UI:RelationTooltip:ImpactedItems_N_of_M' => '# geïmpacteerde items: %1$d / %2$d', 'UI:RelationTooltip:CriticalThreshold_N_of_M' => 'Kritieke drempelwaarde: %1$d / %2$d', - 'Portal:Title' => ITOP_APPLICATION_SHORT.' gebruikersportaal', - 'Portal:NoRequestMgmt' => 'Beste %1$s, je bent naar deze pagina doorverwezen omdat jouw account is geconfigureerd met het profiel "Portal user". Helaas is '.ITOP_APPLICATION_SHORT.' niet geïnstalleerd met de optie "Request Management". Neem contact op met jouw beheerder.', + 'Portal:Title' => 'ITOP_APPLICATION_SHORT gebruikersportaal', + 'Portal:NoRequestMgmt' => 'Beste %1$s, je bent naar deze pagina doorverwezen omdat jouw account is geconfigureerd met het profiel "Portal user". Helaas is ITOP_APPLICATION_SHORT niet geïnstalleerd met de optie "Request Management". Neem contact op met jouw beheerder.', 'Portal:Refresh' => 'Herlaad', 'Portal:Back' => 'Vorige', 'Portal:WelcomeUserOrg' => 'Welkom %1$s, van %2$s', @@ -1160,7 +1162,7 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:Favorites:ShowObsoleteData+' => 'Toon "Buiten dienst"-data in zoekresultaten en in keuzelijsten.', 'UI:NavigateAwayConfirmationMessage' => 'Bewerkingen zullen worden genegeerd.', 'UI:CancelConfirmationMessage' => 'Je zult jouw aanpassingen verliezen. Wil je toch doorgaan?', - 'UI:AutoApplyConfirmationMessage' => 'Sommige veranderingen zijn nog niet doorgevoerd. Wil je dat '.ITOP_APPLICATION_SHORT.' deze meeneemt?', + 'UI:AutoApplyConfirmationMessage' => 'Sommige veranderingen zijn nog niet doorgevoerd. Wil je dat ITOP_APPLICATION_SHORT deze meeneemt?', 'UI:Create_Class_InState' => 'Maak %1$s aan in deze fase: ', 'UI:OrderByHint_Values' => 'Sorteervolgorde: %1$s', 'UI:Menu:AddToDashboard' => 'Voeg toe aan dashboard...', @@ -1224,7 +1226,7 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:DashletUnknown:Label' => 'Onbekend', 'UI:DashletUnknown:Description' => 'Onbekende dashlet (mogelijk verwijderd)', 'UI:DashletUnknown:RenderText:View' => 'Kan deze dashlet niet weergeven.', - 'UI:DashletUnknown:RenderText:Edit' => 'Kan deze dashlet niet weergeven (klasse "%1$s"). Controleer bij je '.ITOP_APPLICATION_SHORT.'-beheerder of dit nog beschikbaar is.', + 'UI:DashletUnknown:RenderText:Edit' => 'Kan deze dashlet niet weergeven (klasse "%1$s"). Controleer bij je ITOP_APPLICATION_SHORT-beheerder of dit nog beschikbaar is.', 'UI:DashletUnknown:RenderNoDataText:Edit' => 'Geen voorbeeld mogelijk van deze dashlet (klasse "%1$s").', 'UI:DashletUnknown:Prop-XMLConfiguration' => 'Configuratie (getoond als ruwe XML)', @@ -1404,8 +1406,8 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:AddAnExisting_Class' => 'Voeg objecten van type %1$s toe...', 'UI:SelectionOf_Class' => 'Selectie van objecten van type %1$s', - 'UI:AboutBox' => 'Over '.ITOP_APPLICATION_SHORT.'...', - 'UI:About:Title' => 'Over '.ITOP_APPLICATION_SHORT, + 'UI:AboutBox' => 'Over ITOP_APPLICATION_SHORT...', + 'UI:About:Title' => 'Over ITOP_APPLICATION_SHORT', 'UI:About:DataModel' => 'Datamodel', 'UI:About:Support' => 'Support informatie', 'UI:About:Licenses' => 'Licenties', @@ -1430,7 +1432,7 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'ExcelExport:PreparingExport' => 'Export aan het voorbereiden...', 'ExcelExport:Statistics' => 'Statistieken', 'portal:legacy_portal' => 'Portaal voor eindgebruikers', - 'portal:backoffice' => ITOP_APPLICATION_SHORT.' Back-Office User Interface', + 'portal:backoffice' => 'ITOP_APPLICATION_SHORT Back-Office User Interface', 'UI:CurrentObjectIsLockedBy_User' => 'Het object is vergrendeld omdat het momenteel aangepast wordt door %1$s.', 'UI:CurrentObjectIsLockedBy_User_Explanation' => 'Het object wordt aangepast door %1$s. Jouw wijzigingen kunnen niet opgeslagen worden omdat ze een conflict kunnen veroorzaken.', @@ -1562,6 +1564,8 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:Search:Criteria:Raw:Filtered' => 'Gefilterd', 'UI:Search:Criteria:Raw:FilteredOn' => 'Gefiltered op %1$s', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1598,3 +1602,20 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Bekijk berichten van %1$s', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Toon maximaal %1$s berichten in het %2$s menu.', )); + + +// OAuth +Dict::Add('NL NL', 'Dutch', 'Nederlands', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/pt_br.dictionary.itop.core.php b/dictionaries/pt_br.dictionary.itop.core.php index 887873b3e..dc5fc782c 100644 --- a/dictionaries/pt_br.dictionary.itop.core.php +++ b/dictionaries/pt_br.dictionary.itop.core.php @@ -1045,6 +1045,16 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:AsyncTask/Attribute:event_id+' => '', 'Class:AsyncTask/Attribute:finalclass' => 'Aula final', 'Class:AsyncTask/Attribute:finalclass+' => '', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); // Additional language entries not present in English dict diff --git a/dictionaries/pt_br.dictionary.itop.ui.php b/dictionaries/pt_br.dictionary.itop.ui.php index 0ffe1a005..c0d934124 100644 --- a/dictionaries/pt_br.dictionary.itop.ui.php +++ b/dictionaries/pt_br.dictionary.itop.ui.php @@ -453,6 +453,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Erro: objetos já foram apagados', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Você não tem permissão de executar exclusão em massa dos objetos da classe %1$s', 'UI:Error:DeleteNotAllowedOn_Class' => 'Você não tem permissão para excluir objeto(s) da classe %1$s', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Você não tem permissão de executar atualização em massa dos objetos da classe %1$s', 'UI:Error:ObjectAlreadyCloned' => 'Erro: o objeto já foi clonado.', 'UI:Error:ObjectAlreadyCreated' => 'Erro: o objeto já foi criado.', @@ -461,6 +462,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'UI:Error:InvalidDashboard' => 'Erro: painel inválido', 'UI:Error:MaintenanceMode' => 'Application is currently in maintenance~~', 'UI:Error:MaintenanceTitle' => 'Maintenance~~', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Número', 'UI:GroupBy:Count+' => 'Número de elementos', @@ -1553,6 +1555,8 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Search:Criteria:Raw:Filtered' => 'Filtered', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtrado em %1$s', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1589,3 +1593,20 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Mostrar mensagens de %1$s', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Exibir até %1$s mensagens no menu %2$s.', )); + + +// OAuth +Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/ru.dictionary.itop.core.php b/dictionaries/ru.dictionary.itop.core.php index f1766a116..3aedc2f27 100644 --- a/dictionaries/ru.dictionary.itop.core.php +++ b/dictionaries/ru.dictionary.itop.core.php @@ -1032,4 +1032,14 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'Class:AsyncTask/Attribute:event_id+' => '~~', 'Class:AsyncTask/Attribute:finalclass' => 'Final class~~', 'Class:AsyncTask/Attribute:finalclass+' => '~~', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/ru.dictionary.itop.ui.php b/dictionaries/ru.dictionary.itop.ui.php index 784b77186..1a10d0b74 100644 --- a/dictionaries/ru.dictionary.itop.ui.php +++ b/dictionaries/ru.dictionary.itop.ui.php @@ -432,6 +432,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Ошибка: объект уже удалён!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Вам не разрешено выполнять массовое удаления объектов класса %1$s', 'UI:Error:DeleteNotAllowedOn_Class' => 'Вы не можете удалять объекты класса %1$s', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Вам не разрешено выполнять массовое обновление объектов класса %1$s', 'UI:Error:ObjectAlreadyCloned' => 'Ошибка: объект уже клонирован!', 'UI:Error:ObjectAlreadyCreated' => 'Ошибка: объект уже создан!', @@ -440,6 +441,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'UI:Error:InvalidDashboard' => 'Ошибка: недопустимый дашборд', 'UI:Error:MaintenanceMode' => 'Приложение в режиме технического обслуживания', 'UI:Error:MaintenanceTitle' => 'Техническое обслуживание', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Количество', 'UI:GroupBy:Count+' => 'Количество элементов', @@ -1531,6 +1533,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'UI:Search:Criteria:Raw:Filtered' => 'Отфильтровано', 'UI:Search:Criteria:Raw:FilteredOn' => 'Отфильтровано по %1$s', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1567,3 +1571,20 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Показать сообщения от %1$s', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Отобразите не более %1$s сообщений в меню %2$s.', )); + + +// OAuth +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/sk.dictionary.itop.core.php b/dictionaries/sk.dictionary.itop.core.php index b59571617..11458e6f3 100644 --- a/dictionaries/sk.dictionary.itop.core.php +++ b/dictionaries/sk.dictionary.itop.core.php @@ -1042,6 +1042,16 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( 'Class:AsyncTask/Attribute:event_id+' => '~~', 'Class:AsyncTask/Attribute:finalclass' => 'Final class~~', 'Class:AsyncTask/Attribute:finalclass+' => '~~', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); // Additional language entries not present in English dict diff --git a/dictionaries/sk.dictionary.itop.ui.php b/dictionaries/sk.dictionary.itop.ui.php index f50dafdfc..74290f499 100644 --- a/dictionaries/sk.dictionary.itop.ui.php +++ b/dictionaries/sk.dictionary.itop.ui.php @@ -440,6 +440,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Chyba: objekty už boli vymazané!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Nemáte povolenie vykonať hromadné vymazanie objektov triedy %1$s', 'UI:Error:DeleteNotAllowedOn_Class' => 'Nemáte povolenie na vymazanie objektov triedy %1$s', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Nemáte povolenie na vykonanie hromadnej aktualizácie objektov triedy %1$s', 'UI:Error:ObjectAlreadyCloned' => 'Chyba: objekt už bol klonovaný!', 'UI:Error:ObjectAlreadyCreated' => 'Chyba: objekt už bol vytvorený!', @@ -448,6 +449,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( 'UI:Error:InvalidDashboard' => 'Error: invalid dashboard~~', 'UI:Error:MaintenanceMode' => 'Application is currently in maintenance~~', 'UI:Error:MaintenanceTitle' => 'Maintenance~~', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Počet', 'UI:GroupBy:Count+' => '', @@ -1542,6 +1544,8 @@ Keď sú priradené spúštačom, každej akcii je dané číslo "príkazu", šp 'UI:Search:Criteria:Raw:Filtered' => 'Filtered~~', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s~~', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1578,3 +1582,20 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Display messages from %1$s~~', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Display up to %1$s messages in the %2$s menu.~~', )); + + +// OAuth +Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/tr.dictionary.itop.core.php b/dictionaries/tr.dictionary.itop.core.php index a3b71dda2..0d99e1b94 100644 --- a/dictionaries/tr.dictionary.itop.core.php +++ b/dictionaries/tr.dictionary.itop.core.php @@ -681,9 +681,9 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'Class:SynchroDataSource/Attribute:notify_contact_id' => 'Bildirim iletilecek kişi', 'Class:SynchroDataSource/Attribute:notify_contact_id+' => 'Hata durumunda bildirmek yapılacak kişi', 'Class:SynchroDataSource/Attribute:url_icon' => 'Simge\'nin köprüsü', - 'Class:SynchroDataSource/Attribute:url_icon+' => 'Hyprinlink, '.ITOP_APPLICATION_SHORT.'\'un senkronize edildiği uygulamayı temsil eden (küçük) bir görüntü', + 'Class:SynchroDataSource/Attribute:url_icon+' => 'Hyprinlink, ITOP_APPLICATION_SHORT\'un senkronize edildiği uygulamayı temsil eden (küçük) bir görüntü', 'Class:SynchroDataSource/Attribute:url_application' => 'Uygulama\'nın köprüsü', - 'Class:SynchroDataSource/Attribute:url_application+' => ITOP_APPLICATION_SHORT.'\'un senkronize edildiği harici uygulamadaki '.ITOP_APPLICATION_SHORT.' nesnesine köprü (varsa). Muhtemel yer tutucular: $this->attribute$ ve $replica->primary_key$', + 'Class:SynchroDataSource/Attribute:url_application+' => 'ITOP_APPLICATION_SHORT\'un senkronize edildiği harici uygulamadaki ITOP_APPLICATION_SHORT nesnesine köprü (varsa). Muhtemel yer tutucular: $this->attribute$ ve $replica->primary_key$', 'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Uzlaşma Politikası', 'Class:SynchroDataSource/Attribute:full_load_periodicity' => 'Tam Yük Aralığı', 'Class:SynchroDataSource/Attribute:full_load_periodicity+' => 'Tüm verilerin yeniden yüklenmesi, en azından burada belirtilen sıklıkta olmalıdır', @@ -890,7 +890,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( // Bulk export 'Core:BulkExport:MissingParameter_Param' => 'Eksik parametre \\"%1$s\\"', - 'Core:BulkExport:InvalidParameter_Query' => '\"Sorgu\" parametresi için geçersiz değer. ID\'ye karşılık gelen sorgu dizimi yok: \"%1$s\".', + 'Core:BulkExport:InvalidParameter_Query' => '\\"Sorgu\\" parametresi için geçersiz değer. ID\'ye karşılık gelen sorgu dizimi yok: \\"%1$s\\".', 'Core:BulkExport:ExportFormatPrompt' => 'Dışarı çıkartma formatı:', 'Core:BulkExportOf_Class' => '%1$s dışarı çıkartıldı', 'Core:BulkExport:ClickHereToDownload_FileName' => '%1$s \'indirmek için buraya tıklayın', @@ -1053,4 +1053,14 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'Class:AsyncTask/Attribute:event_id+' => '~~', 'Class:AsyncTask/Attribute:finalclass' => 'Final class~~', 'Class:AsyncTask/Attribute:finalclass+' => '~~', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); diff --git a/dictionaries/tr.dictionary.itop.ui.php b/dictionaries/tr.dictionary.itop.ui.php index 84d3cef88..f87d86e6b 100644 --- a/dictionaries/tr.dictionary.itop.ui.php +++ b/dictionaries/tr.dictionary.itop.ui.php @@ -141,7 +141,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'Class:User/Error:UserOrganizationNotAllowed' => 'The user account does not belong to your allowed organizations.~~', 'Class:User/Error:PersonIsMandatory' => 'The Contact is mandatory.~~', 'Class:UserInternal' => 'Dahili kullanıcı', - 'Class:UserInternal+' => ITOP_APPLICATION_SHORT.'\'ta tanımlanan kullanıcı', + 'Class:UserInternal+' => 'ITOP_APPLICATION_SHORT\'ta tanımlanan kullanıcı', )); // @@ -454,6 +454,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:Error:ObjectsAlreadyDeleted' => 'Hata: nesne hali hazırda silinmiş!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => '%1$s sınıfına ait nesnelerin toplu silimine yetkiniz yok.', 'UI:Error:DeleteNotAllowedOn_Class' => '%1$s sınıfına ait nesnelerin silimine yetkiniz yok.', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => '%1$s sınıfına ait nesnelerin toplu güncellenmesine yetkiniz yok.', 'UI:Error:ObjectAlreadyCloned' => 'Hata: nesne hali hazırda klonlanmış!', 'UI:Error:ObjectAlreadyCreated' => 'Hata: nesne hali hazırda yaratılmış!', @@ -462,6 +463,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:Error:InvalidDashboard' => 'Error: invalid dashboard~~', 'UI:Error:MaintenanceMode' => 'Application is currently in maintenance~~', 'UI:Error:MaintenanceTitle' => 'Maintenance~~', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => 'Say', 'UI:GroupBy:Count+' => 'Eleman sayısı', @@ -518,14 +520,14 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:SearchValue:CheckAll' => 'Hepsini işaretleyin', 'UI:SearchValue:UncheckAll' => 'Hepsinin işaretini kaldırın', 'UI:SelectOne' => '-- Birini seçiniz --', - 'UI:Login:Welcome' => ITOP_APPLICATION_SHORT.'\'a Hoşgeldiniz!', + 'UI:Login:Welcome' => 'ITOP_APPLICATION_SHORT\'a Hoşgeldiniz!', 'UI:Login:IncorrectLoginPassword' => 'Hatalı kullanıcı/şifre tekrar deneyiniz.', 'UI:Login:IdentifyYourself' => 'Devam etmeden önce kendinizi tanıtınız', 'UI:Login:UserNamePrompt' => 'Kullanıcı Adı', 'UI:Login:PasswordPrompt' => 'Şifre', 'UI:Login:ForgotPwd' => 'Şifrenizi mi unuttunuz?', 'UI:Login:ForgotPwdForm' => 'Şifrenizi mi unuttunuz?', - 'UI:Login:ForgotPwdForm+' => ITOP_APPLICATION_SHORT.', hesabınızı sıfırlamak için izleyeceğiniz talimatları bulacağınız bir e-posta gönderebilir.', + 'UI:Login:ForgotPwdForm+' => 'ITOP_APPLICATION_SHORT, hesabınızı sıfırlamak için izleyeceğiniz talimatları bulacağınız bir e-posta gönderebilir.', 'UI:Login:ResetPassword' => 'Şimdi gönder!', 'UI:Login:ResetPwdFailed' => 'Bir e-posta gönderilemedi: %1$s', 'UI:Login:SeparatorOr' => 'Or~~', @@ -538,8 +540,8 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:ResetPwd-Error-NoEmail' => 'Bir e-posta adresi eksik. Lütfen yöneticinize başvurun.', 'UI:ResetPwd-Error-Send' => 'E-posta ulaştırma teknik sorunu. Lütfen yöneticinize başvurun.', 'UI:ResetPwd-EmailSent' => 'Lütfen e-posta kutunuzu kontrol edin ve talimatları izleyin...', - 'UI:ResetPwd-EmailSubject' => ITOP_APPLICATION_SHORT.'şifrenizi sıfırlayın', - 'UI:ResetPwd-EmailBody' => '

      '.ITOP_APPLICATION_SHORT.' şifrenizin sıfırlanması talebinde bulundunuz.

      Yeni şifre oluşturmak için lütfen aşağıdaki tek kullanımlık bağlantıyı takip ediniz.

      ', + 'UI:ResetPwd-EmailSubject' => 'ITOP_APPLICATION_SHORTşifrenizi sıfırlayın', + 'UI:ResetPwd-EmailBody' => '

      ITOP_APPLICATION_SHORT şifrenizin sıfırlanması talebinde bulundunuz.

      Yeni şifre oluşturmak için lütfen aşağıdaki tek kullanımlık bağlantıyı takip ediniz.

      ', 'UI:ResetPwd-Title' => 'Şifre sıfırla', 'UI:ResetPwd-Error-InvalidToken' => 'Üzgünüz, ya parola zaten sıfırlandı ya da birkaç e-posta aldınız. Lütfen aldığınız en son e-postada verilen bağlantıyı kullandığınızdan emin olun', @@ -558,8 +560,8 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:LogOff:ClickHereToLoginAgain' => 'Tekrar bağlanmak için tıklayınız...', 'UI:ChangePwdMenu' => 'Şifre değiştir...', 'UI:Login:PasswordChanged' => 'Şifre başarıyla ayarlandı!', - 'UI:AccessRO-All' => ITOP_APPLICATION_SHORT.' salt okunurdur', - 'UI:AccessRO-Users' => ITOP_APPLICATION_SHORT.' sadece son kullanıcılar için okunurdur', + 'UI:AccessRO-All' => 'ITOP_APPLICATION_SHORT salt okunurdur', + 'UI:AccessRO-Users' => 'ITOP_APPLICATION_SHORT sadece son kullanıcılar için okunurdur', 'UI:ApplicationEnvironment' => 'Uygulama Ortamı: %1$s', 'UI:Login:RetypePwdDoesNotMatch' => 'Yeni şifre eşlenmedi !', 'UI:Button:Login' => 'iTop\'a Giriş', @@ -626,7 +628,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:CSVImport:AlertMultipleMapping' => 'Lütfen bir hedef alanın yalnızca bir kez eşlendiğinden emin olun.', 'UI:CSVImport:AlertNoSearchCriteria' => 'Lütfen en az bir sorgu kriteri seçiniz.', 'UI:CSVImport:Encoding' => 'Karakter kodlaması', - 'UI:UniversalSearchTitle' => ITOP_APPLICATION_SHORT.' - Genel arama', + 'UI:UniversalSearchTitle' => 'ITOP_APPLICATION_SHORT - Genel arama', 'UI:UniversalSearch:Error' => 'Hata: %1$s', 'UI:UniversalSearch:LabelSelectTheClass' => 'Aranacak sınıfı seçiniz: ', @@ -674,9 +676,9 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:CSVExport:AdvancedMode' => 'Gelişmiş Mod', 'UI:CSVExport:AdvancedMode+' => 'Gelişmiş modda, dışa aktarmaya birkaç sütun eklenir: nesnenin kimliği, harici anahtarların kimliği ve bunların uzlaşma özellikleri', 'UI:CSVExport:LostChars' => 'Kodlama sorunu', - 'UI:CSVExport:LostChars+' => 'İndirilen dosya %1$s\'ye kodlanır. '.ITOP_APPLICATION_SHORT.', bu formatla uyumlu olmayan bazı karakterleri tespit etti. Bu karakterler ya bir ikame ile değiştirilecektir (örneğin, vurgulanmış karakterleri aksanı kaybedilen) veya atılacaklardır. Verileri web tarayıcınızdan kopyalayabilir / yapıştırabilirsiniz. Alternatif olarak, kodlamayı değiştirmek için yöneticinize başvurabilirsiniz (bkz. Parametre \'csv_file_default_charset \').', + 'UI:CSVExport:LostChars+' => 'İndirilen dosya %1$s\'ye kodlanır. ITOP_APPLICATION_SHORT, bu formatla uyumlu olmayan bazı karakterleri tespit etti. Bu karakterler ya bir ikame ile değiştirilecektir (örneğin, vurgulanmış karakterleri aksanı kaybedilen) veya atılacaklardır. Verileri web tarayıcınızdan kopyalayabilir / yapıştırabilirsiniz. Alternatif olarak, kodlamayı değiştirmek için yöneticinize başvurabilirsiniz (bkz. Parametre \'csv_file_default_charset \').', - 'UI:Audit:Title' => ITOP_APPLICATION_SHORT.' - CMDB Denetleme', + 'UI:Audit:Title' => 'ITOP_APPLICATION_SHORT - CMDB Denetleme', 'UI:Audit:InteractiveAudit' => 'Etkileşimli Denetleme', 'UI:Audit:HeaderAuditRule' => 'Denetleme Kuralı', 'UI:Audit:HeaderNbObjects' => 'Nesne Sayısı', @@ -685,7 +687,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:Audit:ErrorIn_Rule_Reason' => 'Kuraldaki OQL hatası %1$s:%2$s.', 'UI:Audit:ErrorIn_Category_Reason' => 'Kategorideki OQL Hatası %1$s:%2$s.', - 'UI:RunQuery:Title' => ITOP_APPLICATION_SHORT.' - OQL Sorgu değerlendirme', + 'UI:RunQuery:Title' => 'ITOP_APPLICATION_SHORT - OQL Sorgu değerlendirme', 'UI:RunQuery:QueryExamples' => 'Sorgu örnekleri', 'UI:RunQuery:HeaderPurpose' => 'Amaç', 'UI:RunQuery:HeaderPurpose+' => 'Sorgu açıklaması', @@ -701,7 +703,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:RunQuery:ResultSQL' => 'Resulting SQL~~', 'UI:RunQuery:Error' => 'Sorgu sırasında hata oluştu: %1$s', 'UI:Query:UrlForExcel' => 'MS-Excel Web Queries için Kullanım URL\'si', - 'UI:Query:UrlV1' => 'Alanların listesi belirtilmeden bırakılmıştır. export-V2.php sayfası bu bilgi olmadan çağrılamaz. Bu nedenle, aşağıda önerilen URL eski sayfaya işaret etmektedir: export.php. Dışa aktarmanın bu eski sürümü aşağıdaki sınırlamaya sahiptir: dışa aktarılan alanların listesi, '.ITOP_APPLICATION_SHORT.'\'un çıktı biçimine ve veri modeline bağlı olarak değişebilir. Dışa aktarılan sütunların listesinin uzun vadede sabit kalacağını garanti etmek istiyorsanız, "Alanlar" özelliği için bir değer belirtmeli ve export-V2.php sayfasını kullanmalısınız.', + 'UI:Query:UrlV1' => 'Alanların listesi belirtilmeden bırakılmıştır. export-V2.php sayfası bu bilgi olmadan çağrılamaz. Bu nedenle, aşağıda önerilen URL eski sayfaya işaret etmektedir: export.php. Dışa aktarmanın bu eski sürümü aşağıdaki sınırlamaya sahiptir: dışa aktarılan alanların listesi, ITOP_APPLICATION_SHORT\'un çıktı biçimine ve veri modeline bağlı olarak değişebilir. Dışa aktarılan sütunların listesinin uzun vadede sabit kalacağını garanti etmek istiyorsanız, "Alanlar" özelliği için bir değer belirtmeli ve export-V2.php sayfasını kullanmalısınız.', 'UI:Schema:Title' => 'iTop objects schema', 'UI:Schema:CategoryMenuItem' => 'Kategori %1$s', 'UI:Schema:Relationships' => 'İlişkiler', @@ -817,9 +819,9 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:Delete:PleaseDoTheManualOperations' => 'Bu nesneyi silmeden önce yukarıdaki işlemleri manuel olarak yapınız', 'UI:Delect:Confirm_Object' => '%1$s\'i silmek istediğnizden emin misiniz?', 'UI:Delect:Confirm_Count_ObjectsOf_Class' => '%1$d nesnesini (sınıfı %2$s) silmek istediğinizden emin misiniz?', - 'UI:WelcomeToITop' => ITOP_APPLICATION_SHORT.'\'a Hoşgeldiniz', - 'UI:DetailsPageTitle' => ITOP_APPLICATION_SHORT.' - %1$s - %2$s detayları', - 'UI:ErrorPageTitle' => ITOP_APPLICATION_SHORT.' - Hata', + 'UI:WelcomeToITop' => 'ITOP_APPLICATION_SHORT\'a Hoşgeldiniz', + 'UI:DetailsPageTitle' => 'ITOP_APPLICATION_SHORT - %1$s - %2$s detayları', + 'UI:ErrorPageTitle' => 'ITOP_APPLICATION_SHORT - Hata', 'UI:ObjectDoesNotExist' => 'Nesne mevcut değil veya yetkiniz yok.', 'UI:ObjectArchived' => 'Bu nesne arşivlendi. Lütfen arşiv modunu etkinleştirin veya yöneticinize başvurun', 'Tag:Archived' => 'Arşivlendi', @@ -829,7 +831,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'Tag:Synchronized' => 'Senkronize edildi', 'ObjectRef:Archived' => 'Arşivlendi', 'ObjectRef:Obsolete' => 'Kullanım dışı', - 'UI:SearchResultsPageTitle' => ITOP_APPLICATION_SHORT.' - Arama Sonuçları', + 'UI:SearchResultsPageTitle' => 'ITOP_APPLICATION_SHORT - Arama Sonuçları', 'UI:SearchResultsTitle' => 'Arama Sonuçları', 'UI:SearchResultsTitle+' => 'Tam Metin Arama Sonuçları', 'UI:Search:NoSearch' => 'Nothing to search for~~', @@ -839,22 +841,22 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:FullTextSearchTitle_Text' => '"%1$s" için arama sonuçları:', 'UI:Search:Count_ObjectsOf_Class_Found' => '%2$s sınıfına ait %1$d nesne bulundu.', 'UI:Search:NoObjectFound' => 'Kayıt bulunamadı.', - 'UI:ModificationPageTitle_Object_Class' => ITOP_APPLICATION_SHORT.' - %1$s - %2$s modifikasyon', + 'UI:ModificationPageTitle_Object_Class' => 'ITOP_APPLICATION_SHORT - %1$s - %2$s modifikasyon', 'UI:ModificationTitle_Class_Object' => '%1$s: %2$s modifikasyonu', - 'UI:ClonePageTitle_Object_Class' => ITOP_APPLICATION_SHORT.' - %1$s - %2$s modifikasyonunu klonlayınız', + 'UI:ClonePageTitle_Object_Class' => 'ITOP_APPLICATION_SHORT - %1$s - %2$s modifikasyonunu klonlayınız', 'UI:CloneTitle_Class_Object' => '%1$s klonu: %2$s', - 'UI:CreationPageTitle_Class' => ITOP_APPLICATION_SHORT.' - Yeni %1$s yaratımı', + 'UI:CreationPageTitle_Class' => 'ITOP_APPLICATION_SHORT - Yeni %1$s yaratımı', 'UI:CreationTitle_Class' => 'Yeni %1$s yarat', 'UI:SelectTheTypeOf_Class_ToCreate' => 'Yaratılacak %1$s nesne tipini seçiniz', 'UI:Class_Object_NotUpdated' => 'Değişiklik tespit edilemedi, %1$s (%2$s) güncellenmedi.', 'UI:Class_Object_Updated' => '%1$s (%2$s) güncellendi.', - 'UI:BulkDeletePageTitle' => ITOP_APPLICATION_SHORT.' - Toplu silme işlemi', + 'UI:BulkDeletePageTitle' => 'ITOP_APPLICATION_SHORT - Toplu silme işlemi', 'UI:BulkDeleteTitle' => 'Silmek istediğiniz nesneleri seçiniz:', - 'UI:PageTitle:ObjectCreated' => ITOP_APPLICATION_SHORT.' Nesne yaratıldı.', + 'UI:PageTitle:ObjectCreated' => 'ITOP_APPLICATION_SHORT Nesne yaratıldı.', 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s yaratıldı.', 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => '%1$s işlemi %2$s durumunda %3$s nesnesine uygulanır. Bir sonraki durum: %4$s.', 'UI:ObjectCouldNotBeWritten' => 'Nesne kaydedilemedi: %1$s', - 'UI:PageTitle:FatalError' => ITOP_APPLICATION_SHORT.' - Kritik Hata', + 'UI:PageTitle:FatalError' => 'ITOP_APPLICATION_SHORT - Kritik Hata', 'UI:SystemIntrusion' => 'Bu işlem için yetkiniz yok', 'UI:FatalErrorMessage' => 'Kritik Hata, iTop devam edemiyor.', 'UI:Error_Details' => 'Hata: %1$s.', @@ -1067,8 +1069,8 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe 'UI:RelationTooltip:Redundancy' => 'Yedeklilik', 'UI:RelationTooltip:ImpactedItems_N_of_M' => 'Etkilenmiş nesnelerin sayısı: %1$d / %2$d', 'UI:RelationTooltip:CriticalThreshold_N_of_M' => 'Kritik Eşik: %1$d / %2$d', - 'Portal:Title' => ITOP_APPLICATION_SHORT.' Kullanıcı Portalı', - 'Portal:NoRequestMgmt' => 'Sevgili %1$s, hesabınız profil \'Portal kullanıcısı \' ile yapılandırıldığından bu sayfaya yönlendirildiniz. Ne yazık ki, '.ITOP_APPLICATION_SHORT.', özellik \'istek yönetimi\' ile kurulmamıştır. Lütfen yöneticinize başvurun', + 'Portal:Title' => 'ITOP_APPLICATION_SHORT Kullanıcı Portalı', + 'Portal:NoRequestMgmt' => 'Sevgili %1$s, hesabınız profil \'Portal kullanıcısı \' ile yapılandırıldığından bu sayfaya yönlendirildiniz. Ne yazık ki, ITOP_APPLICATION_SHORT, özellik \'istek yönetimi\' ile kurulmamıştır. Lütfen yöneticinize başvurun', 'Portal:Refresh' => 'Yenile', 'Portal:Back' => 'Geri', 'Portal:WelcomeUserOrg' => 'Welcome %1$s, from %2$s~~', @@ -1152,7 +1154,7 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe 'UI:Favorites:ShowObsoleteData+' => 'Arama sonuçlarında ve seçilecek öğelerin listelerinde eski bilgileri gösterin', 'UI:NavigateAwayConfirmationMessage' => 'Herhangi bir değişiklik atılır', 'UI:CancelConfirmationMessage' => 'Değişikliklerinizi kaybedersiniz. Yine de devam et?', - 'UI:AutoApplyConfirmationMessage' => 'Bazı değişiklikler henüz uygulanmadı. '.ITOP_APPLICATION_SHORT.'\'un değişiklikleri uygulamasını istiyor musunuz?', + 'UI:AutoApplyConfirmationMessage' => 'Bazı değişiklikler henüz uygulanmadı. ITOP_APPLICATION_SHORT\'un değişiklikleri uygulamasını istiyor musunuz?', 'UI:Create_Class_InState' => '%1$s durumunda oluşturun: ', 'UI:OrderByHint_Values' => 'Sıralama düzeni: %1$s', 'UI:Menu:AddToDashboard' => 'Panoya ekleyin...', @@ -1396,8 +1398,8 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe 'UI:AddAnExisting_Class' => '%1$s tipi nesneleri ekleyin...', 'UI:SelectionOf_Class' => '%1$s türünün nesnelerinin seçimi', - 'UI:AboutBox' => 'About'.ITOP_APPLICATION_SHORT.'...', - 'UI:About:Title' => 'About '.ITOP_APPLICATION_SHORT, + 'UI:AboutBox' => 'AboutITOP_APPLICATION_SHORT...', + 'UI:About:Title' => 'About ITOP_APPLICATION_SHORT', 'UI:About:DataModel' => 'Veri modeli', 'UI:About:Support' => 'Destek bilgisi', 'UI:About:Licenses' => 'Lisanslar', @@ -1422,7 +1424,7 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe 'ExcelExport:PreparingExport' => 'Dışarı aktarma hazırlanıyor...', 'ExcelExport:Statistics' => 'İstatistikler', 'portal:legacy_portal' => 'Son Kullanıcı Arayüzü', - 'portal:backoffice' => ITOP_APPLICATION_SHORT.'Arka Ofis Kullanıcı Arayüzü', + 'portal:backoffice' => 'ITOP_APPLICATION_SHORTArka Ofis Kullanıcı Arayüzü', 'UI:CurrentObjectIsLockedBy_User' => 'Nesne %1$s tarafından değiştirildiğinden beri kilitli.', 'UI:CurrentObjectIsLockedBy_User_Explanation' => 'Nesne şu anda %1$s tarafından değiştiriliyor. Değişiklikleriniz üzerine yazıldığı için gönderilemez.', @@ -1554,6 +1556,8 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe 'UI:Search:Criteria:Raw:Filtered' => 'Filtered~~', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s~~', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1590,3 +1594,20 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Display messages from %1$s~~', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Display up to %1$s messages in the %2$s menu.~~', )); + + +// OAuth +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file diff --git a/dictionaries/zh_cn.dictionary.itop.core.php b/dictionaries/zh_cn.dictionary.itop.core.php index 4be152588..5e0f58541 100644 --- a/dictionaries/zh_cn.dictionary.itop.core.php +++ b/dictionaries/zh_cn.dictionary.itop.core.php @@ -1044,6 +1044,16 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:AsyncTask/Attribute:event_id+' => '', 'Class:AsyncTask/Attribute:finalclass' => 'Final class~~', 'Class:AsyncTask/Attribute:finalclass+' => '~~', + 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status+' => '~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries+' => '~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code+' => '~~', + 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error+' => '~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt+' => '~~', )); // Additional language entries not present in English dict diff --git a/dictionaries/zh_cn.dictionary.itop.ui.php b/dictionaries/zh_cn.dictionary.itop.ui.php index 677afda00..ee498ef97 100644 --- a/dictionaries/zh_cn.dictionary.itop.ui.php +++ b/dictionaries/zh_cn.dictionary.itop.ui.php @@ -453,6 +453,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:Error:ObjectsAlreadyDeleted' => '错误: 对象已被删除!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => '您无权进行 %1$s 类对象的批量删除', 'UI:Error:DeleteNotAllowedOn_Class' => '您无权删除 %1$s 类的对象', + 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', 'UI:Error:BulkModifyNotAllowedOn_Class' => '您无权进行 %1$s 类对象的批量更新', 'UI:Error:ObjectAlreadyCloned' => '错误: 该对象已被克隆!', 'UI:Error:ObjectAlreadyCreated' => '错误: 该对象已被创建!', @@ -461,6 +462,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:Error:InvalidDashboard' => 'Error: invalid dashboard~~', 'UI:Error:MaintenanceMode' => '应用正在维护中', 'UI:Error:MaintenanceTitle' => '维护', + 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', 'UI:GroupBy:Count' => '个数', 'UI:GroupBy:Count+' => '元素数量', @@ -1553,6 +1555,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:Search:Criteria:Raw:Filtered' => '已过滤', 'UI:Search:Criteria:Raw:FilteredOn' => '基于 %1$s 过滤', + + 'UI:StateChanged' => 'State changed~~', )); // @@ -1589,3 +1593,20 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:Newsroom:DisplayMessagesFor_Provider' => '显示来自 %1$s 的消息', 'UI:Newsroom:DisplayAtMost_X_Messages' => '在 %2$s 菜单中最多显示 %1$s 条消息.', )); + + +// OAuth +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', +)); \ No newline at end of file From 3d26f28f9ba31a60dfae399570dbbdd8d5ca0457 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Wed, 1 Jun 2022 11:44:02 +0200 Subject: [PATCH 29/41] =?UTF-8?q?N=C2=B05102=20-=20Allow=20to=20send=20ema?= =?UTF-8?q?ils=20(eg.=20notifications)=20using=20GSuite=20SMTP=20and=20OAu?= =?UTF-8?q?th=20=20*=20Add=20icons=20to=20wizard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- images/icons/icons8-azure.svg | 1 + images/icons/icons8-google.svg | 1 + 2 files changed, 2 insertions(+) create mode 100644 images/icons/icons8-azure.svg create mode 100644 images/icons/icons8-google.svg diff --git a/images/icons/icons8-azure.svg b/images/icons/icons8-azure.svg new file mode 100644 index 000000000..36986851b --- /dev/null +++ b/images/icons/icons8-azure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/icons8-google.svg b/images/icons/icons8-google.svg new file mode 100644 index 000000000..d5616b32e --- /dev/null +++ b/images/icons/icons8-google.svg @@ -0,0 +1 @@ + \ No newline at end of file From 265415030e5ff23242cb646f74e4efac82174498 Mon Sep 17 00:00:00 2001 From: acognet Date: Thu, 2 Jun 2022 12:35:42 +0200 Subject: [PATCH 30/41] =?UTF-8?q?N=C2=B04867=20-=20"Twig=20content=20not?= =?UTF-8?q?=20allowed"=20error=20when=20use=20the=20extkey=20widget=20sear?= =?UTF-8?q?ch=20icon=20in=20the=20user=20portal=20-=20Add=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../itop-portal-base/portal/src/Form/ObjectFormManager.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php index 5e8d99e16..e4bf72d05 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php @@ -91,6 +91,7 @@ class ObjectFormManager extends FormManager * @return array formmanager_data as a PHP array * * @since 2.7.6 3.0.0 N°4384 method creation : factorize as this is used twice now + * @since 2.7.7 3.0.1 only used once but kept */ protected static function DecodeFormManagerData($formManagerData) { @@ -106,14 +107,13 @@ class ObjectFormManager extends FormManager * - formobject_class : The class of the object that is being edited/viewed * - formmode : view|edit|create * - values for parent - * @param bool $bTrustContent if false then won't allow modified TWIG content * * @return \Combodo\iTop\Portal\Form\ObjectFormManager new instance init from JSON data * * @inheritDoc * @throws \Exception - * @throws \SecurityException if twig content is present and $bTrustContent is false - * + * @since 2.7.6 3.0.0 N°4384 new $bTrustContent parameter + * @since 2.7.7 3.0.1 N°4867 remove param $bTrustContent */ public static function FromJSON($sJson) { From 834084a3e210d7ce5fb2beb3cfcd52d6fd05c08d Mon Sep 17 00:00:00 2001 From: Benjamin Dalsass Date: Thu, 2 Jun 2022 17:15:10 +0200 Subject: [PATCH 31/41] Correct dictionary entry --- dictionaries/da.dictionary.itop.ui.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/da.dictionary.itop.ui.php b/dictionaries/da.dictionary.itop.ui.php index d27c5de4e..0de5a64fb 100644 --- a/dictionaries/da.dictionary.itop.ui.php +++ b/dictionaries/da.dictionary.itop.ui.php @@ -1666,7 +1666,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array( )); // OAuth -Dict::Add('CS CZ', 'Czech', 'Čeština', array( +Dict::Add('DA DA', 'Danish', 'Dansk', array( 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', From 596c62aec873d07f62e7a616d398881f086ba96e Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Fri, 3 Jun 2022 09:53:30 +0200 Subject: [PATCH 32/41] =?UTF-8?q?:bulb:=20N=C2=B04867=20Add=20bug=20refere?= =?UTF-8?q?nce=20in=20phpdoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php index e4bf72d05..f354ee5d2 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php @@ -91,7 +91,7 @@ class ObjectFormManager extends FormManager * @return array formmanager_data as a PHP array * * @since 2.7.6 3.0.0 N°4384 method creation : factorize as this is used twice now - * @since 2.7.7 3.0.1 only used once but kept + * @since 2.7.7 3.0.1 N°4867 now only used once, but we decided to keep this method anyway */ protected static function DecodeFormManagerData($formManagerData) { From 3ea82e37d5a57358a5dd689f3e88f9ba1404bd5c Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Fri, 3 Jun 2022 18:00:29 +0200 Subject: [PATCH 33/41] =?UTF-8?q?N=C2=B04635=20Report=20\LogChannels::NOTI?= =?UTF-8?q?FICATIONS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/log.class.inc.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/log.class.inc.php b/core/log.class.inc.php index c7a56c277..9facb1599 100644 --- a/core/log.class.inc.php +++ b/core/log.class.inc.php @@ -551,6 +551,13 @@ class LogChannels const INLINE_IMAGE = 'InlineImage'; + /** + * @var string + * @since 3.0.1 N°4849 + * @since 2.7.7 N°4635 + */ + const NOTIFICATIONS = 'notifications'; + const PORTAL = 'portal'; } From 34bed5ec4fd19e252f324aa33df2403bb06c9800 Mon Sep 17 00:00:00 2001 From: Benjamin Dalsass Date: Tue, 7 Jun 2022 11:14:26 +0200 Subject: [PATCH 34/41] =?UTF-8?q?N=C2=B05215=20-=20Portal=20insufficient?= =?UTF-8?q?=20access=20control=20for=20ajax=20search=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/ajax.searchform.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/ajax.searchform.php b/pages/ajax.searchform.php index cf6ebae27..e6e3c9572 100644 --- a/pages/ajax.searchform.php +++ b/pages/ajax.searchform.php @@ -37,7 +37,7 @@ try $oKPI->ComputeAndReport('Data model loaded'); $oKPI = new ExecutionKPI(); - if (LoginWebPage::EXIT_CODE_OK != LoginWebPage::DoLoginEx(null /* any portal */, false, LoginWebPage::EXIT_RETURN)) + if (LoginWebPage::EXIT_CODE_OK != LoginWebPage::DoLoginEx('backoffice', false, LoginWebPage::EXIT_RETURN)) { throw new SecurityException('You must be logged in'); } From cdd7dcdc5cc27fc7454eac23cc2430a1c59b8234 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Wed, 8 Jun 2022 10:12:19 +0200 Subject: [PATCH 35/41] =?UTF-8?q?N=C2=B05211=20-=20Core=20update=20not=20w?= =?UTF-8?q?orking=20with=20auto-selected=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Service/RunTimeEnvironmentCoreUpdater.php | 14 +------------- setup/modelfactory.class.inc.php | 4 ++-- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php index 6715878e0..5164e428a 100644 --- a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php +++ b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php @@ -138,19 +138,7 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment { throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)"); } - $aDirsToCompile = array($sSourceDirFull); - if (is_dir(APPROOT.'extensions')) - { - $aDirsToCompile[] = APPROOT.'extensions'; - } - $sExtraDir = APPROOT.'data/'.$this->sTargetEnv.'-modules/'; - if (is_dir($sExtraDir)) - { - $aDirsToCompile[] = $sExtraDir; - } - - $aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile); - $aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs); + $aDirsToCompile = [$sSourceDirFull]; $oFactory = new ModelFactory($aDirsToCompile); $aModules = $oFactory->FindModules(); diff --git a/setup/modelfactory.class.inc.php b/setup/modelfactory.class.inc.php index 4daeedbdd..123e5cca3 100644 --- a/setup/modelfactory.class.inc.php +++ b/setup/modelfactory.class.inc.php @@ -944,9 +944,9 @@ class ModelFactory $aLoadedModuleNames = array(); foreach (self::$aLoadedModules as $oModule) { - $aLoadedModuleNames[] = $oModule->GetName(); + $aLoadedModuleNames[] = $oModule->GetName().':'.$oModule->GetVersion(); } - throw new Exception('Error loading module "'.$oModule->GetName().'": '.$e->getMessage().' - Loaded modules: '.implode(',', + throw new Exception('Error loading module "'.$oModule->GetName().'": '.$e->getMessage().' - Loaded modules: '.implode(', ', $aLoadedModuleNames)); } } From 9e314ba77b5acdc252b910c77c2ed24bfde8fd13 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Wed, 8 Jun 2022 10:24:03 +0200 Subject: [PATCH 36/41] =?UTF-8?q?N=C2=B05211=20-=20Core=20update=20not=20w?= =?UTF-8?q?orking=20with=20auto-selected=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Service/RunTimeEnvironmentCoreUpdater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php index 5164e428a..3c27c3fb3 100644 --- a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php +++ b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php @@ -150,7 +150,7 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension) { if ($oExtension->bMarkedAsChosen) { foreach ($oExtension->aModules as $sModuleName) { - if (!isset($aRet[$sModuleName])) { + if (!isset($aRet[$sModuleName]) && isset($aAvailableModules[$sModuleName])) { $aRet[$sModuleName] = $aAvailableModules[$sModuleName]; } } From 9674378c56491bbdb5945a3bfe63c99029bc1a97 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Wed, 8 Jun 2022 10:36:55 +0200 Subject: [PATCH 37/41] =?UTF-8?q?N=C2=B05211=20-=20Core=20update=20not=20w?= =?UTF-8?q?orking=20with=20auto-selected=20modules=20(comments)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Service/RunTimeEnvironmentCoreUpdater.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php index 3c27c3fb3..d28a684d1 100644 --- a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php +++ b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php @@ -132,7 +132,7 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment { $aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir); - // Add new mandatory modules + // Add new mandatory modules from datamodel 2.x only $sSourceDirFull = APPROOT.$sSourceDir; if (!is_dir($sSourceDirFull)) { @@ -147,6 +147,7 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment foreach ($aModules as $oModule) { $aAvailableModules[$oModule->GetName()] = $oModule; } + // TODO check the auto-selected modules here foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension) { if ($oExtension->bMarkedAsChosen) { foreach ($oExtension->aModules as $sModuleName) { From 4c585614cdc1f8ecfaf0f25b213f659559406aa3 Mon Sep 17 00:00:00 2001 From: odain Date: Wed, 8 Jun 2022 11:07:31 +0200 Subject: [PATCH 38/41] ease testing: CreateTestOrganization returns org object --- test/ItopDataTestCase.php | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ItopDataTestCase.php b/test/ItopDataTestCase.php index 89a0fd1d2..97ed4b2ce 100644 --- a/test/ItopDataTestCase.php +++ b/test/ItopDataTestCase.php @@ -785,6 +785,7 @@ class ItopDataTestCase extends ItopTestCase // Create a specific organization for the tests $oOrg = $this->CreateOrganization('UnitTestOrganization'); $this->iTestOrgId = $oOrg->GetKey(); + return $oOrg; } From c94c727058462c6f2dbf2c3e8af4ac9807c194c8 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Tue, 7 Jun 2022 09:02:53 +0200 Subject: [PATCH 39/41] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20co?= =?UTF-8?q?nnect=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20?= =?UTF-8?q?Add=20feature=20to=20connect=20Office=20mail=20box=20with=20OAu?= =?UTF-8?q?th2=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20?= =?UTF-8?q?send=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP?= =?UTF-8?q?=20and=20OAuth=20=20*=20migration=20to=20iTop=203.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- css/backoffice/pages/_all.scss | 3 +- css/backoffice/pages/_oauth.wizard.scss | 16 ++++++ .../pages/backoffice/oauth/Wizard.html.twig | 54 +++++++------------ 3 files changed, 38 insertions(+), 35 deletions(-) create mode 100644 css/backoffice/pages/_oauth.wizard.scss diff --git a/css/backoffice/pages/_all.scss b/css/backoffice/pages/_all.scss index d911ff390..3e6b6b39a 100644 --- a/css/backoffice/pages/_all.scss +++ b/css/backoffice/pages/_all.scss @@ -14,4 +14,5 @@ @import "csv-import"; @import "global-search"; @import "run-query"; -@import "welcome-popup"; \ No newline at end of file +@import "welcome-popup"; +@import "oauth.wizard"; \ No newline at end of file diff --git a/css/backoffice/pages/_oauth.wizard.scss b/css/backoffice/pages/_oauth.wizard.scss new file mode 100644 index 000000000..04fb33089 --- /dev/null +++ b/css/backoffice/pages/_oauth.wizard.scss @@ -0,0 +1,16 @@ +.ibo-oauth-wizard .ibo-panel--body{ + .ibo-oauth-wizard--form--container{ + display: flex; + flex-direction: row; + flex-grow: 1; + } + .ibo-oauth-wizard--form { + + } + .ibo-oauth-wizard--illustration svg{ + max-height: 400px; + } +} +#ibo-oauth-wizard--conf--result{ + white-space: pre-wrap +} diff --git a/templates/pages/backoffice/oauth/Wizard.html.twig b/templates/pages/backoffice/oauth/Wizard.html.twig index 0aee723b3..ee4395e68 100644 --- a/templates/pages/backoffice/oauth/Wizard.html.twig +++ b/templates/pages/backoffice/oauth/Wizard.html.twig @@ -1,13 +1,11 @@ {# @copyright Copyright (C) 2010-2022 Combodo SARL #} {# @license http://opensource.org/licenses/AGPL-3.0 #} -
      -

      {{ 'UI:OAuth:Wizard:Page:Title'|dict_s }}

      +{% UIContentBlock Standard {AddCSSClasses:["ibo-oauth-wizard"]} %} -
      - -
      -
      + {% UIPanel Neutral {sTitle:'UI:OAuth:Wizard:Form:Panel:Title'|dict_s, AddCSSClasses:['ibo-oauth-wizard']} %} + {% UIContentBlock Standard {AddCSSClasses:["ibo-oauth-wizard--form--container"]} %} + {% UIContentBlock Standard {sId:'select_layout'} %} {% set sIsChecked = 'checked' %} {% for aSelect in aProviders %} @@ -17,37 +15,25 @@ {% set sIsChecked = '' %} {% endfor %} -
      -
      + {% EndUIContentBlock %} + {% EndUIContentBlock %} + + {% set aCSSClasses = ['ibo-oauth-wizard--form'] %} + {% for aSelect in aProviders %} + {% set aCSSClasses = aCSSClasses|merge(['ibo-oauth-wizard--form-' ~ aSelect.name]) %} + {% endfor %} + {% UIForm Standard {AddCSSClasses:aCSSClasses} %} + {% UIInput ForHidden {sName:'url', sValue:''} %} + {% for sName, aInput in aInputs %} + {% UIInput ForInputWithLabel {sLabel:aInput.label, sInputName:sName, sInputValue:aInput.value, sInputId:"wizard_input_"~sName, sInputType:aInput.type} %} + {% endfor %} + {% UIButton ForPrimaryAction {sLabel:'UI:OAuth:Wizard:Form:Button:Submit:Label'|dict_s, sName:'value', sValue:'value', bIsSubmit:true, sId:'value', AddCSSClasses:["ibo-oauth-wizard--form--submit"]} %} + {% EndUIForm %} + {% EndUIPanel %} -
      - {{ 'UI:OAuth:Wizard:Form:Panel:Title'|dict_s }} -
      - - {% for sName, aInput in aInputs %} -
      -
      -
      -
      - -
      -
      -
      - {% endfor %} - -
      -
      -
      {% for sTemplate in aAdditionalBlocks %} {% include sTemplate %} {% endfor %} -
      \ No newline at end of file +{% EndUIContentBlock %} From 6e4fb5888cd7db7cd98b139039319e43cdbe7a02 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Wed, 8 Jun 2022 15:47:52 +0200 Subject: [PATCH 40/41] =?UTF-8?q?N=C2=B05102=20-=20Allow=20to=20send=20ema?= =?UTF-8?q?ils=20(eg.=20notifications)=20using=20GSuite=20SMTP=20and=20OAu?= =?UTF-8?q?th=20=20*=20migration=20to=20iTop=203.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/email.class.inc.php | 1 + lib/composer/InstalledVersions.php | 13 -- lib/composer/autoload_classmap.php | 116 ----------------- lib/composer/autoload_files.php | 2 +- lib/composer/autoload_psr4.php | 2 +- lib/composer/autoload_static.php | 122 +----------------- lib/composer/installed.php | 8 +- .../laminas-mail/src/Header/HeaderLocator.php | 75 +++++++++++ .../src/Header/HeaderLocatorInterface.php | 25 ++++ 9 files changed, 110 insertions(+), 254 deletions(-) create mode 100644 lib/laminas/laminas-mail/src/Header/HeaderLocator.php create mode 100644 lib/laminas/laminas-mail/src/Header/HeaderLocatorInterface.php diff --git a/core/email.class.inc.php b/core/email.class.inc.php index 4e2337a63..dd309b077 100644 --- a/core/email.class.inc.php +++ b/core/email.class.inc.php @@ -24,6 +24,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ +use Combodo\iTop\Core\Email\EmailFactory; use Combodo\iTop\Core\Email\iEMail; Swift_Preferences::getInstance()->setCharset('UTF-8'); diff --git a/lib/composer/InstalledVersions.php b/lib/composer/InstalledVersions.php index d50e0c9fc..7c5502ca4 100644 --- a/lib/composer/InstalledVersions.php +++ b/lib/composer/InstalledVersions.php @@ -24,21 +24,8 @@ use Composer\Semver\VersionParser; */ class InstalledVersions { - /** - * @var mixed[]|null - * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null - */ private static $installed; - - /** - * @var bool|null - */ private static $canGetVendors; - - /** - * @var array[] - * @psalm-var array}> - */ private static $installedByVendor = array(); /** diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index a2017cb08..52d64443b 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -819,7 +819,6 @@ return array( 'Laminas\\ServiceManager\\PsrContainerDecorator' => $vendorDir . '/laminas/laminas-servicemanager/src/PsrContainerDecorator.php', 'Laminas\\ServiceManager\\ServiceLocatorInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php', 'Laminas\\ServiceManager\\ServiceManager' => $vendorDir . '/laminas/laminas-servicemanager/src/ServiceManager.php', - 'Laminas\\ServiceManager\\Test\\CommonPluginManagerTrait' => $vendorDir . '/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php', 'Laminas\\ServiceManager\\Tool\\ConfigDumper' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php', 'Laminas\\ServiceManager\\Tool\\ConfigDumperCommand' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php', 'Laminas\\ServiceManager\\Tool\\FactoryCreator' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php', @@ -971,7 +970,6 @@ return array( 'Laminas\\Validator\\Timezone' => $vendorDir . '/laminas/laminas-validator/src/Timezone.php', 'Laminas\\Validator\\Translator\\TranslatorAwareInterface' => $vendorDir . '/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php', 'Laminas\\Validator\\Translator\\TranslatorInterface' => $vendorDir . '/laminas/laminas-validator/src/Translator/TranslatorInterface.php', - 'Laminas\\Validator\\UndisclosedPassword' => $vendorDir . '/laminas/laminas-validator/src/UndisclosedPassword.php', 'Laminas\\Validator\\Uri' => $vendorDir . '/laminas/laminas-validator/src/Uri.php', 'Laminas\\Validator\\Uuid' => $vendorDir . '/laminas/laminas-validator/src/Uuid.php', 'Laminas\\Validator\\ValidatorChain' => $vendorDir . '/laminas/laminas-validator/src/ValidatorChain.php', @@ -1467,11 +1465,8 @@ return array( 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => $vendorDir . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => $vendorDir . '/symfony/twig-bridge/Command/LintCommand.php', 'Symfony\\Bridge\\Twig\\DataCollector\\TwigDataCollector' => $vendorDir . '/symfony/twig-bridge/DataCollector/TwigDataCollector.php', - 'Symfony\\Bridge\\Twig\\ErrorRenderer\\TwigErrorRenderer' => $vendorDir . '/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php', 'Symfony\\Bridge\\Twig\\Extension\\AssetExtension' => $vendorDir . '/symfony/twig-bridge/Extension/AssetExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\CodeExtension' => $vendorDir . '/symfony/twig-bridge/Extension/CodeExtension.php', - 'Symfony\\Bridge\\Twig\\Extension\\CsrfExtension' => $vendorDir . '/symfony/twig-bridge/Extension/CsrfExtension.php', - 'Symfony\\Bridge\\Twig\\Extension\\CsrfRuntime' => $vendorDir . '/symfony/twig-bridge/Extension/CsrfRuntime.php', 'Symfony\\Bridge\\Twig\\Extension\\DumpExtension' => $vendorDir . '/symfony/twig-bridge/Extension/DumpExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ExpressionExtension' => $vendorDir . '/symfony/twig-bridge/Extension/ExpressionExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\FormExtension' => $vendorDir . '/symfony/twig-bridge/Extension/FormExtension.php', @@ -1492,10 +1487,6 @@ return array( 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngine' => $vendorDir . '/symfony/twig-bridge/Form/TwigRendererEngine.php', 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngineInterface' => $vendorDir . '/symfony/twig-bridge/Form/TwigRendererEngineInterface.php', 'Symfony\\Bridge\\Twig\\Form\\TwigRendererInterface' => $vendorDir . '/symfony/twig-bridge/Form/TwigRendererInterface.php', - 'Symfony\\Bridge\\Twig\\Mime\\BodyRenderer' => $vendorDir . '/symfony/twig-bridge/Mime/BodyRenderer.php', - 'Symfony\\Bridge\\Twig\\Mime\\NotificationEmail' => $vendorDir . '/symfony/twig-bridge/Mime/NotificationEmail.php', - 'Symfony\\Bridge\\Twig\\Mime\\TemplatedEmail' => $vendorDir . '/symfony/twig-bridge/Mime/TemplatedEmail.php', - 'Symfony\\Bridge\\Twig\\Mime\\WrappedTemplatedEmail' => $vendorDir . '/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\Scope' => $vendorDir . '/symfony/twig-bridge/NodeVisitor/Scope.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationDefaultDomainNodeVisitor' => $vendorDir . '/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationNodeVisitor' => $vendorDir . '/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php', @@ -1660,23 +1651,19 @@ return array( 'Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension' => $vendorDir . '/symfony/web-profiler-bundle/Twig/WebProfilerExtension.php', 'Symfony\\Bundle\\WebProfilerBundle\\WebProfilerBundle' => $vendorDir . '/symfony/web-profiler-bundle/WebProfilerBundle.php', 'Symfony\\Component\\Cache\\Adapter\\AbstractAdapter' => $vendorDir . '/symfony/cache/Adapter/AbstractAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\AbstractTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/AbstractTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\AdapterInterface' => $vendorDir . '/symfony/cache/Adapter/AdapterInterface.php', 'Symfony\\Component\\Cache\\Adapter\\ApcuAdapter' => $vendorDir . '/symfony/cache/Adapter/ApcuAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ArrayAdapter' => $vendorDir . '/symfony/cache/Adapter/ArrayAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => $vendorDir . '/symfony/cache/Adapter/ChainAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => $vendorDir . '/symfony/cache/Adapter/DoctrineAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter' => $vendorDir . '/symfony/cache/Adapter/MemcachedAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\NullAdapter' => $vendorDir . '/symfony/cache/Adapter/NullAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PdoAdapter' => $vendorDir . '/symfony/cache/Adapter/PdoAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PhpArrayAdapter' => $vendorDir . '/symfony/cache/Adapter/PhpArrayAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PhpFilesAdapter' => $vendorDir . '/symfony/cache/Adapter/PhpFilesAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ProxyAdapter' => $vendorDir . '/symfony/cache/Adapter/ProxyAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\Psr16Adapter' => $vendorDir . '/symfony/cache/Adapter/Psr16Adapter.php', 'Symfony\\Component\\Cache\\Adapter\\RedisAdapter' => $vendorDir . '/symfony/cache/Adapter/RedisAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/RedisTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\SimpleCacheAdapter' => $vendorDir . '/symfony/cache/Adapter/SimpleCacheAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/TagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface' => $vendorDir . '/symfony/cache/Adapter/TagAwareAdapterInterface.php', @@ -1684,21 +1671,10 @@ return array( 'Symfony\\Component\\Cache\\Adapter\\TraceableTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/TraceableTagAwareAdapter.php', 'Symfony\\Component\\Cache\\CacheItem' => $vendorDir . '/symfony/cache/CacheItem.php', 'Symfony\\Component\\Cache\\DataCollector\\CacheDataCollector' => $vendorDir . '/symfony/cache/DataCollector/CacheDataCollector.php', - 'Symfony\\Component\\Cache\\DependencyInjection\\CacheCollectorPass' => $vendorDir . '/symfony/cache/DependencyInjection/CacheCollectorPass.php', - 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php', - 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPass.php', - 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php', 'Symfony\\Component\\Cache\\DoctrineProvider' => $vendorDir . '/symfony/cache/DoctrineProvider.php', 'Symfony\\Component\\Cache\\Exception\\CacheException' => $vendorDir . '/symfony/cache/Exception/CacheException.php', 'Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/cache/Exception/InvalidArgumentException.php', - 'Symfony\\Component\\Cache\\Exception\\LogicException' => $vendorDir . '/symfony/cache/Exception/LogicException.php', - 'Symfony\\Component\\Cache\\LockRegistry' => $vendorDir . '/symfony/cache/LockRegistry.php', - 'Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller' => $vendorDir . '/symfony/cache/Marshaller/DefaultMarshaller.php', - 'Symfony\\Component\\Cache\\Marshaller\\DeflateMarshaller' => $vendorDir . '/symfony/cache/Marshaller/DeflateMarshaller.php', - 'Symfony\\Component\\Cache\\Marshaller\\MarshallerInterface' => $vendorDir . '/symfony/cache/Marshaller/MarshallerInterface.php', - 'Symfony\\Component\\Cache\\Marshaller\\TagAwareMarshaller' => $vendorDir . '/symfony/cache/Marshaller/TagAwareMarshaller.php', 'Symfony\\Component\\Cache\\PruneableInterface' => $vendorDir . '/symfony/cache/PruneableInterface.php', - 'Symfony\\Component\\Cache\\Psr16Cache' => $vendorDir . '/symfony/cache/Psr16Cache.php', 'Symfony\\Component\\Cache\\ResettableInterface' => $vendorDir . '/symfony/cache/ResettableInterface.php', 'Symfony\\Component\\Cache\\Simple\\AbstractCache' => $vendorDir . '/symfony/cache/Simple/AbstractCache.php', 'Symfony\\Component\\Cache\\Simple\\ApcuCache' => $vendorDir . '/symfony/cache/Simple/ApcuCache.php', @@ -1714,11 +1690,9 @@ return array( 'Symfony\\Component\\Cache\\Simple\\Psr6Cache' => $vendorDir . '/symfony/cache/Simple/Psr6Cache.php', 'Symfony\\Component\\Cache\\Simple\\RedisCache' => $vendorDir . '/symfony/cache/Simple/RedisCache.php', 'Symfony\\Component\\Cache\\Simple\\TraceableCache' => $vendorDir . '/symfony/cache/Simple/TraceableCache.php', - 'Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => $vendorDir . '/symfony/cache/Traits/AbstractAdapterTrait.php', 'Symfony\\Component\\Cache\\Traits\\AbstractTrait' => $vendorDir . '/symfony/cache/Traits/AbstractTrait.php', 'Symfony\\Component\\Cache\\Traits\\ApcuTrait' => $vendorDir . '/symfony/cache/Traits/ApcuTrait.php', 'Symfony\\Component\\Cache\\Traits\\ArrayTrait' => $vendorDir . '/symfony/cache/Traits/ArrayTrait.php', - 'Symfony\\Component\\Cache\\Traits\\ContractsTrait' => $vendorDir . '/symfony/cache/Traits/ContractsTrait.php', 'Symfony\\Component\\Cache\\Traits\\DoctrineTrait' => $vendorDir . '/symfony/cache/Traits/DoctrineTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemCommonTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemTrait.php', @@ -1727,8 +1701,6 @@ return array( 'Symfony\\Component\\Cache\\Traits\\PhpArrayTrait' => $vendorDir . '/symfony/cache/Traits/PhpArrayTrait.php', 'Symfony\\Component\\Cache\\Traits\\PhpFilesTrait' => $vendorDir . '/symfony/cache/Traits/PhpFilesTrait.php', 'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => $vendorDir . '/symfony/cache/Traits/ProxyTrait.php', - 'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterNodeProxy.php', - 'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisProxy' => $vendorDir . '/symfony/cache/Traits/RedisProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisTrait' => $vendorDir . '/symfony/cache/Traits/RedisTrait.php', 'Symfony\\Component\\ClassLoader\\ApcClassLoader' => $vendorDir . '/symfony/class-loader/ApcClassLoader.php', @@ -1748,7 +1720,6 @@ return array( 'Symfony\\Component\\Config\\Definition\\BooleanNode' => $vendorDir . '/symfony/config/Definition/BooleanNode.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', 'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', - 'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => $vendorDir . '/symfony/config/Definition/Builder/BuilderAwareInterface.php', 'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ExprBuilder.php', 'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', @@ -1774,7 +1745,6 @@ return array( 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidConfigurationException.php', 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidDefinitionException.php', 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidTypeException.php', - 'Symfony\\Component\\Config\\Definition\\Exception\\TreeWithoutRootNodeException' => $vendorDir . '/symfony/config/Definition/Exception/TreeWithoutRootNodeException.php', 'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => $vendorDir . '/symfony/config/Definition/Exception/UnsetKeyException.php', 'Symfony\\Component\\Config\\Definition\\FloatNode' => $vendorDir . '/symfony/config/Definition/FloatNode.php', 'Symfony\\Component\\Config\\Definition\\IntegerNode' => $vendorDir . '/symfony/config/Definition/IntegerNode.php', @@ -1789,7 +1759,6 @@ return array( 'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => $vendorDir . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', 'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => $vendorDir . '/symfony/config/Exception/FileLoaderLoadException.php', 'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => $vendorDir . '/symfony/config/Exception/FileLocatorFileNotFoundException.php', - 'Symfony\\Component\\Config\\Exception\\LoaderLoadException' => $vendorDir . '/symfony/config/Exception/LoaderLoadException.php', 'Symfony\\Component\\Config\\FileLocator' => $vendorDir . '/symfony/config/FileLocator.php', 'Symfony\\Component\\Config\\FileLocatorInterface' => $vendorDir . '/symfony/config/FileLocatorInterface.php', 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => $vendorDir . '/symfony/config/Loader/DelegatingLoader.php', @@ -1962,11 +1931,8 @@ return array( 'Symfony\\Component\\DependencyInjection\\Argument\\ArgumentInterface' => $vendorDir . '/symfony/dependency-injection/Argument/ArgumentInterface.php', 'Symfony\\Component\\DependencyInjection\\Argument\\BoundArgument' => $vendorDir . '/symfony/dependency-injection/Argument/BoundArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\IteratorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/IteratorArgument.php', - 'Symfony\\Component\\DependencyInjection\\Argument\\ReferenceSetArgumentTrait' => $vendorDir . '/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php', 'Symfony\\Component\\DependencyInjection\\Argument\\RewindableGenerator' => $vendorDir . '/symfony/dependency-injection/Argument/RewindableGenerator.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceClosureArgument' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceClosureArgument.php', - 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocator' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceLocator.php', - 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocatorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceLocatorArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\TaggedIteratorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/TaggedIteratorArgument.php', 'Symfony\\Component\\DependencyInjection\\ChildDefinition' => $vendorDir . '/symfony/dependency-injection/ChildDefinition.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AbstractRecursivePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AbstractRecursivePass.php', @@ -1980,7 +1946,6 @@ return array( 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckDefinitionValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckExceptionOnInvalidReferenceBehaviorPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckReferenceValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckTypeDeclarationsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\Compiler' => $vendorDir . '/symfony/dependency-injection/Compiler/Compiler.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface' => $vendorDir . '/symfony/dependency-injection/Compiler/CompilerPassInterface.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\DecoratorServicePass' => $vendorDir . '/symfony/dependency-injection/Compiler/DecoratorServicePass.php', @@ -1993,7 +1958,6 @@ return array( 'Symfony\\Component\\DependencyInjection\\Compiler\\PassConfig' => $vendorDir . '/symfony/dependency-injection/Compiler/PassConfig.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\PriorityTaggedServiceTrait' => $vendorDir . '/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterEnvVarProcessorsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterReverseContainerPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterServiceSubscribersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', @@ -2020,7 +1984,6 @@ return array( 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraph' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphEdge' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphNode' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\ValidateEnvPlaceholdersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php', 'Symfony\\Component\\DependencyInjection\\Config\\AutowireServiceResource' => $vendorDir . '/symfony/dependency-injection/Config/AutowireServiceResource.php', 'Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResource' => $vendorDir . '/symfony/dependency-injection/Config/ContainerParametersResource.php', 'Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResourceChecker' => $vendorDir . '/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php', @@ -2035,10 +1998,8 @@ return array( 'Symfony\\Component\\DependencyInjection\\Dumper\\DumperInterface' => $vendorDir . '/symfony/dependency-injection/Dumper/DumperInterface.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\GraphvizDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/GraphvizDumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/PhpDumper.php', - 'Symfony\\Component\\DependencyInjection\\Dumper\\Preloader' => $vendorDir . '/symfony/dependency-injection/Dumper/Preloader.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\XmlDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/XmlDumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\YamlDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/YamlDumper.php', - 'Symfony\\Component\\DependencyInjection\\EnvVarLoaderInterface' => $vendorDir . '/symfony/dependency-injection/EnvVarLoaderInterface.php', 'Symfony\\Component\\DependencyInjection\\EnvVarProcessor' => $vendorDir . '/symfony/dependency-injection/EnvVarProcessor.php', 'Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface' => $vendorDir . '/symfony/dependency-injection/EnvVarProcessorInterface.php', 'Symfony\\Component\\DependencyInjection\\Exception\\AutowiringFailedException' => $vendorDir . '/symfony/dependency-injection/Exception/AutowiringFailedException.php', @@ -2047,7 +2008,6 @@ return array( 'Symfony\\Component\\DependencyInjection\\Exception\\EnvParameterException' => $vendorDir . '/symfony/dependency-injection/Exception/EnvParameterException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/dependency-injection/Exception/ExceptionInterface.php', 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/dependency-injection/Exception/InvalidArgumentException.php', - 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidParameterTypeException' => $vendorDir . '/symfony/dependency-injection/Exception/InvalidParameterTypeException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\LogicException' => $vendorDir . '/symfony/dependency-injection/Exception/LogicException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\OutOfBoundsException' => $vendorDir . '/symfony/dependency-injection/Exception/OutOfBoundsException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\ParameterCircularReferenceException' => $vendorDir . '/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php', @@ -2106,15 +2066,12 @@ return array( 'Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/XmlFileLoader.php', 'Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/YamlFileLoader.php', 'Symfony\\Component\\DependencyInjection\\Parameter' => $vendorDir . '/symfony/dependency-injection/Parameter.php', - 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ContainerBag.php', - 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\EnvPlaceholderParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\FrozenParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBagInterface' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php', 'Symfony\\Component\\DependencyInjection\\Reference' => $vendorDir . '/symfony/dependency-injection/Reference.php', 'Symfony\\Component\\DependencyInjection\\ResettableContainerInterface' => $vendorDir . '/symfony/dependency-injection/ResettableContainerInterface.php', - 'Symfony\\Component\\DependencyInjection\\ReverseContainer' => $vendorDir . '/symfony/dependency-injection/ReverseContainer.php', 'Symfony\\Component\\DependencyInjection\\ServiceLocator' => $vendorDir . '/symfony/dependency-injection/ServiceLocator.php', 'Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface' => $vendorDir . '/symfony/dependency-injection/ServiceSubscriberInterface.php', 'Symfony\\Component\\DependencyInjection\\TaggedContainerInterface' => $vendorDir . '/symfony/dependency-injection/TaggedContainerInterface.php', @@ -2129,7 +2086,6 @@ return array( 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php', 'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => $vendorDir . '/symfony/event-dispatcher/Debug/WrappedListener.php', - 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php', 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', 'Symfony\\Component\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher/Event.php', 'Symfony\\Component\\EventDispatcher\\EventDispatcher' => $vendorDir . '/symfony/event-dispatcher/EventDispatcher.php', @@ -2137,23 +2093,18 @@ return array( 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/symfony/event-dispatcher/EventSubscriberInterface.php', 'Symfony\\Component\\EventDispatcher\\GenericEvent' => $vendorDir . '/symfony/event-dispatcher/GenericEvent.php', 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', - 'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => $vendorDir . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', - 'Symfony\\Component\\EventDispatcher\\LegacyEventProxy' => $vendorDir . '/symfony/event-dispatcher/LegacyEventProxy.php', 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php', 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php', - 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php', 'Symfony\\Component\\Filesystem\\LockHandler' => $vendorDir . '/symfony/filesystem/LockHandler.php', 'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php', 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php', 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php', 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Exception/AccessDeniedException.php', - 'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => $vendorDir . '/symfony/finder/Exception/DirectoryNotFoundException.php', 'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/finder/Exception/ExceptionInterface.php', 'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.php', - 'Symfony\\Component\\Finder\\Gitignore' => $vendorDir . '/symfony/finder/Gitignore.php', 'Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Glob.php', 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Iterator/CustomFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DateRangeFilterIterator.php', @@ -2163,7 +2114,6 @@ return array( 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilecontentFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilenameFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilterIterator.php', - 'Symfony\\Component\\Finder\\Iterator\\LazyIterator' => $vendorDir . '/symfony/finder/Iterator/LazyIterator.php', 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Iterator/PathFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', @@ -2181,15 +2131,8 @@ return array( 'Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => $vendorDir . '/symfony/http-foundation/ExpressionRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\FileBag' => $vendorDir . '/symfony/http-foundation/FileBag.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\CannotWriteFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/CannotWriteFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\ExtensionFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/ExtensionFileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException' => $vendorDir . '/symfony/http-foundation/File/Exception/FileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/http-foundation/File/Exception/FileNotFoundException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FormSizeFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/FormSizeFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\IniSizeFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/IniSizeFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\NoFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/NoFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\NoTmpDirFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/NoTmpDirFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\PartialFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/PartialFileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UnexpectedTypeException' => $vendorDir . '/symfony/http-foundation/File/Exception/UnexpectedTypeException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UploadException' => $vendorDir . '/symfony/http-foundation/File/Exception/UploadException.php', 'Symfony\\Component\\HttpFoundation\\File\\File' => $vendorDir . '/symfony/http-foundation/File/File.php', @@ -2203,7 +2146,6 @@ return array( 'Symfony\\Component\\HttpFoundation\\File\\Stream' => $vendorDir . '/symfony/http-foundation/File/Stream.php', 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' => $vendorDir . '/symfony/http-foundation/File/UploadedFile.php', 'Symfony\\Component\\HttpFoundation\\HeaderBag' => $vendorDir . '/symfony/http-foundation/HeaderBag.php', - 'Symfony\\Component\\HttpFoundation\\HeaderUtils' => $vendorDir . '/symfony/http-foundation/HeaderUtils.php', 'Symfony\\Component\\HttpFoundation\\IpUtils' => $vendorDir . '/symfony/http-foundation/IpUtils.php', 'Symfony\\Component\\HttpFoundation\\JsonResponse' => $vendorDir . '/symfony/http-foundation/JsonResponse.php', 'Symfony\\Component\\HttpFoundation\\ParameterBag' => $vendorDir . '/symfony/http-foundation/ParameterBag.php', @@ -2229,14 +2171,11 @@ return array( 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\AbstractSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\RedisSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\StrictSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\WriteCheckSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag' => $vendorDir . '/symfony/http-foundation/Session/Storage/MetadataBag.php', @@ -2249,15 +2188,6 @@ return array( 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => $vendorDir . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', 'Symfony\\Component\\HttpFoundation\\StreamedResponse' => $vendorDir . '/symfony/http-foundation/StreamedResponse.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\RequestAttributeValueSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseCookieValueSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasCookie' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasHeader' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHeaderSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsRedirected' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsSuccessful' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseStatusCodeSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php', - 'Symfony\\Component\\HttpFoundation\\UrlHelper' => $vendorDir . '/symfony/http-foundation/UrlHelper.php', 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => $vendorDir . '/symfony/http-kernel/Bundle/Bundle.php', 'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => $vendorDir . '/symfony/http-kernel/Bundle/BundleInterface.php', 'Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => $vendorDir . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', @@ -2276,19 +2206,16 @@ return array( 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php', - 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\NotTaggedControllerValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php', - 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\TraceableValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ContainerControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/ContainerControllerResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerReference' => $vendorDir . '/symfony/http-kernel/Controller/ControllerReference.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/ControllerResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ControllerResolverInterface.php', - 'Symfony\\Component\\HttpKernel\\Controller\\ErrorController' => $vendorDir . '/symfony/http-kernel/Controller/ErrorController.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableArgumentResolver' => $vendorDir . '/symfony/http-kernel/Controller/TraceableArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', @@ -2317,7 +2244,6 @@ return array( 'Symfony\\Component\\HttpKernel\\DependencyInjection\\LoggerPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/LoggerPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\MergeExtensionConfigurationPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterControllerArgumentLocatorsPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php', - 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterLocaleAwareServicesPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RemoveEmptyControllerArgumentLocatorsPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ResettableServicePass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ResettableServicePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ServicesResetter' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ServicesResetter.php', @@ -2325,12 +2251,9 @@ return array( 'Symfony\\Component\\HttpKernel\\EventListener\\AbstractTestSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/AbstractTestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => $vendorDir . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => $vendorDir . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\DisallowRobotsIndexingListener' => $vendorDir . '/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => $vendorDir . '/symfony/http-kernel/EventListener/DumpListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener' => $vendorDir . '/symfony/http-kernel/EventListener/ErrorListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener' => $vendorDir . '/symfony/http-kernel/EventListener/ExceptionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\FragmentListener' => $vendorDir . '/symfony/http-kernel/EventListener/FragmentListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleAwareListener' => $vendorDir . '/symfony/http-kernel/EventListener/LocaleAwareListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener' => $vendorDir . '/symfony/http-kernel/EventListener/LocaleListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener' => $vendorDir . '/symfony/http-kernel/EventListener/ProfilerListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener' => $vendorDir . '/symfony/http-kernel/EventListener/ResponseListener.php', @@ -2342,9 +2265,6 @@ return array( 'Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/TestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\TranslatorListener' => $vendorDir . '/symfony/http-kernel/EventListener/TranslatorListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => $vendorDir . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', - 'Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent' => $vendorDir . '/symfony/http-kernel/Event/ControllerArgumentsEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\ControllerEvent' => $vendorDir . '/symfony/http-kernel/Event/ControllerEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent' => $vendorDir . '/symfony/http-kernel/Event/ExceptionEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerArgumentsEvent' => $vendorDir . '/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent' => $vendorDir . '/symfony/http-kernel/Event/FilterControllerEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/FilterResponseEvent.php', @@ -2354,14 +2274,9 @@ return array( 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent' => $vendorDir . '/symfony/http-kernel/Event/GetResponseForExceptionEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent' => $vendorDir . '/symfony/http-kernel/Event/KernelEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/PostResponseEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\RequestEvent' => $vendorDir . '/symfony/http-kernel/Event/RequestEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\ResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/ResponseEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\TerminateEvent' => $vendorDir . '/symfony/http-kernel/Event/TerminateEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\ViewEvent' => $vendorDir . '/symfony/http-kernel/Event/ViewEvent.php', 'Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/AccessDeniedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException' => $vendorDir . '/symfony/http-kernel/Exception/BadRequestHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException' => $vendorDir . '/symfony/http-kernel/Exception/ConflictHttpException.php', - 'Symfony\\Component\\HttpKernel\\Exception\\ControllerDoesNotReturnResponseException' => $vendorDir . '/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php', 'Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException' => $vendorDir . '/symfony/http-kernel/Exception/GoneHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\HttpException' => $vendorDir . '/symfony/http-kernel/Exception/HttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => $vendorDir . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', @@ -2394,9 +2309,7 @@ return array( 'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/StoreInterface.php', 'Symfony\\Component\\HttpKernel\\HttpCache\\SubRequestHandler' => $vendorDir . '/symfony/http-kernel/HttpCache/SubRequestHandler.php', 'Symfony\\Component\\HttpKernel\\HttpCache\\SurrogateInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/SurrogateInterface.php', - 'Symfony\\Component\\HttpKernel\\HttpClientKernel' => $vendorDir . '/symfony/http-kernel/HttpClientKernel.php', 'Symfony\\Component\\HttpKernel\\HttpKernel' => $vendorDir . '/symfony/http-kernel/HttpKernel.php', - 'Symfony\\Component\\HttpKernel\\HttpKernelBrowser' => $vendorDir . '/symfony/http-kernel/HttpKernelBrowser.php', 'Symfony\\Component\\HttpKernel\\HttpKernelInterface' => $vendorDir . '/symfony/http-kernel/HttpKernelInterface.php', 'Symfony\\Component\\HttpKernel\\Kernel' => $vendorDir . '/symfony/http-kernel/Kernel.php', 'Symfony\\Component\\HttpKernel\\KernelEvents' => $vendorDir . '/symfony/http-kernel/KernelEvents.php', @@ -2420,9 +2333,7 @@ return array( 'Symfony\\Component\\Routing\\Exception\\NoConfigurationException' => $vendorDir . '/symfony/routing/Exception/NoConfigurationException.php', 'Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException' => $vendorDir . '/symfony/routing/Exception/ResourceNotFoundException.php', 'Symfony\\Component\\Routing\\Exception\\RouteNotFoundException' => $vendorDir . '/symfony/routing/Exception/RouteNotFoundException.php', - 'Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator' => $vendorDir . '/symfony/routing/Generator/CompiledUrlGenerator.php', 'Symfony\\Component\\Routing\\Generator\\ConfigurableRequirementsInterface' => $vendorDir . '/symfony/routing/Generator/ConfigurableRequirementsInterface.php', - 'Symfony\\Component\\Routing\\Generator\\Dumper\\CompiledUrlGeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/GeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumperInterface' => $vendorDir . '/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php', @@ -2438,18 +2349,13 @@ return array( 'Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/RoutingConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\AddTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/AddTrait.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\RouteTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/RouteTrait.php', - 'Symfony\\Component\\Routing\\Loader\\ContainerLoader' => $vendorDir . '/symfony/routing/Loader/ContainerLoader.php', 'Symfony\\Component\\Routing\\Loader\\DependencyInjection\\ServiceRouterLoader' => $vendorDir . '/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php', 'Symfony\\Component\\Routing\\Loader\\DirectoryLoader' => $vendorDir . '/symfony/routing/Loader/DirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/routing/Loader/GlobFileLoader.php', - 'Symfony\\Component\\Routing\\Loader\\ObjectLoader' => $vendorDir . '/symfony/routing/Loader/ObjectLoader.php', 'Symfony\\Component\\Routing\\Loader\\ObjectRouteLoader' => $vendorDir . '/symfony/routing/Loader/ObjectRouteLoader.php', 'Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/routing/Loader/PhpFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/routing/Loader/XmlFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/routing/Loader/YamlFileLoader.php', - 'Symfony\\Component\\Routing\\Matcher\\CompiledUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/CompiledUrlMatcher.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherTrait' => $vendorDir . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperCollection' => $vendorDir . '/symfony/routing/Matcher/Dumper/DumperCollection.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperRoute' => $vendorDir . '/symfony/routing/Matcher/Dumper/DumperRoute.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/MatcherDumper.php', @@ -2485,22 +2391,13 @@ return array( 'Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => $vendorDir . '/symfony/var-dumper/Caster/DOMCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\DateCaster' => $vendorDir . '/symfony/var-dumper/Caster/DateCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => $vendorDir . '/symfony/var-dumper/Caster/DoctrineCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\DsCaster' => $vendorDir . '/symfony/var-dumper/Caster/DsCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => $vendorDir . '/symfony/var-dumper/Caster/DsPairStub.php', 'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => $vendorDir . '/symfony/var-dumper/Caster/EnumStub.php', 'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ExceptionCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => $vendorDir . '/symfony/var-dumper/Caster/FrameStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => $vendorDir . '/symfony/var-dumper/Caster/GmpCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\ImagineCaster' => $vendorDir . '/symfony/var-dumper/Caster/ImagineCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\ImgStub' => $vendorDir . '/symfony/var-dumper/Caster/ImgStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\IntlCaster' => $vendorDir . '/symfony/var-dumper/Caster/IntlCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\LinkStub' => $vendorDir . '/symfony/var-dumper/Caster/LinkStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\MemcachedCaster' => $vendorDir . '/symfony/var-dumper/Caster/MemcachedCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\MongoCaster' => $vendorDir . '/symfony/var-dumper/Caster/MongoCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\MysqliCaster' => $vendorDir . '/symfony/var-dumper/Caster/MysqliCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => $vendorDir . '/symfony/var-dumper/Caster/PdoCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => $vendorDir . '/symfony/var-dumper/Caster/PgSqlCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\ProxyManagerCaster' => $vendorDir . '/symfony/var-dumper/Caster/ProxyManagerCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => $vendorDir . '/symfony/var-dumper/Caster/RedisCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ReflectionCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/ResourceCaster.php', @@ -2508,7 +2405,6 @@ return array( 'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => $vendorDir . '/symfony/var-dumper/Caster/StubCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => $vendorDir . '/symfony/var-dumper/Caster/SymfonyCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => $vendorDir . '/symfony/var-dumper/Caster/TraceStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => $vendorDir . '/symfony/var-dumper/Caster/UuidCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlReaderCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlResourceCaster.php', 'Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => $vendorDir . '/symfony/var-dumper/Cloner/AbstractCloner.php', @@ -2518,23 +2414,11 @@ return array( 'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => $vendorDir . '/symfony/var-dumper/Cloner/DumperInterface.php', 'Symfony\\Component\\VarDumper\\Cloner\\Stub' => $vendorDir . '/symfony/var-dumper/Cloner/Stub.php', 'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => $vendorDir . '/symfony/var-dumper/Cloner/VarCloner.php', - 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php', - 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\DumpDescriptorInterface' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php', - 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\HtmlDescriptor' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php', - 'Symfony\\Component\\VarDumper\\Command\\ServerDumpCommand' => $vendorDir . '/symfony/var-dumper/Command/ServerDumpCommand.php', 'Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => $vendorDir . '/symfony/var-dumper/Dumper/AbstractDumper.php', 'Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => $vendorDir . '/symfony/var-dumper/Dumper/CliDumper.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\CliContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\ContextProviderInterface' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\RequestContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\SourceContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextualizedDumper' => $vendorDir . '/symfony/var-dumper/Dumper/ContextualizedDumper.php', 'Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => $vendorDir . '/symfony/var-dumper/Dumper/DataDumperInterface.php', 'Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => $vendorDir . '/symfony/var-dumper/Dumper/HtmlDumper.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ServerDumper' => $vendorDir . '/symfony/var-dumper/Dumper/ServerDumper.php', 'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => $vendorDir . '/symfony/var-dumper/Exception/ThrowingCasterException.php', - 'Symfony\\Component\\VarDumper\\Server\\Connection' => $vendorDir . '/symfony/var-dumper/Server/Connection.php', - 'Symfony\\Component\\VarDumper\\Server\\DumpServer' => $vendorDir . '/symfony/var-dumper/Server/DumpServer.php', 'Symfony\\Component\\VarDumper\\VarDumper' => $vendorDir . '/symfony/var-dumper/VarDumper.php', 'Symfony\\Component\\Yaml\\Command\\LintCommand' => $vendorDir . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php', diff --git a/lib/composer/autoload_files.php b/lib/composer/autoload_files.php index 3608b3dbf..7be757bea 100644 --- a/lib/composer/autoload_files.php +++ b/lib/composer/autoload_files.php @@ -18,8 +18,8 @@ return array( '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', - 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php', '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php index d02b713f5..ca8b4b9f6 100644 --- a/lib/composer/autoload_psr4.php +++ b/lib/composer/autoload_psr4.php @@ -48,7 +48,7 @@ return array( 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), 'Pelago\\' => array($vendorDir . '/pelago/emogrifier/src'), - 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-google/src', $vendorDir . '/league/oauth2-client/src'), + 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'), 'Laminas\\ZendFrameworkBridge\\' => array($vendorDir . '/laminas/laminas-zendframework-bridge/src'), 'Laminas\\Validator\\' => array($vendorDir . '/laminas/laminas-validator/src'), 'Laminas\\Stdlib\\' => array($vendorDir . '/laminas/laminas-stdlib/src'), diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 15efd126d..4713e836b 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -19,8 +19,8 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', - 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php', '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', @@ -284,8 +284,8 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 ), 'League\\OAuth2\\Client\\' => array ( - 0 => __DIR__ . '/..' . '/league/oauth2-google/src', - 1 => __DIR__ . '/..' . '/league/oauth2-client/src', + 0 => __DIR__ . '/..' . '/league/oauth2-client/src', + 1 => __DIR__ . '/..' . '/league/oauth2-google/src', ), 'Laminas\\ZendFrameworkBridge\\' => array ( @@ -1187,7 +1187,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Laminas\\ServiceManager\\PsrContainerDecorator' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/PsrContainerDecorator.php', 'Laminas\\ServiceManager\\ServiceLocatorInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php', 'Laminas\\ServiceManager\\ServiceManager' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ServiceManager.php', - 'Laminas\\ServiceManager\\Test\\CommonPluginManagerTrait' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php', 'Laminas\\ServiceManager\\Tool\\ConfigDumper' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php', 'Laminas\\ServiceManager\\Tool\\ConfigDumperCommand' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php', 'Laminas\\ServiceManager\\Tool\\FactoryCreator' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php', @@ -1339,7 +1338,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Laminas\\Validator\\Timezone' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Timezone.php', 'Laminas\\Validator\\Translator\\TranslatorAwareInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php', 'Laminas\\Validator\\Translator\\TranslatorInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Translator/TranslatorInterface.php', - 'Laminas\\Validator\\UndisclosedPassword' => __DIR__ . '/..' . '/laminas/laminas-validator/src/UndisclosedPassword.php', 'Laminas\\Validator\\Uri' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Uri.php', 'Laminas\\Validator\\Uuid' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Uuid.php', 'Laminas\\Validator\\ValidatorChain' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorChain.php', @@ -1835,11 +1833,8 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/LintCommand.php', 'Symfony\\Bridge\\Twig\\DataCollector\\TwigDataCollector' => __DIR__ . '/..' . '/symfony/twig-bridge/DataCollector/TwigDataCollector.php', - 'Symfony\\Bridge\\Twig\\ErrorRenderer\\TwigErrorRenderer' => __DIR__ . '/..' . '/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php', 'Symfony\\Bridge\\Twig\\Extension\\AssetExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/AssetExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\CodeExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/CodeExtension.php', - 'Symfony\\Bridge\\Twig\\Extension\\CsrfExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/CsrfExtension.php', - 'Symfony\\Bridge\\Twig\\Extension\\CsrfRuntime' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/CsrfRuntime.php', 'Symfony\\Bridge\\Twig\\Extension\\DumpExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/DumpExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ExpressionExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/ExpressionExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\FormExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/FormExtension.php', @@ -1860,10 +1855,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngine' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRendererEngine.php', 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngineInterface' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRendererEngineInterface.php', 'Symfony\\Bridge\\Twig\\Form\\TwigRendererInterface' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRendererInterface.php', - 'Symfony\\Bridge\\Twig\\Mime\\BodyRenderer' => __DIR__ . '/..' . '/symfony/twig-bridge/Mime/BodyRenderer.php', - 'Symfony\\Bridge\\Twig\\Mime\\NotificationEmail' => __DIR__ . '/..' . '/symfony/twig-bridge/Mime/NotificationEmail.php', - 'Symfony\\Bridge\\Twig\\Mime\\TemplatedEmail' => __DIR__ . '/..' . '/symfony/twig-bridge/Mime/TemplatedEmail.php', - 'Symfony\\Bridge\\Twig\\Mime\\WrappedTemplatedEmail' => __DIR__ . '/..' . '/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\Scope' => __DIR__ . '/..' . '/symfony/twig-bridge/NodeVisitor/Scope.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationDefaultDomainNodeVisitor' => __DIR__ . '/..' . '/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationNodeVisitor' => __DIR__ . '/..' . '/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php', @@ -2028,23 +2019,19 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension' => __DIR__ . '/..' . '/symfony/web-profiler-bundle/Twig/WebProfilerExtension.php', 'Symfony\\Bundle\\WebProfilerBundle\\WebProfilerBundle' => __DIR__ . '/..' . '/symfony/web-profiler-bundle/WebProfilerBundle.php', 'Symfony\\Component\\Cache\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/AbstractAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\AbstractTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/AbstractTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/symfony/cache/Adapter/AdapterInterface.php', 'Symfony\\Component\\Cache\\Adapter\\ApcuAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ApcuAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ArrayAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ArrayAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ChainAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/DoctrineAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/MemcachedAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\NullAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/NullAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PdoAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PdoAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PhpArrayAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PhpArrayAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PhpFilesAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PhpFilesAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ProxyAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ProxyAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\Psr16Adapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/Psr16Adapter.php', 'Symfony\\Component\\Cache\\Adapter\\RedisAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/RedisAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/RedisTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\SimpleCacheAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/SimpleCacheAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface' => __DIR__ . '/..' . '/symfony/cache/Adapter/TagAwareAdapterInterface.php', @@ -2052,21 +2039,10 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\Cache\\Adapter\\TraceableTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TraceableTagAwareAdapter.php', 'Symfony\\Component\\Cache\\CacheItem' => __DIR__ . '/..' . '/symfony/cache/CacheItem.php', 'Symfony\\Component\\Cache\\DataCollector\\CacheDataCollector' => __DIR__ . '/..' . '/symfony/cache/DataCollector/CacheDataCollector.php', - 'Symfony\\Component\\Cache\\DependencyInjection\\CacheCollectorPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CacheCollectorPass.php', - 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php', - 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPass.php', - 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php', 'Symfony\\Component\\Cache\\DoctrineProvider' => __DIR__ . '/..' . '/symfony/cache/DoctrineProvider.php', 'Symfony\\Component\\Cache\\Exception\\CacheException' => __DIR__ . '/..' . '/symfony/cache/Exception/CacheException.php', 'Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/cache/Exception/InvalidArgumentException.php', - 'Symfony\\Component\\Cache\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/cache/Exception/LogicException.php', - 'Symfony\\Component\\Cache\\LockRegistry' => __DIR__ . '/..' . '/symfony/cache/LockRegistry.php', - 'Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/DefaultMarshaller.php', - 'Symfony\\Component\\Cache\\Marshaller\\DeflateMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/DeflateMarshaller.php', - 'Symfony\\Component\\Cache\\Marshaller\\MarshallerInterface' => __DIR__ . '/..' . '/symfony/cache/Marshaller/MarshallerInterface.php', - 'Symfony\\Component\\Cache\\Marshaller\\TagAwareMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/TagAwareMarshaller.php', 'Symfony\\Component\\Cache\\PruneableInterface' => __DIR__ . '/..' . '/symfony/cache/PruneableInterface.php', - 'Symfony\\Component\\Cache\\Psr16Cache' => __DIR__ . '/..' . '/symfony/cache/Psr16Cache.php', 'Symfony\\Component\\Cache\\ResettableInterface' => __DIR__ . '/..' . '/symfony/cache/ResettableInterface.php', 'Symfony\\Component\\Cache\\Simple\\AbstractCache' => __DIR__ . '/..' . '/symfony/cache/Simple/AbstractCache.php', 'Symfony\\Component\\Cache\\Simple\\ApcuCache' => __DIR__ . '/..' . '/symfony/cache/Simple/ApcuCache.php', @@ -2082,11 +2058,9 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\Cache\\Simple\\Psr6Cache' => __DIR__ . '/..' . '/symfony/cache/Simple/Psr6Cache.php', 'Symfony\\Component\\Cache\\Simple\\RedisCache' => __DIR__ . '/..' . '/symfony/cache/Simple/RedisCache.php', 'Symfony\\Component\\Cache\\Simple\\TraceableCache' => __DIR__ . '/..' . '/symfony/cache/Simple/TraceableCache.php', - 'Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/AbstractAdapterTrait.php', 'Symfony\\Component\\Cache\\Traits\\AbstractTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/AbstractTrait.php', 'Symfony\\Component\\Cache\\Traits\\ApcuTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ApcuTrait.php', 'Symfony\\Component\\Cache\\Traits\\ArrayTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ArrayTrait.php', - 'Symfony\\Component\\Cache\\Traits\\ContractsTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ContractsTrait.php', 'Symfony\\Component\\Cache\\Traits\\DoctrineTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/DoctrineTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemCommonTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemTrait.php', @@ -2095,8 +2069,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\Cache\\Traits\\PhpArrayTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/PhpArrayTrait.php', 'Symfony\\Component\\Cache\\Traits\\PhpFilesTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/PhpFilesTrait.php', 'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ProxyTrait.php', - 'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterNodeProxy.php', - 'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisTrait.php', 'Symfony\\Component\\ClassLoader\\ApcClassLoader' => __DIR__ . '/..' . '/symfony/class-loader/ApcClassLoader.php', @@ -2116,7 +2088,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\Config\\Definition\\BooleanNode' => __DIR__ . '/..' . '/symfony/config/Definition/BooleanNode.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', 'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', - 'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BuilderAwareInterface.php', 'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ExprBuilder.php', 'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', @@ -2142,7 +2113,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidConfigurationException.php', 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidDefinitionException.php', 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidTypeException.php', - 'Symfony\\Component\\Config\\Definition\\Exception\\TreeWithoutRootNodeException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/TreeWithoutRootNodeException.php', 'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/UnsetKeyException.php', 'Symfony\\Component\\Config\\Definition\\FloatNode' => __DIR__ . '/..' . '/symfony/config/Definition/FloatNode.php', 'Symfony\\Component\\Config\\Definition\\IntegerNode' => __DIR__ . '/..' . '/symfony/config/Definition/IntegerNode.php', @@ -2157,7 +2127,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', 'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderLoadException.php', 'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLocatorFileNotFoundException.php', - 'Symfony\\Component\\Config\\Exception\\LoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/LoaderLoadException.php', 'Symfony\\Component\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/config/FileLocator.php', 'Symfony\\Component\\Config\\FileLocatorInterface' => __DIR__ . '/..' . '/symfony/config/FileLocatorInterface.php', 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/config/Loader/DelegatingLoader.php', @@ -2330,11 +2299,8 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\DependencyInjection\\Argument\\ArgumentInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ArgumentInterface.php', 'Symfony\\Component\\DependencyInjection\\Argument\\BoundArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/BoundArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\IteratorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/IteratorArgument.php', - 'Symfony\\Component\\DependencyInjection\\Argument\\ReferenceSetArgumentTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php', 'Symfony\\Component\\DependencyInjection\\Argument\\RewindableGenerator' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/RewindableGenerator.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceClosureArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceClosureArgument.php', - 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceLocator.php', - 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocatorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceLocatorArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\TaggedIteratorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/TaggedIteratorArgument.php', 'Symfony\\Component\\DependencyInjection\\ChildDefinition' => __DIR__ . '/..' . '/symfony/dependency-injection/ChildDefinition.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AbstractRecursivePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AbstractRecursivePass.php', @@ -2348,7 +2314,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckDefinitionValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckExceptionOnInvalidReferenceBehaviorPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckReferenceValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckTypeDeclarationsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\Compiler' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/Compiler.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CompilerPassInterface.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\DecoratorServicePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/DecoratorServicePass.php', @@ -2361,7 +2326,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\DependencyInjection\\Compiler\\PassConfig' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/PassConfig.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\PriorityTaggedServiceTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterEnvVarProcessorsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterReverseContainerPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterServiceSubscribersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', @@ -2388,7 +2352,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraph' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphEdge' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphNode' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\ValidateEnvPlaceholdersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php', 'Symfony\\Component\\DependencyInjection\\Config\\AutowireServiceResource' => __DIR__ . '/..' . '/symfony/dependency-injection/Config/AutowireServiceResource.php', 'Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResource' => __DIR__ . '/..' . '/symfony/dependency-injection/Config/ContainerParametersResource.php', 'Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResourceChecker' => __DIR__ . '/..' . '/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php', @@ -2403,10 +2366,8 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\DependencyInjection\\Dumper\\DumperInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/DumperInterface.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\GraphvizDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/GraphvizDumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/PhpDumper.php', - 'Symfony\\Component\\DependencyInjection\\Dumper\\Preloader' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/Preloader.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\XmlDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/XmlDumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\YamlDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/YamlDumper.php', - 'Symfony\\Component\\DependencyInjection\\EnvVarLoaderInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/EnvVarLoaderInterface.php', 'Symfony\\Component\\DependencyInjection\\EnvVarProcessor' => __DIR__ . '/..' . '/symfony/dependency-injection/EnvVarProcessor.php', 'Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/EnvVarProcessorInterface.php', 'Symfony\\Component\\DependencyInjection\\Exception\\AutowiringFailedException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/AutowiringFailedException.php', @@ -2415,7 +2376,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\DependencyInjection\\Exception\\EnvParameterException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/EnvParameterException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ExceptionInterface.php', 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/InvalidArgumentException.php', - 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidParameterTypeException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/InvalidParameterTypeException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/LogicException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/OutOfBoundsException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\ParameterCircularReferenceException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php', @@ -2474,15 +2434,12 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/XmlFileLoader.php', 'Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/YamlFileLoader.php', 'Symfony\\Component\\DependencyInjection\\Parameter' => __DIR__ . '/..' . '/symfony/dependency-injection/Parameter.php', - 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ContainerBag.php', - 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\EnvPlaceholderParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\FrozenParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBagInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php', 'Symfony\\Component\\DependencyInjection\\Reference' => __DIR__ . '/..' . '/symfony/dependency-injection/Reference.php', 'Symfony\\Component\\DependencyInjection\\ResettableContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ResettableContainerInterface.php', - 'Symfony\\Component\\DependencyInjection\\ReverseContainer' => __DIR__ . '/..' . '/symfony/dependency-injection/ReverseContainer.php', 'Symfony\\Component\\DependencyInjection\\ServiceLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/ServiceLocator.php', 'Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ServiceSubscriberInterface.php', 'Symfony\\Component\\DependencyInjection\\TaggedContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/TaggedContainerInterface.php', @@ -2497,7 +2454,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php', 'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/WrappedListener.php', - 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php', 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', 'Symfony\\Component\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/event-dispatcher/Event.php', 'Symfony\\Component\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcher.php', @@ -2505,23 +2461,18 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventSubscriberInterface.php', 'Symfony\\Component\\EventDispatcher\\GenericEvent' => __DIR__ . '/..' . '/symfony/event-dispatcher/GenericEvent.php', 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', - 'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => __DIR__ . '/..' . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', - 'Symfony\\Component\\EventDispatcher\\LegacyEventProxy' => __DIR__ . '/..' . '/symfony/event-dispatcher/LegacyEventProxy.php', 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php', 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php', - 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php', 'Symfony\\Component\\Filesystem\\LockHandler' => __DIR__ . '/..' . '/symfony/filesystem/LockHandler.php', 'Symfony\\Component\\Finder\\Comparator\\Comparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/Comparator.php', 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/DateComparator.php', 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/NumberComparator.php', 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/finder/Exception/AccessDeniedException.php', - 'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => __DIR__ . '/..' . '/symfony/finder/Exception/DirectoryNotFoundException.php', 'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/finder/Exception/ExceptionInterface.php', 'Symfony\\Component\\Finder\\Finder' => __DIR__ . '/..' . '/symfony/finder/Finder.php', - 'Symfony\\Component\\Finder\\Gitignore' => __DIR__ . '/..' . '/symfony/finder/Gitignore.php', 'Symfony\\Component\\Finder\\Glob' => __DIR__ . '/..' . '/symfony/finder/Glob.php', 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/CustomFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DateRangeFilterIterator.php', @@ -2531,7 +2482,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilecontentFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilenameFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilterIterator.php', - 'Symfony\\Component\\Finder\\Iterator\\LazyIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/LazyIterator.php', 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/PathFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', @@ -2549,15 +2499,8 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/ExpressionRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\FileBag' => __DIR__ . '/..' . '/symfony/http-foundation/FileBag.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\CannotWriteFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/CannotWriteFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\ExtensionFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/ExtensionFileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FileNotFoundException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FormSizeFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FormSizeFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\IniSizeFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/IniSizeFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\NoFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/NoFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\NoTmpDirFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/NoTmpDirFileException.php', - 'Symfony\\Component\\HttpFoundation\\File\\Exception\\PartialFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/PartialFileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UnexpectedTypeException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/UnexpectedTypeException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UploadException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/UploadException.php', 'Symfony\\Component\\HttpFoundation\\File\\File' => __DIR__ . '/..' . '/symfony/http-foundation/File/File.php', @@ -2571,7 +2514,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpFoundation\\File\\Stream' => __DIR__ . '/..' . '/symfony/http-foundation/File/Stream.php', 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' => __DIR__ . '/..' . '/symfony/http-foundation/File/UploadedFile.php', 'Symfony\\Component\\HttpFoundation\\HeaderBag' => __DIR__ . '/..' . '/symfony/http-foundation/HeaderBag.php', - 'Symfony\\Component\\HttpFoundation\\HeaderUtils' => __DIR__ . '/..' . '/symfony/http-foundation/HeaderUtils.php', 'Symfony\\Component\\HttpFoundation\\IpUtils' => __DIR__ . '/..' . '/symfony/http-foundation/IpUtils.php', 'Symfony\\Component\\HttpFoundation\\JsonResponse' => __DIR__ . '/..' . '/symfony/http-foundation/JsonResponse.php', 'Symfony\\Component\\HttpFoundation\\ParameterBag' => __DIR__ . '/..' . '/symfony/http-foundation/ParameterBag.php', @@ -2597,14 +2539,11 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\AbstractSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\RedisSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\StrictSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\WriteCheckSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MetadataBag.php', @@ -2617,15 +2556,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', 'Symfony\\Component\\HttpFoundation\\StreamedResponse' => __DIR__ . '/..' . '/symfony/http-foundation/StreamedResponse.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\RequestAttributeValueSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseCookieValueSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasCookie' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasHeader' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHeaderSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsRedirected' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsSuccessful' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php', - 'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseStatusCodeSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php', - 'Symfony\\Component\\HttpFoundation\\UrlHelper' => __DIR__ . '/..' . '/symfony/http-foundation/UrlHelper.php', 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/Bundle.php', 'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/BundleInterface.php', 'Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', @@ -2644,19 +2574,16 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php', - 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\NotTaggedControllerValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php', - 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\TraceableValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ContainerControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ContainerControllerResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerReference' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerReference.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerResolverInterface.php', - 'Symfony\\Component\\HttpKernel\\Controller\\ErrorController' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ErrorController.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableArgumentResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/TraceableArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', @@ -2685,7 +2612,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpKernel\\DependencyInjection\\LoggerPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/LoggerPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\MergeExtensionConfigurationPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterControllerArgumentLocatorsPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php', - 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterLocaleAwareServicesPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RemoveEmptyControllerArgumentLocatorsPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ResettableServicePass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ResettableServicePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ServicesResetter' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ServicesResetter.php', @@ -2693,12 +2619,9 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpKernel\\EventListener\\AbstractTestSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AbstractTestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\DisallowRobotsIndexingListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DumpListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ErrorListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ExceptionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\FragmentListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/FragmentListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleAwareListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/LocaleAwareListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/LocaleListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ProfilerListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ResponseListener.php', @@ -2710,9 +2633,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/TestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\TranslatorListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/TranslatorListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', - 'Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ControllerArgumentsEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\ControllerEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ControllerEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ExceptionEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerArgumentsEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FilterControllerEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FilterResponseEvent.php', @@ -2722,14 +2642,9 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/GetResponseForExceptionEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/KernelEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/PostResponseEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\RequestEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/RequestEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\ResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ResponseEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\TerminateEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/TerminateEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\ViewEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ViewEvent.php', 'Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/AccessDeniedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/BadRequestHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ConflictHttpException.php', - 'Symfony\\Component\\HttpKernel\\Exception\\ControllerDoesNotReturnResponseException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php', 'Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/GoneHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\HttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/HttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', @@ -2762,9 +2677,7 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/StoreInterface.php', 'Symfony\\Component\\HttpKernel\\HttpCache\\SubRequestHandler' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/SubRequestHandler.php', 'Symfony\\Component\\HttpKernel\\HttpCache\\SurrogateInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/SurrogateInterface.php', - 'Symfony\\Component\\HttpKernel\\HttpClientKernel' => __DIR__ . '/..' . '/symfony/http-kernel/HttpClientKernel.php', 'Symfony\\Component\\HttpKernel\\HttpKernel' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernel.php', - 'Symfony\\Component\\HttpKernel\\HttpKernelBrowser' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernelBrowser.php', 'Symfony\\Component\\HttpKernel\\HttpKernelInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernelInterface.php', 'Symfony\\Component\\HttpKernel\\Kernel' => __DIR__ . '/..' . '/symfony/http-kernel/Kernel.php', 'Symfony\\Component\\HttpKernel\\KernelEvents' => __DIR__ . '/..' . '/symfony/http-kernel/KernelEvents.php', @@ -2788,9 +2701,7 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\Routing\\Exception\\NoConfigurationException' => __DIR__ . '/..' . '/symfony/routing/Exception/NoConfigurationException.php', 'Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException' => __DIR__ . '/..' . '/symfony/routing/Exception/ResourceNotFoundException.php', 'Symfony\\Component\\Routing\\Exception\\RouteNotFoundException' => __DIR__ . '/..' . '/symfony/routing/Exception/RouteNotFoundException.php', - 'Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator' => __DIR__ . '/..' . '/symfony/routing/Generator/CompiledUrlGenerator.php', 'Symfony\\Component\\Routing\\Generator\\ConfigurableRequirementsInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/ConfigurableRequirementsInterface.php', - 'Symfony\\Component\\Routing\\Generator\\Dumper\\CompiledUrlGeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/GeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumperInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php', @@ -2806,18 +2717,13 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/RoutingConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\AddTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/AddTrait.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\RouteTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/RouteTrait.php', - 'Symfony\\Component\\Routing\\Loader\\ContainerLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ContainerLoader.php', 'Symfony\\Component\\Routing\\Loader\\DependencyInjection\\ServiceRouterLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php', 'Symfony\\Component\\Routing\\Loader\\DirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/DirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/GlobFileLoader.php', - 'Symfony\\Component\\Routing\\Loader\\ObjectLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ObjectLoader.php', 'Symfony\\Component\\Routing\\Loader\\ObjectRouteLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ObjectRouteLoader.php', 'Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/PhpFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/XmlFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/YamlFileLoader.php', - 'Symfony\\Component\\Routing\\Matcher\\CompiledUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/CompiledUrlMatcher.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherTrait' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperCollection' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/DumperCollection.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperRoute' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/DumperRoute.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/MatcherDumper.php', @@ -2853,22 +2759,13 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DOMCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\DateCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DateCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DoctrineCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\DsCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DsCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DsPairStub.php', 'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/EnumStub.php', 'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ExceptionCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FrameStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/GmpCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\ImagineCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ImagineCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\ImgStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ImgStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\IntlCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/IntlCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\LinkStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/LinkStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\MemcachedCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MemcachedCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\MongoCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MongoCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\MysqliCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MysqliCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PdoCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PgSqlCaster.php', - 'Symfony\\Component\\VarDumper\\Caster\\ProxyManagerCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ProxyManagerCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/RedisCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ReflectionCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ResourceCaster.php', @@ -2876,7 +2773,6 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/StubCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/SymfonyCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/TraceStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/UuidCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlReaderCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlResourceCaster.php', 'Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/AbstractCloner.php', @@ -2886,23 +2782,11 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/DumperInterface.php', 'Symfony\\Component\\VarDumper\\Cloner\\Stub' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Stub.php', 'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/VarCloner.php', - 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php', - 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\DumpDescriptorInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php', - 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\HtmlDescriptor' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php', - 'Symfony\\Component\\VarDumper\\Command\\ServerDumpCommand' => __DIR__ . '/..' . '/symfony/var-dumper/Command/ServerDumpCommand.php', 'Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/AbstractDumper.php', 'Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/CliDumper.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\CliContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\ContextProviderInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\RequestContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\SourceContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ContextualizedDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextualizedDumper.php', 'Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/DataDumperInterface.php', 'Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/HtmlDumper.php', - 'Symfony\\Component\\VarDumper\\Dumper\\ServerDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ServerDumper.php', 'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => __DIR__ . '/..' . '/symfony/var-dumper/Exception/ThrowingCasterException.php', - 'Symfony\\Component\\VarDumper\\Server\\Connection' => __DIR__ . '/..' . '/symfony/var-dumper/Server/Connection.php', - 'Symfony\\Component\\VarDumper\\Server\\DumpServer' => __DIR__ . '/..' . '/symfony/var-dumper/Server/DumpServer.php', 'Symfony\\Component\\VarDumper\\VarDumper' => __DIR__ . '/..' . '/symfony/var-dumper/VarDumper.php', 'Symfony\\Component\\Yaml\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php', diff --git a/lib/composer/installed.php b/lib/composer/installed.php index bf3c57753..9e9ad2068 100644 --- a/lib/composer/installed.php +++ b/lib/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '99cf12097b314f2e30ffb501c442dd08bd3657c5', + 'reference' => 'e72030e52fc219089dcdcae6033b53b843d12a60', 'name' => '__root__', 'dev' => true, ), @@ -16,7 +16,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '99cf12097b314f2e30ffb501c442dd08bd3657c5', + 'reference' => 'e72030e52fc219089dcdcae6033b53b843d12a60', 'dev_requirement' => false, ), 'combodo/tcpdf' => array( @@ -268,8 +268,8 @@ 'psr/container-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0', - 1 => '^1.0', + 0 => '^1.0', + 1 => '1.0', ), ), 'psr/http-message' => array( diff --git a/lib/laminas/laminas-mail/src/Header/HeaderLocator.php b/lib/laminas/laminas-mail/src/Header/HeaderLocator.php new file mode 100644 index 000000000..060466650 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderLocator.php @@ -0,0 +1,75 @@ + Bcc::class, + 'cc' => Cc::class, + 'contentdisposition' => ContentDisposition::class, + 'content_disposition' => ContentDisposition::class, + 'content-disposition' => ContentDisposition::class, + 'contenttype' => ContentType::class, + 'content_type' => ContentType::class, + 'content-type' => ContentType::class, + 'contenttransferencoding' => ContentTransferEncoding::class, + 'content_transfer_encoding' => ContentTransferEncoding::class, + 'content-transfer-encoding' => ContentTransferEncoding::class, + 'date' => Date::class, + 'from' => From::class, + 'in-reply-to' => InReplyTo::class, + 'message-id' => MessageId::class, + 'mimeversion' => MimeVersion::class, + 'mime_version' => MimeVersion::class, + 'mime-version' => MimeVersion::class, + 'received' => Received::class, + 'references' => References::class, + 'replyto' => ReplyTo::class, + 'reply_to' => ReplyTo::class, + 'reply-to' => ReplyTo::class, + 'sender' => Sender::class, + 'subject' => Subject::class, + 'to' => To::class, + ]; + + public function get(string $name, ?string $default = null): ?string + { + $name = $this->normalizeName($name); + return isset($this->plugins[$name]) ? $this->plugins[$name] : $default; + } + + public function has(string $name): bool + { + return isset($this->plugins[$this->normalizeName($name)]); + } + + public function add(string $name, string $class): void + { + $this->plugins[$this->normalizeName($name)] = $class; + } + + public function remove(string $name): void + { + unset($this->plugins[$this->normalizeName($name)]); + } + + private function normalizeName(string $name): string + { + return strtolower($name); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderLocatorInterface.php b/lib/laminas/laminas-mail/src/Header/HeaderLocatorInterface.php new file mode 100644 index 000000000..b7cbb8a1c --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderLocatorInterface.php @@ -0,0 +1,25 @@ + Date: Wed, 8 Jun 2022 18:10:03 +0200 Subject: [PATCH 41/41] Fix syntax error in Darkmoon theme --- .../combodo-backoffice-darkmoon-theme/scss/scss-variables.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datamodels/2.x/combodo-backoffice-darkmoon-theme/scss/scss-variables.scss b/datamodels/2.x/combodo-backoffice-darkmoon-theme/scss/scss-variables.scss index a8b2e550b..d6135be7b 100644 --- a/datamodels/2.x/combodo-backoffice-darkmoon-theme/scss/scss-variables.scss +++ b/datamodels/2.x/combodo-backoffice-darkmoon-theme/scss/scss-variables.scss @@ -202,7 +202,7 @@ $ibo-tab-container--tab-header--text-color: $ibo-color-grey-50; $ibo-vendors-datatables--row-highlight--colors:('red': ($ibo-color-red-700),'danger': ($ibo-color-red-700),'alert': ($ibo-color-red-700),'orange': ($ibo-color-orange-700),'warning': (#d08770),'blue': (#5e81ac),'info': (#5e81ac),); -$ibo-vendors-jqueryui--ui-dialog--background-color: $ibo-color-grey-800;jquer +$ibo-vendors-jqueryui--ui-dialog--background-color: $ibo-color-grey-800; $ibo-vendors-jqueryui--ui-dialog-titlebar--border-bottom: solid 1px $ibo-color-grey-500;