diff --git a/.gitignore b/.gitignore index b77455160..d78eca3d1 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ package-lock.json *.pid *.gz wp-stateless.wiki +.agents/loop-state/ diff --git a/changelog.txt b/changelog.txt index e9c588860..bb81dacae 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,9 @@ == Changelog == -= 4.4.0 = += 4.4.1 - 2026-01-16 = +* COMPATIBILITY - WooCommerce Extra Product Options Compatibility replaced with [WP-Stateless – WooCommerce Extra Product Options Addon](https://wordpress.org/plugins/wp-stateless-woocommerce-extra-product-options-addon/). +* FIX - resolve critical errors with `firebase/php-jwt` library if `AUTH_SALT` WordPress constant is not set or too short. + += 4.4.0 - 2026-01-10 = * NEW - plugin requires PHP 8.1+. * ENHANCEMENT - updated `firebase/php-jwt` library from 6.11.1 to 7.0.2. * ENHANCEMENT - Updated Client library for Google APIs from 2.18.3 to 2.19.0. diff --git a/changes.md b/changes.md index f4b5d9760..bdd35b8a3 100644 --- a/changes.md +++ b/changes.md @@ -1,4 +1,8 @@ -#### 4.4.0 +#### 4.4.1 - 2026-01-16 +* COMPATIBILITY - WooCommerce Extra Product Options Compatibility replaced with [WP-Stateless – WooCommerce Extra Product Options Addon](https://wordpress.org/plugins/wp-stateless-woocommerce-extra-product-options-addon/). +* FIX - resolve critical errors with `firebase/php-jwt` library if `AUTH_SALT` WordPress constant is not set or too short. + +#### 4.4.0 - 2026-01-10 * NEW - plugin requires PHP 8.1+. * ENHANCEMENT - updated `firebase/php-jwt` library from 6.11.1 to 7.0.2. * ENHANCEMENT - Updated Client library for Google APIs from 2.18.3 to 2.19.0. diff --git a/composer.json b/composer.json index 490494d87..a0cf82fad 100644 --- a/composer.json +++ b/composer.json @@ -32,10 +32,14 @@ "php": ">=7.4", "composer/installers": "~2.3", "ccampbell/chromephp": "^4.1", - "firebase/php-jwt": "^6.1.2", + "firebase/php-jwt": "^7.0", "udx/lib-ud-api-client": "^1.2", "udx/lib-wp-bootstrap": "^1.3", - "wpackagist-plugin/meta-box": "^5.10" + "wpackagist-plugin/meta-box": "^5.10", + "automattic/jetpack-autoloader": "^3.0 || ^4.0 || ^5.0", + "psr/log": "^3.0", + "monolog/monolog": "^3.0", + "psr/simple-cache": "^1.0" }, "autoload": { "classmap": [ @@ -55,14 +59,14 @@ ] }, "extra": { - "mozart": { - "dep_namespace": "wpCloud\\StatelessMedia\\", - "dep_directory": "/lib/ns-vendor/deps", - "classmap_directory": "/lib/ns-vendor/classes", + "strauss": { + "target_directory": "lib/ns-vendor", + "namespace_prefix": "wpCloud\\StatelessMedia\\", "classmap_prefix": "UDX_", "packages": [ "deliciousbrains/wp-background-processing" - ] + ], + "delete_vendor_files": false }, "featureFlags": [ { @@ -101,12 +105,14 @@ } }, "require-dev": { - "coenjacobs/mozart": "0.7.1", + "brianhenryie/strauss": "^0.19", "deliciousbrains/wp-background-processing": "1.3.1" }, "config": { "allow-plugins": { - "composer/installers": true + "composer/installers": true, + "automattic/jetpack-autoloader": true, + "brianhenryie/strauss": true } } } diff --git a/composer.lock b/composer.lock index 7e3a1cd83..56ba01e53 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,73 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "efe78ab64a63ac2a59fdd7be8e49f336", + "content-hash": "ee95a957c24f06d08fb3b3fd562627fb", "packages": [ + { + "name": "automattic/jetpack-autoloader", + "version": "v5.0.16", + "source": { + "type": "git", + "url": "https://github.com/Automattic/jetpack-autoloader.git", + "reference": "d8ae822a35e7431137e860ee60eceedaa745e4d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/d8ae822a35e7431137e860ee60eceedaa745e4d1", + "reference": "d8ae822a35e7431137e860ee60eceedaa745e4d1", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.2", + "php": ">=7.2" + }, + "require-dev": { + "automattic/jetpack-changelogger": "^6.0.14", + "automattic/phpunit-select-config": "^1.0.3", + "composer/composer": "^2.2", + "yoast/phpunit-polyfills": "^4.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin", + "autotagger": true, + "mirror-repo": "Automattic/jetpack-autoloader", + "branch-alias": { + "dev-trunk": "5.0.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}" + }, + "version-constants": { + "::VERSION": "src/AutoloadGenerator.php" + } + }, + "autoload": { + "psr-4": { + "Automattic\\Jetpack\\Autoloader\\": "src" + }, + "classmap": [ + "src/AutoloadGenerator.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Creates a custom autoloader for a plugin or theme.", + "keywords": [ + "autoload", + "autoloader", + "composer", + "jetpack", + "plugin", + "wordpress" + ], + "support": { + "source": "https://github.com/Automattic/jetpack-autoloader/tree/v5.0.16" + }, + "time": "2026-02-16T10:33:15+00:00" + }, { "name": "ccampbell/chromephp", "version": "4.1.0", @@ -201,16 +266,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.11.1", + "version": "v7.0.5", "source": { "type": "git", - "url": "https://github.com/firebase/php-jwt.git", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", "shasum": "" }, "require": { @@ -218,6 +283,7 @@ }, "require-dev": { "guzzlehttp/guzzle": "^7.4", + "phpfastcache/phpfastcache": "^9.2", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", "psr/cache": "^2.0||^3.0", @@ -257,10 +323,214 @@ "php" ], "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5" + }, + "time": "2026-04-01T20:38:03+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-02T08:56:05+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" }, - "time": "2025-04-09T20:32:01+00:00" + "time": "2017-10-23T01:57:42+00:00" }, { "name": "udx/lib-ud-api-client", @@ -307,15 +577,15 @@ }, { "name": "udx/lib-wp-bootstrap", - "version": "1.3.4", + "version": "1.3.3", "source": { "type": "git", "url": "git@github.com:udx/lib-wp-bootstrap", - "reference": "1.3.4" + "reference": "1.3.3" }, "dist": { "type": "zip", - "url": "https://github.com/udx/lib-wp-bootstrap/archive/1.3.4.zip" + "url": "https://github.com/udx/lib-wp-bootstrap/archive/1.3.3.zip" }, "require": { "php": ">=5.3" @@ -340,15 +610,15 @@ }, { "name": "wpackagist-plugin/meta-box", - "version": "5.10.19", + "version": "5.11.4", "source": { "type": "svn", "url": "https://plugins.svn.wordpress.org/meta-box/", - "reference": "tags/5.10.19" + "reference": "tags/5.11.4" }, "dist": { "type": "zip", - "url": "https://downloads.wordpress.org/plugin/meta-box.5.10.19.zip" + "url": "https://downloads.wordpress.org/plugin/meta-box.5.11.4.zip" }, "require": { "composer/installers": "^1.0 || ^2.0" @@ -359,38 +629,48 @@ ], "packages-dev": [ { - "name": "coenjacobs/mozart", - "version": "0.7.1", + "name": "brianhenryie/strauss", + "version": "0.19.5", "source": { "type": "git", - "url": "https://github.com/coenjacobs/mozart.git", - "reference": "dbcdeb992d20d9c8914eef090f9a0d684bb1102c" + "url": "https://github.com/BrianHenryIE/strauss.git", + "reference": "25c58001a786d4357a65aa9b045a8ca79b60bf06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/coenjacobs/mozart/zipball/dbcdeb992d20d9c8914eef090f9a0d684bb1102c", - "reference": "dbcdeb992d20d9c8914eef090f9a0d684bb1102c", + "url": "https://api.github.com/repos/BrianHenryIE/strauss/zipball/25c58001a786d4357a65aa9b045a8ca79b60bf06", + "reference": "25c58001a786d4357a65aa9b045a8ca79b60bf06", "shasum": "" }, "require": { - "league/flysystem": "^1.0", - "php": "^7.3|^8.0", - "symfony/console": "^4|^5", - "symfony/finder": "^4|^5" + "composer/composer": "*", + "json-mapper/json-mapper": "^2.2", + "league/flysystem": "^2.1|^3.0", + "symfony/console": "^4|^5|^6|^7", + "symfony/finder": "^4|^5|^6|^7" + }, + "replace": { + "coenjacobs/mozart": "*" }, "require-dev": { + "brianhenryie/php-diff-test": "dev-master", + "clue/phar-composer": "^1.2", + "ext-json": "*", + "jaschilz/php-coverage-badger": "^2.0", "mheap/phpunit-github-actions-printer": "^1.4", - "phpunit/phpunit": "^8.5", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.4" + "mockery/mockery": "^1.6", + "php": "^7.4|^8.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9|^10", + "squizlabs/php_codesniffer": "^3.5" }, "bin": [ - "bin/mozart" + "bin/strauss" ], "type": "library", "autoload": { "psr-4": { - "CoenJacobs\\Mozart\\": "src/" + "BrianHenryIE\\Strauss\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -398,6 +678,10 @@ "MIT" ], "authors": [ + { + "name": "Brian Henry", + "email": "BrianHenryIE@gmail.com" + }, { "name": "Coen Jacobs", "email": "coenjacobs@gmail.com" @@ -405,117 +689,119 @@ ], "description": "Composes all dependencies as a package inside a WordPress plugin", "support": { - "issues": "https://github.com/coenjacobs/mozart/issues", - "source": "https://github.com/coenjacobs/mozart/tree/0.7.1" + "issues": "https://github.com/BrianHenryIE/strauss/issues", + "source": "https://github.com/BrianHenryIE/strauss/tree/0.19.5" }, - "funding": [ - { - "url": "https://github.com/coenjacobs", - "type": "github" - } - ], - "time": "2021-02-02T21:37:03+00:00" + "time": "2024-10-25T03:22:39+00:00" }, { - "name": "deliciousbrains/wp-background-processing", - "version": "1.3.1", + "name": "composer/ca-bundle", + "version": "1.5.11", "source": { "type": "git", - "url": "https://github.com/deliciousbrains/wp-background-processing.git", - "reference": "6d1e48165e461260075b9f161b3861c7278f71e7" + "url": "https://github.com/composer/ca-bundle.git", + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/deliciousbrains/wp-background-processing/zipball/6d1e48165e461260075b9f161b3861c7278f71e7", - "reference": "6d1e48165e461260075b9f161b3861c7278f71e7", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/68ff39175e8e94a4bb1d259407ce51a6a60f09e6", + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6", "shasum": "" }, "require": { - "php": ">=7.0" + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpcompatibility/phpcompatibility-wp": "*", - "phpunit/phpunit": "^8.0", - "spryker/code-sniffer": "^0.17.18", - "wp-coding-standards/wpcs": "^2.3", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "coenjacobs/mozart": "Easily wrap this library with your own prefix, to prevent collisions when multiple plugins use this library" + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, "autoload": { - "classmap": [ - "classes/" - ] + "psr-4": { + "Composer\\CaBundle\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "GPL-2.0-or-later" + "MIT" ], "authors": [ { - "name": "Delicious Brains", - "email": "nom@deliciousbrains.com" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "WP Background Processing can be used to fire off non-blocking asynchronous requests or as a background processing tool, allowing you to queue tasks.", + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], "support": { - "issues": "https://github.com/deliciousbrains/wp-background-processing/issues", - "source": "https://github.com/deliciousbrains/wp-background-processing/tree/1.3.1" + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.11" }, - "time": "2024-02-28T13:39:06+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-03-30T09:16:10+00:00" }, { - "name": "league/flysystem", - "version": "1.1.10", + "name": "composer/class-map-generator", + "version": "1.7.2", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1" + "url": "https://github.com/composer/class-map-generator.git", + "reference": "6a9c2f0970022ab00dc58c07d0685dd712f2231b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1", - "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/6a9c2f0970022ab00dc58c07d0685dd712f2231b", + "reference": "6a9c2f0970022ab00dc58c07d0685dd712f2231b", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "league/mime-type-detection": "^1.3", - "php": "^7.2.5 || ^8.0" - }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" }, "require-dev": { - "phpspec/prophecy": "^1.11.1", - "phpunit/phpunit": "^8.5.8" - }, - "suggest": { - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { - "League\\Flysystem\\": "src/" + "Composer\\ClassMapGenerator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -524,69 +810,101 @@ ], "authors": [ { - "name": "Frank de Jonge", - "email": "info@frenky.net" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" } ], - "description": "Filesystem abstraction: Many filesystems, one API.", + "description": "Utilities to scan PHP code and generate class maps.", "keywords": [ - "Cloud Files", - "WebDAV", - "abstraction", - "aws", - "cloud", - "copy.com", - "dropbox", - "file systems", - "files", - "filesystem", - "filesystems", - "ftp", - "rackspace", - "remote", - "s3", - "sftp", - "storage" + "classmap" ], "support": { - "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/1.1.10" + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.7.2" }, "funding": [ { - "url": "https://offset.earth/frankdejonge", - "type": "other" + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" } ], - "time": "2022-10-04T09:16:37+00:00" + "time": "2026-03-30T15:36:56+00:00" }, { - "name": "league/mime-type-detection", - "version": "1.16.0", + "name": "composer/composer", + "version": "2.9.5", "source": { "type": "git", - "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + "url": "https://github.com/composer/composer.git", + "reference": "72a8f8e653710e18d83e5dd531eb5a71fc3223e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "url": "https://api.github.com/repos/composer/composer/zipball/72a8f8e653710e18d83e5dd531eb5a71fc3223e6", + "reference": "72a8f8e653710e18d83e5dd531eb5a71fc3223e6", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "php": "^7.4 || ^8.0" + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^2.3 || ^3.3", + "composer/semver": "^3.3", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "ext-json": "*", + "justinrainbow/json-schema": "^6.5.1", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "react/promise": "^3.3", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "seld/signal-handler": "^2.0", + "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "symfony/polyfill-php84": "^1.30", + "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0", + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" + }, + "suggest": { + "ext-curl": "Provides HTTP support (will fallback to PHP streams if missing)", + "ext-openssl": "Enables access to repositories and packages over HTTPS", + "ext-zip": "Allows direct extraction of ZIP archives (unzip/7z binaries will be used instead if available)", + "ext-zlib": "Enables gzip for HTTP requests" }, + "bin": [ + "bin/composer" + ], "type": "library", + "extra": { + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] + }, + "branch-alias": { + "dev-main": "2.9-dev" + } + }, "autoload": { "psr-4": { - "League\\MimeTypeDetection\\": "src" + "Composer\\": "src/Composer/" } }, "notification-url": "https://packagist.org/downloads/", @@ -595,54 +913,1978 @@ ], "authors": [ { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" } ], - "description": "Mime-type detection for Flysystem", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "security": "https://github.com/composer/composer/security/policy", + "source": "https://github.com/composer/composer/tree/2.9.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-01-29T10:40:53+00:00" + }, + { + "name": "composer/metadata-minifier", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-04-07T13:37:33+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/5ecd0cb4177696f9fd48f1605dda81db3dee7889", + "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.6.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-04-08T20:18:39+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "deliciousbrains/wp-background-processing", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/deliciousbrains/wp-background-processing.git", + "reference": "6d1e48165e461260075b9f161b3861c7278f71e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/deliciousbrains/wp-background-processing/zipball/6d1e48165e461260075b9f161b3861c7278f71e7", + "reference": "6d1e48165e461260075b9f161b3861c7278f71e7", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpcompatibility/phpcompatibility-wp": "*", + "phpunit/phpunit": "^8.0", + "spryker/code-sniffer": "^0.17.18", + "wp-coding-standards/wpcs": "^2.3", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "coenjacobs/mozart": "Easily wrap this library with your own prefix, to prevent collisions when multiple plugins use this library" + }, + "type": "library", + "autoload": { + "classmap": [ + "classes/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Delicious Brains", + "email": "nom@deliciousbrains.com" + } + ], + "description": "WP Background Processing can be used to fire off non-blocking asynchronous requests or as a background processing tool, allowing you to queue tasks.", + "support": { + "issues": "https://github.com/deliciousbrains/wp-background-processing/issues", + "source": "https://github.com/deliciousbrains/wp-background-processing/tree/1.3.1" + }, + "time": "2024-02-28T13:39:06+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, + { + "name": "json-mapper/json-mapper", + "version": "2.25.1", + "source": { + "type": "git", + "url": "https://github.com/JsonMapper/JsonMapper.git", + "reference": "4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JsonMapper/JsonMapper/zipball/4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61", + "reference": "4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61", + "shasum": "" + }, + "require": { + "ext-json": "*", + "myclabs/php-enum": "^1.7", + "nikic/php-parser": "^4.13 || ^5.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-docblock": "^5.6", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "psr/simple-cache": " ^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/polyfill-php73": "^1.18" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5 || ^7.0", + "php-coveralls/php-coveralls": "^2.4", + "phpstan/phpstan": "^0.12.14", + "phpstan/phpstan-phpunit": "^0.12.17", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0", + "vimeo/psalm": "^4.10 || ^5.0" + }, + "suggest": { + "json-mapper/laravel-package": "Use JsonMapper directly with Laravel", + "json-mapper/symfony-bundle": "Use JsonMapper directly with Symfony" + }, + "type": "library", + "autoload": { + "psr-4": { + "JsonMapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Map JSON structures to PHP classes", + "homepage": "https://jsonmapper.net", + "keywords": [ + "json", + "jsonmapper", + "mapper", + "middleware" + ], + "support": { + "docs": "https://jsonmapper.net", + "issues": "https://github.com/JsonMapper/JsonMapper/issues", + "source": "https://github.com/JsonMapper/JsonMapper" + }, + "funding": [ + { + "url": "https://github.com/DannyvdSluijs", + "type": "github" + } + ], + "time": "2025-05-26T09:51:24+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.8.0", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "89ac92bcfe5d0a8a4433c7b89d394553ae7250cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/89ac92bcfe5d0a8a4433c7b89d394553ae7250cc", + "reference": "89ac92bcfe5d0a8a4433c7b89d394553ae7250cc", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "^23.2", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.0" + }, + "time": "2026-04-02T12:43:11+00:00" + }, + { + "name": "league/flysystem", + "version": "3.33.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "570b8871e0ce693764434b29154c54b434905350" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350", + "reference": "570b8871e0ce693764434b29154c54b434905350", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.33.0" + }, + "time": "2026-03-25T07:59:30+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.31.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + }, + "time": "2026-01-23T15:30:45+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.2", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "time": "2025-09-14T11:18:39+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.5", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "https://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2025-01-14T11:49:03+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.7", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.7" + }, + "time": "2026-03-18T20:47:46+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + }, + "time": "2025-11-21T15:09:14+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-07-11T14:55:45+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "time": "2023-09-03T09:24:00+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "467464da294734b0fb17e853e5712abc8470f819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/467464da294734b0fb17e853e5712abc8470f819", + "reference": "467464da294734b0fb17e853e5712abc8470f819", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "ext-redis": "<6.1", + "ext-relay": "<0.12.1", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:15:47+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-13T15:25:07+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", + "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T13:54:39+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { - "url": "https://github.com/frankdejonge", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-21T08:32:55+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "psr/container", - "version": "2.0.2", + "name": "symfony/filesystem", + "version": "v8.0.8", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/symfony/filesystem.git", + "reference": "66b769ae743ce2d13e435528fbef4af03d623e5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/66b769ae743ce2d13e435528fbef4af03d623e5a", + "reference": "66b769ae743ce2d13e435528fbef4af03d623e5a", "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } + "require-dev": { + "symfony/process": "^7.4|^8.0" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Container\\": "src/" - } + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -650,78 +2892,63 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "source": "https://github.com/symfony/filesystem/tree/v8.0.8" }, - "time": "2021-11-05T16:47:00+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" }, { - "name": "symfony/console", - "version": "v5.4.47", + "name": "symfony/finder", + "version": "v7.4.8", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" + "url": "https://github.com/symfony/finder.git", + "reference": "e0be088d22278583a82da281886e8c3592fbf149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "url": "https://api.github.com/repos/symfony/finder/zipball/e0be088d22278583a82da281886e8c3592fbf149", + "reference": "e0be088d22278583a82da281886e8c3592fbf149", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" + "php": ">=8.2" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" + "Symfony\\Component\\Finder\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -741,16 +2968,93 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-24T13:12:05+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.34.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ - "cli", - "command-line", - "console", - "terminal" + "compatibility", + "ctype", + "polyfill", + "portable" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.47" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.34.0" }, "funding": [ { @@ -761,44 +3065,51 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-06T11:30:55+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.0.2", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.34.0", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df", + "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.0-dev" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { "files": [ - "function.php" - ] + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -814,10 +3125,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.34.0" }, "funding": [ { @@ -828,39 +3147,53 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/finder", - "version": "v5.4.45", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.34.0", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "63741784cd7b9967975eec610b256eed3ede022b" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", - "reference": "63741784cd7b9967975eec610b256eed3ede022b", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Finder\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -869,18 +3202,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.45" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.34.0" }, "funding": [ { @@ -891,35 +3232,40 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-28T13:32:08+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.33.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.34.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { - "ext-ctype": "*" + "ext-mbstring": "*" }, "suggest": { - "ext-ctype": "For best performance" + "ext-mbstring": "For best performance" }, "type": "library", "extra": { @@ -933,7 +3279,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" + "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -942,24 +3288,25 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "ctype", + "mbstring", "polyfill", - "portable" + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.34.0" }, "funding": [ { @@ -979,28 +3326,25 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T17:25:58+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "name": "symfony/polyfill-php73", + "version": "v1.34.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { "php": ">=7.2" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -1013,8 +3357,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1030,18 +3377,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "grapheme", - "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.34.0" }, "funding": [ { @@ -1061,28 +3406,25 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "name": "symfony/polyfill-php80", + "version": "v1.34.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "shasum": "" }, "require": { "php": ">=7.2" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -1095,7 +3437,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Polyfill\\Php80\\": "" }, "classmap": [ "Resources/stubs" @@ -1106,6 +3448,10 @@ "MIT" ], "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -1115,18 +3461,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "intl", - "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.34.0" }, "funding": [ { @@ -1146,32 +3490,25 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "name": "symfony/polyfill-php81", + "version": "v1.34.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "ext-iconv": "*", "php": ">=7.2" }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -1184,8 +3521,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1201,17 +3541,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.34.0" }, "funding": [ { @@ -1231,20 +3570,20 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.33.0", + "name": "symfony/polyfill-php84", + "version": "v1.34.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", "shasum": "" }, "require": { @@ -1262,7 +3601,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" + "Symfony\\Polyfill\\Php84\\": "" }, "classmap": [ "Resources/stubs" @@ -1282,7 +3621,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -1291,7 +3630,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.34.0" }, "funding": [ { @@ -1311,41 +3650,32 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T18:47:49+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.33.0", + "name": "symfony/process", + "version": "v8.0.8", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + "url": "https://github.com/symfony/process.git", + "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", + "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.4" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Component\\Process\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1354,28 +3684,18 @@ ], "authors": [ { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + "source": "https://github.com/symfony/process/tree/v8.0.8" }, "funding": [ { @@ -1395,32 +3715,30 @@ "type": "tidelift" } ], - "time": "2025-01-02T08:10:11+00:00" + "time": "2026-03-30T15:14:47+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.0.2", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d78d39c1599bd1188b8e26bb341da52c3c6d8a66", - "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "php": ">=8.0.2", - "psr/container": "^2.0" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "thanks": { @@ -1428,13 +3746,16 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1461,7 +3782,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -1472,42 +3793,47 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-30T19:17:58+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v6.0.19", + "version": "v8.0.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d9e72497367c23e08bf94176d2be45b00a9d232a" + "reference": "ae9488f874d7603f9d2dfbf120203882b645d963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d9e72497367c23e08bf94176d2be45b00a9d232a", - "reference": "d9e72497367c23e08bf94176d2be45b00a9d232a", + "url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963", + "reference": "ae9488f874d7603f9d2dfbf120203882b645d963", "shasum": "" }, "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { - "symfony/translation-contracts": "<2.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1546,7 +3872,87 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.0.19" + "source": "https://github.com/symfony/string/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/15776bb07a91b089037da89f8832fa41d5fa6ec6", + "reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v8.0.8" }, "funding": [ { @@ -1557,12 +3963,78 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-01-01T08:36:10+00:00" + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.3.0" + }, + "time": "2026-04-11T10:33:05+00:00" } ], "aliases": [], diff --git a/lib/classes/class-module.php b/lib/classes/class-module.php index 1078dd9d8..479db761d 100644 --- a/lib/classes/class-module.php +++ b/lib/classes/class-module.php @@ -50,11 +50,6 @@ public function __construct() { */ new TheEventsCalendar(); - /** - * Support for WooCommerce Extra Product Options - */ - new CompatibilityWooExtraProductOptions(); - /** * Support for WPBakery Page Builder */ diff --git a/lib/classes/class-utility.php b/lib/classes/class-utility.php index d4f0d23e3..94725bbb6 100644 --- a/lib/classes/class-utility.php +++ b/lib/classes/class-utility.php @@ -712,8 +712,54 @@ public static function sync_get_attachment_if_exist($url, $save_to) { } /** - * Generate JWT token signed by current site AUTH_SALT - * If no AUTH_SALT defined - admin email used + * Get a secure JWT signing key + * Priority: AUTH_SALT (if valid length) > Plugin-specific stored key > Generated key + * + * @return string A key suitable for HS256 (minimum 32 bytes) + */ + public static function get_jwt_signing_key() { + // Minimum key length for HS256 (256 bits = 32 bytes) + $min_key_length = 32; + + // Try AUTH_SALT first if it's long enough + if (defined('AUTH_SALT') && !empty(AUTH_SALT) && strlen(AUTH_SALT) >= $min_key_length) { + return AUTH_SALT; + } + + // Try to get stored plugin-specific key + $stored_key = get_option('wp_stateless_jwt_key'); + + if ($stored_key && strlen($stored_key) >= $min_key_length) { + return $stored_key; + } + + // Generate a new secure key + $new_key = self::generate_secure_key($min_key_length); + update_option('wp_stateless_jwt_key', $new_key, false); + + return $new_key; + } + + /** + * Generate a cryptographically secure random key + * + * @param int $length Key length in bytes + * @return string Base64-encoded key + */ + private static function generate_secure_key($length = 32) { + try { + // Use random_bytes for PHP 7+ + $random_bytes = random_bytes($length); + return base64_encode($random_bytes); + } catch (\Exception $e) { + // Fallback: use wp_generate_password + return wp_generate_password($length * 2, true, true); + } + } + + /** + * Generate JWT token signed by secure key + * Uses AUTH_SALT if valid, otherwise uses plugin-specific stored key * * @param $payload * @param int $ttl @@ -727,13 +773,13 @@ public static function generate_jwt_token($payload, $ttl = 3600) { 'exp' => $now + $ttl ]); - $key = defined('AUTH_SALT') && !empty(AUTH_SALT) ? AUTH_SALT : get_option('admin_email'); + $key = self::get_jwt_signing_key(); return JWT::encode($payload, $key, 'HS256'); } /** * Verify and decode token - * If no AUTH_SALT defined - admin email used + * Uses the same secure key retrieval as generation * Throws exceptions if cannot decode * * @param $token @@ -741,7 +787,7 @@ public static function generate_jwt_token($payload, $ttl = 3600) { * @throws \Exception */ public static function verify_jwt_token($token) { - $key = defined('AUTH_SALT') ? AUTH_SALT : get_option('admin_email'); + $key = self::get_jwt_signing_key(); return JWT::decode($token, new Key($key, 'HS256')); } diff --git a/lib/classes/compatibility/woo-extra-product-options.php b/lib/classes/compatibility/woo-extra-product-options.php deleted file mode 100644 index d489be0f7..000000000 --- a/lib/classes/compatibility/woo-extra-product-options.php +++ /dev/null @@ -1,82 +0,0 @@ -get_client(); - - $file_path = apply_filters('wp_stateless_file_name', $file, 0); - $file_info = @getimagesize($file); - - if ($file_info) { - $_metadata = array( - 'width' => $file_info[0], - 'height' => $file_info[1], - 'object-id' => 'unknown', // we really don't know it - 'source-id' => md5($file . ud_get_stateless_media()->get('sm.bucket')), - 'file-hash' => md5($file) - ); - } - - $media = $client->add_media(apply_filters('sm:item:on_fly:before_add', array( - 'use_root' => false, - 'name' => $file_path, - 'absolutePath' => wp_normalize_path($file), - 'cacheControl' => apply_filters('sm:item:cacheControl', 'public, max-age=36000, must-revalidate', $_metadata), - 'contentDisposition' => null, - 'mimeType' => $type, - 'metadata' => $_metadata - ))); - - $upload['url'] = ud_get_stateless_media()->get_gs_host() . '/' . $file_path; - return $upload; - } - } - } -} diff --git a/readme.txt b/readme.txt index dec5901f2..a22bb6a32 100644 --- a/readme.txt +++ b/readme.txt @@ -6,7 +6,7 @@ License: GPLv2 or later Requires PHP: 8.1 Requires at least: 5.0 Tested up to: 6.9 -Stable tag: 4.4.0 +Stable tag: 4.4.1 Upload and serve your WordPress media files from Google Cloud Storage. @@ -136,7 +136,11 @@ Before upgrading to WP-Stateless 3.2.0, please, make sure you use PHP 7.2 or abo Before upgrading to WP-Stateless 3.0, please, make sure you tested it on your development environment. == Changelog == -= 4.4.0 = += 4.4.1 - 2026-01-16 = +* COMPATIBILITY - WooCommerce Extra Product Options Compatibility replaced with [WP-Stateless – WooCommerce Extra Product Options Addon](https://wordpress.org/plugins/wp-stateless-woocommerce-extra-product-options-addon/). +* FIX - resolve critical errors with `firebase/php-jwt` library if `AUTH_SALT` WordPress constant is not set or too short. + += 4.4.0 - 2026-01-10 = * NEW - plugin requires PHP 8.1+. * ENHANCEMENT - updated `firebase/php-jwt` library from 6.11.1 to 7.0.2. * ENHANCEMENT - Updated Client library for Google APIs from 2.18.3 to 2.19.0. diff --git a/static/data/addons.php b/static/data/addons.php index f81f1f7e1..a518aa585 100644 --- a/static/data/addons.php +++ b/static/data/addons.php @@ -167,4 +167,14 @@ 'hubspot_id' => '151480507697', ], + 'woo-extra-product-options' => [ + 'title' => 'WooCommerce Extra Product Options Addon', + 'plugin_files' => ['woocommerce-tm-extra-product-options/tm-woo-extra-product-options.php'], + 'addon_file' => 'wp-stateless-woocommerce-extra-product-options-addon/wp-stateless-woo-extra-product-options-addon.php.php', + 'icon' => 'https://ps.w.org/woocommerce/assets/icon.svg', + 'repo' => 'udx/wp-stateless-woo-extra-product-options-addon', + 'wp' => 'https://wordpress.org/plugins/wp-stateless-woocommerce-extra-product-options-addon/', + 'hubspot_id' => '151478251017', + ], + ]; diff --git a/vendor/autoload_packages.php b/vendor/autoload_packages.php new file mode 100644 index 000000000..1a5ad1107 --- /dev/null +++ b/vendor/autoload_packages.php @@ -0,0 +1,13 @@ += 7.0. [#34192] + +## [2.12.0] - 2023-09-28 +### Added +- Add an `AutoloadGenerator::VERSION` constant, and use that for the autoloader's version in preference to whatever Composer has. [#33156] + +## [2.11.23] - 2023-09-19 + +- Minor internal updates. + +## [2.11.22] - 2023-08-23 +### Changed +- Updated package dependencies. [#32605] + +## [2.11.21] - 2023-05-22 +### Added +- Set keywords in `composer.json`. [#30756] + +## [2.11.20] - 2023-05-11 + +- Updated package dependencies + +## [2.11.19] - 2023-04-25 +### Fixed +- Fix example in README [#30225] + +## [2.11.18] - 2023-03-28 +### Changed +- Minor internal updates. + +## [2.11.17] - 2023-03-27 +### Fixed +- Don't error when processing packages specifying missing PSR paths. [#29669] + +## [2.11.16] - 2023-02-20 +### Changed +- Minor internal updates. + +## [2.11.15] - 2023-01-11 +### Changed +- Updated package dependencies. + +## [2.11.14] - 2022-12-19 +### Changed +- Use `Composer\ClassMapGenerator\ClassMapGenerator` when available (i.e. with composer 2.4). [#27812] + +### Fixed +- Declare fields for PHP 8.2 compatibility. [#27949] + +## [2.11.13] - 2022-12-02 +### Changed +- Updated package dependencies. [#27688] + +## [2.11.12] - 2022-11-22 +### Changed +- Updated package dependencies. [#27043] + +## [2.11.11] - 2022-10-25 +### Changed +- Sort data in generated `vendor/composer/jetpack_autoload_classmap.php` to avoid spurious diffs. [#26929] + +## [2.11.10] - 2022-10-05 + +- Tests: Clear `COMPOSER_AUTH` environment variable when running Composer for tests. [#26404] + +## [2.11.9] - 2022-09-27 +### Fixed +- Tests: Clear `COMPOSER_AUTH` environment variable when running Composer for tests. [#26404] + +## [2.11.8] - 2022-09-20 +### Fixed +- Tests: skip test if it requires a version of Composer not compatible with the running version of PHP. [#26143] + +## [2.11.7] - 2022-07-26 +### Changed +- Updated package dependencies. [#25158] + +## [2.11.6] - 2022-06-21 +### Changed +- Renaming `master` to `trunk`. + +## [2.11.5] - 2022-05-18 +### Fixed +- Fix new PHPCS sniffs. [#24366] + +## [2.11.4] - 2022-04-26 +### Changed +- Updated package dependencies. + +## [2.11.3] - 2022-04-19 +### Changed +- PHPCS: Fix `WordPress.Security.ValidatedSanitizedInput` + +## [2.11.2] - 2022-03-29 +### Changed +- Microperformance: Use === null instead of is_null + +## [2.11.1] - 2022-03-08 +### Removed +- Removed the Upgrade Handler. + +## [2.11.0] - 2022-03-08 +### Added +- On plugin update, pre-load all (non-PSR-4) classes from the plugin to avoid mid-upgrade fatals. + +## [2.10.13] - 2022-03-01 +### Fixed +- Fix tests for upstream phpunit change. + +## [2.10.12] - 2022-01-25 +### Changed +- Updated package dependencies. + +## [2.10.11] - 2022-01-04 +### Changed +- Switch to pcov for code coverage. +- Updated package dependencies + +## [2.10.10] - 2021-11-16 +### Added +- Soft return if autoloader chain is not available. + +## [2.10.9] - 2021-11-02 +### Changed +- Set `convertDeprecationsToExceptions` true in PHPUnit config. + +## [2.10.8] - 2021-10-13 +### Changed +- Updated package dependencies. + +## [2.10.7] - 2021-10-07 +### Changed +- Updated package dependencies + +## [2.10.6] - 2021-09-28 +### Changed +- Updated package dependencies. + +## [2.10.5] - 2021-08-31 +### Changed +- Run composer update on test-php command instead of phpunit +- Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills). + +## [2.10.4] - 2021-08-10 +### Changed +- Updated package dependencies. + +## [2.10.3] - 2021-05-25 +### Changed +- Updated package dependencies. + +## [2.10.2] - 2021-04-27 +### Changed +- Updated package dependencies. + +## [2.10.1] - 2021-03-30 +### Added +- Composer alias for dev-master, to improve dependencies +- Tests: Added code coverage transformation + +### Changed +- Update package dependencies. + +### Fixed +- Fix coverage test +- Fix uninstallation fatal +- Update tests for changed composer 2.0.9 hash. +- Use `composer update` rather than `install` in scripts, as composer.lock isn't checked in. + +## [2.10.0] - 2021-02-09 + +- Autoloader: test suite refactor + +## [2.9.1] - 2021-02-05 + +- CI: Make tests more generic +- Autoloader: stricter type-checking on WP functions +- Autoloader: prevent transitive plugin execution + +## [2.9.0] - 2021-01-25 + +- Autoloader: revised latest autoloader inclusion semantics +- Add mirror-repo information to all current composer packages +- Monorepo: Reorganize all projects +- Autoloader: Don't cache deactivating plugins + +## [2.8.0] - 2020-12-18 + +## [2.7.1] - 2020-12-18 + +- Autoloader: Added realpath resolution to plugin paths + +## [2.7.0] - 2020-12-08 + +- Autoloader: Preemptively load unknown plugins from cache +- Removed unwanted dot +- Pin dependencies +- Packages: Update for PHP 8 testing + +## [2.6.0] - 2020-11-19 + +- Autoloader: AutoloadGenerator no longer extends Composer's AutoloadGenerator class +- Autoloader: Reuse an existing autoloader suffix if available +- Updated PHPCS: Packages and Debugger + +## [2.5.0] - 2020-10-08 + +- Autoloader: remove the defined('JETPACK_AUTOLOAD_DEV') checks from the tests + +## [2.4.0] - 2020-09-28 + +- Autoloader: remove the plugins_loaded bullet point from the README +- Packages: avoid PHPCS warnings +- Autoloader: add PSR-0 support +- Autoloader: Detect filtering of active_plugins +- Autoloader: Support unoptimized PSR-4 + +## [2.3.0] - 2020-08-21 + +- Autoloader: remove the plugin update hook + +## [2.2.0] - 2020-08-14 + +- Autoloader: don't reset the autoloader version during plugin update +- CI: Try collect js coverage + +## [2.1.0] - 2020-07-27 + +- Autoloader: convert '\' directory separators to '/' in plugin paths +- Autoloader: Avoid a PHP warning when an empty string is passed to `is_directory_plugin()`. +- Autoloader: Tests: Use a string with define + +## [2.0.2] - 2020-07-09 + +- Autoloader: Avoid a PHP warning when an empty string is passed to `is_directory_plugin()`. + +## [2.0.1] - 2020-07-02 + +- Autoloader: Tests: Use a string with define + +## [2.0.0] - 2020-06-29 + +## [2.0.0-beta] - 2020-06-29 + +- Autoloader: Support Composer v2.0 +- Autoloader: use paths to identify plugins instead of the directories +- Autoloader: fix the fatal that occurs during plugin update +- Autoloader: add fallback check for plugin path in mu-plugins +- Autoloader: use JETPACK__PLUGIN_DIR when looking for the jetpack plugin directory. +- Feature Branch: Update the Autoloader +- PHPCS: Clean up the packages +- PHPCS Updates after WPCS 2.3 + +## [1.7.0] - 2020-04-23 + +- Jetpack: Move comment notification override back to the constructor + +## [1.6.0] - 2020-03-26 + +- Autoloader: Remove file check to improve performance. + +## [1.5.0] - 2020-02-25 + +- Jetpack: instantiate manager object if it's null + +## [1.4.1] - 2020-02-14 + +- Autoloader: Load only latest version of autoload files to avoid conflicts. + +## [1.4.0] - 2020-01-23 + +- Autoloader: Remove the ignored classes + +## [1.3.8] - 2020-01-14 + +- Trying to add deterministic initialization. +- Autoloader: Remove Manager_Interface and Plugin\Tracking from ignored list +- Autoloader: Remove Jetpack_IXR_Client from ignore list + +## [1.3.7] - 2019-12-10 + +## [1.3.6] - 2019-12-09 + +- Autoloader: Use long-form sytax for array + +## [1.3.5] - 2019-11-26 + +- Fix/php notice status + +## [1.3.4] - 2019-11-08 + +- Deprecate Jetpack::is_development_mode() in favor of the packaged Status()->is_development_mode() + +## [1.3.3] - 2019-10-28 + +- Packages: Add gitattributes files to all packages that need th… + +## [1.3.2] - 2019-09-24 + +- Autoloader: Cover scenarios where composer/autoload_files.php… + +## [1.3.1] - 2019-09-20 + +- Docs: Unify usage of @package phpdoc tags + +## [1.3.0] - 2019-09-14 + +- Fix for empty namespaces. #13459 +- Connection: Move the Jetpack IXR client to the package +- Adds full connection cycle capability to the Connection package. +- Jetpack 7.5: Back compatibility package + +## [1.2.0] - 2019-06-24 + +- Jetpack DNA: Add full classmap support to Autoloader +- Move Jetpack_Sync_Main from legacy to PSR-4 + +## [1.1.0] - 2019-06-19 + +- Packages: Move autoloader tests to the package +- DNA: Move Jetpack Usage tracking to its own file +- Jetpack DNA: More isolation of Tracks Package +- Autoloader: Ignore XMLRPC_Connector if called too early +- Autoloader: Ignore Jetpack_Signature if called too early + +## 1.0.0 - 2019-06-11 + +- Add Custom Autoloader + +[5.0.16]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.15...v5.0.16 +[5.0.15]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.14...v5.0.15 +[5.0.14]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.13...v5.0.14 +[5.0.13]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.12...v5.0.13 +[5.0.12]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.11...v5.0.12 +[5.0.11]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.10...v5.0.11 +[5.0.10]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.9...v5.0.10 +[5.0.9]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.8...v5.0.9 +[5.0.8]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.7...v5.0.8 +[5.0.7]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.6...v5.0.7 +[5.0.6]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.5...v5.0.6 +[5.0.5]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.4...v5.0.5 +[5.0.4]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.3...v5.0.4 +[5.0.3]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.2...v5.0.3 +[5.0.2]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.1...v5.0.2 +[5.0.1]: https://github.com/Automattic/jetpack-autoloader/compare/v5.0.0...v5.0.1 +[5.0.0]: https://github.com/Automattic/jetpack-autoloader/compare/v4.0.0...v5.0.0 +[4.0.0]: https://github.com/Automattic/jetpack-autoloader/compare/v3.1.3...v4.0.0 +[3.1.3]: https://github.com/Automattic/jetpack-autoloader/compare/v3.1.2...v3.1.3 +[3.1.2]: https://github.com/Automattic/jetpack-autoloader/compare/v3.1.1...v3.1.2 +[3.1.1]: https://github.com/Automattic/jetpack-autoloader/compare/v3.1.0...v3.1.1 +[3.1.0]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.10...v3.1.0 +[3.0.10]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.9...v3.0.10 +[3.0.9]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.8...v3.0.9 +[3.0.8]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.7...v3.0.8 +[3.0.7]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.6...v3.0.7 +[3.0.6]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.5...v3.0.6 +[3.0.5]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.4...v3.0.5 +[3.0.4]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.3...v3.0.4 +[3.0.3]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.2...v3.0.3 +[3.0.2]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.1...v3.0.2 +[3.0.1]: https://github.com/Automattic/jetpack-autoloader/compare/v3.0.0...v3.0.1 +[3.0.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.12.0...v3.0.0 +[2.12.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.23...v2.12.0 +[2.11.23]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.22...v2.11.23 +[2.11.22]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.21...v2.11.22 +[2.11.21]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.20...v2.11.21 +[2.11.20]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.19...v2.11.20 +[2.11.19]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.18...v2.11.19 +[2.11.18]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.17...v2.11.18 +[2.11.17]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.16...v2.11.17 +[2.11.16]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.15...v2.11.16 +[2.11.15]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.14...v2.11.15 +[2.11.14]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.13...v2.11.14 +[2.11.13]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.12...v2.11.13 +[2.11.12]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.11...v2.11.12 +[2.11.11]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.10...v2.11.11 +[2.11.10]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.9...v2.11.10 +[2.11.9]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.8...v2.11.9 +[2.11.8]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.7...v2.11.8 +[2.11.7]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.6...v2.11.7 +[2.11.6]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.5...v2.11.6 +[2.11.5]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.4...v2.11.5 +[2.11.4]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.3...v2.11.4 +[2.11.3]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.2...v2.11.3 +[2.11.2]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.1...v2.11.2 +[2.11.1]: https://github.com/Automattic/jetpack-autoloader/compare/v2.11.0...v2.11.1 +[2.11.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.13...v2.11.0 +[2.10.13]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.12...v2.10.13 +[2.10.12]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.11...v2.10.12 +[2.10.11]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.10...v2.10.11 +[2.10.10]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.9...v2.10.10 +[2.10.9]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.8...v2.10.9 +[2.10.8]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.7...v2.10.8 +[2.10.7]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.6...v2.10.7 +[2.10.6]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.5...v2.10.6 +[2.10.5]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.4...v2.10.5 +[2.10.4]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.3...v2.10.4 +[2.10.3]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.2...v2.10.3 +[2.10.2]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.1...v2.10.2 +[2.10.1]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.0...v2.10.1 +[2.10.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.9.1...v2.10.0 +[2.9.1]: https://github.com/Automattic/jetpack-autoloader/compare/v2.9.0...v2.9.1 +[2.9.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.8.0...v2.9.0 +[2.8.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.7.1...v2.8.0 +[2.7.1]: https://github.com/Automattic/jetpack-autoloader/compare/v2.7.0...v2.7.1 +[2.7.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.6.0...v2.7.0 +[2.6.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.5.0...v2.6.0 +[2.5.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.4.0...v2.5.0 +[2.4.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.3.0...v2.4.0 +[2.3.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.2.0...v2.3.0 +[2.2.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.1.0...v2.2.0 +[2.1.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.0.2...v2.1.0 +[2.0.2]: https://github.com/Automattic/jetpack-autoloader/compare/v2.0.1...v2.0.2 +[2.0.1]: https://github.com/Automattic/jetpack-autoloader/compare/v2.0.0...v2.0.1 +[2.0.0]: https://github.com/Automattic/jetpack-autoloader/compare/v2.0.0-beta...v2.0.0 +[2.0.0-beta]: https://github.com/Automattic/jetpack-autoloader/compare/v1.7.0...v2.0.0-beta +[1.7.0]: https://github.com/Automattic/jetpack-autoloader/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/Automattic/jetpack-autoloader/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/Automattic/jetpack-autoloader/compare/v1.4.1...v1.5.0 +[1.4.1]: https://github.com/Automattic/jetpack-autoloader/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/Automattic/jetpack-autoloader/compare/v1.3.8...v1.4.0 +[1.3.8]: https://github.com/Automattic/jetpack-autoloader/compare/v1.3.7...v1.3.8 +[1.3.7]: https://github.com/Automattic/jetpack-autoloader/compare/v1.3.6...v1.3.7 +[1.3.6]: https://github.com/Automattic/jetpack-autoloader/compare/v1.3.5...v1.3.6 +[1.3.5]: https://github.com/Automattic/jetpack-autoloader/compare/v1.3.4...v1.3.5 +[1.3.4]: https://github.com/Automattic/jetpack-autoloader/compare/v1.3.3...v1.3.4 +[1.3.3]: https://github.com/Automattic/jetpack-autoloader/compare/v1.3.2...v1.3.3 +[1.3.2]: https://github.com/Automattic/jetpack-autoloader/compare/v1.3.1...v1.3.2 +[1.3.1]: https://github.com/Automattic/jetpack-autoloader/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/Automattic/jetpack-autoloader/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/Automattic/jetpack-autoloader/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/Automattic/jetpack-autoloader/compare/v1.0.0...v1.1.0 diff --git a/vendor/automattic/jetpack-autoloader/LICENSE.txt b/vendor/automattic/jetpack-autoloader/LICENSE.txt new file mode 100644 index 000000000..490c84a7c --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/LICENSE.txt @@ -0,0 +1,355 @@ +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see . + + +=================================== + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Moe Ghoul, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/automattic/jetpack-autoloader/README.md b/vendor/automattic/jetpack-autoloader/README.md new file mode 100644 index 000000000..4d53925a4 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/README.md @@ -0,0 +1,92 @@ +A custom autoloader for Composer +===================================== + +This is a custom autoloader generator that uses a classmap to always load the latest version of a class. + +The problem this autoloader is trying to solve is conflicts that arise when two or more plugins use the same package, but one of the plugins uses an older version of said package. + +This is solved by keeping an in memory map of all the different classes that can be loaded, and updating the map with the path to the latest version of the package for the autoloader to find when we instantiate the class. + +It diverges from the default Composer autoloader setup in the following ways: + +* It creates `jetpack_autoload_classmap.php` and `jetpack_autoload_filemap.php` files in the `vendor/composer` directory. +* This file includes the version numbers from each package that is used. +* The autoloader will only load the latest version of the package no matter what plugin loads the package. This behavior is guaranteed only when every plugin that uses the package uses this autoloader. If any plugin that requires the package uses a different autoloader, this autoloader may not be able to control which version of the package is loaded. + +Usage +----- + +In your project's `composer.json`, add the following lines: + +```json +{ + "require": { + "automattic/jetpack-autoloader": "^2" + } +} +``` + +Your project must use the default composer vendor directory, `vendor`. + +After the next update/install, you will have a `vendor/autoload_packages.php` file. +Load the file in your plugin via main plugin file. + +In the main plugin you will also need to include the files like this. +```php +require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload_packages.php'; +``` + +Working with Development Versions of Packages +----- + +The autoloader will attempt to use the package with the latest semantic version. + +During development, you can force the autoloader to use development package versions by setting the `JETPACK_AUTOLOAD_DEV` constant to true. When `JETPACK_AUTOLOAD_DEV` is true, the autoloader will prefer the following versions over semantic versions: + - `9999999-dev` + - Versions with a `dev-` prefix. + + +Autoloader Limitations and Caveats +----- + +### Plugin Updates + +When moving a package class file, renaming a package class file, or changing a package class namespace, make sure that the class will not be loaded after a plugin update. + +The autoloader builds the in memory classmap as soon as the autoloader is loaded. The package class file paths in the map are not updated after a plugin update. If a plugins's package class files are moved during a plugin update and a moved file is autoloaded after the update, an error will occur. + +### Moving classes to a different package + +Jetpack Autoloader determines the hierarchy of class versions by package version numbers. It can cause problems if a class is moved to a newer package with a lower version number, it will get overshadowed by the old package. + +For instance, if your newer version of a class comes from a new package versioned 0.1.0, and the older version comes from a different package with a greater version number 2.0.1, the newer class will not get loaded. + +### Jetpack Autoloader uses transient cache + +This is a caveat to be aware of when dealing with issues. The JP Autoloader uses transients to cache a list of available plugins to speed up the lookup process. This can sometimes mask problems that arise when loading code too early. See the [Debugging](#debugging) section for more information on how to detect situations like this. + +Debugging +----- + +A common cause of confusing errors is when a plugin autoloads classes during the plugin load, before the 'plugins_loaded' hook. If that plugin has an older version of the class, that older version may be loaded before a plugin providing the newer version of the class has a chance to register. Even more confusingly, this will likely be intermittent, only showing up when the autoloader's plugin cache is invalidated. To debug this situation, you can set the `JETPACK_AUTOLOAD_DEBUG_EARLY_LOADS` constant to true. + +Another common cause of confusing errors is when a plugin registers its own autoloader at a higher priority than the Jetpack Autoloader, and that autoloader would load packages that should be handled by the Jetpack Autoloader. Setting the `JETPACK_AUTOLOAD_DEBUG_CONFLICTING_LOADERS` constant to true will check for standard Composer autoloaders with such a conflict. + + +Autoloading Standards +---- + +All new Jetpack package development should use classmap autoloading, which allows the class and file names to comply with the WordPress Coding Standards. + +### Optimized Autoloader + +An optimized autoloader is generated when: + * `composer install` or `composer update` is called with `-o` or `--optimize-autoloader` + * `composer dump-autoload` is called with `-o` or `--optimize` + +PSR-4 and PSR-0 namespaces are converted to classmaps. + +### Unoptimized Autoloader + +Supports PSR-4 autoloading. PSR-0 namespaces are converted to classmaps. + diff --git a/vendor/automattic/jetpack-autoloader/SECURITY.md b/vendor/automattic/jetpack-autoloader/SECURITY.md new file mode 100644 index 000000000..98f48dd1d --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/SECURITY.md @@ -0,0 +1,47 @@ +# Security Policy + +Full details of the Automattic Security Policy can be found on [automattic.com](https://automattic.com/security/). + +## Supported Versions + +Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, we may opt to backport any patches to previous versions. + +## Reporting a Vulnerability + +Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure: + +* [Jetpack](https://jetpack.com/) +* Jetpack Backup +* Jetpack Boost +* Jetpack CRM +* Jetpack Protect +* Jetpack Search +* Jetpack Social +* Jetpack VideoPress + +**For responsible disclosure of security issues and to be eligible for our bug bounty program, please submit your report via the [HackerOne](https://hackerone.com/automattic) portal.** + +Our most critical targets are: + +* Jetpack and the Jetpack composer packages (all within this repo) +* Jetpack.com -- the primary marketing site. +* cloud.jetpack.com -- a management site. +* wordpress.com -- the shared management site for both Jetpack and WordPress.com sites. + +For more targets, see the `In Scope` section on [HackerOne](https://hackerone.com/automattic). + +_Please note that the **WordPress software is a separate entity** from Automattic. Please report vulnerabilities for WordPress through [the WordPress Foundation's HackerOne page](https://hackerone.com/wordpress)._ + +## Guidelines + +We're committed to working with security researchers to resolve the vulnerabilities they discover. You can help us by following these guidelines: + +* Follow [HackerOne's disclosure guidelines](https://www.hackerone.com/disclosure-guidelines). +* Pen-testing Production: + * Please **setup a local environment** instead whenever possible. Most of our code is open source (see above). + * If that's not possible, **limit any data access/modification** to the bare minimum necessary to reproduce a PoC. + * **_Don't_ automate form submissions!** That's very annoying for us, because it adds extra work for the volunteers who manage those systems, and reduces the signal/noise ratio in our communication channels. + * To be eligible for a bounty, all of these guidelines must be followed. +* Be Patient - Give us a reasonable time to correct the issue before you disclose the vulnerability. + +We also expect you to comply with all applicable laws. You're responsible to pay any taxes associated with your bounties. diff --git a/vendor/automattic/jetpack-autoloader/composer.json b/vendor/automattic/jetpack-autoloader/composer.json new file mode 100644 index 000000000..a309d89e8 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/composer.json @@ -0,0 +1,60 @@ +{ + "name": "automattic/jetpack-autoloader", + "description": "Creates a custom autoloader for a plugin or theme.", + "type": "composer-plugin", + "license": "GPL-2.0-or-later", + "keywords": [ + "autoload", + "autoloader", + "composer", + "plugin", + "jetpack", + "wordpress" + ], + "require": { + "php": ">=7.2", + "composer-plugin-api": "^2.2" + }, + "require-dev": { + "composer/composer": "^2.2", + "yoast/phpunit-polyfills": "^4.0.0", + "automattic/jetpack-changelogger": "^6.0.14", + "automattic/phpunit-select-config": "^1.0.3" + }, + "autoload": { + "classmap": [ + "src/AutoloadGenerator.php" + ], + "psr-4": { + "Automattic\\Jetpack\\Autoloader\\": "src" + } + }, + "scripts": { + "phpunit": [ + "phpunit-select-config phpunit.#.xml.dist --colors=always" + ], + "test-coverage": [ + "php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"./tests/php/tmp/coverage-report.php\"", + "php ./tests/php/bin/test-coverage.php \"$COVERAGE_DIR/php.cov\"" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "extra": { + "autotagger": true, + "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin", + "mirror-repo": "Automattic/jetpack-autoloader", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}" + }, + "version-constants": { + "::VERSION": "src/AutoloadGenerator.php" + }, + "branch-alias": { + "dev-trunk": "5.0.x-dev" + } + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/AutoloadFileWriter.php b/vendor/automattic/jetpack-autoloader/src/AutoloadFileWriter.php new file mode 100644 index 000000000..c0e3368c7 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/AutoloadFileWriter.php @@ -0,0 +1,101 @@ + '../autoload_packages.php', + ); + $ignoreList = array( + 'AutoloadGenerator.php', + 'AutoloadProcessor.php', + 'CustomAutoloaderPlugin.php', + 'ManifestGenerator.php', + 'AutoloadFileWriter.php', + ); + + // Copy all of the autoloader files. + $files = scandir( __DIR__ ); + foreach ( $files as $file ) { + // Only PHP files will be copied. + if ( substr( $file, -4 ) !== '.php' ) { + continue; + } + + if ( in_array( $file, $ignoreList, true ) ) { + continue; + } + + $newFile = $renameList[ $file ] ?? $file; + $content = self::prepareAutoloaderFile( $file, $suffix ); + + $written = file_put_contents( $outDir . '/' . $newFile, $content ); + if ( $io ) { + if ( $written ) { + $io->writeError( " Generated: $newFile" ); + } else { + $io->writeError( " Error: $newFile" ); + } + } + } + } + + /** + * Prepares an autoloader file to be written to the destination. + * + * @param String $filename a file to prepare. + * @param String $suffix Unique suffix used in the namespace. + * + * @return string + */ + private static function prepareAutoloaderFile( $filename, $suffix ) { + $header = self::COMMENT; + $header .= PHP_EOL; + if ( $suffix === 'Current' ) { + // Unit testing. + $header .= 'namespace Automattic\Jetpack\Autoloader\jpCurrent;'; + } else { + $header .= 'namespace Automattic\Jetpack\Autoloader\jp' . $suffix . '\al' . preg_replace( '/[^0-9a-zA-Z]/', '_', AutoloadGenerator::VERSION ) . ';'; + } + $header .= PHP_EOL . PHP_EOL; + + $sourceLoader = fopen( __DIR__ . '/' . $filename, 'r' ); + $file_contents = stream_get_contents( $sourceLoader ); + return str_replace( + '/* HEADER */', + $header, + $file_contents + ); + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/AutoloadGenerator.php b/vendor/automattic/jetpack-autoloader/src/AutoloadGenerator.php new file mode 100644 index 000000000..beee9c273 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/AutoloadGenerator.php @@ -0,0 +1,403 @@ +io = $io; + $this->filesystem = new Filesystem(); + } + + /** + * Dump the Jetpack autoloader files. + * + * @param Composer $composer The Composer object. + * @param Config $config Config object. + * @param InstalledRepositoryInterface $localRepo Installed Repository object. + * @param PackageInterface $mainPackage Main Package object. + * @param InstallationManager $installationManager Manager for installing packages. + * @param string $targetDir Path to the current target directory. + * @param bool $scanPsrPackages Whether or not PSR packages should be converted to a classmap. + * @param string $suffix The autoloader suffix. + */ + public function dump( + Composer $composer, + Config $config, + InstalledRepositoryInterface $localRepo, + PackageInterface $mainPackage, + InstallationManager $installationManager, + $targetDir, + $scanPsrPackages = false, + $suffix = null + ) { + $this->filesystem->ensureDirectoryExists( $config->get( 'vendor-dir' ) ); + + $packageMap = $composer->getAutoloadGenerator()->buildPackageMap( $installationManager, $mainPackage, $localRepo->getCanonicalPackages() ); + $autoloads = $this->parseAutoloads( $packageMap, $mainPackage ); + + // Convert the autoloads into a format that the manifest generator can consume more easily. + $basePath = $this->filesystem->normalizePath( realpath( getcwd() ) ); + $vendorPath = $this->filesystem->normalizePath( realpath( $config->get( 'vendor-dir' ) ) ); + $processedAutoloads = $this->processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath ); + unset( $packageMap, $autoloads ); + + // Make sure none of the legacy files remain that can lead to problems with the autoloader. + $this->removeLegacyFiles( $vendorPath ); + + // Write all of the files now that we're done. + $this->writeAutoloaderFiles( $vendorPath . '/jetpack-autoloader/', $suffix ); + $this->writeManifests( $vendorPath . '/' . $targetDir, $processedAutoloads ); + + if ( ! $scanPsrPackages ) { + $this->io->writeError( 'You are generating an unoptimized autoloader. If this is a production build, consider using the -o option.' ); + } + } + + /** + * Compiles an ordered list of namespace => path mappings + * + * @param array $packageMap Array of array(package, installDir-relative-to-composer.json). + * @param PackageInterface $mainPackage Main package instance. + * + * @return array The list of path mappings. + */ + public function parseAutoloads( array $packageMap, PackageInterface $mainPackage ) { + $rootPackageMap = array_shift( $packageMap ); + + $sortedPackageMap = $this->sortPackageMap( $packageMap ); + $sortedPackageMap[] = $rootPackageMap; + array_unshift( $packageMap, $rootPackageMap ); + + $psr0 = $this->parseAutoloadsType( $packageMap, 'psr-0', $mainPackage ); + $psr4 = $this->parseAutoloadsType( $packageMap, 'psr-4', $mainPackage ); + $classmap = $this->parseAutoloadsType( array_reverse( $sortedPackageMap ), 'classmap', $mainPackage ); + $files = $this->parseAutoloadsType( $sortedPackageMap, 'files', $mainPackage ); + + krsort( $psr0 ); + krsort( $psr4 ); + + return array( + 'psr-0' => $psr0, + 'psr-4' => $psr4, + 'classmap' => $classmap, + 'files' => $files, + ); + } + + /** + * Sorts packages by dependency weight + * + * Packages of equal weight retain the original order + * + * @param array $packageMap The package map. + * + * @return array + */ + protected function sortPackageMap( array $packageMap ) { + $packages = array(); + $paths = array(); + + foreach ( $packageMap as $item ) { + list( $package, $path ) = $item; + $name = $package->getName(); + $packages[ $name ] = $package; + $paths[ $name ] = $path; + } + + $sortedPackages = PackageSorter::sortPackages( $packages ); + + $sortedPackageMap = array(); + + foreach ( $sortedPackages as $package ) { + $name = $package->getName(); + $sortedPackageMap[] = array( $packages[ $name ], $paths[ $name ] ); + } + + return $sortedPackageMap; + } + + /** + * Returns the file identifier. + * + * @param PackageInterface $package The package instance. + * @param string $path The path. + */ + protected function getFileIdentifier( PackageInterface $package, $path ) { + return md5( $package->getName() . ':' . $path ); + } + + /** + * Returns the path code for the given path. + * + * @param Filesystem $filesystem The filesystem instance. + * @param string $basePath The base path. + * @param string $vendorPath The vendor path. + * @param string $path The path. + * + * @return string The path code. + */ + protected function getPathCode( Filesystem $filesystem, $basePath, $vendorPath, $path ) { + if ( ! $filesystem->isAbsolutePath( $path ) ) { + $path = $basePath . '/' . $path; + } + $path = $filesystem->normalizePath( $path ); + + $baseDir = ''; + if ( 0 === strpos( $path . '/', $vendorPath . '/' ) ) { + $path = substr( $path, strlen( $vendorPath ) ); + $baseDir = '$vendorDir'; + + if ( false !== $path ) { + $baseDir .= ' . '; + } + } else { + $path = $filesystem->normalizePath( $filesystem->findShortestPath( $basePath, $path, true ) ); + if ( ! $filesystem->isAbsolutePath( $path ) ) { + $baseDir = '$baseDir . '; + $path = '/' . $path; + } + } + + if ( strpos( $path, '.phar' ) !== false ) { + $baseDir = "'phar://' . " . $baseDir; + } + + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export + return $baseDir . ( ( false !== $path ) ? var_export( $path, true ) : '' ); + } + + /** + * This function differs from the composer parseAutoloadsType in that beside returning the path. + * It also return the path and the version of a package. + * + * Supports PSR-4, PSR-0, and classmap parsing. + * + * @param array $packageMap Map of all the packages. + * @param string $type Type of autoloader to use. + * @param PackageInterface $mainPackage Instance of the Package Object. + * + * @return array + */ + protected function parseAutoloadsType( array $packageMap, $type, PackageInterface $mainPackage ) { + $autoloads = array(); + + foreach ( $packageMap as $item ) { + list($package, $installPath) = $item; + $autoload = $package->getAutoload(); + $version = $package->getVersion(); // Version of the class comes from the package - should we try to parse it? + + // Store our own actual package version, not "dev-trunk" or whatever. + if ( $package->getName() === 'automattic/jetpack-autoloader' ) { + $version = self::VERSION; + } + + if ( $package === $mainPackage ) { + $autoload = array_merge_recursive( $autoload, $package->getDevAutoload() ); + } + + if ( null !== $package->getTargetDir() && $package !== $mainPackage ) { + $installPath = substr( $installPath, 0, -strlen( '/' . $package->getTargetDir() ) ); + } + + if ( in_array( $type, array( 'psr-4', 'psr-0' ), true ) && isset( $autoload[ $type ] ) && is_array( $autoload[ $type ] ) ) { + foreach ( $autoload[ $type ] as $namespace => $paths ) { + $paths = is_array( $paths ) ? $paths : array( $paths ); + foreach ( $paths as $path ) { + $relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path; + $autoloads[ $namespace ][] = array( + 'path' => $relativePath, + 'version' => $version, + ); + } + } + } + + if ( 'classmap' === $type && isset( $autoload['classmap'] ) && is_array( $autoload['classmap'] ) ) { + foreach ( $autoload['classmap'] as $paths ) { + $paths = is_array( $paths ) ? $paths : array( $paths ); + foreach ( $paths as $path ) { + $relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path; + $autoloads[] = array( + 'path' => $relativePath, + 'version' => $version, + ); + } + } + } + if ( 'files' === $type && isset( $autoload['files'] ) && is_array( $autoload['files'] ) ) { + foreach ( $autoload['files'] as $paths ) { + $paths = is_array( $paths ) ? $paths : array( $paths ); + foreach ( $paths as $path ) { + $relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path; + $autoloads[ $this->getFileIdentifier( $package, $path ) ] = array( + 'path' => $relativePath, + 'version' => $version, + ); + } + } + } + } + + return $autoloads; + } + + /** + * Given Composer's autoloads this will convert them to a version that we can use to generate the manifests. + * + * When the $scanPsrPackages argument is true, PSR-4 namespaces are converted to classmaps. When $scanPsrPackages + * is false, PSR-4 namespaces are not converted to classmaps. + * + * PSR-0 namespaces are always converted to classmaps. + * + * @param array $autoloads The autoloads we want to process. + * @param bool $scanPsrPackages Whether or not PSR-4 packages should be converted to a classmap. + * @param string $vendorPath The path to the vendor directory. + * @param string $basePath The path to the current directory. + * + * @return array $processedAutoloads + */ + private function processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath ) { + $processor = new AutoloadProcessor( + function ( $path, $excludedClasses, $namespace ) use ( $basePath ) { + $dir = $this->filesystem->normalizePath( + $this->filesystem->isAbsolutePath( $path ) ? $path : $basePath . '/' . $path + ); + + // Composer 2.4 changed the name of the class. + if ( class_exists( \Composer\ClassMapGenerator\ClassMapGenerator::class ) ) { + if ( ! is_dir( $dir ) && ! is_file( $dir ) ) { + return array(); + } + $generator = new \Composer\ClassMapGenerator\ClassMapGenerator(); + $generator->scanPaths( $dir, $excludedClasses, 'classmap', empty( $namespace ) ? null : $namespace ); + return $generator->getClassMap()->getMap(); + } + + return \Composer\Autoload\ClassMapGenerator::createMap( + $dir, + $excludedClasses, + null, // Don't pass the IOInterface since the normal autoload generation will have reported already. + empty( $namespace ) ? null : $namespace + ); + }, + function ( $path ) use ( $basePath, $vendorPath ) { + return $this->getPathCode( $this->filesystem, $basePath, $vendorPath, $path ); + } + ); + + return array( + 'psr-4' => $processor->processPsr4Packages( $autoloads, $scanPsrPackages ), + 'classmap' => $processor->processClassmap( $autoloads, $scanPsrPackages ), + 'files' => $processor->processFiles( $autoloads ), + ); + } + + /** + * Removes all of the legacy autoloader files so they don't cause any problems. + * + * @param string $outDir The directory legacy files are written to. + */ + private function removeLegacyFiles( $outDir ) { + $files = array( + 'autoload_functions.php', + 'class-autoloader-handler.php', + 'class-classes-handler.php', + 'class-files-handler.php', + 'class-plugins-handler.php', + 'class-version-selector.php', + ); + foreach ( $files as $file ) { + $this->filesystem->remove( $outDir . '/' . $file ); + } + } + + /** + * Writes all of the autoloader files to disk. + * + * @param string $outDir The directory to write to. + * @param string $suffix The unique autoloader suffix. + */ + private function writeAutoloaderFiles( $outDir, $suffix ) { + $this->io->writeError( "Generating jetpack autoloader ($outDir)" ); + + // We will remove all autoloader files to generate this again. + $this->filesystem->emptyDirectory( $outDir ); + + // Write the autoloader files. + AutoloadFileWriter::copyAutoloaderFiles( $this->io, $outDir, $suffix ); + } + + /** + * Writes all of the manifest files to disk. + * + * @param string $outDir The directory to write to. + * @param array $processedAutoloads The processed autoloads. + */ + private function writeManifests( $outDir, $processedAutoloads ) { + $this->io->writeError( "Generating jetpack autoloader manifests ($outDir)" ); + + $manifestFiles = array( + 'classmap' => 'jetpack_autoload_classmap.php', + 'psr-4' => 'jetpack_autoload_psr4.php', + 'files' => 'jetpack_autoload_filemap.php', + ); + + foreach ( $manifestFiles as $key => $file ) { + // Make sure the file doesn't exist so it isn't there if we don't write it. + $this->filesystem->remove( $outDir . '/' . $file ); + if ( empty( $processedAutoloads[ $key ] ) ) { + continue; + } + + $content = ManifestGenerator::buildManifest( $key, $file, $processedAutoloads[ $key ] ); + if ( empty( $content ) ) { + continue; + } + + if ( file_put_contents( $outDir . '/' . $file, $content ) ) { + $this->io->writeError( " Generated: $file" ); + } else { + $this->io->writeError( " Error: $file" ); + } + } + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/AutoloadProcessor.php b/vendor/automattic/jetpack-autoloader/src/AutoloadProcessor.php new file mode 100644 index 000000000..ca5ccd99b --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/AutoloadProcessor.php @@ -0,0 +1,176 @@ +classmapScanner = $classmapScanner; + $this->pathCodeTransformer = $pathCodeTransformer; + } + + /** + * Processes the classmap autoloads into a relative path format including the version for each file. + * + * @param array $autoloads The autoloads we are processing. + * @param bool $scanPsrPackages Whether or not PSR packages should be converted to a classmap. + * + * @return array|null $processed + * @phan-param array{classmap:?array{path:string,version:string}[],psr-4:?array,psr-0:?array} $autoloads + */ + public function processClassmap( $autoloads, $scanPsrPackages ) { + // We can't scan PSR packages if we don't actually have any. + if ( empty( $autoloads['psr-4'] ) ) { + $scanPsrPackages = false; + } + + if ( empty( $autoloads['classmap'] ) && ! $scanPsrPackages ) { + return null; + } + + $excludedClasses = null; + if ( ! empty( $autoloads['exclude-from-classmap'] ) ) { + $excludedClasses = '{(' . implode( '|', $autoloads['exclude-from-classmap'] ) . ')}'; + } + + $processed = array(); + + if ( $scanPsrPackages ) { + foreach ( $autoloads['psr-4'] as $namespace => $sources ) { + $namespace = empty( $namespace ) ? null : $namespace; + + foreach ( $sources as $source ) { + $classmap = call_user_func( $this->classmapScanner, $source['path'], $excludedClasses, $namespace ); + + foreach ( $classmap as $class => $path ) { + $processed[ $class ] = array( + 'version' => $source['version'], + 'path' => call_user_func( $this->pathCodeTransformer, $path ), + ); + } + } + } + } + + /* + * PSR-0 namespaces are converted to classmaps for both optimized and unoptimized autoloaders because any new + * development should use classmap or PSR-4 autoloading. + */ + if ( ! empty( $autoloads['psr-0'] ) ) { + foreach ( $autoloads['psr-0'] as $namespace => $sources ) { + $namespace = empty( $namespace ) ? null : $namespace; + + foreach ( $sources as $source ) { + $classmap = call_user_func( $this->classmapScanner, $source['path'], $excludedClasses, $namespace ); + foreach ( $classmap as $class => $path ) { + $processed[ $class ] = array( + 'version' => $source['version'], + 'path' => call_user_func( $this->pathCodeTransformer, $path ), + ); + } + } + } + } + + if ( ! empty( $autoloads['classmap'] ) ) { + foreach ( $autoloads['classmap'] as $package ) { + $classmap = call_user_func( $this->classmapScanner, $package['path'], $excludedClasses, null ); + + foreach ( $classmap as $class => $path ) { + $processed[ $class ] = array( + 'version' => $package['version'], + 'path' => call_user_func( $this->pathCodeTransformer, $path ), + ); + } + } + } + + ksort( $processed ); + + return $processed; + } + + /** + * Processes the PSR-4 autoloads into a relative path format including the version for each file. + * + * @param array $autoloads The autoloads we are processing. + * @param bool $scanPsrPackages Whether or not PSR packages should be converted to a classmap. + * + * @return array|null $processed + */ + public function processPsr4Packages( $autoloads, $scanPsrPackages ) { + if ( $scanPsrPackages || empty( $autoloads['psr-4'] ) ) { + return null; + } + + $processed = array(); + + foreach ( $autoloads['psr-4'] as $namespace => $packages ) { + $namespace = empty( $namespace ) ? null : $namespace; + $paths = array(); + + foreach ( $packages as $package ) { + $paths[] = call_user_func( $this->pathCodeTransformer, $package['path'] ); + } + + $processed[ $namespace ] = array( + 'version' => $package['version'], + 'path' => $paths, + ); + } + + return $processed; + } + + /** + * Processes the file autoloads into a relative format including the version for each file. + * + * @param array $autoloads The autoloads we are processing. + * + * @return array|null $processed + */ + public function processFiles( $autoloads ) { + if ( empty( $autoloads['files'] ) ) { + return null; + } + + $processed = array(); + + foreach ( $autoloads['files'] as $file_id => $package ) { + $processed[ $file_id ] = array( + 'version' => $package['version'], + 'path' => call_user_func( $this->pathCodeTransformer, $package['path'] ), + ); + } + + return $processed; + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/CustomAutoloaderPlugin.php b/vendor/automattic/jetpack-autoloader/src/CustomAutoloaderPlugin.php new file mode 100644 index 000000000..6e29a0909 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/CustomAutoloaderPlugin.php @@ -0,0 +1,188 @@ +composer = $composer; + $this->io = $io; + } + + /** + * Do nothing. + * phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + * + * @param Composer $composer Composer object. + * @param IOInterface $io IO object. + */ + public function deactivate( Composer $composer, IOInterface $io ) { + /* + * Intentionally left empty. This is a PluginInterface method. + * phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + */ + } + + /** + * Do nothing. + * phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + * + * @param Composer $composer Composer object. + * @param IOInterface $io IO object. + */ + public function uninstall( Composer $composer, IOInterface $io ) { + /* + * Intentionally left empty. This is a PluginInterface method. + * phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + */ + } + + /** + * Tell composer to listen for events and do something with them. + * + * @return array List of subscribed events. + */ + public static function getSubscribedEvents() { + return array( + ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump', + ); + } + + /** + * Generate the custom autolaoder. + * + * @param Event $event Script event object. + */ + public function postAutoloadDump( Event $event ) { + // When the autoloader is not required by the root package we don't want to execute it. + // This prevents unwanted transitive execution that generates unused autoloaders or + // at worst throws fatal executions. + if ( ! $this->isRequiredByRoot() ) { + return; + } + + $config = $this->composer->getConfig(); + + if ( 'vendor' !== $config->raw()['config']['vendor-dir'] ) { + $this->io->writeError( "\nAn error occurred while generating the autoloader files:", true ); + $this->io->writeError( 'The project\'s composer.json or composer environment set a non-default vendor directory.', true ); + $this->io->writeError( 'The default composer vendor directory must be used.', true ); + exit( 0 ); + } + + $installationManager = $this->composer->getInstallationManager(); + $repoManager = $this->composer->getRepositoryManager(); + $localRepo = $repoManager->getLocalRepository(); + $package = $this->composer->getPackage(); + $optimize = $event->getFlags()['optimize']; + $suffix = $this->determineSuffix(); + + $generator = new AutoloadGenerator( $this->io ); + $generator->dump( $this->composer, $config, $localRepo, $package, $installationManager, 'composer', $optimize, $suffix ); + } + + /** + * Determine the suffix for the autoloader class. + * + * Reuses an existing suffix from vendor/autoload_packages.php or vendor/autoload.php if possible. + * + * @return string Suffix. + */ + private function determineSuffix() { + $config = $this->composer->getConfig(); + $vendorPath = $config->get( 'vendor-dir' ); + + // Command line. + $suffix = $config->get( 'autoloader-suffix' ); + if ( $suffix ) { + return $suffix; + } + + // Reuse our own suffix, if any. + if ( is_readable( $vendorPath . '/autoload_packages.php' ) ) { + $content = file_get_contents( $vendorPath . '/autoload_packages.php' ); + if ( preg_match( '/^namespace Automattic\\\\Jetpack\\\\Autoloader\\\\jp([^;\s]+?)(?:\\\\al[^;\s]+)?;/m', $content, $match ) ) { + return $match[1]; + } + } + + // Reuse Composer's suffix, if any. + if ( is_readable( $vendorPath . '/autoload.php' ) ) { + $content = file_get_contents( $vendorPath . '/autoload.php' ); + if ( preg_match( '{ComposerAutoloaderInit([^:\s]+)::}', $content, $match ) ) { + return $match[1]; + } + } + + // Generate a random suffix. + return md5( uniqid( '', true ) ); + } + + /** + * Checks to see whether or not the root package is the one that required the autoloader. + * + * @return bool + */ + private function isRequiredByRoot() { + $package = $this->composer->getPackage(); + $requires = $package->getRequires(); + if ( ! is_array( $requires ) ) { // @phan-suppress-current-line PhanRedundantCondition -- Earlier Composer versions may not have guaranteed this. + $requires = array(); + } + $devRequires = $package->getDevRequires(); + if ( ! is_array( $devRequires ) ) { // @phan-suppress-current-line PhanRedundantCondition -- Earlier Composer versions may not have guaranteed this. + $devRequires = array(); + } + $requires = array_merge( $requires, $devRequires ); + + if ( empty( $requires ) ) { + $this->io->writeError( "\nThe package is not required and this should never happen?", true ); + exit( 0 ); + } + + foreach ( $requires as $require ) { + if ( 'automattic/jetpack-autoloader' === $require->getTarget() ) { + return true; + } + } + + return false; + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/ManifestGenerator.php b/vendor/automattic/jetpack-autoloader/src/ManifestGenerator.php new file mode 100644 index 000000000..254d46a59 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/ManifestGenerator.php @@ -0,0 +1,115 @@ + $data ) { + $key = var_export( $key, true ); + $versionCode = var_export( $data['version'], true ); + $fileContent .= << array( + 'version' => $versionCode, + 'path' => {$data['path']} + ), +MANIFEST_CODE; + $fileContent .= PHP_EOL; + } + + return self::buildFile( $fileName, $fileContent ); + } + + /** + * Builds the contents for the PSR-4 manifest file. + * + * @param string $fileName The filename we are building. + * @param array $namespaces The formatted PSR-4 data for the manifest. + * + * @return string|null $manifestFile + */ + private static function buildPsr4Manifest( $fileName, $namespaces ) { + $fileContent = PHP_EOL; + foreach ( $namespaces as $namespace => $data ) { + $namespaceCode = var_export( $namespace, true ); + $versionCode = var_export( $data['version'], true ); + $pathCode = 'array( ' . implode( ', ', $data['path'] ) . ' )'; + $fileContent .= << array( + 'version' => $versionCode, + 'path' => $pathCode + ), +MANIFEST_CODE; + $fileContent .= PHP_EOL; + } + + return self::buildFile( $fileName, $fileContent ); + } + + /** + * Generate the PHP that will be used in the file. + * + * @param string $fileName The filename we are building. + * @param string $content The content to be written into the file. + * + * @return string $fileContent + */ + private static function buildFile( $fileName, $content ) { + return <<php_autoloader = $php_autoloader; + $this->hook_manager = $hook_manager; + $this->manifest_reader = $manifest_reader; + $this->version_selector = $version_selector; + } + + /** + * Checks to see whether or not an autoloader is currently in the process of initializing. + * + * @return bool + */ + public function is_initializing() { + // If no version has been set it means that no autoloader has started initializing yet. + global $jetpack_autoloader_latest_version; + if ( ! isset( $jetpack_autoloader_latest_version ) ) { + return false; + } + + // When the version is set but the classmap is not it ALWAYS means that this is the + // latest autoloader and is being included by an older one. + global $jetpack_packages_classmap; + if ( empty( $jetpack_packages_classmap ) ) { + return true; + } + + // Version 2.4.0 added a new global and altered the reset semantics. We need to check + // the other global as well since it may also point at initialization. + // Note: We don't need to check for the class first because every autoloader that + // will set the latest version global requires this class in the classmap. + $replacing_version = $jetpack_packages_classmap[ AutoloadGenerator::class ]['version']; + if ( $this->version_selector->is_dev_version( $replacing_version ) || version_compare( $replacing_version, '2.4.0.0', '>=' ) ) { + global $jetpack_autoloader_loader; + if ( ! isset( $jetpack_autoloader_loader ) ) { + return true; + } + } + + return false; + } + + /** + * Activates an autoloader using the given plugins and activates it. + * + * @param string[] $plugins The plugins to initialize the autoloader for. + */ + public function activate_autoloader( $plugins ) { + global $jetpack_packages_psr4; + $jetpack_packages_psr4 = array(); + $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_psr4.php', $jetpack_packages_psr4 ); + + global $jetpack_packages_classmap; + $jetpack_packages_classmap = array(); + $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_classmap.php', $jetpack_packages_classmap ); + + global $jetpack_packages_filemap; + $jetpack_packages_filemap = array(); + $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_filemap.php', $jetpack_packages_filemap ); + + $loader = new Version_Loader( + $this->version_selector, + $jetpack_packages_classmap, + $jetpack_packages_psr4, + $jetpack_packages_filemap + ); + + $this->php_autoloader->register_autoloader( $loader ); + + // Now that the autoloader is active we can load the filemap. + $loader->load_filemap(); + } + + /** + * Resets the active autoloader and all related global state. + */ + public function reset_autoloader() { + $this->php_autoloader->unregister_autoloader(); + $this->hook_manager->reset(); + + // Clear all of the autoloader globals so that older autoloaders don't do anything strange. + global $jetpack_autoloader_latest_version; + $jetpack_autoloader_latest_version = null; + + global $jetpack_packages_classmap; + $jetpack_packages_classmap = array(); // Must be array to avoid exceptions in old autoloaders! + + global $jetpack_packages_psr4; + $jetpack_packages_psr4 = array(); // Must be array to avoid exceptions in old autoloaders! + + global $jetpack_packages_filemap; + $jetpack_packages_filemap = array(); // Must be array to avoid exceptions in old autoloaders! + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-autoloader-locator.php b/vendor/automattic/jetpack-autoloader/src/class-autoloader-locator.php new file mode 100644 index 000000000..908222601 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-autoloader-locator.php @@ -0,0 +1,82 @@ +version_selector = $version_selector; + } + + /** + * Finds the path to the plugin with the latest autoloader. + * + * @param array $plugin_paths An array of plugin paths. + * @param string $latest_version The latest version reference. @phan-output-reference. + * + * @return string|null + */ + public function find_latest_autoloader( $plugin_paths, &$latest_version ) { + $latest_plugin = null; + + foreach ( $plugin_paths as $plugin_path ) { + $version = $this->get_autoloader_version( $plugin_path ); + if ( ! $version || ! $this->version_selector->is_version_update_required( $latest_version, $version ) ) { + continue; + } + + $latest_version = $version; + $latest_plugin = $plugin_path; + } + + return $latest_plugin; + } + + /** + * Gets the path to the autoloader. + * + * @param string $plugin_path The path to the plugin. + * + * @return string + */ + public function get_autoloader_path( $plugin_path ) { + return trailingslashit( $plugin_path ) . 'vendor/autoload_packages.php'; + } + + /** + * Gets the version for the autoloader. + * + * @param string $plugin_path The path to the plugin. + * + * @return string|null + */ + public function get_autoloader_version( $plugin_path ) { + $classmap = trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_classmap.php'; + if ( ! file_exists( $classmap ) ) { + return null; + } + + $classmap = require $classmap; + if ( isset( $classmap[ AutoloadGenerator::class ] ) ) { + return $classmap[ AutoloadGenerator::class ]['version']; + } + + return null; + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-autoloader.php b/vendor/automattic/jetpack-autoloader/src/class-autoloader.php new file mode 100644 index 000000000..c9b66080a --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-autoloader.php @@ -0,0 +1,85 @@ +get( Autoloader_Handler::class ); + + // If the autoloader is already initializing it means that it has included us as the latest. + $was_included_by_autoloader = $autoloader_handler->is_initializing(); + + /** @var Plugin_Locator $plugin_locator */ + $plugin_locator = $container->get( Plugin_Locator::class ); + + /** @var Plugins_Handler $plugins_handler */ + $plugins_handler = $container->get( Plugins_Handler::class ); + + // The current plugin is the one that we are attempting to initialize here. + $current_plugin = $plugin_locator->find_current_plugin(); + + // The active plugins are those that we were able to discover on the site. This list will not + // include mu-plugins, those activated by code, or those who are hidden by filtering. We also + // want to take care to not consider the current plugin unknown if it was included by an + // autoloader. This avoids the case where a plugin will be marked "active" while deactivated + // due to it having the latest autoloader. + $active_plugins = $plugins_handler->get_active_plugins( true, ! $was_included_by_autoloader ); + + // The cached plugins are all of those that were active or discovered by the autoloader during a previous request. + // Note that it's possible this list will include plugins that have since been deactivated, but after a request + // the cache should be updated and the deactivated plugins will be removed. + $cached_plugins = $plugins_handler->get_cached_plugins(); + + // We combine the active list and cached list to preemptively load classes for plugins that are + // presently unknown but will be loaded during the request. While this may result in us considering packages in + // deactivated plugins there shouldn't be any problems as a result and the eventual consistency is sufficient. + $all_plugins = array_merge( $active_plugins, $cached_plugins ); + + // In particular we also include the current plugin to address the case where it is the latest autoloader + // but also unknown (and not cached). We don't want it in the active list because we don't know that it + // is active but we need it in the all plugins list so that it is considered by the autoloader. + $all_plugins[] = $current_plugin; + + // We require uniqueness in the array to avoid processing the same plugin more than once. + $all_plugins = array_values( array_unique( $all_plugins ) ); + + /** @var Latest_Autoloader_Guard $guard */ + $guard = $container->get( Latest_Autoloader_Guard::class ); + if ( $guard->should_stop_init( $current_plugin, $all_plugins, $was_included_by_autoloader ) ) { + return; + } + + // Initialize the autoloader using the handler now that we're ready. + $autoloader_handler->activate_autoloader( $all_plugins ); + + /** @var Hook_Manager $hook_manager */ + $hook_manager = $container->get( Hook_Manager::class ); + + // Register a shutdown handler to clean up the autoloader. + $hook_manager->add_action( 'shutdown', new Shutdown_Handler( $plugins_handler, $cached_plugins, $was_included_by_autoloader ) ); + + // Register a plugins_loaded handler to check for conflicting autoloaders. + $hook_manager->add_action( 'plugins_loaded', array( $guard, 'check_for_conflicting_autoloaders' ), 1 ); + + // phpcs:enable Generic.Commenting.DocComment.MissingShort + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-container.php b/vendor/automattic/jetpack-autoloader/src/class-container.php new file mode 100644 index 000000000..19ea95cb0 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-container.php @@ -0,0 +1,142 @@ + 'Hook_Manager', + ); + + /** + * A map of all the dependencies we've registered with the container and created. + * + * @var array + */ + protected $dependencies; + + /** + * The constructor. + */ + public function __construct() { + $this->dependencies = array(); + + $this->register_shared_dependencies(); + $this->register_dependencies(); + $this->initialize_globals(); + } + + /** + * Gets a dependency out of the container. + * + * @param string $class The class to fetch. + * + * @return mixed + * @throws \InvalidArgumentException When a class that isn't registered with the container is fetched. + */ + public function get( $class ) { + if ( ! isset( $this->dependencies[ $class ] ) ) { + throw new \InvalidArgumentException( "Class '$class' is not registered with the container." ); + } + + return $this->dependencies[ $class ]; + } + + /** + * Registers all of the dependencies that are shared between all instances of the autoloader. + */ + private function register_shared_dependencies() { + global $jetpack_autoloader_container_shared; + if ( ! isset( $jetpack_autoloader_container_shared ) ) { + $jetpack_autoloader_container_shared = array(); + } + + $key = self::SHARED_DEPENDENCY_KEYS[ Hook_Manager::class ]; + if ( ! isset( $jetpack_autoloader_container_shared[ $key ] ) ) { + require_once __DIR__ . '/class-hook-manager.php'; + $jetpack_autoloader_container_shared[ $key ] = new Hook_Manager(); + } + $this->dependencies[ Hook_Manager::class ] = &$jetpack_autoloader_container_shared[ $key ]; + } + + /** + * Registers all of the dependencies with the container. + */ + private function register_dependencies() { + require_once __DIR__ . '/class-path-processor.php'; + $this->dependencies[ Path_Processor::class ] = new Path_Processor(); + + require_once __DIR__ . '/class-plugin-locator.php'; + $this->dependencies[ Plugin_Locator::class ] = new Plugin_Locator( + $this->get( Path_Processor::class ) + ); + + require_once __DIR__ . '/class-version-selector.php'; + $this->dependencies[ Version_Selector::class ] = new Version_Selector(); + + require_once __DIR__ . '/class-autoloader-locator.php'; + $this->dependencies[ Autoloader_Locator::class ] = new Autoloader_Locator( + $this->get( Version_Selector::class ) + ); + + require_once __DIR__ . '/class-php-autoloader.php'; + $this->dependencies[ PHP_Autoloader::class ] = new PHP_Autoloader(); + + require_once __DIR__ . '/class-manifest-reader.php'; + $this->dependencies[ Manifest_Reader::class ] = new Manifest_Reader( + $this->get( Version_Selector::class ) + ); + + require_once __DIR__ . '/class-plugins-handler.php'; + $this->dependencies[ Plugins_Handler::class ] = new Plugins_Handler( + $this->get( Plugin_Locator::class ), + $this->get( Path_Processor::class ) + ); + + require_once __DIR__ . '/class-autoloader-handler.php'; + $this->dependencies[ Autoloader_Handler::class ] = new Autoloader_Handler( + $this->get( PHP_Autoloader::class ), + $this->get( Hook_Manager::class ), + $this->get( Manifest_Reader::class ), + $this->get( Version_Selector::class ) + ); + + require_once __DIR__ . '/class-latest-autoloader-guard.php'; + $this->dependencies[ Latest_Autoloader_Guard::class ] = new Latest_Autoloader_Guard( + $this->get( Plugins_Handler::class ), + $this->get( Autoloader_Handler::class ), + $this->get( Autoloader_Locator::class ) + ); + + // Register any classes that we will use elsewhere. + require_once __DIR__ . '/class-version-loader.php'; + require_once __DIR__ . '/class-shutdown-handler.php'; + } + + /** + * Initializes any of the globals needed by the autoloader. + */ + private function initialize_globals() { + /* + * This global was retired in version 2.9. The value is set to 'false' to maintain + * compatibility with older versions of the autoloader. + */ + global $jetpack_autoloader_including_latest; + $jetpack_autoloader_including_latest = false; + + // Not all plugins can be found using the locator. In cases where a plugin loads the autoloader + // but was not discoverable, we will record them in this array to track them as "active". + global $jetpack_autoloader_activating_plugins_paths; + if ( ! isset( $jetpack_autoloader_activating_plugins_paths ) ) { + $jetpack_autoloader_activating_plugins_paths = array(); + } + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-hook-manager.php b/vendor/automattic/jetpack-autoloader/src/class-hook-manager.php new file mode 100644 index 000000000..1d64c756b --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-hook-manager.php @@ -0,0 +1,68 @@ +registered_hooks = array(); + } + + /** + * Adds an action to WordPress and registers it internally. + * + * @param string $tag The name of the action which is hooked. + * @param callable $callable The function to call. + * @param int $priority Used to specify the priority of the action. + * @param int $accepted_args Used to specify the number of arguments the callable accepts. + */ + public function add_action( $tag, $callable, $priority = 10, $accepted_args = 1 ) { + $this->registered_hooks[ $tag ][] = array( + 'priority' => $priority, + 'callable' => $callable, + ); + + add_action( $tag, $callable, $priority, $accepted_args ); + } + + /** + * Adds a filter to WordPress and registers it internally. + * + * @param string $tag The name of the filter which is hooked. + * @param callable $callable The function to call. + * @param int $priority Used to specify the priority of the filter. + * @param int $accepted_args Used to specify the number of arguments the callable accepts. + */ + public function add_filter( $tag, $callable, $priority = 10, $accepted_args = 1 ) { + $this->registered_hooks[ $tag ][] = array( + 'priority' => $priority, + 'callable' => $callable, + ); + + add_filter( $tag, $callable, $priority, $accepted_args ); + } + + /** + * Removes all of the registered hooks. + */ + public function reset() { + foreach ( $this->registered_hooks as $tag => $hooks ) { + foreach ( $hooks as $hook ) { + remove_filter( $tag, $hook['callable'], $hook['priority'] ); + } + } + $this->registered_hooks = array(); + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-latest-autoloader-guard.php b/vendor/automattic/jetpack-autoloader/src/class-latest-autoloader-guard.php new file mode 100644 index 000000000..285fe739a --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-latest-autoloader-guard.php @@ -0,0 +1,157 @@ +plugins_handler = $plugins_handler; + $this->autoloader_handler = $autoloader_handler; + $this->autoloader_locator = $autoloader_locator; + } + + /** + * Indicates whether or not the autoloader should be initialized. Note that this function + * has the side-effect of actually loading the latest autoloader in the event that this + * is not it. + * + * @param string $current_plugin The current plugin we're checking. + * @param string[] $plugins The active plugins to check for autoloaders in. + * @param bool $was_included_by_autoloader Indicates whether or not this autoloader was included by another. + * + * @return bool True if we should stop initialization, otherwise false. + */ + public function should_stop_init( $current_plugin, $plugins, $was_included_by_autoloader ) { + global $jetpack_autoloader_latest_version; + + // We need to reset the autoloader when the plugins change because + // that means the autoloader was generated with a different list. + if ( $this->plugins_handler->have_plugins_changed( $plugins ) ) { + $this->autoloader_handler->reset_autoloader(); + } + + // When the latest autoloader has already been found we don't need to search for it again. + // We should take care however because this will also trigger if the autoloader has been + // included by an older one. + if ( isset( $jetpack_autoloader_latest_version ) && ! $was_included_by_autoloader ) { + return true; + } + + $latest_plugin = $this->autoloader_locator->find_latest_autoloader( $plugins, $jetpack_autoloader_latest_version ); + if ( isset( $latest_plugin ) && $latest_plugin !== $current_plugin ) { + require $this->autoloader_locator->get_autoloader_path( $latest_plugin ); + return true; + } + + return false; + } + + /** + * Check for conflicting autoloaders. + * + * A common source of strange and confusing problems is when another plugin + * registers a Composer autoloader at a higher priority that us. If enabled, + * check for this problem and warn about it. + * + * Called from the plugins_loaded hook. + * + * @since 3.1.0 + * @return void + */ + public function check_for_conflicting_autoloaders() { + if ( ! defined( 'JETPACK_AUTOLOAD_DEBUG_CONFLICTING_LOADERS' ) || ! JETPACK_AUTOLOAD_DEBUG_CONFLICTING_LOADERS ) { + return; + } + + global $jetpack_autoloader_loader; + if ( ! isset( $jetpack_autoloader_loader ) ) { + return; + } + $prefixes = array(); + foreach ( ( $jetpack_autoloader_loader->get_class_map() ?? array() ) as $classname => $data ) { + $parts = explode( '\\', trim( $classname, '\\' ) ); + array_pop( $parts ); + while ( $parts ) { + $prefixes[ implode( '\\', $parts ) . '\\' ] = true; + array_pop( $parts ); + } + } + foreach ( ( $jetpack_autoloader_loader->get_psr4_map() ?? array() ) as $prefix => $data ) { + $parts = explode( '\\', trim( $prefix, '\\' ) ); + while ( $parts ) { + $prefixes[ implode( '\\', $parts ) . '\\' ] = true; + array_pop( $parts ); + } + } + + $autoload_chain = spl_autoload_functions(); + if ( ! $autoload_chain ) { + return; + } + + foreach ( $autoload_chain as $autoloader ) { + // No need to check anything after us. + if ( is_array( $autoloader ) && is_string( $autoloader[0] ) && substr( $autoloader[0], 0, strlen( __NAMESPACE__ ) + 1 ) === __NAMESPACE__ . '\\' ) { + break; + } + + // We can check Composer autoloaders easily enough. + if ( is_array( $autoloader ) && $autoloader[0] instanceof \Composer\Autoload\ClassLoader && is_callable( array( $autoloader[0], 'getPrefixesPsr4' ) ) ) { + $composer_autoloader = $autoloader[0]; + foreach ( $composer_autoloader->getClassMap() as $classname => $path ) { + if ( $jetpack_autoloader_loader->find_class_file( $classname ) ) { + $msg = "A Composer autoloader is registered with a higher priority than the Jetpack Autoloader and would also handle some of the classes we handle (e.g. $classname => $path). This may cause strange and confusing problems."; + wp_trigger_error( '', $msg ); + continue 2; + } + } + foreach ( $composer_autoloader->getPrefixesPsr4() as $prefix => $paths ) { + if ( isset( $prefixes[ $prefix ] ) ) { + $path = array_pop( $paths ); + $msg = "A Composer autoloader is registered with a higher priority than the Jetpack Autoloader and would also handle some of the namespaces we handle (e.g. $prefix => $path). This may cause strange and confusing problems."; + wp_trigger_error( '', $msg ); + continue 2; + } + } + foreach ( $composer_autoloader->getPrefixes() as $prefix => $paths ) { + if ( isset( $prefixes[ $prefix ] ) ) { + $path = array_pop( $paths ); + $msg = "A Composer autoloader is registered with a higher priority than the Jetpack Autoloader and would also handle some of the namespaces we handle (e.g. $prefix => $path). This may cause strange and confusing problems."; + wp_trigger_error( '', $msg ); + continue 2; + } + } + } + } + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-manifest-reader.php b/vendor/automattic/jetpack-autoloader/src/class-manifest-reader.php new file mode 100644 index 000000000..8eb4825fb --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-manifest-reader.php @@ -0,0 +1,91 @@ +version_selector = $version_selector; + } + + /** + * Reads all of the manifests in the given plugin paths. + * + * @param array $plugin_paths The paths to the plugins we're loading the manifest in. + * @param string $manifest_path The path that we're loading the manifest from in each plugin. + * @param array $path_map The path map to add the contents of the manifests to. + * + * @return array $path_map The path map we've built using the manifests in each plugin. + */ + public function read_manifests( $plugin_paths, $manifest_path, &$path_map ) { + $file_paths = array_map( + function ( $path ) use ( $manifest_path ) { + return trailingslashit( $path ) . $manifest_path; + }, + $plugin_paths + ); + + foreach ( $file_paths as $path ) { + $this->register_manifest( $path, $path_map ); + } + + return $path_map; + } + + /** + * Registers a plugin's manifest file with the path map. + * + * @param string $manifest_path The absolute path to the manifest that we're loading. + * @param array $path_map The path map to add the contents of the manifest to. + */ + protected function register_manifest( $manifest_path, &$path_map ) { + if ( ! is_readable( $manifest_path ) ) { + return; + } + + $manifest = require $manifest_path; + if ( ! is_array( $manifest ) ) { + return; + } + + foreach ( $manifest as $key => $data ) { + $this->register_record( $key, $data, $path_map ); + } + } + + /** + * Registers an entry from the manifest in the path map. + * + * @param string $key The identifier for the entry we're registering. + * @param array $data The data for the entry we're registering. + * @param array $path_map The path map to add the contents of the manifest to. + */ + protected function register_record( $key, $data, &$path_map ) { + if ( isset( $path_map[ $key ]['version'] ) ) { + $selected_version = $path_map[ $key ]['version']; + } else { + $selected_version = null; + } + + if ( $this->version_selector->is_version_update_required( $selected_version, $data['version'] ) ) { + $path_map[ $key ] = array( + 'version' => $data['version'], + 'path' => $data['path'], + ); + } + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-path-processor.php b/vendor/automattic/jetpack-autoloader/src/class-path-processor.php new file mode 100644 index 000000000..4763274ee --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-path-processor.php @@ -0,0 +1,186 @@ +get_normalized_constants(); + foreach ( $constants as $constant => $constant_path ) { + $len = strlen( $constant_path ); + if ( substr( $path, 0, $len ) !== $constant_path ) { + continue; + } + + return substr_replace( $path, '{{' . $constant . '}}', 0, $len ); + } + + return $path; + } + + /** + * Given a path this will replace any of the path constant tokens with the expanded path. + * + * @param string $tokenized_path The path we want to process. + * + * @return string The expanded path. + */ + public function untokenize_path_constants( $tokenized_path ) { + $tokenized_path = wp_normalize_path( $tokenized_path ); + + $constants = $this->get_normalized_constants(); + foreach ( $constants as $constant => $constant_path ) { + $constant = '{{' . $constant . '}}'; + + $len = strlen( $constant ); + if ( substr( $tokenized_path, 0, $len ) !== $constant ) { + continue; + } + + return $this->get_real_path( substr_replace( $tokenized_path, $constant_path, 0, $len ) ); + } + + return $tokenized_path; + } + + /** + * Given a file and an array of places it might be, this will find the absolute path and return it. + * + * @param string $file The plugin or theme file to resolve. + * @param array $directories_to_check The directories we should check for the file if it isn't an absolute path. + * + * @return string|false Returns the absolute path to the directory, otherwise false. + */ + public function find_directory_with_autoloader( $file, $directories_to_check ) { + $file = wp_normalize_path( $file ); + + if ( ! $this->is_absolute_path( $file ) ) { + $file = $this->find_absolute_plugin_path( $file, $directories_to_check ); + if ( ! isset( $file ) ) { + return false; + } + } + + // We need the real path for consistency with __DIR__ paths. + $file = $this->get_real_path( $file ); + + // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged + $directory = @is_file( $file ) ? dirname( $file ) : $file; + if ( ! @is_file( $directory . '/vendor/composer/jetpack_autoload_classmap.php' ) ) { + return false; + } + // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged + + return $directory; + } + + /** + * Fetches an array of normalized paths keyed by the constant they came from. + * + * @return string[] The normalized paths keyed by the constant. + */ + private function get_normalized_constants() { + $raw_constants = array( + // Order the constants from most-specific to least-specific. + 'WP_PLUGIN_DIR', + 'WPMU_PLUGIN_DIR', + 'WP_CONTENT_DIR', + 'ABSPATH', + ); + + $constants = array(); + foreach ( $raw_constants as $raw ) { + if ( ! defined( $raw ) ) { + continue; + } + + $path = wp_normalize_path( constant( $raw ) ); + if ( isset( $path ) ) { + $constants[ $raw ] = $path; + } + } + + return $constants; + } + + /** + * Indicates whether or not a path is absolute. + * + * @param string $path The path to check. + * + * @return bool True if the path is absolute, otherwise false. + */ + private function is_absolute_path( $path ) { + if ( empty( $path ) || 0 === strlen( $path ) || '.' === $path[0] ) { + return false; + } + + // Absolute paths on Windows may begin with a drive letter. + if ( preg_match( '/^[a-zA-Z]:[\/\\\\]/', $path ) ) { + return true; + } + + // A path starting with / or \ is absolute; anything else is relative. + return ( '/' === $path[0] || '\\' === $path[0] ); + } + + /** + * Given a file and a list of directories to check, this method will try to figure out + * the absolute path to the file in question. + * + * @param string $normalized_path The normalized path to the plugin or theme file to resolve. + * @param array $directories_to_check The directories we should check for the file if it isn't an absolute path. + * + * @return string|null The absolute path to the plugin file, otherwise null. + */ + private function find_absolute_plugin_path( $normalized_path, $directories_to_check ) { + // We're only able to find the absolute path for plugin/theme PHP files. + if ( ! is_string( $normalized_path ) || '.php' !== substr( $normalized_path, -4 ) ) { + return null; + } + + foreach ( $directories_to_check as $directory ) { + $normalized_check = wp_normalize_path( trailingslashit( $directory ) ) . $normalized_path; + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + if ( @is_file( $normalized_check ) ) { + return $normalized_check; + } + } + + return null; + } + + /** + * Given a path this will figure out the real path that we should be using. + * + * @param string $path The path to resolve. + * + * @return string The resolved path. + */ + private function get_real_path( $path ) { + // We want to resolve symbolic links for consistency with __DIR__ paths. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + $real_path = @realpath( $path ); + if ( false === $real_path ) { + // Let the autoloader deal with paths that don't exist. + $real_path = $path; + } + + // Using realpath will make it platform-specific so we must normalize it after. + if ( $path !== $real_path ) { + $real_path = wp_normalize_path( $real_path ); + } + + return $real_path; + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-php-autoloader.php b/vendor/automattic/jetpack-autoloader/src/class-php-autoloader.php new file mode 100644 index 000000000..f8765a442 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-php-autoloader.php @@ -0,0 +1,97 @@ +unregister_autoloader(); + + // Set the global so that it can be used to load classes. + global $jetpack_autoloader_loader; + $jetpack_autoloader_loader = $version_loader; + + // Ensure that the autoloader is first to avoid contention with others. + spl_autoload_register( array( self::class, 'load_class' ), true, true ); + } + + /** + * Unregisters the active autoloader so that it will no longer autoload classes. + */ + public function unregister_autoloader() { + // Remove any v2 autoloader that we've already registered. + $autoload_chain = spl_autoload_functions(); + if ( ! $autoload_chain ) { + return; + } + foreach ( $autoload_chain as $autoloader ) { + // We can identify a v2 autoloader using the namespace. + $namespace_check = null; + + // Functions are recorded as strings. + if ( is_string( $autoloader ) ) { + $namespace_check = $autoloader; + } elseif ( is_array( $autoloader ) && is_string( $autoloader[0] ) ) { + // Static method calls have the class as the first array element. + $namespace_check = $autoloader[0]; + } else { + // Since the autoloader has only ever been a function or a static method we don't currently need to check anything else. + continue; + } + + // Check for the namespace without the generated suffix. + if ( 'Automattic\\Jetpack\\Autoloader\\jp' === substr( $namespace_check, 0, 32 ) ) { + spl_autoload_unregister( $autoloader ); + } + } + + // Clear the global now that the autoloader has been unregistered. + global $jetpack_autoloader_loader; + $jetpack_autoloader_loader = null; + } + + /** + * Loads a class file if one could be found. + * + * Note: This function is static so that the autoloader can be easily unregistered. If + * it was a class method we would have to unwrap the object to check the namespace. + * + * @param string $class_name The name of the class to autoload. + * + * @return bool Indicates whether or not a class file was loaded. + */ + public static function load_class( $class_name ) { + global $jetpack_autoloader_loader; + if ( ! isset( $jetpack_autoloader_loader ) ) { + return false; + } + + $file = $jetpack_autoloader_loader->find_class_file( $class_name ); + if ( ! isset( $file ) ) { + return false; + } + + // A common source of strange and confusing problems is when a vendor + // file is autoloaded before all plugins have had a chance to register + // with the autoloader. Detect that, if a development constant is set. + if ( defined( 'JETPACK_AUTOLOAD_DEBUG_EARLY_LOADS' ) && JETPACK_AUTOLOAD_DEBUG_EARLY_LOADS && + ( strpos( $file, '/vendor/' ) !== false || strpos( $file, '/jetpack_vendor/' ) !== false ) && + is_callable( 'did_action' ) && ! did_action( 'plugins_loaded' ) + ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary -- This is a debug log message. + $msg = "Jetpack Autoloader: Autoloading `$class_name` before the plugins_loaded hook may cause strange and confusing problems. " . wp_debug_backtrace_summary( '', 1 ); + wp_trigger_error( '', $msg ); + } + + require $file; + return true; + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-plugin-locator.php b/vendor/automattic/jetpack-autoloader/src/class-plugin-locator.php new file mode 100644 index 000000000..8dafeff50 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-plugin-locator.php @@ -0,0 +1,145 @@ +path_processor = $path_processor; + } + + /** + * Finds the path to the current plugin. + * + * @return string $path The path to the current plugin. + * + * @throws \RuntimeException If the current plugin does not have an autoloader. + */ + public function find_current_plugin() { + // Escape from `vendor/__DIR__` to root plugin directory. + $plugin_directory = dirname( __DIR__, 2 ); + + // Use the path processor to ensure that this is an autoloader we're referencing. + $path = $this->path_processor->find_directory_with_autoloader( $plugin_directory, array() ); + if ( false === $path ) { + throw new \RuntimeException( 'Failed to locate plugin ' . $plugin_directory ); + } + + return $path; + } + + /** + * Checks a given option for plugin paths. + * + * @param string $option_name The option that we want to check for plugin information. + * @param bool $site_option Indicates whether or not we want to check the site option. + * + * @return array $plugin_paths The list of absolute paths we've found. + */ + public function find_using_option( $option_name, $site_option = false ) { + $raw = $site_option ? get_site_option( $option_name ) : get_option( $option_name ); + if ( false === $raw ) { + return array(); + } + + return $this->convert_plugins_to_paths( $raw ); + } + + /** + * Checks for plugins in the `action` request parameter. + * + * @param string[] $allowed_actions The actions that we're allowed to return plugins for. + * + * @return array $plugin_paths The list of absolute paths we've found. + */ + public function find_using_request_action( $allowed_actions ) { + /** + * Note: we're not actually checking the nonce here because it's too early + * in the execution. The pluggable functions are not yet loaded to give + * plugins a chance to plug their versions. Therefore we're doing the bare + * minimum: checking whether the nonce exists and it's in the right place. + * The request will fail later if the nonce doesn't pass the check. + */ + if ( empty( $_REQUEST['_wpnonce'] ) ) { + return array(); + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated just below. + $action = isset( $_REQUEST['action'] ) ? wp_unslash( $_REQUEST['action'] ) : false; + if ( ! in_array( $action, $allowed_actions, true ) ) { + return array(); + } + + $plugin_slugs = array(); + switch ( $action ) { + case 'activate': + case 'deactivate': + if ( empty( $_REQUEST['plugin'] ) ) { + break; + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated by convert_plugins_to_paths. + $plugin_slugs[] = wp_unslash( $_REQUEST['plugin'] ); + break; + + case 'activate-selected': + case 'deactivate-selected': + if ( empty( $_REQUEST['checked'] ) ) { + break; + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated by convert_plugins_to_paths. + $plugin_slugs = wp_unslash( $_REQUEST['checked'] ); + break; + } + + return $this->convert_plugins_to_paths( $plugin_slugs ); + } + + /** + * Given an array of plugin slugs or paths, this will convert them to absolute paths and filter + * out the plugins that are not directory plugins. Note that array keys will also be included + * if they are plugin paths! + * + * @param string[] $plugins Plugin paths or slugs to filter. + * + * @return string[] + */ + private function convert_plugins_to_paths( $plugins ) { + if ( ! is_array( $plugins ) || empty( $plugins ) ) { + return array(); + } + + // We're going to look for plugins in the standard directories. + $path_constants = array( WP_PLUGIN_DIR, WPMU_PLUGIN_DIR ); + + $plugin_paths = array(); + foreach ( $plugins as $key => $value ) { + $path = $this->path_processor->find_directory_with_autoloader( $key, $path_constants ); + if ( $path ) { + $plugin_paths[] = $path; + } + + $path = $this->path_processor->find_directory_with_autoloader( $value, $path_constants ); + if ( $path ) { + $plugin_paths[] = $path; + } + } + + return $plugin_paths; + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-plugins-handler.php b/vendor/automattic/jetpack-autoloader/src/class-plugins-handler.php new file mode 100644 index 000000000..dd00ac121 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-plugins-handler.php @@ -0,0 +1,156 @@ +plugin_locator = $plugin_locator; + $this->path_processor = $path_processor; + } + + /** + * Gets all of the active plugins we can find. + * + * @param bool $include_deactivating When true, plugins deactivating this request will be considered active. + * @param bool $record_unknown When true, the current plugin will be marked as active and recorded when unknown. + * + * @return string[] + */ + public function get_active_plugins( $include_deactivating, $record_unknown ) { + global $jetpack_autoloader_activating_plugins_paths; + + // We're going to build a unique list of plugins from a few different sources + // to find all of our "active" plugins. While we need to return an integer + // array, we're going to use an associative array internally to reduce + // the amount of time that we're going to spend checking uniqueness + // and merging different arrays together to form the output. + $active_plugins = array(); + + // Make sure that plugins which have activated this request are considered as "active" even though + // they probably won't be present in any option. + if ( is_array( $jetpack_autoloader_activating_plugins_paths ) ) { + foreach ( $jetpack_autoloader_activating_plugins_paths as $path ) { + $active_plugins[ $path ] = $path; + } + } + + // This option contains all of the plugins that have been activated. + $plugins = $this->plugin_locator->find_using_option( 'active_plugins' ); + foreach ( $plugins as $path ) { + $active_plugins[ $path ] = $path; + } + + // This option contains all of the multisite plugins that have been activated. + if ( is_multisite() ) { + $plugins = $this->plugin_locator->find_using_option( 'active_sitewide_plugins', true ); + foreach ( $plugins as $path ) { + $active_plugins[ $path ] = $path; + } + } + + // These actions contain plugins that are being activated/deactivated during this request. + $plugins = $this->plugin_locator->find_using_request_action( array( 'activate', 'activate-selected', 'deactivate', 'deactivate-selected' ) ); + foreach ( $plugins as $path ) { + $active_plugins[ $path ] = $path; + } + + // When the current plugin isn't considered "active" there's a problem. + // Since we're here, the plugin is active and currently being loaded. + // We can support this case (mu-plugins and non-standard activation) + // by adding the current plugin to the active list and marking it + // as an unknown (activating) plugin. This also has the benefit + // of causing a reset because the active plugins list has + // been changed since it was saved in the global. + $current_plugin = $this->plugin_locator->find_current_plugin(); + if ( $record_unknown && ! in_array( $current_plugin, $active_plugins, true ) ) { + $active_plugins[ $current_plugin ] = $current_plugin; + $jetpack_autoloader_activating_plugins_paths[] = $current_plugin; + } + + // When deactivating plugins aren't desired we should entirely remove them from the active list. + if ( ! $include_deactivating ) { + // These actions contain plugins that are being deactivated during this request. + $plugins = $this->plugin_locator->find_using_request_action( array( 'deactivate', 'deactivate-selected' ) ); + foreach ( $plugins as $path ) { + unset( $active_plugins[ $path ] ); + } + } + + // Transform the array so that we don't have to worry about the keys interacting with other array types later. + return array_values( $active_plugins ); + } + + /** + * Gets all of the cached plugins if there are any. + * + * @return string[] + */ + public function get_cached_plugins() { + $cached = get_transient( self::TRANSIENT_KEY ); + if ( ! is_array( $cached ) || empty( $cached ) ) { + return array(); + } + + // We need to expand the tokens to an absolute path for this webserver. + return array_map( array( $this->path_processor, 'untokenize_path_constants' ), $cached ); + } + + /** + * Saves the plugin list to the cache. + * + * @param array $plugins The plugin list to save to the cache. + */ + public function cache_plugins( $plugins ) { + // We store the paths in a tokenized form so that that webservers with different absolute paths don't break. + $plugins = array_map( array( $this->path_processor, 'tokenize_path_constants' ), $plugins ); + + set_transient( self::TRANSIENT_KEY, $plugins ); + } + + /** + * Checks to see whether or not the plugin list given has changed when compared to the + * shared `$jetpack_autoloader_cached_plugin_paths` global. This allows us to deal + * with cases where the active list may change due to filtering.. + * + * @param string[] $plugins The plugins list to check against the global cache. + * + * @return bool True if the plugins have changed, otherwise false. + */ + public function have_plugins_changed( $plugins ) { + global $jetpack_autoloader_cached_plugin_paths; + + if ( $jetpack_autoloader_cached_plugin_paths !== $plugins ) { + $jetpack_autoloader_cached_plugin_paths = $plugins; + return true; + } + + return false; + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-shutdown-handler.php b/vendor/automattic/jetpack-autoloader/src/class-shutdown-handler.php new file mode 100644 index 000000000..198b19c6f --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-shutdown-handler.php @@ -0,0 +1,84 @@ +plugins_handler = $plugins_handler; + $this->cached_plugins = $cached_plugins; + $this->was_included_by_autoloader = $was_included_by_autoloader; + } + + /** + * Handles the shutdown of the autoloader. + */ + public function __invoke() { + // Don't save a broken cache if an error happens during some plugin's initialization. + if ( ! did_action( 'plugins_loaded' ) ) { + // Ensure that the cache is emptied to prevent consecutive failures if the cache is to blame. + if ( ! empty( $this->cached_plugins ) ) { + $this->plugins_handler->cache_plugins( array() ); + } + + return; + } + + // Load the active plugins fresh since the list we pulled earlier might not contain + // plugins that were activated but did not reset the autoloader. This happens + // when a plugin is in the cache but not "active" when the autoloader loads. + // We also want to make sure that plugins which are deactivating are not + // considered "active" so that they will be removed from the cache now. + try { + $active_plugins = $this->plugins_handler->get_active_plugins( false, ! $this->was_included_by_autoloader ); + } catch ( \Exception $ex ) { + // When the package is deleted before shutdown it will throw an exception. + // In the event this happens we should erase the cache. + if ( ! empty( $this->cached_plugins ) ) { + $this->plugins_handler->cache_plugins( array() ); + } + return; + } + + // The paths should be sorted for easy comparisons with those loaded from the cache. + // Note we don't need to sort the cached entries because they're already sorted. + sort( $active_plugins ); + + // We don't want to waste time saving a cache that hasn't changed. + if ( $this->cached_plugins === $active_plugins ) { + return; + } + + $this->plugins_handler->cache_plugins( $active_plugins ); + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-version-loader.php b/vendor/automattic/jetpack-autoloader/src/class-version-loader.php new file mode 100644 index 000000000..cc7dcd6d4 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-version-loader.php @@ -0,0 +1,176 @@ +version_selector = $version_selector; + $this->classmap = $classmap; + $this->psr4_map = $psr4_map; + $this->filemap = $filemap; + } + + /** + * Fetch the classmap. + * + * @since 3.1.0 + * @return array + */ + public function get_class_map() { + return $this->classmap; + } + + /** + * Fetch the psr-4 mappings. + * + * @since 3.1.0 + * @return array + */ + public function get_psr4_map() { + return $this->psr4_map; + } + + /** + * Finds the file path for the given class. + * + * @param string $class_name The class to find. + * + * @return string|null $file_path The path to the file if found, null if no class was found. + */ + public function find_class_file( $class_name ) { + $data = $this->select_newest_file( + $this->classmap[ $class_name ] ?? null, + $this->find_psr4_file( $class_name ) + ); + if ( ! isset( $data ) ) { + return null; + } + + return $data['path']; + } + + /** + * Load all of the files in the filemap. + */ + public function load_filemap() { + if ( empty( $this->filemap ) ) { + return; + } + + foreach ( $this->filemap as $file_identifier => $file_data ) { + if ( empty( $GLOBALS['__composer_autoload_files'][ $file_identifier ] ) ) { + require_once $file_data['path']; + + $GLOBALS['__composer_autoload_files'][ $file_identifier ] = true; + } + } + } + + /** + * Compares different class sources and returns the newest. + * + * @param array|null $classmap_data The classmap class data. + * @param array|null $psr4_data The PSR-4 class data. + * + * @return array|null $data + */ + private function select_newest_file( $classmap_data, $psr4_data ) { + if ( ! isset( $classmap_data ) ) { + return $psr4_data; + } elseif ( ! isset( $psr4_data ) ) { + return $classmap_data; + } + + if ( $this->version_selector->is_version_update_required( $classmap_data['version'], $psr4_data['version'] ) ) { + return $psr4_data; + } + + return $classmap_data; + } + + /** + * Finds the file for a given class in a PSR-4 namespace. + * + * @param string $class_name The class to find. + * + * @return array|null $data The version and path path to the file if found, null otherwise. + */ + private function find_psr4_file( $class_name ) { + if ( empty( $this->psr4_map ) ) { + return null; + } + + // Don't bother with classes that have no namespace. + $class_index = strrpos( $class_name, '\\' ); + if ( ! $class_index ) { + return null; + } + $class_for_path = str_replace( '\\', '/', $class_name ); + + // Search for the namespace by iteratively cutting off the last segment until + // we find a match. This allows us to check the most-specific namespaces + // first as well as minimize the amount of time spent looking. + for ( + $class_namespace = substr( $class_name, 0, $class_index ); + ! empty( $class_namespace ); + $class_namespace = substr( $class_namespace, 0, strrpos( $class_namespace, '\\' ) ) + ) { + $namespace = $class_namespace . '\\'; + if ( ! isset( $this->psr4_map[ $namespace ] ) ) { + continue; + } + $data = $this->psr4_map[ $namespace ]; + + foreach ( $data['path'] as $path ) { + $path .= '/' . substr( $class_for_path, strlen( $namespace ) ) . '.php'; + if ( file_exists( $path ) ) { + return array( + 'version' => $data['version'], + 'path' => $path, + ); + } + } + } + + return null; + } +} diff --git a/vendor/automattic/jetpack-autoloader/src/class-version-selector.php b/vendor/automattic/jetpack-autoloader/src/class-version-selector.php new file mode 100644 index 000000000..5b201ff92 --- /dev/null +++ b/vendor/automattic/jetpack-autoloader/src/class-version-selector.php @@ -0,0 +1,61 @@ +is_dev_version( $selected_version ) ) { + return false; + } + + if ( $this->is_dev_version( $compare_version ) ) { + if ( $use_dev_versions ) { + return true; + } else { + return false; + } + } + + if ( version_compare( $selected_version, $compare_version, '<' ) ) { + return true; + } + + return false; + } + + /** + * Checks whether the given package version is a development version. + * + * @param String $version The package version. + * + * @return bool True if the version is a dev version, else false. + */ + public function is_dev_version( $version ) { + if ( 'dev-' === substr( $version, 0, 4 ) || '9999999-dev' === $version ) { + return true; + } + + return false; + } +} diff --git a/vendor/bin/composer b/vendor/bin/composer new file mode 100755 index 000000000..fd55d73eb --- /dev/null +++ b/vendor/bin/composer @@ -0,0 +1,119 @@ +#!/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_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/composer/composer/bin/composer'); + } +} + +return include __DIR__ . '/..'.'/composer/composer/bin/composer'; diff --git a/vendor/bin/jsonlint b/vendor/bin/jsonlint new file mode 100755 index 000000000..ca6e04d4a --- /dev/null +++ b/vendor/bin/jsonlint @@ -0,0 +1,119 @@ +#!/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_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/seld/jsonlint/bin/jsonlint'); + } +} + +return include __DIR__ . '/..'.'/seld/jsonlint/bin/jsonlint'; diff --git a/vendor/bin/php-parse b/vendor/bin/php-parse new file mode 100755 index 000000000..61566e60c --- /dev/null +++ b/vendor/bin/php-parse @@ -0,0 +1,119 @@ +#!/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_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'); + } +} + +return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'; diff --git a/vendor/bin/strauss b/vendor/bin/strauss new file mode 100755 index 000000000..b675c5c59 --- /dev/null +++ b/vendor/bin/strauss @@ -0,0 +1,119 @@ +#!/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_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/brianhenryie/strauss/bin/strauss'); + } +} + +return include __DIR__ . '/..'.'/brianhenryie/strauss/bin/strauss'; diff --git a/vendor/bin/validate-json b/vendor/bin/validate-json new file mode 100755 index 000000000..8be90f428 --- /dev/null +++ b/vendor/bin/validate-json @@ -0,0 +1,119 @@ +#!/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_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/justinrainbow/json-schema/bin/validate-json'); + } +} + +return include __DIR__ . '/..'.'/justinrainbow/json-schema/bin/validate-json'; diff --git a/vendor/brianhenryie/strauss/CHANGELOG.md b/vendor/brianhenryie/strauss/CHANGELOG.md new file mode 100644 index 000000000..736e65d1f --- /dev/null +++ b/vendor/brianhenryie/strauss/CHANGELOG.md @@ -0,0 +1,55 @@ +# Change Log + +## 0.19.5 October 2024 + +* Fix: `use GlobalClass as Alias;` not prefixed +* Add: `.gitattributes` file to exclude dev files from distribution +* CI: Fail releases if `bin/strauss` version number is out of sync +* Tests: Add first tests for `DiscoveredFiles.php` +* Improve `README.md` +* Fix: typos in code + +## 0.19.4 October 2024 + +* Fix: out of sync version number in `bin/strauss` + +## 0.19.3 October 2024 + +* Fix: handle `@` symbol for error suppression +* Fix: handle `preg_replace...` returning `null` in `Licenser` +* Fix: only search for symbols in PHP files + +## 0.19.2 June 2024 + +* Fix: available CLI arguments were overwriting extra.strauss config +* Fix: updating `league/flysystem` changed the default directory permissions + +## 0.19.1 April 2024 + +* Fix: was incorrectly deleting autoload keys from installed.json + +## 0.19.0 April 2024 + +* Fix: check for array before loop +* Fix: filepaths on Windows (still work to do for Windows) +* Update: tidy `bin/strauss` +* Run tests with project classes + with built phar +* Allow `symfony/console` & `symfony/finder` `^7` for Laravel 11 compatibility +* Add: `scripts/createphar.sh` +* Lint: most PhpStan level 7 + +## 0.18.0 April 2024 + +* Add: GitHub Action to update bin version number from CHANGELOG.md +* Fix: casting a namespaced class to a string +* Fix: composer dump-autoload error after delete-vendor-files/delete-vendor-packages +* Fix: add missing built-in PHP interfaces to exclude rules +* Fix: Undefined offset when seeing namespace +* Refactoring for clarity and pending issues + +## 0.14.0 07-March-2023 + +* Merge `in-situ` branch (bugs expected) +* Add: `delete_vendor_packages` option (`delete_vendor_files` is maybe deprecated now) +* Add: GPG .phar signing for Phive +* Breaking: Stop excluding `psr/*` from `file_patterns` prefixing diff --git a/vendor/brianhenryie/strauss/LICENSE b/vendor/brianhenryie/strauss/LICENSE new file mode 100644 index 000000000..2f9c25703 --- /dev/null +++ b/vendor/brianhenryie/strauss/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Coen Jacobs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/brianhenryie/strauss/README.md b/vendor/brianhenryie/strauss/README.md new file mode 100644 index 000000000..a7ddad2b5 --- /dev/null +++ b/vendor/brianhenryie/strauss/README.md @@ -0,0 +1,270 @@ +[![PHPUnit ](.github/coverage.svg)](https://brianhenryie.github.io/strauss/) [![PHPStan ](https://img.shields.io/badge/PHPStan-Level%207-2a5ea7.svg)](https://phpstan.org/) + +# Strauss – PHP Namespace Renamer + +A tool to prefix namespaces, classnames, and constants in PHP files to avoid autoloading collisions. + +A fork of [Mozart](https://github.com/coenjacobs/mozart/) for [Composer](https://getcomposer.org/) for PHP. + +Have you ever activated a WordPress plugin that has a conflict with another because the plugins use two different versions of the same PHP library? **Strauss is the solution to that problem** - it ensures that _your_ plugin's PHP dependencies are isolated and loaded from your plugin rather than loading from whichever plugin's autoloader registers & runs first. + +> ⚠️ **Sponsorship**: I don't want your money. [Please write a unit test to help the project](https://brianhenryie.github.io/strauss/). + +## Table of Contents + +* [Installation](#installation) + * [As a `.phar` file](#as-a-phar-file-within-composerjson-recommended) (recommended) + * [As a dev dependency via composer](#as-a-dev-dependency-via-composer-not-recommended) (not recommended) + * [Adding `composer dump-autoload`](#adding-composer-dump-autoload) +* [Usage](#usage) +* [Configuration](#configuration) +* [Autoloading](#autoloading) +* [Motivation & Comparison to Mozart](#motivation--comparison-to-mozart) +* [Alternatives](#alternatives) +* [Breaking Changes](#breaking-changes) +* [Acknowledgements](#acknowledgements) + +## Installation + +### As a `.phar` file (recommended) + +There are a couple of small steps to make this possible. + +#### Create a `bin/.gitkeep` file + +This ensures that there is a `bin/` directory in the root of your project. This is where the `.phar` file will go. + +```bash +mkdir bin +touch bin/.gitkeep +``` + +#### `.gitignore` the `.phar` file + +Add the following to your `.gitignore`: + +```bash +bin/strauss.phar +``` + +#### Edit `composer.json` `scripts + +In your `composer.json`, add `strauss` to the `scripts` section: + +```json +"scripts": { + "prefix-namespaces": [ + "sh -c 'test -f ./bin/strauss.phar || curl -o bin/strauss.phar -L -C - https://github.com/BrianHenryIE/strauss/releases/latest/download/strauss.phar'", + "@php bin/strauss.phar", + "@php composer dump-autoload" + ], + "post-install-cmd": [ + "@prefix-namespaces" + ], + "post-update-cmd": [ + "@prefix-namespaces" + ] +} +``` + +This provides `composer strauss`, which does the following: + +1. The `sh -c` command tests if `bin/strauss.phar` exists, and if not, downloads it from [releases](https://github.com/BrianHenryIE/strauss/releases). +2. Then `@php bin/strauss.phar` is run to prefix the namespaces. +3. Ensure that composer's autoload map is updated. + +### As a dev dependency via composer (not recommended) + +If you prefer to include Strauss as a dev dependency, you can still do so. You mileage may vary when you include it this way. + +``` +composer require --dev brianhenryie/strauss +``` + +#### Edit `composer.json` `scripts + +```json +"scripts": { + "prefix-namespaces": [ + "strauss", + "@php composer dump-autoload" + ], + "post-install-cmd": [ + "@prefix-namespaces" + ], + "post-update-cmd": [ + "@prefix-namespaces" + ] +} +``` + +## Usage + +If you add Strauss to your `composer.json` as indicated in [Installation](#installation), it will run when you `composer install` or `composer update`. To run Strauss directly, simply use: + +```bash +composer prefix-namespaces +``` + +To update the files that call the prefixed classes, you can use `--updateCallSites=true` which uses your autoload key, or `--updateCallSites=includes,templates` to explicitly specify the files and directories. + +```bash +composer -- prefix-namespaces --updateCallSites=true +``` + +or + +```bash +composer -- prefix-namespaces --updateCallSites=includes,templates +``` + +## Configuration + +Strauss potentially requires zero configuration, but likely you'll want to customize a little, by adding in your `composer.json` an `extra/strauss` object. The following is the default config, where the `namespace_prefix` and `classmap_prefix` are determined from your `composer.json`'s `autoload` or `name` key and `packages` is determined from the `require` key: + +```json +"extra": { + "strauss": { + "target_directory": "vendor-prefixed", + "namespace_prefix": "BrianHenryIE\\My_Project\\", + "classmap_prefix": "BrianHenryIE_My_Project_", + "constant_prefix": "BHMP_", + "packages": [ + ], + "update_call_sites": false, + "override_autoload": { + }, + "exclude_from_copy": { + "packages": [ + ], + "namespaces": [ + ], + "file_patterns": [ + ] + }, + "exclude_from_prefix": { + "packages": [ + ], + "namespaces": [ + ], + "file_patterns": [ + ] + }, + "namespace_replacement_patterns" : { + }, + "delete_vendor_packages": false, + "delete_vendor_files": false + } +}, +``` + +The following configuration is inferred: + +- `target_directory` defines the directory the files will be copied to, default `vendor-prefixed` +- `namespace_prefix` defines the default string to prefix each namespace with +- `classmap_prefix` defines the default string to prefix class names in the global namespace +- `packages` is the list of packages to process. If absent, all packages in the `require` key of your `composer.json` are included +- `classmap_output` is a `bool` to decide if Strauss will create `autoload-classmap.php` and `autoload.php`. If it is not set, it is `false` if `target_directory` is in your project's `autoload` key, `true` otherwise. + +The following configuration is default: + +- `delete_vendor_packages`: `false` a boolean flag to indicate if the packages' vendor directories should be deleted after being processed. It defaults to false, so any destructive change is opt-in. +- `delete_vendor_files`: `false` a boolean flag to indicate if files copied from the packages' vendor directories should be deleted after being processed. It defaults to false, so any destructive change is opt-in. This is maybe deprecated! Is there any use to this that is more appropriate than `delete_vendor_packages`? +- `include_modified_date` is a `bool` to decide if Strauss should include a date in the (phpdoc) header written to modified files. Defaults to `true`. +- `include_author` is a `bool` to decide if Strauss should include the author name in the (phpdoc) header written to modified files. Defaults to `true`. + + +- `update_call_sites`: `false`. This can be `true`, `false` or an `array` of directories/filepaths. When set to `true` it defaults to the directories and files in the project's `autoload` key. The PHP files and directories' PHP files will be updated where they call the prefixed classes. + +The remainder is empty: + +- `constant_prefix` is for `define( "A_CONSTANT", value );` -> `define( "MY_PREFIX_A_CONSTANT", value );`. If it is empty, constants are not prefixed (this may change to an inferred value). +- `override_autoload` a dictionary, keyed with the package names, of autoload settings to replace those in the original packages' `composer.json` `autoload` property. +- `exclude_from_prefix` / [`file_patterns`](https://github.com/BrianHenryIE/strauss/blob/83484b79cfaa399bba55af0bf4569c24d6eb169d/src/ChangeEnumerator.php#L92-L96) +- `exclude_from_copy` + - [`packages`](https://github.com/BrianHenryIE/strauss/blob/83484b79cfaa399bba55af0bf4569c24d6eb169d/src/FileEnumerator.php#L77-L79) array of package names to be skipped + - [`namespaces`](https://github.com/BrianHenryIE/strauss/blob/83484b79cfaa399bba55af0bf4569c24d6eb169d/src/FileEnumerator.php#L95-L97) array of namespaces to skip (exact match from the package autoload keys) + - [`file_patterns`](https://github.com/BrianHenryIE/strauss/blob/83484b79cfaa399bba55af0bf4569c24d6eb169d/src/FileEnumerator.php#L133-L137) array of regex patterns to check filenames against (including vendor relative path) where Strauss will skip that file if there is a match +- `exclude_from_prefix` + - [`packages`](https://github.com/BrianHenryIE/strauss/blob/83484b79cfaa399bba55af0bf4569c24d6eb169d/src/ChangeEnumerator.php#L86-L90) array of package names to exclude from prefixing. + - [`namespaces`](https://github.com/BrianHenryIE/strauss/blob/83484b79cfaa399bba55af0bf4569c24d6eb169d/src/ChangeEnumerator.php#L177-L181) array of exact match namespaces to exclude (i.e. not substring/parent namespaces) +- [`namespace_replacement_patterns`](https://github.com/BrianHenryIE/strauss/blob/83484b79cfaa399bba55af0bf4569c24d6eb169d/src/ChangeEnumerator.php#L183-L190) a dictionary to use in `preg_replace` instead of prefixing with `namespace_prefix`. + +## Autoloading + +Strauss uses Composer's own tools to generate a classmap file in the `target_directory` and creates an `autoload.php` alongside it, so in many projects autoloading is just a matter of: + +```php +require_once __DIR__ . '/strauss/autoload.php'; +``` + +If you prefer to use Composer's autoloader, add your `target_directory` (default `vendor-prefixed`) to your `autoload` `classmap` and Strauss will not create its own `autoload.php` when run. Then run `composer dump-autoload` to include the newly copied and prefixed files in Composer's own classmap. + +``` +"autoload": { + "classmap": [ + "vendor-prefixed/" + ] +}, +``` + +## Motivation & Comparison to Mozart + +I was happy to make PRs to Mozart to fix bugs, but they weren't being reviewed and merged. At the time of writing, somewhere approaching 50% of Mozart's code [was written by me](https://github.com/coenjacobs/mozart/graphs/contributors) with an additional [nine open PRs](https://github.com/coenjacobs/mozart/pulls?q=is%3Apr+author%3ABrianHenryIE+) and the majority of issues' solutions [provided by me](https://github.com/coenjacobs/mozart/issues?q=is%3Aissue+). This fork is a means to merge all outstanding bugfixes I've written and make some more drastic changes I see as a better approach to the problem. + +Benefits over Mozart: + +* A single output directory whose structure matches source vendor directory structure (conceptually easier than Mozart's independent `classmap_directory` and `dep_directory`) +* A generated `autoload.php` to `include` in your project (analogous to Composer's `vendor/autoload.php`) +* Handles `files` autoloaders – and any autoloaders that Composer itself recognises, since Strauss uses Composer's own tooling to parse the packages +* Zero configuration – Strauss infers sensible defaults from your `composer.json` +* No destructive defaults – `delete_vendor_files` defaults to `false`, so any destruction is explicitly opt-in +* Licence files are included and PHP file headers are edited to adhere to licence requirements around modifications. My understanding is that re-distributing code that Mozart has handled is non-compliant with most open source licences – illegal! +* Extensively tested – PhpUnit tests have been written to validate that many of Mozart's bugs are not present in Strauss +* More configuration options – allowing exclusions in copying and editing files, and allowing specific/multiple namespace renaming +* Respects `composer.json` `vendor-dir` configuration +* Prefixes constants (`define`) +* Handles meta-packages and virtual-packages + +Strauss will read the Mozart configuration from your `composer.json` to enable a seamless migration. + +## Alternatives + +I don't have a strong opinion on these. I began using Mozart because it was easy, then I adapted it to what I felt was most natural. I've never used these. + +* [humbug/php-scoper](https://github.com/humbug/php-scoper) +* [TypistTech/imposter-plugin](https://github.com/TypistTech/imposter-plugin) +* [Automattic/jetpack-autoloader](https://github.com/Automattic/jetpack-autoloader) +* [tschallacka/wordpress-composer-plugin-builder](https://github.com/tschallacka/wordpress-composer-plugin-builder) +* [Interfacelab/namespacer](https://github.com/Interfacelab/namespacer) +* [PHP-Prefixer](https://github.com/PHP-Prefixer) SaaS! + +### Interesting + +* [composer-unused/composer-unused](https://github.com/composer-unused/composer-unused) +* [sdrobov/autopsr4](https://github.com/sdrobov/autopsr4) +* [jaem3l/unfuck](https://github.com/jaem3l/unfuck) + +## Breaking Changes + +* v0.16.0 – will no longer prefix PHP built-in classes seen in polyfill packages +* v0.14.0 – `psr/*` packages no longer excluded by default +* v0.12.0 – default output `target_directory` changes from `strauss` to `vendor-prefixed` + +Please open issues to suggest possible breaking changes. I think we can probably move to 1.0.0 soon. + +## Changes before v1.0 + +* Comprehensive attribution of code forked from Mozart – changes have been drastic and `git blame` is now useless, so I intend to add more attributions +* More consistent naming. Are we prefixing or are we renaming? +* Further unit tests, particularly file-system related +* Regex patterns in config need to be validated +* Change the name? "Renamespacer"? + +## Changes before v2.0 + +The correct approach to this problem is probably via [PHP-Parser](https://github.com/nikic/PHP-Parser/). At least all the tests will be useful. + +## Acknowledgements + +[Coen Jacobs](https://github.com/coenjacobs/) and all the [contributors to Mozart](https://github.com/coenjacobs/mozart/graphs/contributors), particularly those who wrote nice issues. diff --git a/vendor/brianhenryie/strauss/bin/strauss b/vendor/brianhenryie/strauss/bin/strauss new file mode 100755 index 000000000..eb34edc8e --- /dev/null +++ b/vendor/brianhenryie/strauss/bin/strauss @@ -0,0 +1,36 @@ +#!/usr/bin/env php +run(); +}, '0.19.5'); diff --git a/vendor/brianhenryie/strauss/composer.json b/vendor/brianhenryie/strauss/composer.json new file mode 100644 index 000000000..3355d1a6d --- /dev/null +++ b/vendor/brianhenryie/strauss/composer.json @@ -0,0 +1,73 @@ +{ + "name": "brianhenryie/strauss", + "description": "Composes all dependencies as a package inside a WordPress plugin", + "authors": [ + { + "name": "Brian Henry", + "email": "BrianHenryIE@gmail.com" + }, + { + "name": "Coen Jacobs", + "email": "coenjacobs@gmail.com" + } + ], + "bin": ["bin/strauss"], + "minimum-stability": "dev", + "prefer-stable": true, + "license": "MIT", + "require": { + "composer/composer": "*", + "json-mapper/json-mapper": "^2.2", + "symfony/console": "^4|^5|^6|^7", + "symfony/finder": "^4|^5|^6|^7", + "league/flysystem": "^2.1|^3.0" + }, + "autoload": { + "psr-4": { + "BrianHenryIE\\Strauss\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "BrianHenryIE\\Strauss\\Tests\\": "tests/", + "BrianHenryIE\\Strauss\\": "tests/" + } + }, + "require-dev": { + "brianhenryie/php-diff-test": "dev-master", + "clue/phar-composer": "^1.2", + "ext-json": "*", + "jaschilz/php-coverage-badger": "^2.0", + "mheap/phpunit-github-actions-printer": "^1.4", + "php": "^7.4|^8.0", + "phpunit/phpunit": "^9|^10", + "squizlabs/php_codesniffer": "^3.5", + "phpstan/phpstan": "^1.10", + "mockery/mockery": "^1.6" + }, + "scripts": { + "cs": [ + "phpcs", + "phpstan" + ], + "cs-fix": [ + "phpcbf || true", + "@cs" + ], + "test": [ + "phpunit" + ], + "test-coverage": [ + "phpunit --coverage-text --coverage-php tests/reports/phpunit.cov" + ] + }, + "replace":{ + "coenjacobs/mozart": "*" + }, + "repositories": { + "brianhenryie/php-diff-test": { + "type": "git", + "url": "https://github.com/brianhenryie/php-diff-test" + } + } +} diff --git a/vendor/brianhenryie/strauss/src/Autoload.php b/vendor/brianhenryie/strauss/src/Autoload.php new file mode 100644 index 000000000..c51e93683 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Autoload.php @@ -0,0 +1,195 @@ +> $discoveredFilesAutoloaders Array of packagePath => array of relativeFilePaths. + */ + protected array $discoveredFilesAutoloaders; + + /** + * Autoload constructor. + * @param StraussConfig $config + * @param string $workingDir + * @param array> $discoveredFilesAutoloaders + */ + public function __construct(StraussConfig $config, string $workingDir, array $discoveredFilesAutoloaders) + { + $this->config = $config; + $this->workingDir = $workingDir; + $this->discoveredFilesAutoloaders = $discoveredFilesAutoloaders; + + $this->filesystem = new Filesystem(new LocalFilesystemAdapter($workingDir)); + } + + public function generate(): void + { + // Do not overwrite Composer's autoload.php. + // The correct solution is to add "classmap": ["vendor"] to composer.json, then run composer dump-autoload. + if ($this->config->getTargetDirectory() === $this->config->getVendorDirectory()) { + return; + } + + if (! $this->config->isClassmapOutput()) { + return; + } + + // TODO Don't do this if vendor is the target dir (i.e. in-situ updating). + + $this->generateClassmap(); + + $this->generateFilesAutoloader(); + + $this->generateAutoloadPhp(); + } + + /** + * Uses Composer's `ClassMapGenerator::createMap()` to scan the directories for classes and generate the map. + * + * createMap() returns the full local path, so we then replace the root of the path with a variable. + * + * @see ClassMapGenerator::dump() + * + */ + protected function generateClassmap(): void + { + + // Hyphen used to match WordPress Coding Standards. + $output_filename = "autoload-classmap.php"; + + $targetDirectory = getcwd() + . DIRECTORY_SEPARATOR + . ltrim($this->config->getTargetDirectory(), DIRECTORY_SEPARATOR); + + $dirs = array( + $targetDirectory + ); + + foreach ($dirs as $dir) { + if (!is_dir($dir)) { + continue; + } + + $dirMap = ClassMapGenerator::createMap($dir); + + array_walk( + $dirMap, + function (&$filepath, $_class) use ($dir) { + $filepath = "\$strauss_src . '" + . DIRECTORY_SEPARATOR + . ltrim(str_replace($dir, '', $filepath), DIRECTORY_SEPARATOR) . "'"; + } + ); + + ob_start(); + + echo " $file) { + // Always use `/` in paths. + $file = str_replace(DIRECTORY_SEPARATOR, '/', $file); + echo " '{$class}' => {$file},\n"; + } + echo ");"; + + file_put_contents($dir . $output_filename, ob_get_clean()); + } + } + + protected function generateFilesAutoloader(): void + { + + // Hyphen used to match WordPress Coding Standards. + $outputFilename = "autoload-files.php"; + + $filesAutoloaders = $this->discoveredFilesAutoloaders; + + if (empty($filesAutoloaders)) { + return; + } + + $targetDirectory = getcwd() + . DIRECTORY_SEPARATOR + . ltrim($this->config->getTargetDirectory(), DIRECTORY_SEPARATOR); + +// $dirname = preg_replace('/[^a-z]/i', '', str_replace(getcwd(), '', $targetDirectory)); + + ob_start(); + + echo " $files) { + foreach ($files as $file) { + $filepath = DIRECTORY_SEPARATOR . $packagePath . DIRECTORY_SEPARATOR . $file; + $filePathinfo = pathinfo(__DIR__ . $filepath); + if (!isset($filePathinfo['extension']) || 'php' !== $filePathinfo['extension']) { + continue; + } + // Always use `/` in paths. + $filepath = str_replace(DIRECTORY_SEPARATOR, '/', $filepath); + echo "require_once __DIR__ . '{$filepath}';\n"; + } + } + + file_put_contents($targetDirectory . $outputFilename, ob_get_clean()); + } + + protected function generateAutoloadPhp(): void + { + + $autoloadPhp = <<<'EOD' +config->getTargetDirectory() . 'autoload.php'; + $absoluteFilepath = $this->workingDir . $relativeFilepath; + + file_put_contents($absoluteFilepath, $autoloadPhp); + } +} diff --git a/vendor/brianhenryie/strauss/src/ChangeEnumerator.php b/vendor/brianhenryie/strauss/src/ChangeEnumerator.php new file mode 100644 index 000000000..3719978fa --- /dev/null +++ b/vendor/brianhenryie/strauss/src/ChangeEnumerator.php @@ -0,0 +1,69 @@ +config = $config; + $this->workingDir = $workingDir; + + $absoluteTargetDir = $workingDir . $config->getTargetDirectory(); + } + + public function determineReplacements(DiscoveredSymbols $discoveredSymbols): void + { + foreach ($discoveredSymbols->getSymbols() as $symbol) { + if (in_array( + $symbol->getFile()->getDependency()->getPackageName(), + $this->config->getExcludePackagesFromPrefixing(), + true + ) + ) { + continue; + } + + foreach ($this->config->getExcludeFilePatternsFromPrefixing() as $excludeFilePattern) { + if (1 === preg_match($excludeFilePattern, $symbol->getFile()->getTargetRelativePath())) { + continue 2; + } + } + + if ($symbol instanceof NamespaceSymbol) { + // Don't double-prefix namespaces. + if (str_starts_with($symbol->getOriginalSymbol(), $this->config->getNamespacePrefix())) { + continue; + } + + foreach ($this->config->getNamespaceReplacementPatterns() as $namespaceReplacementPattern => $replacement) { + $prefixed = preg_replace($namespaceReplacementPattern, $replacement, $symbol->getOriginalSymbol()); + + if ($prefixed !== $symbol->getOriginalSymbol()) { + $symbol->setReplacement($prefixed); + continue 2; + } + } + + $prefixed = "{$this->config->getNamespacePrefix()}\\{$symbol->getOriginalSymbol()}"; + $symbol->setReplacement($prefixed); + } + + if ($symbol instanceof ClassSymbol) { + // Don't double-prefix classnames. + if (str_starts_with($symbol->getOriginalSymbol(), $this->config->getClassmapPrefix())) { + continue; + } + + $symbol->setReplacement($this->config->getClassmapPrefix() . $symbol->getOriginalSymbol()); + } + } + } +} diff --git a/vendor/brianhenryie/strauss/src/Cleanup.php b/vendor/brianhenryie/strauss/src/Cleanup.php new file mode 100644 index 000000000..232909152 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Cleanup.php @@ -0,0 +1,271 @@ +vendorDirectory = $config->getVendorDirectory(); + $this->targetDirectory = $config->getTargetDirectory(); + $this->workingDir = $workingDir; + + $this->isDeleteVendorFiles = $config->isDeleteVendorFiles() && $config->getTargetDirectory() !== $config->getVendorDirectory(); + $this->isDeleteVendorPackages = $config->isDeleteVendorPackages() && $config->getTargetDirectory() !== $config->getVendorDirectory(); + + $this->filesystem = new Filesystem(new LocalFilesystemAdapter($workingDir)); + } + + /** + * Maybe delete the source files that were copied (depending on config), + * then delete empty directories. + * + * @param string[] $sourceFiles Relative filepaths. + */ + public function cleanup(array $sourceFiles): void + { + if (!$this->isDeleteVendorPackages && !$this->isDeleteVendorFiles) { + return; + } + + if ($this->isDeleteVendorPackages) { + $package_dirs = array_unique(array_map(function (string $relativeFilePath): string { + list( $vendor, $package ) = explode('/', $relativeFilePath); + return "{$vendor}/{$package}"; + }, $sourceFiles)); + + foreach ($package_dirs as $package_dir) { + $relativeDirectoryPath = $this->vendorDirectory . $package_dir; + + $absolutePath = $this->workingDir . $relativeDirectoryPath; + + if ($absolutePath !== realpath($absolutePath)) { + if (false !== strpos('WIN', PHP_OS)) { + /** + * `unlink()` will not work on Windows. `rmdir()` will not work if there are files in the directory. + * "On windows, take care that `is_link()` returns false for Junctions." + * + * @see https://www.php.net/manual/en/function.is-link.php#113263 + * @see https://stackoverflow.com/a/18262809/336146 + */ + rmdir($absolutePath); + } else { + unlink($absolutePath); + } + + continue; + } + + $this->filesystem->deleteDirectory($relativeDirectoryPath); + } + } elseif ($this->isDeleteVendorFiles) { + foreach ($sourceFiles as $sourceFile) { + $relativeFilepath = $this->vendorDirectory . $sourceFile; + + $absolutePath = $this->workingDir . $relativeFilepath; + + if ($absolutePath !== realpath($absolutePath)) { + continue; + } + + $this->filesystem->delete($relativeFilepath); + } + + $this->cleanupFilesAutoloader(); + } + + // Get the root folders of the moved files. + $rootSourceDirectories = []; + foreach ($sourceFiles as $sourceFile) { + $arr = explode("/", $sourceFile, 2); + $dir = $arr[0]; + $rootSourceDirectories[ $dir ] = $dir; + } + $rootSourceDirectories = array_map( + function (string $path): string { + return $this->vendorDirectory . $path; + }, + array_keys($rootSourceDirectories) + ); + + foreach ($rootSourceDirectories as $rootSourceDirectory) { + if (!is_dir($rootSourceDirectory) || is_link($rootSourceDirectory)) { + continue; + } + + $it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $this->workingDir . $rootSourceDirectory, + FilesystemIterator::SKIP_DOTS + ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($it as $file) { + if ($file->isDir() && $this->dirIsEmpty((string) $file)) { + rmdir((string)$file); + } + } + } + + $this->cleanupInstalledJson(); + } + + // TODO: Use Symfony or Flysystem functions. + protected function dirIsEmpty(string $dir): bool + { + $di = new RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS); + return iterator_count($di) === 0; + } + + /** + * Composer creates a file `vendor/composer/installed.json` which is uses when running `composer dump-autoload`. + * When `delete-vendor-packages` or `delete-vendor-files` is true, files and directories which have been deleted + * must also be removed from `installed.json` or Composer will throw an error. + * + * TODO: {@see self::cleanupFilesAutoloader()} might be redundant if we run this function and then run `composer dump-autoload`. + */ + public function cleanupInstalledJson(): void + { + $installedJsonFile = new JsonFile($this->workingDir . '/vendor/composer/installed.json'); + if (!$installedJsonFile->exists()) { + return; + } + $installedJsonArray = $installedJsonFile->read(); + + foreach ($installedJsonArray['packages'] as $key => $package) { + if (!isset($package['autoload'])) { + continue; + } + $packageDir = $this->workingDir . $this->vendorDirectory . ltrim($package['install-path'], '.' . DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + if (!is_dir($packageDir)) { + // pcre, xdebug-handler. + continue; + } + $autoload_key = $package['autoload']; + foreach ($autoload_key as $type => $autoload) { + switch ($type) { + case 'psr-4': + foreach ($autoload_key[$type] as $namespace => $dirs) { + if (is_array($dirs)) { + $autoload_key[$type][$namespace] = array_filter($dirs, function ($dir) use ($packageDir) { + $dir = $packageDir . $dir; + return is_readable($dir); + }); + } else { + $dir = $packageDir . $dirs; + if (! is_readable($dir)) { + unset($autoload_key[$type][$namespace]); + } + } + } + break; + default: // files, classmap + $autoload_key[$type] = array_filter($autoload, function ($file) use ($packageDir) { + $filename = $packageDir . $file; + return file_exists($packageDir . $file); + }); + break; + } + } + $installedJsonArray['packages'][$key]['autoload'] = array_filter($autoload_key); + } + $installedJsonFile->write($installedJsonArray); + } + + /** + * After files are deleted, remove them from the Composer files autoloaders. + * + * @see https://github.com/BrianHenryIE/strauss/issues/34#issuecomment-922503813 + */ + protected function cleanupFilesAutoloader(): void + { + if (! file_exists($this->workingDir . 'vendor/composer/autoload_files.php')) { + return; + } + + $files = include $this->workingDir . 'vendor/composer/autoload_files.php'; + + $missingFiles = array(); + + foreach ($files as $file) { + if (! file_exists($file)) { + $missingFiles[] = str_replace([ $this->workingDir, 'vendor/composer/../', 'vendor/' ], '', $file); + // When `composer install --no-dev` is run, it creates an index of files autoload files which + // references the non-existent files. This causes a fatal error when the autoloader is included. + // TODO: if delete_vendor_packages is true, do not create this file. + $this->filesystem->write( + str_replace($this->workingDir, '', $file), + 'targetDirectory; + + foreach (array('autoload_static.php', 'autoload_files.php') as $autoloadFile) { + $autoloadStaticPhp = $this->filesystem->read('vendor/composer/'.$autoloadFile); + + $autoloadStaticPhpAsArray = explode(PHP_EOL, $autoloadStaticPhp); + + $newAutoloadStaticPhpAsArray = array_map( + function (string $line) use ($missingFiles, $targetDirectory): string { + $containsFile = array_reduce( + $missingFiles, + function (bool $carry, string $filepath) use ($line): bool { + return $carry || false !== strpos($line, $filepath); + }, + false + ); + + if (!$containsFile) { + return $line; + } + + // TODO: Check the file does exist at the new location. It definitely should be. + // TODO: If the Strauss autoloader is being created, just return an empty string here. + + return str_replace([ + "=> __DIR__ . '/..' . '/", + "=> \$vendorDir . '/" + ], [ + "=> __DIR__ . '/../../$targetDirectory' . '/", + "=> \$baseDir . '/$targetDirectory" + ], $line); + }, + $autoloadStaticPhpAsArray + ); + + $newAutoloadStaticPhp = implode(PHP_EOL, $newAutoloadStaticPhpAsArray); + + $this->filesystem->write('vendor/composer/'.$autoloadFile, $newAutoloadStaticPhp); + } + } +} diff --git a/vendor/brianhenryie/strauss/src/Composer/ComposerPackage.php b/vendor/brianhenryie/strauss/src/Composer/ComposerPackage.php new file mode 100644 index 000000000..74d213903 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Composer/ComposerPackage.php @@ -0,0 +1,206 @@ +,classmap?:array,"psr-4"?:array>} + */ +class ComposerPackage +{ + /** + * The composer.json file as parsed by Composer. + * + * @see \Composer\Factory::create + * + * @var \Composer\Composer + */ + protected \Composer\Composer $composer; + + /** + * The name of the project in composer.json. + * + * e.g. brianhenryie/my-project + * + * @var string + */ + protected string $packageName; + + /** + * Virtual packages and meta packages do not have a composer.json. + * Some packages are installed in a different directory name than their package name. + * + * @var ?string + */ + protected ?string $relativePath = null; + + /** + * Packages can be symlinked from outside the current project directory. + * + * @var ?string + */ + protected ?string $packageAbsolutePath = null; + + /** + * The discovered files, classmap, psr0 and psr4 autoload keys discovered (as parsed by Composer). + * + * @var AutoloadKey + */ + protected array $autoload = []; + + /** + * The names in the composer.json's "requires" field (without versions). + * + * @var string[] + */ + protected array $requiresNames = []; + + protected string $license; + + /** + * @param string $absolutePath The absolute path to the vendor folder with the composer.json "name", + * i.e. the domain/package definition, which is the vendor subdir from where the package's + * composer.json should be read. + * @param ?array{files?:array, classmap?:array, psr?:array>} $overrideAutoload Optional configuration to replace the package's own autoload definition with + * another which Strauss can use. + * @return ComposerPackage + */ + public static function fromFile(string $absolutePath, array $overrideAutoload = null): ComposerPackage + { + if (is_dir($absolutePath)) { + $absolutePath = rtrim($absolutePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'composer.json'; + } + + $composer = Factory::create(new NullIO(), $absolutePath, true); + + return new ComposerPackage($composer, $overrideAutoload); + } + + /** + * This is used for virtual packages, which don't have a composer.json. + * + * @param array{name?:string, license?:string, requires?:array, autoload?:AutoloadKey} $jsonArray composer.json decoded to array + * @param ?AutoloadKey $overrideAutoload New autoload rules to replace the existing ones. + */ + public static function fromComposerJsonArray($jsonArray, array $overrideAutoload = null): ComposerPackage + { + $factory = new Factory(); + $io = new NullIO(); + $composer = $factory->createComposer($io, $jsonArray, true); + + return new ComposerPackage($composer, $overrideAutoload); + } + + /** + * Create a PHP object to represent a composer package. + * + * @param Composer $composer + * @param ?AutoloadKey $overrideAutoload Optional configuration to replace the package's own autoload definition with another which Strauss can use. + */ + public function __construct(Composer $composer, array $overrideAutoload = null) + { + $this->composer = $composer; + + $this->packageName = $composer->getPackage()->getName(); + + $composerJsonFileAbsolute = $composer->getConfig()->getConfigSource()->getName(); + + $absolutePath = realpath(dirname($composerJsonFileAbsolute)); + if (false !== $absolutePath) { + $this->packageAbsolutePath = $absolutePath . DIRECTORY_SEPARATOR; + } + + $vendorDirectory = $this->composer->getConfig()->get('vendor-dir'); + if (file_exists($vendorDirectory . DIRECTORY_SEPARATOR . $this->packageName)) { + $this->relativePath = $this->packageName; + $this->packageAbsolutePath = realpath($vendorDirectory . DIRECTORY_SEPARATOR . $this->packageName) . DIRECTORY_SEPARATOR; + // If the package is symlinked, the path will be outside the working directory. + } elseif (0 !== strpos($absolutePath, getcwd()) && 1 === preg_match('/.*[\/\\\\]([^\/\\\\]*[\/\\\\][^\/\\\\]*)[\/\\\\][^\/\\\\]*/', $vendorDirectory, $output_array)) { + $this->relativePath = $output_array[1]; + } elseif (1 === preg_match('/.*[\/\\\\]([^\/\\\\]+[\/\\\\][^\/\\\\]+)[\/\\\\]composer.json/', $composerJsonFileAbsolute, $output_array)) { + // Not every package gets installed to a folder matching its name (crewlabs/unsplash). + $this->relativePath = $output_array[1]; + } + + if (!is_null($overrideAutoload)) { + $composer->getPackage()->setAutoload($overrideAutoload); + } + + $this->autoload = $composer->getPackage()->getAutoload(); + + foreach ($composer->getPackage()->getRequires() as $_name => $packageLink) { + $this->requiresNames[] = $packageLink->getTarget(); + } + + // Try to get the license from the package's composer.json, assume proprietary (all rights reserved!). + $this->license = !empty($composer->getPackage()->getLicense()) + ? implode(',', $composer->getPackage()->getLicense()) + : 'proprietary?'; + } + + /** + * Composer package project name. + * + * vendor/project-name + * + * @return string + */ + public function getPackageName(): string + { + return $this->packageName; + } + + public function getRelativePath(): ?string + { + return $this->relativePath . DIRECTORY_SEPARATOR; + } + + public function getPackageAbsolutePath(): ?string + { + return $this->packageAbsolutePath; + } + + /** + * + * e.g. ['psr-4' => [ 'BrianHenryIE\Project' => 'src' ]] + * e.g. ['psr-4' => [ 'BrianHenryIE\Project' => ['src','lib] ]] + * e.g. ['classmap' => [ 'src', 'lib' ]] + * e.g. ['files' => [ 'lib', 'functions.php' ]] + * + * @return AutoloadKey + */ + public function getAutoload(): array + { + return $this->autoload; + } + + /** + * The names of the packages in the composer.json's "requires" field (without version). + * + * Excludes PHP, ext-*, since we won't be copying or prefixing them. + * + * @return string[] + */ + public function getRequiresNames(): array + { + // Unset PHP, ext-*. + $removePhpExt = function ($element) { + return !( 0 === strpos($element, 'ext') || 'php' === $element ); + }; + + return array_filter($this->requiresNames, $removePhpExt); + } + + public function getLicense():string + { + return $this->license; + } +} diff --git a/vendor/brianhenryie/strauss/src/Composer/Extra/StraussConfig.php b/vendor/brianhenryie/strauss/src/Composer/Extra/StraussConfig.php new file mode 100644 index 000000000..c043bb581 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Composer/Extra/StraussConfig.php @@ -0,0 +1,655 @@ +array(),'namespaces'=>array(),'packages'=>array()); + + /** + * @var array{packages: string[], namespaces: string[], file_patterns: string[]} + */ + protected array $excludeFromPrefix = array('file_patterns'=>array(),'namespaces'=>array(),'packages'=>array()); + + /** + * An array of autoload keys to replace packages' existing autoload key. + * + * e.g. when + * * A package has no autoloader + * * A package specified both a PSR-4 and a classmap but only needs one + * ... + * + * @var array,classmap?:array,"psr-4":array>}>|array{} $overrideAutoload + */ + protected array $overrideAutoload = []; + + /** + * After completing prefixing should the source files be deleted? + * This does not affect symlinked directories. + */ + protected bool $deleteVendorFiles = false; + + /** + * After completing prefixing should the source packages be deleted? + * This does not affect symlinked directories. + */ + protected bool $deleteVendorPackages = false; + + protected bool $classmapOutput; + + /** + * A dictionary of regex captures => regex replacements. + * + * E.g. used to avoid repetition of the plugin vendor name in namespaces. + * `"~BrianHenryIE\\\\(.*)~" : "BrianHenryIE\\WC_Cash_App_Gateway\\\\$1"`. + * + * @var array $namespaceReplacementPatterns + */ + protected array $namespaceReplacementPatterns = array(); + + /** + * Should a modified date be included in the header for modified files? + * + * @var bool + */ + protected $includeModifiedDate = true; + + /** + * Should the author name be included in the header for modified files? + * + * @var bool + */ + protected $includeAuthor = true; + + /** + * Read any existing Mozart config. + * Overwrite it with any Strauss config. + * Provide sensible defaults. + * + * @param Composer $composer + * + * @throws Exception + */ + public function __construct(Composer $composer) + { + + $configExtraSettings = null; + + // Backwards compatibility with Mozart. + if (isset($composer->getPackage()->getExtra()['mozart'])) { + $configExtraSettings = (object)$composer->getPackage()->getExtra()['mozart']; + + // Default setting for Mozart. + $this->setDeleteVendorFiles(true); + } + + if (isset($composer->getPackage()->getExtra()['strauss'])) { + $configExtraSettings = (object)$composer->getPackage()->getExtra()['strauss']; + } + + if (!is_null($configExtraSettings)) { + $mapper = (new JsonMapperFactory())->bestFit(); + + $rename = new Rename(); + $rename->addMapping(StraussConfig::class, 'dep_directory', 'targetDirectory'); + $rename->addMapping(StraussConfig::class, 'dep_namespace', 'namespacePrefix'); + + $rename->addMapping(StraussConfig::class, 'exclude_packages', 'excludePackages'); + $rename->addMapping(StraussConfig::class, 'delete_vendor_files', 'deleteVendorFiles'); + $rename->addMapping(StraussConfig::class, 'delete_vendor_packages', 'deleteVendorPackages'); + + $rename->addMapping(StraussConfig::class, 'exclude_prefix_packages', 'excludePackagesFromPrefixing'); + + $mapper->unshift($rename); + $mapper->push(new \JsonMapper\Middleware\CaseConversion( + \JsonMapper\Enums\TextNotation::UNDERSCORE(), + \JsonMapper\Enums\TextNotation::CAMEL_CASE() + )); + + $mapper->mapObject($configExtraSettings, $this); + } + + // Defaults. + // * Use PSR-4 autoloader key + // * Use PSR-0 autoloader key + // * Use the package name + if (! isset($this->namespacePrefix)) { + if (isset($composer->getPackage()->getAutoload()['psr-4'])) { + $this->setNamespacePrefix(array_key_first($composer->getPackage()->getAutoload()['psr-4'])); + } elseif (isset($composer->getPackage()->getAutoload()['psr-0'])) { + $this->setNamespacePrefix(array_key_first($composer->getPackage()->getAutoload()['psr-0'])); + } elseif ('__root__' !== $composer->getPackage()->getName()) { + $packageName = $composer->getPackage()->getName(); + $namespacePrefix = preg_replace('/[^\w\/]+/', '_', $packageName); + $namespacePrefix = str_replace('/', '\\', $namespacePrefix) . '\\'; + $namespacePrefix = preg_replace_callback('/(?<=^|_|\\\\)[a-z]/', function ($match) { + return strtoupper($match[0]); + }, $namespacePrefix); + $this->setNamespacePrefix($namespacePrefix); + } elseif (isset($this->classmapPrefix)) { + $namespacePrefix = rtrim($this->getClassmapPrefix(), '_'); + $this->setNamespacePrefix($namespacePrefix); + } + } + + if (! isset($this->classmapPrefix)) { + if (isset($composer->getPackage()->getAutoload()['psr-4'])) { + $autoloadKey = array_key_first($composer->getPackage()->getAutoload()['psr-4']); + $classmapPrefix = str_replace("\\", "_", $autoloadKey); + $this->setClassmapPrefix($classmapPrefix); + } elseif (isset($composer->getPackage()->getAutoload()['psr-0'])) { + $autoloadKey = array_key_first($composer->getPackage()->getAutoload()['psr-0']); + $classmapPrefix = str_replace("\\", "_", $autoloadKey); + $this->setClassmapPrefix($classmapPrefix); + } elseif ('__root__' !== $composer->getPackage()->getName()) { + $packageName = $composer->getPackage()->getName(); + $classmapPrefix = preg_replace('/[^\w\/]+/', '_', $packageName); + $classmapPrefix = str_replace('/', '\\', $classmapPrefix); + // Uppercase the first letter of each word. + $classmapPrefix = preg_replace_callback('/(?<=^|_|\\\\)[a-z]/', function ($match) { + return strtoupper($match[0]); + }, $classmapPrefix); + $classmapPrefix = str_replace("\\", "_", $classmapPrefix); + $this->setClassmapPrefix($classmapPrefix); + } elseif (isset($this->namespacePrefix)) { + $classmapPrefix = preg_replace('/[^\w\/]+/', '_', $this->getNamespacePrefix()); + $classmapPrefix = rtrim($classmapPrefix, '_') . '_'; + $this->setClassmapPrefix($classmapPrefix); + } + } + + if (!isset($this->namespacePrefix) || !isset($this->classmapPrefix)) { + throw new Exception('Prefix not set. Please set `namespace_prefix`, `classmap_prefix` in composer.json/extra/strauss.'); + } + + if (empty($this->packages)) { + $this->packages = array_map(function (\Composer\Package\Link $element) { + return $element->getTarget(); + }, $composer->getPackage()->getRequires()); + } + + // If the bool flag for classmapOutput wasn't set in the Json config. + if (!isset($this->classmapOutput)) { + $this->classmapOutput = true; + // Check each autoloader. + foreach ($composer->getPackage()->getAutoload() as $autoload) { + // To see if one of its paths. + foreach ($autoload as $entry) { + $paths = (array) $entry; + foreach ($paths as $path) { + // Matches the target directory. + if (trim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR === $this->getTargetDirectory()) { + $this->classmapOutput = false; + break 3; + } + } + } + } + } + + // TODO: Throw an exception if any regex patterns in config are invalid. + // https://stackoverflow.com/questions/4440626/how-can-i-validate-regex + // preg_match('~Valid(Regular)Expression~', null) === false); + + if (isset($configExtraSettings->updateCallSites)) { + if (true === $configExtraSettings->updateCallSites) { + $this->updateCallSites = null; + } elseif (false === $configExtraSettings->updateCallSites) { + $this->updateCallSites = array(); + } elseif (is_array($configExtraSettings->updateCallSites)) { + $this->updateCallSites = $configExtraSettings->updateCallSites; + } else { + // uh oh. + } + } + } + + /** + * `target_directory` will always be returned without a leading slash and with a trailing slash. + * + * @return string + */ + public function getTargetDirectory(): string + { + return trim($this->targetDirectory, DIRECTORY_SEPARATOR . '\\/') . DIRECTORY_SEPARATOR; + } + + /** + * @param string $targetDirectory + */ + public function setTargetDirectory(string $targetDirectory): void + { + $this->targetDirectory = trim( + preg_replace( + '/[\/\\\\]+/', + DIRECTORY_SEPARATOR, + $targetDirectory + ), + DIRECTORY_SEPARATOR + ) + . DIRECTORY_SEPARATOR ; + } + + /** + * @return string + */ + public function getVendorDirectory(): string + { + return trim($this->vendorDirectory, DIRECTORY_SEPARATOR . '\\/') . DIRECTORY_SEPARATOR; + } + + /** + * @param string $vendorDirectory + */ + public function setVendorDirectory(string $vendorDirectory): void + { + $this->vendorDirectory = $vendorDirectory; + } + + /** + * @return string + */ + public function getNamespacePrefix(): string + { + return trim($this->namespacePrefix, '\\'); + } + + /** + * @param string $namespacePrefix + */ + public function setNamespacePrefix(string $namespacePrefix): void + { + $this->namespacePrefix = $namespacePrefix; + } + + /** + * @return string + */ + public function getClassmapPrefix(): string + { + return $this->classmapPrefix; + } + + /** + * @param string $classmapPrefix + */ + public function setClassmapPrefix(string $classmapPrefix): void + { + $this->classmapPrefix = $classmapPrefix; + } + + /** + * @return string + */ + public function getConstantsPrefix(): ?string + { + return $this->constantsPrefix; + } + + /** + * @param string $constantsPrefix + */ + public function setConstantsPrefix(string $constantsPrefix): void + { + $this->constantsPrefix = $constantsPrefix; + } + + /** + * List of files and directories to update call sites in. Empty to disable. Null infers from the project's autoload key. + * + * @return string[]|null + */ + public function getUpdateCallSites(): ?array + { + return $this->updateCallSites; + } + + /** + * @param string[]|null $updateCallSites + */ + public function setUpdateCallSites(?array $updateCallSites): void + { + $this->updateCallSites = $updateCallSites; + } + + /** + * @param array{packages?:array, namespaces?:array, file_patterns?:array} $excludeFromCopy + */ + public function setExcludeFromCopy(array $excludeFromCopy): void + { + foreach (array( 'packages', 'namespaces', 'file_patterns' ) as $key) { + if (isset($excludeFromCopy[$key])) { + $this->excludeFromCopy[$key] = $excludeFromCopy[$key]; + } + } + } + + /** + * @return string[] + */ + public function getExcludePackagesFromCopy(): array + { + return $this->excludeFromCopy['packages'] ?? array(); + } + + /** + * @return string[] + */ + public function getExcludeNamespacesFromCopy(): array + { + return $this->excludeFromCopy['namespaces'] ?? array(); + } + + /** + * @return string[] + */ + public function getExcludeFilePatternsFromCopy(): array + { + return $this->excludeFromCopy['file_patterns'] ?? array(); + } + + /** + * @param array{packages?:array, namespaces?:array, file_patterns?:array} $excludeFromPrefix + */ + public function setExcludeFromPrefix(array $excludeFromPrefix): void + { + if (isset($excludeFromPrefix['packages'])) { + $this->excludeFromPrefix['packages'] = $excludeFromPrefix['packages']; + } + if (isset($excludeFromPrefix['namespaces'])) { + $this->excludeFromPrefix['namespaces'] = $excludeFromPrefix['namespaces']; + } + if (isset($excludeFromPrefix['file_patterns'])) { + $this->excludeFromPrefix['file_patterns'] = $excludeFromPrefix['file_patterns']; + } + } + + /** + * When prefixing, do not prefix these packages (which have been copied). + * + * @return string[] + */ + public function getExcludePackagesFromPrefixing(): array + { + return $this->excludeFromPrefix['packages'] ?? array(); + } + + /** + * @param string[] $excludePackagesFromPrefixing + */ + public function setExcludePackagesFromPrefixing(array $excludePackagesFromPrefixing): void + { + $this->excludeFromPrefix['packages'] = $excludePackagesFromPrefixing; + } + + /** + * @return string[] + */ + public function getExcludeNamespacesFromPrefixing(): array + { + return $this->excludeFromPrefix['namespaces'] ?? array(); + } + + /** + * @return string[] + */ + public function getExcludeFilePatternsFromPrefixing(): array + { + return $this->excludeFromPrefix['file_patterns'] ?? array(); + } + + + /** + * @return array{}|array,classmap?:array,"psr-4":array>}> $overrideAutoload Dictionary of package name: autoload rules. + */ + public function getOverrideAutoload(): array + { + return $this->overrideAutoload; + } + + /** + * @param array,classmap?:array,"psr-4":array>}> $overrideAutoload Dictionary of package name: autoload rules. + */ + public function setOverrideAutoload(array $overrideAutoload): void + { + $this->overrideAutoload = $overrideAutoload; + } + + /** + * @return bool + */ + public function isDeleteVendorFiles(): bool + { + return $this->deleteVendorFiles; + } + + /** + * @return bool + */ + public function isDeleteVendorPackages(): bool + { + return $this->deleteVendorPackages; + } + + /** + * @param bool $deleteVendorFiles + */ + public function setDeleteVendorFiles(bool $deleteVendorFiles): void + { + $this->deleteVendorFiles = $deleteVendorFiles; + } + + /** + * @param bool $deleteVendorPackages + */ + public function setDeleteVendorPackages(bool $deleteVendorPackages): void + { + $this->deleteVendorPackages = $deleteVendorPackages; + } + + /** + * @return string[] + */ + public function getPackages(): array + { + return $this->packages; + } + + /** + * @param string[] $packages + */ + public function setPackages(array $packages): void + { + $this->packages = $packages; + } + + /** + * @return bool + */ + public function isClassmapOutput(): bool + { + return $this->classmapOutput; + } + + /** + * @param bool $classmapOutput + */ + public function setClassmapOutput(bool $classmapOutput): void + { + $this->classmapOutput = $classmapOutput; + } + + /** + * Backwards compatibility with Mozart. + * + * @param string[] $excludePackages + */ + public function setExcludePackages(array $excludePackages): void + { + $this->excludeFromPrefix['packages'] = $excludePackages; + } + + + /** + * @return array + */ + public function getNamespaceReplacementPatterns(): array + { + return $this->namespaceReplacementPatterns; + } + + /** + * @param array $namespaceReplacementPatterns + */ + public function setNamespaceReplacementPatterns(array $namespaceReplacementPatterns): void + { + $this->namespaceReplacementPatterns = $namespaceReplacementPatterns; + } + + /** + * @return bool + */ + public function isIncludeModifiedDate(): bool + { + return $this->includeModifiedDate; + } + + /** + * @param bool $includeModifiedDate + */ + public function setIncludeModifiedDate(bool $includeModifiedDate): void + { + $this->includeModifiedDate = $includeModifiedDate; + } + + + /** + * @return bool + */ + public function isIncludeAuthor(): bool + { + return $this->includeAuthor; + } + + /** + * @param bool $includeAuthor + */ + public function setIncludeAuthor(bool $includeAuthor): void + { + $this->includeAuthor = $includeAuthor; + } + + /** + * @param InputInterface $input To access the command line options. + */ + public function updateFromCli(InputInterface $input): void + { + + // strauss --updateCallSites=false (default) + // strauss --updateCallSites=true + // strauss --updateCallSites=src,input,extra + + if ($input->hasOption('updateCallSites') && $input->getOption('updateCallSites') !== null) { + $updateCallSitesInput = $input->getOption('updateCallSites'); + + if ('false' === $updateCallSitesInput) { + $this->updateCallSites = array(); + } elseif ('true' === $updateCallSitesInput) { + $this->updateCallSites = null; + } elseif (! is_null($updateCallSitesInput)) { + $this->updateCallSites = explode(',', $updateCallSitesInput); + } + } + + if ($input->hasOption('deleteVendorPackages') && $input->getOption('deleteVendorPackages') !== null) { + $isDeleteVendorPackagesCommandLine = $input->getOption('deleteVendorPackages') === 'true'; + $this->setDeleteVendorPackages($isDeleteVendorPackagesCommandLine); + } elseif ($input->hasOption('delete_vendor_packages') && $input->getOption('delete_vendor_packages') !== null) { + $isDeleteVendorPackagesCommandLine = $input->getOption('delete_vendor_packages') === 'true'; + $this->setDeleteVendorPackages($isDeleteVendorPackagesCommandLine); + } + } +} diff --git a/vendor/brianhenryie/strauss/src/Composer/ProjectComposerPackage.php b/vendor/brianhenryie/strauss/src/Composer/ProjectComposerPackage.php new file mode 100644 index 000000000..0a5062619 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Composer/ProjectComposerPackage.php @@ -0,0 +1,91 @@ +,classmap?:array,"psr-4"?:array>} $overrideAutoload + */ + public function __construct(string $absolutePath, ?array $overrideAutoload = null) + { + $absolutePathFile = is_dir($absolutePath) + ? $absolutePath . DIRECTORY_SEPARATOR . 'composer.json' + : $absolutePath; + unset($absolutePath); + + $composer = Factory::create(new NullIO(), $absolutePathFile, true); + + parent::__construct($composer, $overrideAutoload); + + $authors = $this->composer->getPackage()->getAuthors(); + if (empty($authors) || !isset($authors[0]['name'])) { + $this->author = explode("/", $this->packageName, 2)[0]; + } else { + $this->author = $authors[0]['name']; + } + + // In rare cases, the vendor directory will not be a single level of directory. File an issue. + $this->vendorDirectory = is_string($this->composer->getConfig()->get('vendor-dir')) + ? basename($this->composer->getConfig()->get('vendor-dir')) + : 'vendor'; + } + + /** + * @return StraussConfig + * @throws \Exception + */ + public function getStraussConfig(): StraussConfig + { + $config = new StraussConfig($this->composer); + $config->setVendorDirectory($this->getVendorDirectory()); + return $config; + } + + + public function getAuthor(): string + { + return $this->author; + } + + /** + * Relative vendor directory with trailing slash. + */ + public function getVendorDirectory(): string + { + return rtrim($this->vendorDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + } + + /** + * Get all values in the autoload key as a flattened array. + * + * @return string[] + */ + public function getFlatAutoloadKey(): array + { + $autoload = $this->getAutoload(); + $values = []; + array_walk_recursive( + $autoload, + function ($value, $key) use (&$values) { + $values[] = $value; + } + ); + return $values; + } +} diff --git a/vendor/brianhenryie/strauss/src/Console/Application.php b/vendor/brianhenryie/strauss/src/Console/Application.php new file mode 100644 index 000000000..96651deab --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Console/Application.php @@ -0,0 +1,22 @@ +add($composeCommand); + + $this->setDefaultCommand('compose'); + } +} diff --git a/vendor/brianhenryie/strauss/src/Console/Commands/Compose.php b/vendor/brianhenryie/strauss/src/Console/Commands/Compose.php new file mode 100644 index 000000000..b70b5a07b --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Console/Commands/Compose.php @@ -0,0 +1,391 @@ +setName('compose'); + $this->setDescription("Copy composer's `require` and prefix their namespace and classnames."); + $this->setHelp(''); + + $this->addOption( + 'updateCallSites', + null, + InputArgument::OPTIONAL, + 'Should replacements also be performed in project files? true|list,of,paths|false' + ); + + $this->addOption( + 'deleteVendorPackages', + null, + InputArgument::OPTIONAL, + 'Should original packages be deleted after copying? true|false' + ); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * + * @return int + * @see Command::execute() + * + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->setLogger( + new ConsoleLogger( + $output, + [ LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL ] + ) + ); + + $workingDir = getcwd() . DIRECTORY_SEPARATOR; + $this->workingDir = $workingDir; + + try { + $this->loadProjectComposerPackage(); + $this->loadConfigFromComposerJson(); + $this->updateConfigFromCli($input); + + $this->buildDependencyList(); + + $this->enumerateFiles(); + + $this->copyFiles(); + + $this->determineChanges(); + + $this->performReplacements(); + + $this->performReplacementsInComposerFiles(); + + $this->performReplacementsInProjectFiles(); + + $this->addLicenses(); + + $this->generateAutoloader(); + + $this->cleanUp(); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + + return 1; + } + + return Command::SUCCESS; + } + + + /** + * 1. Load the composer.json. + * + * @throws Exception + */ + protected function loadProjectComposerPackage(): void + { + $this->logger->info('Loading package...'); + + $this->projectComposerPackage = new ProjectComposerPackage($this->workingDir); + + // TODO: Print the config that Strauss is using. + // Maybe even highlight what is default config and what is custom config. + } + + protected function loadConfigFromComposerJson(): void + { + $this->logger->info('Loading composer.json config...'); + + $this->config = $this->projectComposerPackage->getStraussConfig(); + } + + protected function updateConfigFromCli(InputInterface $input): void + { + $this->logger->info('Loading cli config...'); + + $this->config->updateFromCli($input); + } + + /** + * 2. Built flat list of packages and dependencies. + * + * 2.1 Initiate getting dependencies for the project composer.json. + * + * @see Compose::flatDependencyTree + */ + protected function buildDependencyList(): void + { + $this->logger->info('Building dependency list...'); + + $this->dependenciesEnumerator = new DependenciesEnumerator( + $this->workingDir, + $this->config + ); + $this->flatDependencyTree = $this->dependenciesEnumerator->getAllDependencies(); + + // TODO: Print the dependency tree that Strauss has determined. + } + + protected function enumerateFiles(): void + { + $this->logger->info('Enumerating files...'); + + $fileEnumerator = new FileEnumerator( + $this->flatDependencyTree, + $this->workingDir, + $this->config + ); + + $this->discoveredFiles = $fileEnumerator->compileFileList(); + } + + // 3. Copy autoloaded files for each + protected function copyFiles(): void + { + if ($this->config->getTargetDirectory() === $this->config->getVendorDirectory()) { + // Nothing to do. + return; + } + + $this->logger->info('Copying files...'); + + $copier = new Copier( + $this->discoveredFiles, + $this->workingDir, + $this->config + ); + + $copier->prepareTarget(); + $copier->copy(); + } + + // 4. Determine namespace and classname changes + protected function determineChanges(): void + { + $this->logger->info('Determining changes...'); + + $fileScanner = new FileScanner($this->config); + + $this->discoveredSymbols = $fileScanner->findInFiles($this->discoveredFiles); + + $changeEnumerator = new ChangeEnumerator( + $this->config, + $this->workingDir + ); + $changeEnumerator->determineReplacements($this->discoveredSymbols); + } + + // 5. Update namespaces and class names. + // Replace references to updated namespaces and classnames throughout the dependencies. + protected function performReplacements(): void + { + $this->logger->info('Performing replacements...'); + + $this->replacer = new Prefixer($this->config, $this->workingDir); + + $phpFiles = $this->discoveredFiles->getPhpFilesAndDependencyList(); + + $this->replacer->replaceInFiles($this->discoveredSymbols, $phpFiles); + } + + protected function performReplacementsInComposerFiles(): void + { + if ($this->config->getTargetDirectory() !== $this->config->getVendorDirectory()) { + // Nothing to do. + return; + } + + $projectReplace = new Prefixer($this->config, $this->workingDir); + + $fileEnumerator = new FileEnumerator( + $this->flatDependencyTree, + $this->workingDir, + $this->config + ); + + $composerPhpFileRelativePaths = $fileEnumerator->findFilesInDirectory( + $this->workingDir, + $this->config->getVendorDirectory() . 'composer' + ); + + $projectReplace->replaceInProjectFiles($this->discoveredSymbols, $composerPhpFileRelativePaths); + } + + protected function performReplacementsInProjectFiles(): void + { + + $callSitePaths = + $this->config->getUpdateCallSites() + ?? $this->projectComposerPackage->getFlatAutoloadKey(); + + if (empty($callSitePaths)) { + return; + } + + $projectReplace = new Prefixer($this->config, $this->workingDir); + + $fileEnumerator = new FileEnumerator( + $this->flatDependencyTree, + $this->workingDir, + $this->config + ); + + $phpFilesRelativePaths = []; + foreach ($callSitePaths as $relativePath) { + $absolutePath = $this->workingDir . $relativePath; + if (is_dir($absolutePath)) { + $phpFilesRelativePaths = array_merge($phpFilesRelativePaths, $fileEnumerator->findFilesInDirectory($this->workingDir, $relativePath)); + } elseif (is_readable($absolutePath)) { + $phpFilesRelativePaths[] = $relativePath; + } else { + $this->logger->warning('Expected file not found from project autoload: ' . $absolutePath); + } + } + + $projectReplace->replaceInProjectFiles($this->discoveredSymbols, $phpFilesRelativePaths); + } + + protected function writeClassAliasMap(): void + { + } + + protected function addLicenses(): void + { + $this->logger->info('Adding licenses...'); + + $author = $this->projectComposerPackage->getAuthor(); + + $dependencies = $this->flatDependencyTree; + + $licenser = new Licenser($this->config, $this->workingDir, $dependencies, $author); + + $licenser->copyLicenses(); + + $modifiedFiles = $this->replacer->getModifiedFiles(); + $licenser->addInformationToUpdatedFiles($modifiedFiles); + } + + /** + * 6. Generate autoloader. + */ + protected function generateAutoloader(): void + { + if ($this->config->getTargetDirectory() === $this->config->getVendorDirectory()) { + $this->logger->info('Skipping autoloader generation as target directory is vendor directory.'); + return; + } + if (isset($this->projectComposerPackage->getAutoload()['classmap']) + && in_array( + $this->config->getTargetDirectory(), + $this->projectComposerPackage->getAutoload()['classmap'], + true + ) + ) { + $this->logger->info('Skipping autoloader generation as target directory is in Composer classmap. Run `composer dump-autoload`.'); + return; + } + + $this->logger->info('Generating autoloader...'); + + $allFilesAutoloaders = $this->dependenciesEnumerator->getAllFilesAutoloaders(); + $filesAutoloaders = array(); + foreach ($allFilesAutoloaders as $packageName => $packageFilesAutoloader) { + if (in_array($packageName, $this->config->getExcludePackagesFromCopy())) { + continue; + } + $filesAutoloaders[$packageName] = $packageFilesAutoloader; + } + + $classmap = new Autoload($this->config, $this->workingDir, $filesAutoloaders); + + $classmap->generate(); + } + + /** + * When namespaces are prefixed which are used by by require and require-dev dependencies, + * the require-dev dependencies need class aliases specified to point to the new class names/namespaces. + */ + protected function generateClassAliasList(): void + { + } + + /** + * 7. + * Delete source files if desired. + * Delete empty directories in destination. + */ + protected function cleanUp(): void + { + if ($this->config->getTargetDirectory() === $this->config->getVendorDirectory()) { + // Nothing to do. + return; + } + + $this->logger->info('Cleaning up...'); + + $cleanup = new Cleanup($this->config, $this->workingDir); + + $sourceFiles = array_keys($this->discoveredFiles->getAllFilesAndDependencyList()); + + // TODO: For files autoloaders, delete the contents of the file, not the file itself. + + // This will check the config to check should it delete or not. + $cleanup->cleanup($sourceFiles); + } +} diff --git a/vendor/brianhenryie/strauss/src/Copier.php b/vendor/brianhenryie/strauss/src/Copier.php new file mode 100644 index 000000000..db6ece777 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Copier.php @@ -0,0 +1,89 @@ +files = $files; + + $this->absoluteTargetDir = $workingDir . $config->getTargetDirectory(); + + $this->filesystem = new Filesystem(new LocalFilesystemAdapter('/')); + } + + /** + * If the target dir does not exist, create it. + * If it already exists, delete any files we're about to copy. + * + * @return void + */ + public function prepareTarget(): void + { + if (! is_dir($this->absoluteTargetDir)) { + $this->filesystem->createDirectory($this->absoluteTargetDir); + $this->filesystem->setVisibility($this->absoluteTargetDir, 'public'); + } else { + foreach ($this->files->getFiles() as $file) { + $targetAbsoluteFilepath = $this->absoluteTargetDir . $file->getTargetRelativePath(); + + if ($this->filesystem->fileExists($targetAbsoluteFilepath)) { + $this->filesystem->delete($targetAbsoluteFilepath); + } + } + } + } + + public function copy(): void + { + /** + * @var File $file + */ + foreach ($this->files->getFiles() as $file) { + $sourceAbsoluteFilepath = $file->getSourcePath(); + + $targetAbsolutePath = $this->absoluteTargetDir . $file->getTargetRelativePath(); + + $this->filesystem->copy($sourceAbsoluteFilepath, $targetAbsolutePath); + } + } +} diff --git a/vendor/brianhenryie/strauss/src/DependenciesEnumerator.php b/vendor/brianhenryie/strauss/src/DependenciesEnumerator.php new file mode 100644 index 000000000..b3da77e10 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/DependenciesEnumerator.php @@ -0,0 +1,162 @@ + */ + protected array $flatDependencyTree = array(); + + /** + * Record the files autoloaders for later use in building our own autoloader. + * + * Package-name: [ dir1, file1, file2, ... ]. + * + * @var array + */ + protected array $filesAutoloaders = []; + + /** + * @var array{}|array,classmap?:array,"psr-4":array>}> $overrideAutoload + */ + protected array $overrideAutoload = array(); + + /** + * Constructor. + * + * @param string $workingDir + * @param StraussConfig $config + */ + public function __construct( + string $workingDir, + StraussConfig $config + ) { + $this->workingDir = $workingDir; + $this->vendorDir = $config->getVendorDirectory(); + $this->overrideAutoload = $config->getOverrideAutoload(); + $this->requiredPackageNames = $config->getPackages(); + + $this->filesystem = new Filesystem(new LocalFilesystemAdapter($this->workingDir)); + } + + /** + * @return array Packages indexed by package name. + * @throws Exception + */ + public function getAllDependencies(): array + { + $this->recursiveGetAllDependencies($this->requiredPackageNames); + return $this->flatDependencyTree; + } + + /** + * @param string[] $requiredPackageNames + */ + protected function recursiveGetAllDependencies(array $requiredPackageNames): void + { + $requiredPackageNames = array_filter($requiredPackageNames, array( $this, 'removeVirtualPackagesFilter' )); + + foreach ($requiredPackageNames as $requiredPackageName) { + // Avoid infinite recursion. + if (isset($this->flatDependencyTree[$requiredPackageName])) { + continue; + } + + $packageComposerFile = $this->workingDir . $this->vendorDir + . $requiredPackageName . DIRECTORY_SEPARATOR . 'composer.json'; + + $overrideAutoload = $this->overrideAutoload[ $requiredPackageName ] ?? null; + + if (file_exists($packageComposerFile)) { + $requiredComposerPackage = ComposerPackage::fromFile($packageComposerFile, $overrideAutoload); + } else { + $fileContents = file_get_contents($this->workingDir . 'composer.lock'); + if (false === $fileContents) { + throw new Exception('Failed to read contents of ' . $this->workingDir . 'composer.lock'); + } + $composerLock = json_decode($fileContents, true); + $requiredPackageComposerJson = null; + foreach ($composerLock['packages'] as $packageJson) { + if ($requiredPackageName === $packageJson['name']) { + $requiredPackageComposerJson = $packageJson; + break; + } + } + if (is_null($requiredPackageComposerJson)) { + // e.g. composer-plugin-api. + continue; + } + + $requiredComposerPackage = ComposerPackage::fromComposerJsonArray($requiredPackageComposerJson, $overrideAutoload); + } + + $this->flatDependencyTree[$requiredComposerPackage->getPackageName()] = $requiredComposerPackage; + $nextRequiredPackageNames = $requiredComposerPackage->getRequiresNames(); + + $this->recursiveGetAllDependencies($nextRequiredPackageNames); + } + } + + /** + * Get the recorded files autoloaders. + * + * @return array> + */ + public function getAllFilesAutoloaders(): array + { + $filesAutoloaders = array(); + foreach ($this->flatDependencyTree as $packageName => $composerPackage) { + if (isset($composerPackage->getAutoload()['files'])) { + $filesAutoloaders[$packageName] = $composerPackage->getAutoload()['files']; + } + } + return $filesAutoloaders; + } + + /** + * Unset PHP, ext-*, ... + * + * @param string $requiredPackageName + */ + protected function removeVirtualPackagesFilter(string $requiredPackageName): bool + { + return ! ( + 0 === strpos($requiredPackageName, 'ext') + || 'php' === $requiredPackageName + || in_array($requiredPackageName, $this->virtualPackages) + ); + } +} diff --git a/vendor/brianhenryie/strauss/src/DiscoveredFiles.php b/vendor/brianhenryie/strauss/src/DiscoveredFiles.php new file mode 100644 index 000000000..6d96d43b9 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/DiscoveredFiles.php @@ -0,0 +1,63 @@ + */ + protected array $files = []; + + /** + * @param File $file + */ + public function add(File $file): void + { + $this->files[$file->getTargetRelativePath()] = $file; + } + + /** + * @return File[] + */ + public function getFiles(): array + { + return $this->files; + } + + /** + * Returns all found files. + * + * @return array + */ + public function getAllFilesAndDependencyList(): array + { + $allFiles = []; + foreach ($this->files as $file) { + if (!$file->isDoCopy()) { + continue; + } + $allFiles[ $file->getTargetRelativePath() ] = [ + 'dependency' => $file->getDependency(), + 'sourceAbsoluteFilepath' => $file->getSourcePath(), + 'targetRelativeFilepath' => $file->getTargetRelativePath(), + ]; + } + return $allFiles; + } + + + /** + * Returns found PHP files. + * + * @return array + */ + public function getPhpFilesAndDependencyList(): array + { + // Filter out non .php files by checking the key. + return array_filter($this->getAllFilesAndDependencyList(), function ($value, $key) { + return false !== strpos($key, '.php'); + }, ARRAY_FILTER_USE_BOTH); + } +} diff --git a/vendor/brianhenryie/strauss/src/DiscoveredSymbol.php b/vendor/brianhenryie/strauss/src/DiscoveredSymbol.php new file mode 100644 index 000000000..07b57aecf --- /dev/null +++ b/vendor/brianhenryie/strauss/src/DiscoveredSymbol.php @@ -0,0 +1,57 @@ +symbol = $symbol; + $this->file = $file; + + $file->addDiscoveredSymbol($this); + } + + public function getOriginalSymbol(): string + { + return $this->symbol; + } + + public function setSymbol(string $symbol): void + { + $this->symbol = $symbol; + } + + public function getFile(): ?File + { + return $this->file; + } + + public function setFile(File $file): void + { + $this->file = $file; + } + + public function getReplacement(): string + { + return $this->replacement ?? $this->symbol; + } + + public function setReplacement(string $replacement): void + { + $this->replacement = $replacement; + } +} diff --git a/vendor/brianhenryie/strauss/src/DiscoveredSymbols.php b/vendor/brianhenryie/strauss/src/DiscoveredSymbols.php new file mode 100644 index 000000000..b1c22df38 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/DiscoveredSymbols.php @@ -0,0 +1,126 @@ +> + */ + protected array $types = []; + + public function __construct() + { + $this->types = [ + ClassSymbol::class => [], + ConstantSymbol::class => [], + NamespaceSymbol::class => [], + ]; + } + + /** + * @param DiscoveredSymbol $symbol + */ + public function add(DiscoveredSymbol $symbol): void + { + $this->types[get_class($symbol)][$symbol->getOriginalSymbol()] = $symbol; + } + + /** + * @return DiscoveredSymbol[] + */ + public function getSymbols(): array + { + return array_merge( + array_values($this->getNamespaces()), + array_values($this->getClasses()), + array_values($this->getConstants()) + ); + } + + /** + * @return array + */ + public function getConstants() + { + return $this->types[ConstantSymbol::class]; + } + + /** + * @return array + */ + public function getNamespaces(): array + { + return $this->types[NamespaceSymbol::class]; + } + + /** + * @return array + */ + public function getClasses(): array + { + return $this->types[ClassSymbol::class]; + } + + + /** + * TODO: Order by longest string first. (or instead, record classnames with their namespaces) + * + * @return array + */ + public function getDiscoveredNamespaces(?string $namespacePrefix = ''): array + { + $discoveredNamespaceReplacements = []; + + // When running subsequent times, try to discover the original namespaces. + // This is naive: it will not work where namespace replacement patterns have been used. + foreach ($this->getNamespaces() as $key => $value) { + $discoveredNamespaceReplacements[ $value->getOriginalSymbol() ] = $value; + } + + uksort($discoveredNamespaceReplacements, function ($a, $b) { + return strlen($a) <=> strlen($b); + }); + + return $discoveredNamespaceReplacements; + } + + /** + * @return string[] + */ + public function getDiscoveredClasses(?string $classmapPrefix = ''): array + { + $discoveredClasses = $this->getClasses(); + + $discoveredClasses = array_filter( + array_keys($discoveredClasses), + function (string $replacement) use ($classmapPrefix) { + return empty($classmapPrefix) || ! str_starts_with($replacement, $classmapPrefix); + } + ); + + return $discoveredClasses; + } + + /** + * @return string[] + */ + public function getDiscoveredConstants(?string $constantsPrefix = ''): array + { + $discoveredConstants = $this->getConstants(); + $discoveredConstants = array_filter( + array_keys($discoveredConstants), + function (string $replacement) use ($constantsPrefix) { + return empty($constantsPrefix) || ! str_starts_with($replacement, $constantsPrefix); + } + ); + + return $discoveredConstants; + } +} diff --git a/vendor/brianhenryie/strauss/src/File.php b/vendor/brianhenryie/strauss/src/File.php new file mode 100644 index 000000000..f23e22687 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/File.php @@ -0,0 +1,158 @@ +packageRelativePath = $packageRelativePath; + $this->dependency = $dependency; + $this->sourceAbsolutePath = $sourceAbsolutePath; + } + + public function getDependency(): ComposerPackage + { + return $this->dependency; + } + + public function getSourcePath(string $relativeTo = ''): string + { + return str_replace($relativeTo, '', $this->sourceAbsolutePath); + } + + public function getTargetRelativePath(): string + { + return $this->dependency->getRelativePath() . $this->packageRelativePath; + } + + public function isPhpFile(): bool + { + return substr($this->sourceAbsolutePath, -4) === '.php'; + } + + public function addNamespace(string $namespaceName): void + { + } + public function addClass(string $className): void + { + } + public function addTrait(string $traitName): void + { + } + // isTrait(); + + public function setDoCopy(bool $doCopy): void + { + $this->doCopy = $doCopy; + } + public function isDoCopy(): bool + { + return $this->doCopy; + } + + public function setDoPrefix(bool $doPrefix): void + { + } + public function isDoPrefix(): bool + { + return true; + } + + /** + * Used to mark files that are symlinked as not-to-be-deleted. + * + * @param bool $doDelete + * + * @return void + */ + public function setDoDelete(bool $doDelete): void + { + $this->doDelete = $doDelete; + } + + /** + * Should file be deleted? + * + * NB: Also respect the "delete_vendor_files"|"delete_vendor_packages" settings. + */ + public function isDoDelete(): bool + { + return $this->doDelete; + } + + public function setDidDelete(bool $didDelete): void + { + } + public function getDidDelete(): bool + { + return false; + } + + /** + * Record the autoloader it is found in. Which could be all of them. + */ + public function addAutoloader(string $autoloaderType): void + { + $this->autoloaderTypes = array_unique(array_merge($this->autoloaderTypes, array($autoloaderType))); + } + + public function isFilesAutoloaderFile(): bool + { + return in_array('files', $this->autoloaderTypes, true); + } + + public function addDiscoveredSymbol(DiscoveredSymbol $symbol): void + { + $this->discoveredSymbols[$symbol->getOriginalSymbol()] = $symbol; + } + + public function getContents(): string + { + + // TODO: use flysystem + // $contents = $this->filesystem->read($targetFile); + + $contents = file_get_contents($this->sourceAbsolutePath); + if (false === $contents) { + throw new \Exception("Failed to read file at {$this->sourceAbsolutePath}"); + } + + return $contents; + } +} diff --git a/vendor/brianhenryie/strauss/src/FileEnumerator.php b/vendor/brianhenryie/strauss/src/FileEnumerator.php new file mode 100644 index 000000000..9684ce2b9 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/FileEnumerator.php @@ -0,0 +1,215 @@ + + */ + protected array $filesAutoloaders = []; + + /** + * Copier constructor. + * @param ComposerPackage[] $dependencies + * @param string $workingDir + */ + public function __construct( + array $dependencies, + string $workingDir, + StraussConfig $config + ) { + $this->discoveredFiles = new DiscoveredFiles(); + + $this->workingDir = $workingDir; + $this->vendorDir = $config->getVendorDirectory(); + + $this->dependencies = $dependencies; + + $this->excludeNamespaces = $config->getExcludeNamespacesFromCopy(); + $this->excludePackageNames = $config->getExcludePackagesFromCopy(); + $this->excludeFilePatterns = $config->getExcludeFilePatternsFromCopy(); + + $this->filesystem = new Filesystem(new LocalFilesystemAdapter($this->workingDir)); + } + + /** + * Read the autoload keys of the dependencies and generate a list of the files referenced. + * + * Includes all files in the directories and subdirectories mentioned in the autoloaders. + */ + public function compileFileList(): DiscoveredFiles + { + foreach ($this->dependencies as $dependency) { + if (in_array($dependency->getPackageName(), $this->excludePackageNames)) { + continue; + } + + /** + * Where $dependency->autoload is ~ + * + * [ "psr-4" => [ "BrianHenryIE\Strauss" => "src" ] ] + * Exclude "exclude-from-classmap" + * @see https://getcomposer.org/doc/04-schema.md#exclude-files-from-classmaps + */ + $autoloaders = array_filter($dependency->getAutoload(), function ($type) { + return 'exclude-from-classmap' !== $type; + }, ARRAY_FILTER_USE_KEY); + + foreach ($autoloaders as $type => $value) { + // Might have to switch/case here. + + if ('files' === $type) { + $this->filesAutoloaders[$dependency->getRelativePath()] = $value; + } + + foreach ($value as $namespace => $namespace_relative_paths) { + if (!empty($namespace) && in_array($namespace, $this->excludeNamespaces)) { + continue; + } + + if (! is_array($namespace_relative_paths)) { + $namespace_relative_paths = array( $namespace_relative_paths ); + } + + foreach ($namespace_relative_paths as $namespaceRelativePath) { + $sourceAbsolutePath = $dependency->getPackageAbsolutePath() . $namespaceRelativePath; + + if (is_file($sourceAbsolutePath)) { + $this->addFile($dependency, $namespaceRelativePath, $type); + } elseif (is_dir($sourceAbsolutePath)) { + // trailingslashit(). (to remove duplicates). + $sourcePath = Path::normalize($sourceAbsolutePath); + +// $this->findFilesInDirectory() + $finder = new Finder(); + $finder->files()->in($sourcePath)->followLinks(); + + foreach ($finder as $foundFile) { + $sourceAbsoluteFilepath = $foundFile->getPathname(); + + // No need to record the directory itself. + if (is_dir($sourceAbsoluteFilepath)) { + continue; + } + + $namespaceRelativePath = Path::normalize($namespaceRelativePath); + + $this->addFile( + $dependency, + $namespaceRelativePath . str_replace($sourcePath, '', $sourceAbsoluteFilepath), + $type + ); + } + } + } + } + } + } + + return $this->discoveredFiles; + } + + /** + * @uses \BrianHenryIE\Strauss\DiscoveredFiles::add() + * + * @param ComposerPackage $dependency + * @param string $packageRelativePath + * @param string $autoloaderType + * @throws \League\Flysystem\FilesystemException + */ + protected function addFile(ComposerPackage $dependency, string $packageRelativePath, string $autoloaderType): void + { + $sourceAbsoluteFilepath = $dependency->getPackageAbsolutePath() . $packageRelativePath; + $outputRelativeFilepath = $dependency->getRelativePath() . $packageRelativePath; + $projectRelativePath = $this->vendorDir . $outputRelativeFilepath; + $isOutsideProjectDir = 0 !== strpos($sourceAbsoluteFilepath, $this->workingDir); + + $f = $this->discoveredFiles->getFiles()[$outputRelativeFilepath] + ?? new File($dependency, $packageRelativePath, $sourceAbsoluteFilepath); + + $f->addAutoloader($autoloaderType); + $f->setDoDelete($isOutsideProjectDir); + + foreach ($this->excludeFilePatterns as $excludePattern) { + if (1 === preg_match($excludePattern, $outputRelativeFilepath)) { + $f->setDoCopy(false); + } + } + + if ('filesystem->read($projectRelativePath) + ) { + $f->setDoCopy(false); + } + + $this->discoveredFiles->add($f); + } + + /** + * @param string $workingDir Absolute path to the working directory, results will be relative to this. + * @param string $relativeDirectory + * @param string $regexPattern Default to PHP files. + * + * @return string[] + */ + public function findFilesInDirectory(string $workingDir, string $relativeDirectory = '.', string $regexPattern = '/.+\.php$/'): array + { + $dir = new RecursiveDirectoryIterator($workingDir . $relativeDirectory); + $ite = new RecursiveIteratorIterator($dir); + $files = new RegexIterator($ite, $regexPattern, RegexIterator::GET_MATCH); + $fileList = array(); + foreach ($files as $file) { + $fileList = array_merge($fileList, str_replace($workingDir, '', $file)); + } + return $fileList; + } +} diff --git a/vendor/brianhenryie/strauss/src/FileScanner.php b/vendor/brianhenryie/strauss/src/FileScanner.php new file mode 100644 index 000000000..d201177d1 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/FileScanner.php @@ -0,0 +1,686 @@ +discoveredSymbols = new DiscoveredSymbols(); + + $this->namespacePrefix = $config->getNamespacePrefix(); + $this->classmapPrefix = $config->getClassmapPrefix(); + + $this->excludePackagesFromPrefixing = $config->getExcludePackagesFromPrefixing(); + $this->excludeNamespacesFromPrefixing = $config->getExcludeNamespacesFromPrefixing(); + $this->excludeFilePatternsFromPrefixing = $config->getExcludeFilePatternsFromPrefixing(); + + $this->namespaceReplacementPatterns = $config->getNamespaceReplacementPatterns(); + } + + /** + * @param DiscoveredFiles $files + */ + public function findInFiles(DiscoveredFiles $files): DiscoveredSymbols + { + foreach ($files->getFiles() as $file) { + if (!$file->isPhpFile()) { + continue; + } + + $this->find($file->getContents(), $file); + } + + return $this->discoveredSymbols; + } + + + /** + * TODO: Don't use preg_replace_callback! + * + * @uses self::addDiscoveredNamespaceChange() + * @uses self::addDiscoveredClassChange() + * + * @param string $contents + */ + protected function find(string $contents, File $file): void + { + // If the entire file is under one namespace, all we want is the namespace. + // If there were more than one namespace, it would appear as `namespace MyNamespace { ...`, + // a file with only a single namespace will appear as `namespace MyNamespace;`. + $singleNamespacePattern = '/ + ([0-9A-Za-z_\x7f-\xff\\\\]+)[\s\S]*; # Match a single namespace in the file. + /x'; // # x: ignore whitespace in regex. + if (1 === preg_match($singleNamespacePattern, $contents, $matches)) { + $this->addDiscoveredNamespaceChange($matches['namespace'], $file); + return; + } + + if (0 < preg_match_all('/\s*define\s*\(\s*["\']([^"\']*)["\']\s*,\s*["\'][^"\']*["\']\s*\)\s*;/', $contents, $constants)) { + foreach ($constants[1] as $constant) { + $constantObj = new ConstantSymbol($constant, $file); + $this->discoveredSymbols->add($constantObj); + } + } + + // TODO traits + + // TODO: Is the ";" in this still correct since it's being taken care of in the regex just above? + // Looks like with the preceding regex, it will never match. + + + preg_replace_callback( + ' + ~ # Start the pattern + [\r\n]+\s*namespace\s+([a-zA-Z0-9_\x7f-\xff\\\\]+)[;{\s\n]{1}[\s\S]*?(?=namespace|$) + # Look for a preceding namespace declaration, + # followed by a semicolon, open curly bracket, space or new line + # up until a + # potential second namespace declaration or end of file. + # if found, match that much before continuing the search on + | # the remainder of the string. + \/\*[\s\S]*?\*\/ | # Skip multiline comments + ^\s*\/\/.*$ | # Skip single line comments + \s* # Whitespace is allowed before + (?:abstract\sclass|class|interface)\s+ # Look behind for class, abstract class, interface + ([a-zA-Z0-9_\x7f-\xff]+) # Match the word until the first non-classname-valid character + \s? # Allow a space after + (?:{|extends|implements|\n|$) # Class declaration can be followed by {, extends, implements + # or a new line + ~x', // # x: ignore whitespace in regex. + function ($matches) use ($file) { + + // If we're inside a namespace other than the global namespace: + if (1 === preg_match('/^\s*namespace\s+[a-zA-Z0-9_\x7f-\xff\\\\]+[;{\s\n]{1}.*/', $matches[0])) { + $this->addDiscoveredNamespaceChange($matches[1], $file); + + return $matches[0]; + } + + if (count($matches) < 3) { + return $matches[0]; + } + + // TODO: Why is this [2] and not [1] (which seems to be always empty). + $this->addDiscoveredClassChange($matches[2], $file); + + return $matches[0]; + }, + $contents + ); + } + + protected function addDiscoveredClassChange(string $classname, File $file): void + { + + if ('ReturnTypeWillChange' === $classname) { + return; + } + if (in_array($classname, $this->getBuiltIns())) { + return; + } + + $classSymbol = new ClassSymbol($classname, $file); + $this->discoveredSymbols->add($classSymbol); + } + + protected function addDiscoveredNamespaceChange(string $namespace, File $file): void + { + + foreach ($this->excludeNamespacesFromPrefixing as $excludeNamespace) { + if (0 === strpos($namespace, $excludeNamespace)) { + return; + } + } + + $namespaceObj = new NamespaceSymbol($namespace, $file); + $this->discoveredSymbols->add($namespaceObj); + } + + /** + * Get a list of PHP built-in classes etc. so they are not prefixed. + * + * Polyfilled classes were being prefixed, but the polyfills are only active when the PHP version is below X, + * so calls to those prefixed polyfilled classnames would fail on newer PHP versions. + * + * NB: This list is not exhaustive. Any unloaded PHP extensions are not included. + * + * @see https://github.com/BrianHenryIE/strauss/issues/79 + * + * ``` + * array_filter( + * get_declared_classes(), + * function(string $className): bool { + * $reflector = new \ReflectionClass($className); + * return empty($reflector->getFileName()); + * } + * ); + * ``` + * + * @return string[] + */ + protected function getBuiltIns(): array + { + $builtins = [ + '7.4' => + [ + 'classes' => + [ + 'AppendIterator', + 'ArgumentCountError', + 'ArithmeticError', + 'ArrayIterator', + 'ArrayObject', + 'AssertionError', + 'BadFunctionCallException', + 'BadMethodCallException', + 'CURLFile', + 'CachingIterator', + 'CallbackFilterIterator', + 'ClosedGeneratorException', + 'Closure', + 'Collator', + 'CompileError', + 'DOMAttr', + 'DOMCdataSection', + 'DOMCharacterData', + 'DOMComment', + 'DOMConfiguration', + 'DOMDocument', + 'DOMDocumentFragment', + 'DOMDocumentType', + 'DOMDomError', + 'DOMElement', + 'DOMEntity', + 'DOMEntityReference', + 'DOMErrorHandler', + 'DOMException', + 'DOMImplementation', + 'DOMImplementationList', + 'DOMImplementationSource', + 'DOMLocator', + 'DOMNameList', + 'DOMNameSpaceNode', + 'DOMNamedNodeMap', + 'DOMNode', + 'DOMNodeList', + 'DOMNotation', + 'DOMProcessingInstruction', + 'DOMStringExtend', + 'DOMStringList', + 'DOMText', + 'DOMTypeinfo', + 'DOMUserDataHandler', + 'DOMXPath', + 'DateInterval', + 'DatePeriod', + 'DateTime', + 'DateTimeImmutable', + 'DateTimeZone', + 'Directory', + 'DirectoryIterator', + 'DivisionByZeroError', + 'DomainException', + 'EmptyIterator', + 'Error', + 'ErrorException', + 'Exception', + 'FFI', + 'FFI\\CData', + 'FFI\\CType', + 'FFI\\Exception', + 'FFI\\ParserException', + 'FilesystemIterator', + 'FilterIterator', + 'GMP', + 'Generator', + 'GlobIterator', + 'HashContext', + 'InfiniteIterator', + 'IntlBreakIterator', + 'IntlCalendar', + 'IntlChar', + 'IntlCodePointBreakIterator', + 'IntlDateFormatter', + 'IntlException', + 'IntlGregorianCalendar', + 'IntlIterator', + 'IntlPartsIterator', + 'IntlRuleBasedBreakIterator', + 'IntlTimeZone', + 'InvalidArgumentException', + 'IteratorIterator', + 'JsonException', + 'LengthException', + 'LibXMLError', + 'LimitIterator', + 'Locale', + 'LogicException', + 'MessageFormatter', + 'MultipleIterator', + 'NoRewindIterator', + 'Normalizer', + 'NumberFormatter', + 'OutOfBoundsException', + 'OutOfRangeException', + 'OverflowException', + 'PDO', + 'PDOException', + 'PDORow', + 'PDOStatement', + 'ParentIterator', + 'ParseError', + 'Phar', + 'PharData', + 'PharException', + 'PharFileInfo', + 'RangeException', + 'RecursiveArrayIterator', + 'RecursiveCachingIterator', + 'RecursiveCallbackFilterIterator', + 'RecursiveDirectoryIterator', + 'RecursiveFilterIterator', + 'RecursiveIteratorIterator', + 'RecursiveRegexIterator', + 'RecursiveTreeIterator', + 'Reflection', + 'ReflectionClass', + 'ReflectionClassConstant', + 'ReflectionException', + 'ReflectionExtension', + 'ReflectionFunction', + 'ReflectionFunctionAbstract', + 'ReflectionGenerator', + 'ReflectionMethod', + 'ReflectionNamedType', + 'ReflectionObject', + 'ReflectionParameter', + 'ReflectionProperty', + 'ReflectionReference', + 'ReflectionType', + 'ReflectionZendExtension', + 'RegexIterator', + 'ResourceBundle', + 'RuntimeException', + 'SQLite3', + 'SQLite3Result', + 'SQLite3Stmt', + 'SessionHandler', + 'SimpleXMLElement', + 'SimpleXMLIterator', + 'SoapClient', + 'SoapFault', + 'SoapHeader', + 'SoapParam', + 'SoapServer', + 'SoapVar', + 'SodiumException', + 'SplDoublyLinkedList', + 'SplFileInfo', + 'SplFileObject', + 'SplFixedArray', + 'SplHeap', + 'SplMaxHeap', + 'SplMinHeap', + 'SplObjectStorage', + 'SplPriorityQueue', + 'SplQueue', + 'SplStack', + 'SplTempFileObject', + 'Spoofchecker', + 'Transliterator', + 'TypeError', + 'UConverter', + 'UnderflowException', + 'UnexpectedValueException', + 'WeakReference', + 'XMLReader', + 'XMLWriter', + 'XSLTProcessor', + 'ZipArchive', + '__PHP_Incomplete_Class', + 'finfo', + 'mysqli', + 'mysqli_driver', + 'mysqli_result', + 'mysqli_sql_exception', + 'mysqli_stmt', + 'mysqli_warning', + 'php_user_filter', + 'stdClass', + 'tidy', + 'tidyNode', + ], + 'interfaces' => + [ + 'ArrayAccess', + 'Countable', + 'DateTimeInterface', + 'Iterator', + 'IteratorAggregate', + 'JsonSerializable', + 'OuterIterator', + 'RecursiveIterator', + 'Reflector', + 'SeekableIterator', + 'Serializable', + 'SessionHandlerInterface', + 'SessionIdInterface', + 'SessionUpdateTimestampHandlerInterface', + 'SplObserver', + 'SplSubject', + 'Throwable', + 'Traversable', + ], + 'traits' => + [ + ], + ], + '8.1' => + [ + 'classes' => + [ + 'AddressInfo', + 'Attribute', + 'CURLStringFile', + 'CurlHandle', + 'CurlMultiHandle', + 'CurlShareHandle', + 'DeflateContext', + 'FTP\\Connection', + 'Fiber', + 'FiberError', + 'GdFont', + 'GdImage', + 'InflateContext', + 'InternalIterator', + 'IntlDatePatternGenerator', + 'LDAP\\Connection', + 'LDAP\\Result', + 'LDAP\\ResultEntry', + 'OpenSSLAsymmetricKey', + 'OpenSSLCertificate', + 'OpenSSLCertificateSigningRequest', + 'PSpell\\Config', + 'PSpell\\Dictionary', + 'PgSql\\Connection', + 'PgSql\\Lob', + 'PgSql\\Result', + 'PhpToken', + 'ReflectionAttribute', + 'ReflectionEnum', + 'ReflectionEnumBackedCase', + 'ReflectionEnumUnitCase', + 'ReflectionFiber', + 'ReflectionIntersectionType', + 'ReflectionUnionType', + 'ReturnTypeWillChange', + 'Shmop', + 'Socket', + 'SysvMessageQueue', + 'SysvSemaphore', + 'SysvSharedMemory', + 'UnhandledMatchError', + 'ValueError', + 'WeakMap', + 'XMLParser', + ], + 'interfaces' => + [ + 'BackedEnum', + 'DOMChildNode', + 'DOMParentNode', + 'Stringable', + 'UnitEnum', + ], + 'traits' => + [ + ], + ], + '8.2' => + [ + 'classes' => + [ + 'AddressInfo', + 'AllowDynamicProperties', + 'Attribute', + 'CURLStringFile', + 'CurlHandle', + 'CurlMultiHandle', + 'CurlShareHandle', + 'DeflateContext', + 'FTP\\Connection', + 'Fiber', + 'FiberError', + 'GdFont', + 'GdImage', + 'InflateContext', + 'InternalIterator', + 'IntlDatePatternGenerator', + 'LDAP\\Connection', + 'LDAP\\Result', + 'LDAP\\ResultEntry', + 'OpenSSLAsymmetricKey', + 'OpenSSLCertificate', + 'OpenSSLCertificateSigningRequest', + 'PSpell\\Config', + 'PSpell\\Dictionary', + 'PgSql\\Connection', + 'PgSql\\Lob', + 'PgSql\\Result', + 'PhpToken', + 'Random\\BrokenRandomEngineError', + 'Random\\Engine\\Mt19937', + 'Random\\Engine\\PcgOneseq128XslRr64', + 'Random\\Engine\\Secure', + 'Random\\Engine\\Xoshiro256StarStar', + 'Random\\RandomError', + 'Random\\RandomException', + 'Random\\Randomizer', + 'ReflectionAttribute', + 'ReflectionEnum', + 'ReflectionEnumBackedCase', + 'ReflectionEnumUnitCase', + 'ReflectionFiber', + 'ReflectionIntersectionType', + 'ReflectionUnionType', + 'ReturnTypeWillChange', + 'SensitiveParameter', + 'SensitiveParameterValue', + 'Shmop', + 'Socket', + 'SysvMessageQueue', + 'SysvSemaphore', + 'SysvSharedMemory', + 'UnhandledMatchError', + 'ValueError', + 'WeakMap', + 'XMLParser', + ], + 'interfaces' => + [ + 'BackedEnum', + 'DOMChildNode', + 'DOMParentNode', + 'Random\\CryptoSafeEngine', + 'Random\\Engine', + 'Stringable', + 'UnitEnum', + ], + 'traits' => + [ + ], + ], + '8.3' => + [ + 'classes' => + [ + 'AddressInfo', + 'AllowDynamicProperties', + 'Attribute', + 'CURLStringFile', + 'CurlHandle', + 'CurlMultiHandle', + 'CurlShareHandle', + 'DateError', + 'DateException', + 'DateInvalidOperationException', + 'DateInvalidTimeZoneException', + 'DateMalformedIntervalStringException', + 'DateMalformedPeriodStringException', + 'DateMalformedStringException', + 'DateObjectError', + 'DateRangeError', + 'DeflateContext', + 'FTP\\Connection', + 'Fiber', + 'FiberError', + 'GdFont', + 'GdImage', + 'InflateContext', + 'InternalIterator', + 'IntlDatePatternGenerator', + 'LDAP\\Connection', + 'LDAP\\Result', + 'LDAP\\ResultEntry', + 'OpenSSLAsymmetricKey', + 'OpenSSLCertificate', + 'OpenSSLCertificateSigningRequest', + 'Override', + 'PSpell\\Config', + 'PSpell\\Dictionary', + 'PgSql\\Connection', + 'PgSql\\Lob', + 'PgSql\\Result', + 'PhpToken', + 'Random\\BrokenRandomEngineError', + 'Random\\Engine\\Mt19937', + 'Random\\Engine\\PcgOneseq128XslRr64', + 'Random\\Engine\\Secure', + 'Random\\Engine\\Xoshiro256StarStar', + 'Random\\IntervalBoundary', + 'Random\\RandomError', + 'Random\\RandomException', + 'Random\\Randomizer', + 'ReflectionAttribute', + 'ReflectionEnum', + 'ReflectionEnumBackedCase', + 'ReflectionEnumUnitCase', + 'ReflectionFiber', + 'ReflectionIntersectionType', + 'ReflectionUnionType', + 'ReturnTypeWillChange', + 'SQLite3Exception', + 'SensitiveParameter', + 'SensitiveParameterValue', + 'Shmop', + 'Socket', + 'SysvMessageQueue', + 'SysvSemaphore', + 'SysvSharedMemory', + 'UnhandledMatchError', + 'ValueError', + 'WeakMap', + 'XMLParser', + ], + 'interfaces' => + [ + 'BackedEnum', + 'DOMChildNode', + 'DOMParentNode', + 'Random\\CryptoSafeEngine', + 'Random\\Engine', + 'Stringable', + 'UnitEnum', + ], + 'traits' => + [ + ], + ], + '8.4' => + [ + 'classes' => + [ + 'DOM\\Document', + 'DOM\\HTMLDocument', + 'DOM\\XMLDocument', + 'dom\\attr', + 'dom\\cdatasection', + 'dom\\characterdata', + 'dom\\comment', + 'dom\\documentfragment', + 'dom\\documenttype', + 'dom\\domexception', + 'dom\\element', + 'dom\\entity', + 'dom\\entityreference', + 'dom\\namednodemap', + 'dom\\namespacenode', + 'dom\\node', + 'dom\\nodelist', + 'dom\\notation', + 'dom\\processinginstruction', + 'dom\\text', + 'dom\\xpath', + ], + 'interfaces' => + [ + 'dom\\childnode', + 'dom\\parentnode', + ], + 'traits' => + [ + ], + ], + ]; + + $flatArray = array(); + array_walk_recursive( + $builtins, + function ($array) use (&$flatArray) { + if (is_array($array)) { + $flatArray = array_merge($flatArray, array_values($array)); + } else { + $flatArray[] = $array; + } + } + ); + return $flatArray; + } +} diff --git a/vendor/brianhenryie/strauss/src/Helpers/Path.php b/vendor/brianhenryie/strauss/src/Helpers/Path.php new file mode 100644 index 000000000..5edfd0473 --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Helpers/Path.php @@ -0,0 +1,11 @@ +workingDir = $workingDir; + $this->dependencies = $dependencies; + $this->author = $author; + + $this->targetDirectory = $config->getTargetDirectory(); + $this->vendorDir = $config->getVendorDirectory(); + $this->includeModifiedDate = $config->isIncludeModifiedDate(); + $this->includeAuthor = $config->isIncludeAuthor(); + + $this->filesystem = new Filesystem(new LocalFilesystemAdapter('/')); + } + + public function copyLicenses(): void + { + $this->findLicenseFiles(); + + foreach ($this->getDiscoveredLicenseFiles() as $licenseFile) { + $targetLicenseFile = $this->targetDirectory . $licenseFile; + + $targetLicenseFileDir = dirname($targetLicenseFile); + + // Don't try copy it if it's already there. + if ($this->filesystem->fileExists($targetLicenseFile)) { + continue; + } + + // Don't add licenses to non-existent directories – there were no files copied there! + if (! is_dir($targetLicenseFileDir)) { + continue; + } + + $this->filesystem->copy( + $this->vendorDir . $licenseFile, + $targetLicenseFile + ); + } + } + + /** + * @see https://www.phpliveregex.com/p/A5y + */ + public function findLicenseFiles(?Finder $finder = null): void + { + // Include all license files in the dependency path. + $finder = $finder ?? new Finder(); + + /** @var ComposerPackage $dependency */ + foreach ($this->dependencies as $dependency) { + $packagePath = $dependency->getPackageAbsolutePath(); + + // If packages happen to have their vendor dir, i.e. locally required packages, don't included the licenses + // from their vendor dir (they should be included otherwise anyway). + // $dependency->getVendorDir() + $finder->files()->in($packagePath)->followLinks()->exclude(array( 'vendor' ))->name('/^.*licen.e.*/i'); + + /** @var \SplFileInfo $foundFile */ + foreach ($finder as $foundFile) { + $filePath = $foundFile->getPathname(); + + // Replace multiple \ and/or / with OS native DIRECTORY_SEPARATOR. + $filePath = preg_replace('#[\\\/]+#', DIRECTORY_SEPARATOR, $filePath); + + $this->discoveredLicenseFiles[$filePath] = $dependency->getPackageName(); + } + } + } + + /** + * @return string[] + */ + public function getDiscoveredLicenseFiles(): array + { + return array_keys($this->discoveredLicenseFiles); + } + + /** + * @param array $modifiedFiles + * + * @throws \Exception + */ + public function addInformationToUpdatedFiles(array $modifiedFiles): void + { + // E.g. "25-April-2021". + $date = gmdate("d-F-Y", time()); + + foreach ($modifiedFiles as $relativeFilePath => $package) { + $filepath = $this->workingDir . $this->targetDirectory . $relativeFilePath; + + if (!$this->filesystem->fileExists($filepath)) { + continue; + } + + $contents = $this->filesystem->read($filepath); + + $updatedContents = $this->addChangeDeclarationToPhpString( + $contents, + $date, + $package->getPackageName(), + $package->getLicense() + ); + + if ($updatedContents !== $contents) { + $this->filesystem->write($filepath, $updatedContents); + } + } + } + + /** + * Given a php file as a string, edit its header phpdoc, or add a header, to include: + * + * "Modified by {author} on {date} using Strauss. + * @see https://github.com/BrianHenryIE/strauss" + * + * Should probably include the original license in each file since it'll often be a mix, with the parent + * project often being a GPL WordPress plugin. + * + * Find the string between the end of php-opener and the first valid code. + * First valid code will be a line whose first non-whitespace character is not / or * ?... NO! + * If the first non whitespace string after php-opener is multiline-comment-opener, find the + * closing multiline-comment-closer + * / If there's already a comment, work within that comment + * If there is no mention in the header of the license already, add it. + * Add a note that changes have been made. + * + * @param string $phpString Code. + */ + public function addChangeDeclarationToPhpString( + string $phpString, + string $modifiedDate, + string $packageName, + string $packageLicense + ) : string { + + $author = $this->author; + + $licenseDeclaration = "@license {$packageLicense}"; + $modifiedDeclaration = 'Modified'; + if ($this->includeAuthor) { + $modifiedDeclaration .= " by {$author}"; + } + if ($this->includeModifiedDate) { + $modifiedDeclaration .= " on {$modifiedDate}"; + } + $straussLink = 'https://github.com/BrianHenryIE/strauss'; + $modifiedDeclaration .= " using {@see {$straussLink}}."; + + $startOfFileArray = []; + $tokenizeString = token_get_all($phpString); + + foreach ($tokenizeString as $token) { + if (is_array($token) && !in_array($token[1], ['namespace', '/*', ' /*'])) { + $startOfFileArray[] = $token[1]; + $token = array_shift($tokenizeString); + + if (is_array($token) && stristr($token[1], 'strauss')) { + return $phpString; + } + } elseif (!is_array($token)) { + $startOfFileArray[] = $token; + } + } + // Not in use yet (because all tests are passing) but the idea of capturing the file header and only editing + // that seems more reasonable than searching the whole file. + $startOfFile = implode('', $startOfFileArray); + + // php-open followed by some whitespace and new line until the first ... + $noCommentBetweenPhpOpenAndFirstCodePattern = '~<\?php[\s\n]*[\w\\\?]+~'; + + $multilineCommentCapturePattern = ' + ~ # Start the pattern + ( + <\?php[\S\s]* # match the beginning of the files php-open and following whitespace + ) + ( + \*[\S\s.]* # followed by a multiline-comment-open + ) + ( + \*/ # Capture the multiline-comment-close separately + ) + ~Ux'; // U: Non-greedy matching, x: ignore whitespace in pattern. + + $replaceInMultilineCommentFunction = function ($matches) use ( + $licenseDeclaration, + $modifiedDeclaration + ) { + // Find the line prefix and use it, i.e. could be none, asterisk or space-asterisk. + $commentLines = explode("\n", $matches[2]); + + if (isset($commentLines[1])&& 1 === preg_match('/^([\s\\\*]*)/', $commentLines[1], $output_array)) { + $lineStart = $output_array[1]; + } else { + $lineStart = ' * '; + } + + $appendString = "*\n"; + + // If the license is not already specified in the header, add it. + if (false === strpos($matches[2], 'licen')) { + $appendString .= "{$lineStart}{$licenseDeclaration}\n"; + } + + $appendString .= "{$lineStart}{$modifiedDeclaration}\n"; + + $commentEnd = rtrim(rtrim($lineStart, ' '), '*').'*/'; + + $replaceWith = $matches[1] . $matches[2] . $appendString . $commentEnd; + + return $replaceWith; + }; + + // If it's a simple case where there is no existing header, add the existing license. + if (1 === preg_match($noCommentBetweenPhpOpenAndFirstCodePattern, $phpString)) { + $modifiedComment = "/**\n * {$licenseDeclaration}\n *\n * {$modifiedDeclaration}\n */"; + $updatedPhpString = preg_replace('~<\?php~', " or null if the file is not from a dependency (i.e. a project file). + * + * @var array + */ + protected array $changedFiles = array(); + + public function __construct(StraussConfig $config, string $workingDir) + { + $this->config = $config; + + $this->filesystem = new Filesystem(new LocalFilesystemAdapter($workingDir)); + + $this->targetDirectory = $config->getTargetDirectory(); + $this->namespacePrefix = $config->getNamespacePrefix(); + $this->classmapPrefix = $config->getClassmapPrefix(); + $this->constantsPrefix = $config->getConstantsPrefix(); + + $this->excludePackageNamesFromPrefixing = $config->getExcludePackagesFromPrefixing(); + $this->excludeNamespacesFromPrefixing = $config->getExcludeNamespacesFromPrefixing(); + $this->excludeFilePatternsFromPrefixing = $config->getExcludeFilePatternsFromPrefixing(); + } + + // Don't replace a classname if there's an import for a class with the same name. + // but do replace \Classname always + + + /** + * @param DiscoveredSymbols $discoveredSymbols + * @param array $phpFileArrays + */ + public function replaceInFiles(DiscoveredSymbols $discoveredSymbols, array $phpFileArrays): void + { + + foreach ($phpFileArrays as $targetRelativeFilepath => $fileArray) { + $package = $fileArray['dependency']; + + // Skip excluded namespaces. + if (in_array($package->getPackageName(), $this->excludePackageNamesFromPrefixing)) { + continue; + } + + // Skip files whose filepath matches an excluded pattern. + foreach ($this->excludeFilePatternsFromPrefixing as $excludePattern) { + if (1 === preg_match($excludePattern, $targetRelativeFilepath)) { + continue 2; + } + } + + $targetRelativeFilepathFromProject = $this->targetDirectory. $targetRelativeFilepath; + + if (! $this->filesystem->fileExists($targetRelativeFilepathFromProject)) { + continue; + } + + // Throws an exception, but unlikely to happen. + $contents = $this->filesystem->read($targetRelativeFilepathFromProject); + + $updatedContents = $this->replaceInString($discoveredSymbols, $contents); + + if ($updatedContents !== $contents) { + $this->changedFiles[$targetRelativeFilepath] = $package; + $this->filesystem->write($targetRelativeFilepathFromProject, $updatedContents); + } + } + } + + /** + * @param DiscoveredSymbols $discoveredSymbols + * @param string[] $relativeFilePaths + * @return void + * @throws \League\Flysystem\FilesystemException + */ + public function replaceInProjectFiles(DiscoveredSymbols $discoveredSymbols, array $relativeFilePaths): void + { + foreach ($relativeFilePaths as $workingDirRelativeFilepath) { + if (! $this->filesystem->fileExists($workingDirRelativeFilepath)) { + continue; + } + + // Throws an exception, but unlikely to happen. + $contents = $this->filesystem->read($workingDirRelativeFilepath); + + $updatedContents = $this->replaceInString($discoveredSymbols, $contents); + + if ($updatedContents !== $contents) { + $this->changedFiles[ $workingDirRelativeFilepath ] = null; + $this->filesystem->write($workingDirRelativeFilepath, $updatedContents); + } + } + } + + /** + * @param DiscoveredSymbols $discoveredSymbols + * @param string $contents + */ + public function replaceInString(DiscoveredSymbols $discoveredSymbols, string $contents): string + { + $namespacesChanges = $discoveredSymbols->getDiscoveredNamespaces($this->config->getNamespacePrefix()); + $classes = $discoveredSymbols->getDiscoveredClasses($this->config->getClassmapPrefix()); + $constants = $discoveredSymbols->getDiscoveredConstants($this->config->getConstantsPrefix()); + + foreach ($classes as $originalClassname) { + if ('ReturnTypeWillChange' === $originalClassname) { + continue; + } + + $classmapPrefix = $this->classmapPrefix; + + $contents = $this->replaceClassname($contents, $originalClassname, $classmapPrefix); + } + + foreach ($namespacesChanges as $originalNamespace => $namespaceSymbol) { + if (in_array($originalNamespace, $this->excludeNamespacesFromPrefixing)) { + continue; + } + + $contents = $this->replaceNamespace($contents, $originalNamespace, $namespaceSymbol->getReplacement()); + } + + if (!is_null($this->constantsPrefix)) { + $contents = $this->replaceConstants($contents, $constants, $this->constantsPrefix); + } + + return $contents; + } + + /** + * TODO: Test against traits. + * + * @param string $contents The text to make replacements in. + * @param string $originalNamespace + * @param string $replacement + * + * @return string The updated text. + */ + public function replaceNamespace(string $contents, string $originalNamespace, string $replacement): string + { + + $searchNamespace = '\\'.rtrim($originalNamespace, '\\') . '\\'; + $searchNamespace = str_replace('\\\\', '\\', $searchNamespace); + $searchNamespace = str_replace('\\', '\\\\{0,2}', $searchNamespace); + + $pattern = " + / # Start the pattern + ( + ^\s* # start of the string + |\\n\s* # start of the line + |(\s* # as the value in an associative array + |\[\s* # In a square array + |\?\s* # In a ternary operator + |:\s* # In a ternary operator + |\(string\)\s* # casting a namespaced class to a string + ) + @? # Maybe preceeded by the @ symbol for error suppression + (? + {$searchNamespace} # followed by the namespace to replace + ) + (?!:) # Not followed by : which would only be valid after a classname + ( + \s*; # followed by a semicolon + |\\\\{1,2}[a-zA-Z0-9_\x7f-\xff]{1,} # or a classname no slashes + |\s+as # or the keyword as + |\" # or quotes + |' # or single quote + |: # or a colon to access a static + |\\\\{ + ) + /Ux"; // U: Non-greedy matching, x: ignore whitespace in pattern. + + $replacingFunction = function ($matches) use ($originalNamespace, $replacement) { + $singleBackslash = '\\'; + $doubleBackslash = '\\\\'; + + if (false !== strpos($matches['0'], $doubleBackslash)) { + $originalNamespace = str_replace($singleBackslash, $doubleBackslash, $originalNamespace); + $replacement = str_replace($singleBackslash, $doubleBackslash, $replacement); + } + + $replaced = str_replace($originalNamespace, $replacement, $matches[0]); + + return $replaced; + }; + + $result = preg_replace_callback($pattern, $replacingFunction, $contents); + + $matchingError = preg_last_error(); + if (0 !== $matchingError) { + $message = "Matching error {$matchingError}"; + if (PREG_BACKTRACK_LIMIT_ERROR === $matchingError) { + $message = 'Preg Backtrack limit was exhausted!'; + } + throw new Exception($message); + } + + // For prefixed functions which do not begin with a backslash, add one. + // I'm not certain this is a good idea. + // @see https://github.com/BrianHenryIE/strauss/issues/65 + $functionReplacingPattern = '/\\\\?('.preg_quote(ltrim($replacement, '\\'), '/').'\\\\(?:[a-zA-Z0-9_\x7f-\xff]+\\\\)*[a-zA-Z0-9_\x7f-\xff]+\\()/'; + $result = preg_replace( + $functionReplacingPattern, + "\\\\$1", + $result + ); + + return $result; + } + + /** + * In a namespace: + * * use \Classname; + * * new \Classname() + * + * In a global namespace: + * * new Classname() + * + * @param string $contents + * @param string $originalClassname + * @param string $classnamePrefix + * @throws \Exception + */ + public function replaceClassname(string $contents, string $originalClassname, string $classnamePrefix): string + { + $searchClassname = preg_quote($originalClassname, '/'); + + // This could be more specific if we could enumerate all preceding and proceeding words ("new", "("...). + $pattern = ' + / # Start the pattern + (^\s*namespace|\r\n\s*namespace)\s+[a-zA-Z0-9_\x7f-\xff\\\\]+\s*{(.*?)(namespace|\z) + # Look for a preceding namespace declaration, up until a + # potential second namespace declaration. + | # if found, match that much before continuing the search on + # the remainder of the string. + (^\s*namespace|\r\n\s*namespace)\s+[a-zA-Z0-9_\x7f-\xff\\\\]+\s*;(.*) # Skip lines just declaring the namespace. + | + ([^a-zA-Z0-9_\x7f-\xff\$\\\])('. $searchClassname . ')([^a-zA-Z0-9_\x7f-\xff\\\]) # outside a namespace the class will not be prefixed with a slash + + /xsm'; // # x: ignore whitespace in regex. s dot matches newline, m: ^ and $ match start and end of line + + $replacingFunction = function ($matches) use ($originalClassname, $classnamePrefix) { + + // If we're inside a namespace other than the global namespace: + if (1 === preg_match('/\s*namespace\s+[a-zA-Z0-9_\x7f-\xff\\\\]+[;{\s\n]{1}.*/', $matches[0])) { + $updated = $this->replaceGlobalClassInsideNamedNamespace( + $matches[0], + $originalClassname, + $classnamePrefix + ); + + return $updated; + } else { + $newContents = ''; + foreach ($matches as $index => $captured) { + if (0 === $index) { + continue; + } + + if ($captured == $originalClassname) { + $newContents .= $classnamePrefix; + } + + $newContents .= $captured; + } + return $newContents; + } +// return $matches[1] . $matches[2] . $matches[3] . $classnamePrefix . $originalClassname . $matches[5]; + }; + + $result = preg_replace_callback($pattern, $replacingFunction, $contents); + + if (is_null($result)) { + throw new Exception('preg_replace_callback returned null'); + } + + $matchingError = preg_last_error(); + if (0 !== $matchingError) { + $message = "Matching error {$matchingError}"; + if (PREG_BACKTRACK_LIMIT_ERROR === $matchingError) { + $message = 'Backtrack limit was exhausted!'; + } + throw new Exception($message); + } + + return $result; + } + + /** + * Pass in a string and look for \Classname instances. + * + * @param string $contents + * @param string $originalClassname + * @param string $classnamePrefix + * @return string + */ + protected function replaceGlobalClassInsideNamedNamespace($contents, $originalClassname, $classnamePrefix): string + { + $replacement = $classnamePrefix . $originalClassname; + + // use Prefixed_Class as Class; + $usePattern = '/ + (\s*use\s+) + ('.$originalClassname.') # Followed by the classname + \s*; + /x'; // # x: ignore whitespace in regex. + + $contents = preg_replace_callback( + $usePattern, + function ($matches) use ($replacement) { + return $matches[1] . $replacement . ' as '. $matches[2] . ';'; + }, + $contents + ); + + $bodyPattern = + '/([^a-zA-Z0-9_\x7f-\xff] # Not a class character + \\\) # Followed by a backslash to indicate global namespace + ('.$originalClassname.') # Followed by the classname + ([^\\\;]{1}) # Not a backslash or semicolon which might indicate a namespace + /x'; // # x: ignore whitespace in regex. + + $contents = preg_replace_callback( + $bodyPattern, + function ($matches) use ($replacement) { + return $matches[1] . $replacement . $matches[3]; + }, + $contents + ); + + return $contents; + } + + /** + * TODO: This should be split and brought to FileScanner. + * + * @param string $contents + * @param string[] $originalConstants + * @param string $prefix + */ + protected function replaceConstants(string $contents, array $originalConstants, string $prefix): string + { + + foreach ($originalConstants as $constant) { + $contents = $this->replaceConstant($contents, $constant, $prefix . $constant); + } + + return $contents; + } + + protected function replaceConstant(string $contents, string $originalConstant, string $replacementConstant): string + { + return str_replace($originalConstant, $replacementConstant, $contents); + } + + /** + * @return array + */ + public function getModifiedFiles(): array + { + return $this->changedFiles; + } +} diff --git a/vendor/brianhenryie/strauss/src/Types/ClassSymbol.php b/vendor/brianhenryie/strauss/src/Types/ClassSymbol.php new file mode 100644 index 000000000..c94eadb0f --- /dev/null +++ b/vendor/brianhenryie/strauss/src/Types/ClassSymbol.php @@ -0,0 +1,10 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Automattic\\Jetpack\\Autoloader\\AutoloadGenerator' => $vendorDir . '/automattic/jetpack-autoloader/src/AutoloadGenerator.php', + 'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'Deprecated' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Deprecated.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'Pdo\\Dblib' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php', + 'Pdo\\Firebird' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php', + 'Pdo\\Mysql' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php', + 'Pdo\\Odbc' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php', + 'Pdo\\Pgsql' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php', + 'Pdo\\Sqlite' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'ReflectionConstant' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php', + 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'RoundingMode' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'UDX\\Settings' => $baseDir . '/lib/includes/class-settings.php', 'UDX\\Utility' => $baseDir . '/lib/includes/class-utility.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'UsabilityDynamics\\UD_API\\API' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-api.php', 'UsabilityDynamics\\UD_API\\Admin' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-admin.php', 'UsabilityDynamics\\UD_API\\Bootstrap' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-bootstrap.php', @@ -30,6 +48,9 @@ 'UsabilityDynamics\\WP\\TGM_Bulk_Installer_Skin' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-tgm-bulk-installer.php', 'UsabilityDynamics\\WP\\TGM_Plugin_Activation' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-tgm-plugin-activation.php', 'UsabilityDynamics\\WP\\Utility' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-utility.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'WP_Async_Request' => $vendorDir . '/deliciousbrains/wp-background-processing/classes/wp-async-request.php', + 'WP_Background_Process' => $vendorDir . '/deliciousbrains/wp-background-processing/classes/wp-background-process.php', 'wpCloud\\StatelessMedia\\API' => $baseDir . '/lib/classes/class-api.php', 'wpCloud\\StatelessMedia\\Addons' => $baseDir . '/lib/classes/class-addons.php', 'wpCloud\\StatelessMedia\\Ajax' => $baseDir . '/lib/classes/class-ajax.php', @@ -40,7 +61,6 @@ 'wpCloud\\StatelessMedia\\Batch\\Migration' => $baseDir . '/lib/classes/batch/class-migration.php', 'wpCloud\\StatelessMedia\\Bootstrap' => $baseDir . '/lib/classes/class-bootstrap.php', 'wpCloud\\StatelessMedia\\Compatibility' => $baseDir . '/lib/classes/class-compatibility.php', - 'wpCloud\\StatelessMedia\\CompatibilityWooExtraProductOptions' => $baseDir . '/lib/classes/compatibility/woo-extra-product-options.php', 'wpCloud\\StatelessMedia\\DB' => $baseDir . '/lib/classes/class-db.php', 'wpCloud\\StatelessMedia\\DynamicImageSupport' => $baseDir . '/lib/classes/class-dynamic-image-support.php', 'wpCloud\\StatelessMedia\\EWWW' => $baseDir . '/lib/classes/compatibility/ewww.php', @@ -76,4 +96,5 @@ 'wpCloud\\StatelessMedia\\Utility' => $baseDir . '/lib/classes/class-utility.php', 'wpCloud\\StatelessMedia\\WPBakeryPageBuilder' => $baseDir . '/lib/classes/compatibility/wpbakery-page-builder.php', 'wpCloud\\StatelessMedia\\WPSmush' => $baseDir . '/lib/classes/compatibility/wp-smush.php', + '' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php', ); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 000000000..393ed54fa --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,20 @@ + $vendorDir . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', + 'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', + '9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php', +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index ba5cce735..dd68cf754 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -6,6 +6,54 @@ $baseDir = dirname($vendorDir); return array( + 'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src', $vendorDir . '/phpdocumentor/reflection-common/src'), + 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), + 'Symfony\\Polyfill\\Php84\\' => array($vendorDir . '/symfony/polyfill-php84'), + 'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Contracts\\Cache\\' => array($vendorDir . '/symfony/cache-contracts'), + 'Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'), + 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), + 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'), + 'Seld\\Signal\\' => array($vendorDir . '/seld/signal-handler/src'), + 'Seld\\PharUtils\\' => array($vendorDir . '/seld/phar-utils/src'), + 'Seld\\JsonLint\\' => array($vendorDir . '/seld/jsonlint/src/Seld/JsonLint'), + 'React\\Promise\\' => array($vendorDir . '/react/promise/src'), + 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), + 'PHPStan\\PhpDocParser\\' => array($vendorDir . '/phpstan/phpdoc-parser/src'), + 'MyCLabs\\Enum\\' => array($vendorDir . '/myclabs/php-enum/src'), + 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), + 'MabeEnum\\' => array($vendorDir . '/marc-mabe/php-enum/src'), + 'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'), + 'League\\Flysystem\\Local\\' => array($vendorDir . '/league/flysystem-local'), + 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), + 'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), + 'JsonMapper\\' => array($vendorDir . '/json-mapper/json-mapper/src'), 'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'), + 'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/src'), + 'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'), + 'Composer\\Spdx\\' => array($vendorDir . '/composer/spdx-licenses/src'), + 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), + 'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'), + 'Composer\\MetadataMinifier\\' => array($vendorDir . '/composer/metadata-minifier/src'), 'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'), + 'Composer\\ClassMapGenerator\\' => array($vendorDir . '/composer/class-map-generator/src'), + 'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'), + 'Composer\\' => array($vendorDir . '/composer/composer/src/Composer'), + 'BrianHenryIE\\Strauss\\' => array($vendorDir . '/brianhenryie/strauss/src'), + 'Automattic\\Jetpack\\Autoloader\\' => array($vendorDir . '/automattic/jetpack-autoloader/src'), ); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 4388aef1f..ea6820089 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -33,6 +33,18 @@ public static function getLoader() $loader->register(true); + $filesToLoad = \Composer\Autoload\ComposerStaticInitc59d002476a452800baaf79c430753cb::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + return $loader; } } diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 787d767c3..3fc66c2c6 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -6,26 +6,315 @@ class ComposerStaticInitc59d002476a452800baaf79c430753cb { + public static $files = array ( + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', + 'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', + '9d2b9fc6db0f153a0a149fefb182415e' => __DIR__ . '/..' . '/symfony/polyfill-php84/bootstrap.php', + ); + public static $prefixLengthsPsr4 = array ( + 'p' => + array ( + 'phpDocumentor\\Reflection\\' => 25, + ), + 'W' => + array ( + 'Webmozart\\Assert\\' => 17, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php84\\' => 23, + 'Symfony\\Polyfill\\Php81\\' => 23, + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\Cache\\' => 24, + 'Symfony\\Component\\VarExporter\\' => 30, + 'Symfony\\Component\\String\\' => 25, + 'Symfony\\Component\\Process\\' => 26, + 'Symfony\\Component\\Finder\\' => 25, + 'Symfony\\Component\\Filesystem\\' => 29, + 'Symfony\\Component\\Console\\' => 26, + 'Symfony\\Component\\Cache\\' => 24, + 'Seld\\Signal\\' => 12, + 'Seld\\PharUtils\\' => 15, + 'Seld\\JsonLint\\' => 14, + ), + 'R' => + array ( + 'React\\Promise\\' => 14, + ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Log\\' => 8, + 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + 'PhpParser\\' => 10, + 'PHPStan\\PhpDocParser\\' => 21, + ), + 'M' => + array ( + 'MyCLabs\\Enum\\' => 13, + 'Monolog\\' => 8, + 'MabeEnum\\' => 9, + ), + 'L' => + array ( + 'League\\MimeTypeDetection\\' => 25, + 'League\\Flysystem\\Local\\' => 23, + 'League\\Flysystem\\' => 17, + ), + 'J' => + array ( + 'JsonSchema\\' => 11, + 'JsonMapper\\' => 11, + ), 'F' => array ( 'Firebase\\JWT\\' => 13, ), + 'D' => + array ( + 'Doctrine\\Deprecations\\' => 22, + ), 'C' => array ( + 'Composer\\XdebugHandler\\' => 23, + 'Composer\\Spdx\\' => 14, + 'Composer\\Semver\\' => 16, + 'Composer\\Pcre\\' => 14, + 'Composer\\MetadataMinifier\\' => 26, 'Composer\\Installers\\' => 20, + 'Composer\\ClassMapGenerator\\' => 27, + 'Composer\\CaBundle\\' => 18, + 'Composer\\' => 9, + ), + 'B' => + array ( + 'BrianHenryIE\\Strauss\\' => 21, + ), + 'A' => + array ( + 'Automattic\\Jetpack\\Autoloader\\' => 30, ), ); public static $prefixDirsPsr4 = array ( + 'phpDocumentor\\Reflection\\' => + array ( + 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', + 1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', + 2 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', + ), + 'Webmozart\\Assert\\' => + array ( + 0 => __DIR__ . '/..' . '/webmozart/assert/src', + ), + 'Symfony\\Polyfill\\Php84\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php84', + ), + 'Symfony\\Polyfill\\Php81\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', + ), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Contracts\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache-contracts', + ), + 'Symfony\\Component\\VarExporter\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-exporter', + ), + 'Symfony\\Component\\String\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/string', + ), + 'Symfony\\Component\\Process\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/process', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Symfony\\Component\\Filesystem\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/filesystem', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'Symfony\\Component\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache', + ), + 'Seld\\Signal\\' => + array ( + 0 => __DIR__ . '/..' . '/seld/signal-handler/src', + ), + 'Seld\\PharUtils\\' => + array ( + 0 => __DIR__ . '/..' . '/seld/phar-utils/src', + ), + 'Seld\\JsonLint\\' => + array ( + 0 => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint', + ), + 'React\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/react/promise/src', + ), + 'Psr\\SimpleCache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'PhpParser\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser', + ), + 'PHPStan\\PhpDocParser\\' => + array ( + 0 => __DIR__ . '/..' . '/phpstan/phpdoc-parser/src', + ), + 'MyCLabs\\Enum\\' => + array ( + 0 => __DIR__ . '/..' . '/myclabs/php-enum/src', + ), + 'Monolog\\' => + array ( + 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', + ), + 'MabeEnum\\' => + array ( + 0 => __DIR__ . '/..' . '/marc-mabe/php-enum/src', + ), + 'League\\MimeTypeDetection\\' => + array ( + 0 => __DIR__ . '/..' . '/league/mime-type-detection/src', + ), + 'League\\Flysystem\\Local\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem-local', + ), + 'League\\Flysystem\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem/src', + ), + 'JsonSchema\\' => + array ( + 0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema', + ), + 'JsonMapper\\' => + array ( + 0 => __DIR__ . '/..' . '/json-mapper/json-mapper/src', + ), 'Firebase\\JWT\\' => array ( 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', ), + 'Doctrine\\Deprecations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/deprecations/src', + ), + 'Composer\\XdebugHandler\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/xdebug-handler/src', + ), + 'Composer\\Spdx\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/spdx-licenses/src', + ), + 'Composer\\Semver\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/semver/src', + ), + 'Composer\\Pcre\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/pcre/src', + ), + 'Composer\\MetadataMinifier\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/metadata-minifier/src', + ), 'Composer\\Installers\\' => array ( 0 => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers', ), + 'Composer\\ClassMapGenerator\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/class-map-generator/src', + ), + 'Composer\\CaBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/ca-bundle/src', + ), + 'Composer\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/composer/src/Composer', + ), + 'BrianHenryIE\\Strauss\\' => + array ( + 0 => __DIR__ . '/..' . '/brianhenryie/strauss/src', + ), + 'Automattic\\Jetpack\\Autoloader\\' => + array ( + 0 => __DIR__ . '/..' . '/automattic/jetpack-autoloader/src', + ), ); public static $prefixesPsr0 = array ( @@ -39,9 +328,27 @@ class ComposerStaticInitc59d002476a452800baaf79c430753cb ); public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Automattic\\Jetpack\\Autoloader\\AutoloadGenerator' => __DIR__ . '/..' . '/automattic/jetpack-autoloader/src/AutoloadGenerator.php', + 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Deprecated' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Deprecated.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'Pdo\\Dblib' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php', + 'Pdo\\Firebird' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php', + 'Pdo\\Mysql' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php', + 'Pdo\\Odbc' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php', + 'Pdo\\Pgsql' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php', + 'Pdo\\Sqlite' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'ReflectionConstant' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php', + 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'RoundingMode' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'UDX\\Settings' => __DIR__ . '/../..' . '/lib/includes/class-settings.php', 'UDX\\Utility' => __DIR__ . '/../..' . '/lib/includes/class-utility.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'UsabilityDynamics\\UD_API\\API' => __DIR__ . '/..' . '/udx/lib-ud-api-client/lib/classes/class-api.php', 'UsabilityDynamics\\UD_API\\Admin' => __DIR__ . '/..' . '/udx/lib-ud-api-client/lib/classes/class-admin.php', 'UsabilityDynamics\\UD_API\\Bootstrap' => __DIR__ . '/..' . '/udx/lib-ud-api-client/lib/classes/class-bootstrap.php', @@ -63,6 +370,9 @@ class ComposerStaticInitc59d002476a452800baaf79c430753cb 'UsabilityDynamics\\WP\\TGM_Bulk_Installer_Skin' => __DIR__ . '/..' . '/udx/lib-wp-bootstrap/lib/classes/class-tgm-bulk-installer.php', 'UsabilityDynamics\\WP\\TGM_Plugin_Activation' => __DIR__ . '/..' . '/udx/lib-wp-bootstrap/lib/classes/class-tgm-plugin-activation.php', 'UsabilityDynamics\\WP\\Utility' => __DIR__ . '/..' . '/udx/lib-wp-bootstrap/lib/classes/class-utility.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'WP_Async_Request' => __DIR__ . '/..' . '/deliciousbrains/wp-background-processing/classes/wp-async-request.php', + 'WP_Background_Process' => __DIR__ . '/..' . '/deliciousbrains/wp-background-processing/classes/wp-background-process.php', 'wpCloud\\StatelessMedia\\API' => __DIR__ . '/../..' . '/lib/classes/class-api.php', 'wpCloud\\StatelessMedia\\Addons' => __DIR__ . '/../..' . '/lib/classes/class-addons.php', 'wpCloud\\StatelessMedia\\Ajax' => __DIR__ . '/../..' . '/lib/classes/class-ajax.php', @@ -73,7 +383,6 @@ class ComposerStaticInitc59d002476a452800baaf79c430753cb 'wpCloud\\StatelessMedia\\Batch\\Migration' => __DIR__ . '/../..' . '/lib/classes/batch/class-migration.php', 'wpCloud\\StatelessMedia\\Bootstrap' => __DIR__ . '/../..' . '/lib/classes/class-bootstrap.php', 'wpCloud\\StatelessMedia\\Compatibility' => __DIR__ . '/../..' . '/lib/classes/class-compatibility.php', - 'wpCloud\\StatelessMedia\\CompatibilityWooExtraProductOptions' => __DIR__ . '/../..' . '/lib/classes/compatibility/woo-extra-product-options.php', 'wpCloud\\StatelessMedia\\DB' => __DIR__ . '/../..' . '/lib/classes/class-db.php', 'wpCloud\\StatelessMedia\\DynamicImageSupport' => __DIR__ . '/../..' . '/lib/classes/class-dynamic-image-support.php', 'wpCloud\\StatelessMedia\\EWWW' => __DIR__ . '/../..' . '/lib/classes/compatibility/ewww.php', @@ -109,6 +418,7 @@ class ComposerStaticInitc59d002476a452800baaf79c430753cb 'wpCloud\\StatelessMedia\\Utility' => __DIR__ . '/../..' . '/lib/classes/class-utility.php', 'wpCloud\\StatelessMedia\\WPBakeryPageBuilder' => __DIR__ . '/../..' . '/lib/classes/compatibility/wpbakery-page-builder.php', 'wpCloud\\StatelessMedia\\WPSmush' => __DIR__ . '/../..' . '/lib/classes/compatibility/wp-smush.php', + '' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/vendor/composer/ca-bundle/LICENSE b/vendor/composer/ca-bundle/LICENSE new file mode 100644 index 000000000..c5b5220e8 --- /dev/null +++ b/vendor/composer/ca-bundle/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2016 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/ca-bundle/README.md b/vendor/composer/ca-bundle/README.md new file mode 100644 index 000000000..d8205ec53 --- /dev/null +++ b/vendor/composer/ca-bundle/README.md @@ -0,0 +1,85 @@ +composer/ca-bundle +================== + +Small utility library that lets you find a path to the system CA bundle, +and includes a fallback to the Mozilla CA bundle. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + + +Installation +------------ + +Install the latest version with: + +```bash +$ composer require composer/ca-bundle +``` + + +Requirements +------------ + +* PHP 5.3.2 is required but using the latest version of PHP is highly recommended. + + +Basic usage +----------- + +### `Composer\CaBundle\CaBundle` + +- `CaBundle::getSystemCaRootBundlePath()`: Returns the system CA bundle path, or a path to the bundled one as fallback +- `CaBundle::getBundledCaBundlePath()`: Returns the path to the bundled CA file +- `CaBundle::validateCaFile($filename)`: Validates a CA file using openssl_x509_parse only if it is safe to use +- `CaBundle::isOpensslParseSafe()`: Test if it is safe to use the PHP function openssl_x509_parse() +- `CaBundle::reset()`: Resets the static caches + + +#### To use with curl + +```php +$curl = curl_init("https://example.org/"); + +$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(); +if (is_dir($caPathOrFile)) { + curl_setopt($curl, CURLOPT_CAPATH, $caPathOrFile); +} else { + curl_setopt($curl, CURLOPT_CAINFO, $caPathOrFile); +} + +$result = curl_exec($curl); +``` + +#### To use with php streams + +```php +$opts = array( + 'http' => array( + 'method' => "GET" + ) +); + +$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(); +if (is_dir($caPathOrFile)) { + $opts['ssl']['capath'] = $caPathOrFile; +} else { + $opts['ssl']['cafile'] = $caPathOrFile; +} + +$context = stream_context_create($opts); +$result = file_get_contents('https://example.com', false, $context); +``` + +#### To use with Guzzle + +```php +$client = new \GuzzleHttp\Client([ + \GuzzleHttp\RequestOptions::VERIFY => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath() +]); +``` + +License +------- + +composer/ca-bundle is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/ca-bundle/composer.json b/vendor/composer/ca-bundle/composer.json new file mode 100644 index 000000000..c2ce2bb8e --- /dev/null +++ b/vendor/composer/ca-bundle/composer.json @@ -0,0 +1,54 @@ +{ + "name": "composer/ca-bundle", + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "type": "library", + "license": "MIT", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8 || ^9", + "phpstan/phpstan": "^1.10", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\CaBundle\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "scripts": { + "test": "@php phpunit", + "phpstan": "@php phpstan analyse" + } +} diff --git a/vendor/composer/ca-bundle/res/cacert.pem b/vendor/composer/ca-bundle/res/cacert.pem new file mode 100644 index 000000000..a78e1dd47 --- /dev/null +++ b/vendor/composer/ca-bundle/res/cacert.pem @@ -0,0 +1,3529 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla last updated on: Wed Feb 11 18:26:30 2026 GMT +## +## Find updated versions here: https://curl.se/docs/caextract.html +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.32. +## SHA256: 3b98d4e3ff57a326d9587c33633039c8c3a9cf0b55f7ca581d7598ff329eb1f3 +## + + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +======================================== +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM +BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x +MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw +MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM +LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C +Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 +P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge +oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp +k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z +fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ +gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 +UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 +1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s +bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr +dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf +ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl +u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq +erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj +MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ +vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI +Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y +wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG +A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK +Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 +MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv +bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX +SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz +iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf +jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim +5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe +sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj +0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ +JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u +y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h ++bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG +xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID +AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN +AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw +W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld +y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov ++BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc +eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw +9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 +nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY +hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB +60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 +zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf +LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq +stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o +WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ +OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 +Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE +uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm ++9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj +ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H +PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H +ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla +4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R +vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd +zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O +856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH +Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu +3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +GLOBALTRUST 2020 +================ +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx +IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT +VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh +BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy +MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi +D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO +VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM +CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm +fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA +A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR +JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG +DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU +clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ +mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud +IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw +4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 +iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS +8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 +HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS +vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 +oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF +YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl +gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4 +NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv +bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg +Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw +MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw +EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz +BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv +T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv +B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse +zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM +VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j +7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z +JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe +8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO +Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ +UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx +j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt +dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM +5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb +5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54 +EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H +hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy +g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3 +r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG +EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew +HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY +QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p +fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 +HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 +fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt +g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 +NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk +fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ +P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY +njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK +HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL +LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s +ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K +h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 +CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA +4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo +WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj +6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT +OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck +bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +TunTrust Root CA +================ +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG +A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj +dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw +NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD +ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz +2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b +bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7 +NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd +gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW +VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f +Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ +juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas +DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS +VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI +04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl +0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd +Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY +YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp +adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x +xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP +jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM +MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z +ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r +AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +HARICA TLS RSA Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG +EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz +OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl +bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB +IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN +JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu +a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y +Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K +5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv +dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR +0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH +GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm +haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ +CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU +EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq +QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD +QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR +j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 +vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 +qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 +Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ +PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn +kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= +-----END CERTIFICATE----- + +HARICA TLS ECC Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH +UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD +QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX +DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj +IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv +b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l +AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b +ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW +0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi +rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw +CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud +DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w +gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A +bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL +4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb +LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il +I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP +cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA +LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A +lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH +9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf +NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE +ZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +vTrus ECC Root CA +================= +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE +BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS +b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa +BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c +ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n +TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT +QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL +YgmRWAD5Tfs0aNoJrSEGGJTO +-----END CERTIFICATE----- + +vTrus Root CA +============= +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG +A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv +b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG +A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots +SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI +ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF +XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA +YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 +kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 +AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu +/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu +1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO +9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg +scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr +jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 +8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn +xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg +icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 +sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW +nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc +SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H +l3s= +-----END CERTIFICATE----- + +ISRG Root X2 +============ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV +UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT +UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT +MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS +RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H +ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb +d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF +cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 +U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +HiPKI Root CA - G1 +================== +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ +IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT +AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg +Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 +o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k +wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE +YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA +GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd +hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj +1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 +9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ +Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF +8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD +AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl +tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE +wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q +JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv +5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz +jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg +hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb +yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ +yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW +ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI +KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg +UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 +xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w +B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW +nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk +9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq +kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A +K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX +V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW +cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD +ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi +ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar +J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci +NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me +LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF +fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ +7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 +FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 +gm3c +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl +e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb +a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS ++LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M +kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG +r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q +S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV +J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL +dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD +ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh +swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel +/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn +jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5 +9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M +7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8 +0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR +WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW +HYbL +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq +Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT +L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV +11RZt+cRLInUue4X +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 +PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C +r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh +4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +Telia Root CA v2 +================ +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT +AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 +MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK +DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 +6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q +9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn +pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl +tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW +5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr +RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E +BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 +M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau +BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W +xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 +tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H +eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C +y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC +QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 +h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 +sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 +xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ +raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 +dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu +QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom +AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +D-TRUST EV Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 +ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ +raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR +AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +DigiCert TLS ECC P384 Root G5 +============================= +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 +NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg +Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd +lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj +n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds +Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx +AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +DigiCert TLS RSA4096 Root G5 +============================ +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG +EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 +MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 +IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 +7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU +AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces +tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa +zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV +DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q +TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy +z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ +MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk +wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E +FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN +lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN +MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ +u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G +OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh +47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU +FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ +yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP +bEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +Certainly Root R1 +================= +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN +MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy +dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O +5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl +8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl +DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI +XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN +KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ +AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb +rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 +VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS +p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz +HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v +MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB +GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ +gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH +JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 +fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw +x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S +X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +Certainly Root E1 +================= +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV +UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 +MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu +bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 +fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 +YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E +AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 +rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +Security Communication ECC RootCA1 +================================== +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD +VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t +dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL +MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV +BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo +5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW +BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L +snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e +N9k= +-----END CERTIFICATE----- + +BJCA Global Root CA1 +==================== +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG +EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK +Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG +A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD +DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm +CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS +sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn +P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW +yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj +eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn +MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b +OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh +GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK +H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB +AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ +dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8 +60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh +TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW +4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp +GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx +4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps +3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S +SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI= +-----END CERTIFICATE----- + +BJCA Global Root CA2 +==================== +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD +TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg +R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE +BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC +SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl +SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK +/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI +1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8 +W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g +UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root E46 +============================================= +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH +QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 +ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 +WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 +aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr +gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 +NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud +DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH +lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U +SAGKcw== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root R46 +============================================= +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 +OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k +1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf +GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP +FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu +ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz +Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A +wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF +plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ +EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW +6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI +IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp +E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 +exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M +0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI +84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m +pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd +Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b +E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm +J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +SSL.com TLS RSA Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG +EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg +Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC +VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u +9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y +7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac +oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M +R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG +D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW +TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk +8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq +g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk +7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu +N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN +j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by +iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU +o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo +ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib +MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi +vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 +P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 +9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +SSL.com TLS ECC Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v +dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx +GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy +JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 +5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 +81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG +MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w +7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 +Zn6g6g== +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA ECC TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB +dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD +VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg +VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT +AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K +DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS +b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX +NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ +uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY +a3cpetskz2VAv9LcjBHo9H1/IISpQuQo +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA RSA TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD +DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw +CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 +b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV +BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB +l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG +vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK +ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt +0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK +PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY +sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY +Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ +rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa +fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl +Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX +AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G +slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt +afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q +TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj +1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l +PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W +HYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +TrustAsia Global Root CA G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG +A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM +G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw +MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu +MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz +lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ +Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V +P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag +dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm +9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc +D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg +WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea +mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF +TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj +7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1 +D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T +G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj +duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl +cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys ++TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli +2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y +aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS +ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR +JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH +-----END CERTIFICATE----- + +TrustAsia Global Root CA G4 +=========================== +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE +BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry +dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa +MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw +IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8 +m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/ +pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA +bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk +dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +Telekom Security TLS ECC Root 2020 +================================== +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJE +RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJUZWxl +a29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIwMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIz +NTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkg +R21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqG +SM49AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/OtdKPD/M1 +2kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDPf8iAC8GXs7s1J8nCG6NC +MEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6fMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZ +Mo7k+5Dck2TOrbRBR2Diz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdU +ga/sf+Rn27iQ7t0l +-----END CERTIFICATE----- + +Telekom Security TLS RSA Root 2023 +================================== +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBjMQswCQYDVQQG +EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJU +ZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAyMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMy +NzIzNTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJp +dHkgR21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9cUD/h3VC +KSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHVcp6R+SPWcHu79ZvB7JPP +GeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMAU6DksquDOFczJZSfvkgdmOGjup5czQRx +UX11eKvzWarE4GC+j4NSuHUaQTXtvPM6Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWo +l8hHD/BeEIvnHRz+sTugBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9 +FIS3R/qy8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73Jco4v +zLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg8qKrBC7m8kwOFjQg +rIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8rFEz0ciD0cmfHdRHNCk+y7AO+oML +KFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7S +WWO/gLCMk3PLNaaZlSJhZQNg+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUtqeXgj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQpGv7qHBFfLp+ +sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm9S3ul0A8Yute1hTWjOKWi0Fp +kzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErwM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy +/SKE8YXJN3nptT+/XOR0so8RYgDdGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4 +mZqTuXNnQkYRIer+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtz +aL1txKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+w6jv/naa +oqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aKL4x35bcF7DvB7L6Gs4a8 +wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE +HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0 +o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +FIRMAPROFESIONAL CA ROOT-A WEB +============================== +-----BEGIN CERTIFICATE----- +MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQswCQYDVQQGEwJF +UzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4 +MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENBIFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2 +WhcNNDcwMzMxMDkwMTM2WjBuMQswCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25h +bCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFM +IENBIFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zfe9MEkVz6 +iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6CcyvHZpsKjECcfIr28jlg +st7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FD +Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB +/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgL +cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ +pYXFuXqUPoeovQA= +-----END CERTIFICATE----- + +TWCA CYBER Root CA +================== +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG +EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB +IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG +EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB +IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s +Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh +V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT +o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT +Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK +/c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH +IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM +fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF +2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR +wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83 +QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN +c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x +X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR +IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq +/p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R +FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe +Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv +It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl +IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +SecureSign Root CA12 +==================== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT +ZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgwNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJ +BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU +U2VjdXJlU2lnbiBSb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3 +emhFKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mtp7JIKwcc +J/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zdJ1M3s6oYwlkm7Fsf0uZl +fO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gurFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBF +EaCeVESE99g2zvVQR9wsMJvuwPWW0v4JhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1Uef +NzFJM3IFTQy2VYzxV4+Kh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsFAAOC +AQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6LdmmQOmFxv3Y67ilQi +LUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJmBClnW8Zt7vPemVV2zfrPIpyMpce +mik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPS +vWKErI4cqc1avTc7bgoitPQV55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhga +aaI5gdka9at/yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE----- + +SecureSign Root CA14 +==================== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG +A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT +ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ +BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU +U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh +1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn +bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb +1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa +/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE +kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx +jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz +ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0 +dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY +AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq +YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA +ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds +Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG +FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q +nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/ +OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa +OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO +pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN +eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S +-----END CERTIFICATE----- + +SecureSign Root CA15 +==================== +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE +BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1 +cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV +BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj +dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G +dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB +2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J +fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ +SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT +cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z +sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ +WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6 +++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL +QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv +x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV +MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX +/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9 +CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr +d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv +U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4 +nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij +YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff +/vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP +pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB +WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/ +5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt +n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- + +TrustAsia TLS ECC Root CA +========================= +-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMwWDELMAkGA1UE +BhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMTGVRy +dXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBY +MQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAG +A1UEAxMZVHJ1c3RBc2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/ +pVs/AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDpguMqWzJ8 +S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49 +BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15K +eAIxAKORh/IRM4PDwYqROkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== +-----END CERTIFICATE----- + +TrustAsia TLS RSA Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEMBQAwWDELMAkG +A1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMT +GVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2 +WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEi +MCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+NmDQDIPN +lOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJQ1DNDX3eRA5gEk9bNb2/ +mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fk +zv93uMltrOXVmPGZLmzjyUT5tUMnCE32ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYo +zza/+lcK7Fs/6TAWe8TbxNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyr +z2I8sMeXi9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQUNoy +IBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+jTnhMmCWr8n4uIF6C +FabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DTbE3txci3OE9kxJRMT6DNrqXGJyV1 +J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnT +q1mt1tve1CuBAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZ +ylomkadFK/hTMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 +Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4iqME3mmL5Dw8 +veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt7DlK9RME7I10nYEKqG/odv6L +TytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHx +tlotJnMnlvm5P1vQiJ3koP26TpUJg3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp +27RIGAAtvKLEiUUjpQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87q +qA8MpugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongPXvPKnbwb +PKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIweSsCI3zWQzj8C9GRh3sfI +B5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNz +FrwFuHnYWa8G5z9nODmxfKuU4CkUpijy323imttUQ/hHWKNddBWcwauwxzQ= +-----END CERTIFICATE----- + +D-TRUST EV Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1 +sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/ +MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf +vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM +lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3 +YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910 +7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo +nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa +QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF +AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS +Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2 +QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD +UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V +4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo +dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi +TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi +S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/ +HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L ++KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- + +SwissSign RSA TLS Root CA 2022 - 1 +================================== +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UEAxMiU3dpc3NTaWduIFJTQSBU +TFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgxMTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJ +BgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0Eg +VExTIFJvb3QgQ0EgMjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmji +C8NXvDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7LCTLf5Im +gKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX5XH8irCRIFucdFJtrhUn +WXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyEEPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlf +GUEGjw5NBuBwQCMBauTLE5tzrE0USJIt/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36q +OTw7D59Ke4LKa2/KIj4x0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLO +EGrOyvi5KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM0ZPl +EuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shdOxtYk8EXlFXIC+OC +eYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrtaclXvyFu1cvh43zcgTFeRc5JzrBh3 +Q4IgaezprClG5QtO+DdziZaKHG29777YtvTKwP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow +4UD2p8P98Q+4DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL +BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO310aewCoSPY6W +lkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgzHqp41eZUBDqyggmNzhYzWUUo +8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQiJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zp +y1FVCypM9fJkT6lc/2cyjlUtMoIcgC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3Cjlvr +zG4ngRhZi0Rjn9UMZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6M +OuhFLhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJpzv1/THfQ +wUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/TdAo9QAwKxuDdollDruF/U +KIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0n +hzck5npgL7XTgwSqT0N1osGDsieYK7EOgLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rw +tnu64ZzZ +-----END CERTIFICATE----- + +OISTE Server Root ECC G1 +======================== +-----BEGIN CERTIFICATE----- +MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQswCQYDVQQGEwJD +SDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJvb3Qg +RUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUyNDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAX +BgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBH +MTB2MBAGByqGSM49AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOuj +vqQycvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N2xml4z+c +KrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3TYhlz/w9itWj8UnATgwQ +b0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9CtJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqG +SM49BAMDA2kAMGYCMQCpKjAd0MKfkFFRQD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxg +ZzFDJe0CMQCSia7pXGKDYmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= +-----END CERTIFICATE----- + +OISTE Server Root RSA G1 +======================== +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBLMQswCQYDVQQG +EwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJv +b3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gx +GTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJT +QSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxV +YOPMvLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7brEi56rAU +jtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzkik/HEzxux9UTl7Ko2yRp +g1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4zO8vbUZeUapU8zhhabkvG/AePLhq5Svdk +NCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8RtOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY ++m0o/DjH40ytas7ZTpOSjswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+ +lKXHiHUhsd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+HomnqT +8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu+zrkL8Fl47l6QGzw +Brd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYRi3drVByjtdgQ8K4p92cIiBdcuJd5 +z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnTkCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAU8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC7 +7EUOSh+1sbM2zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33 +I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG5D1rd9QhEOP2 +8yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8qyiWXmFcuCIzGEgWUOrKL+ml +Sdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dPAGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l +8PjaV8GUgeV6Vg27Rn9vkf195hfkgSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+ +FKrDgHGdPY3ofRRsYWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNq +qYY19tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome/msVuduC +msuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3J8tRd/iWkx7P8nd9H0aT +olkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2wq1yVAb+axj5d9spLFKebXd7Yv0PTY6Y +MjAwcRLWJTXjn/hvnLXrahut6hDTlhZyBiElxky8j3C7DOReIoMt0r7+hVu05L0= +-----END CERTIFICATE----- + +e-Szigno TLS Root CA 2023 +========================= +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xFzAVBgNVBGEMDlZBVEhV +LTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBUTFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0 +MDAwMFoXDTM4MDcxNzE0MDAwMFowdTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYw +FAYDVQQKDA1NaWNyb3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZ +ZS1Temlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAGgP36J8 +PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFSAL/fjO1ZrTJlqwlZULUZ +wmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/vSzUaQ49CE0y5LBqcvjC2xN7cS53kpDzL +Ltmt3999Cd8ukv+ho2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E +FgQUWYQCYlpGePVd3I8KECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0Uw +CgYIKoZIzj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpty7Ve +7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZlC9p2x1L/Cx6AcCIw +wzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6uWWL +-----END CERTIFICATE----- diff --git a/vendor/composer/ca-bundle/src/CaBundle.php b/vendor/composer/ca-bundle/src/CaBundle.php new file mode 100644 index 000000000..6cf3b84c9 --- /dev/null +++ b/vendor/composer/ca-bundle/src/CaBundle.php @@ -0,0 +1,322 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\CaBundle; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Process\PhpProcess; + +/** + * @author Chris Smith + * @author Jordi Boggiano + */ +class CaBundle +{ + /** @var string|null */ + private static $caPath; + /** @var array */ + private static $caFileValidity = array(); + + /** + * Returns the system CA bundle path, or a path to the bundled one + * + * This method was adapted from Sslurp. + * https://github.com/EvanDotPro/Sslurp + * + * (c) Evan Coury + * + * For the full copyright and license information, please see below: + * + * Copyright (c) 2013, Evan Coury + * 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. + * + * 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. + * + * @param LoggerInterface $logger optional logger for information about which CA files were loaded + * @return string path to a CA bundle file or directory + */ + public static function getSystemCaRootBundlePath(?LoggerInterface $logger = null) + { + if (self::$caPath !== null) { + return self::$caPath; + } + $caBundlePaths = array(); + + // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that. + // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. + $caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE'); + + // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that. + // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. + $caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR'); + + $caBundlePaths[] = ini_get('openssl.cafile'); + $caBundlePaths[] = ini_get('openssl.capath'); + + $otherLocations = array( + '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', // Fedora, RHEL, CentOS (ca-certificates package) - NEW + '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) - Deprecated + '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) + '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) + '/usr/ssl/certs/ca-bundle.crt', // Cygwin + '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package + '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) + '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? + '/etc/ssl/cert.pem', // OpenBSD + '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package + '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package + '/opt/homebrew/etc/openssl@3/cert.pem', // macOS silicon homebrew, openssl@3 package + '/opt/homebrew/etc/openssl@1.1/cert.pem', // macOS silicon homebrew, openssl@1.1 package + '/etc/pki/tls/certs', + '/etc/ssl/certs', // FreeBSD + ); + + $caBundlePaths = array_merge($caBundlePaths, $otherLocations); + + foreach ($caBundlePaths as $caBundle) { + if ($caBundle && self::caFileUsable($caBundle, $logger)) { + return self::$caPath = $caBundle; + } + + if ($caBundle && self::caDirUsable($caBundle, $logger)) { + return self::$caPath = $caBundle; + } + } + + return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort + } + + /** + * Returns the path to the bundled CA file + * + * In case you don't want to trust the user or the system, you can use this directly + * + * @return string path to a CA bundle file + */ + public static function getBundledCaBundlePath() + { + $caBundleFile = __DIR__.'/../res/cacert.pem'; + + // cURL does not understand 'phar://' paths + // see https://github.com/composer/ca-bundle/issues/10 + if (0 === strpos($caBundleFile, 'phar://')) { + $tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-'); + if (false === $tempCaBundleFile) { + throw new \RuntimeException('Could not create a temporary file to store the bundled CA file'); + } + + file_put_contents( + $tempCaBundleFile, + file_get_contents($caBundleFile) + ); + + register_shutdown_function(function() use ($tempCaBundleFile) { + @unlink($tempCaBundleFile); + }); + + $caBundleFile = $tempCaBundleFile; + } + + return $caBundleFile; + } + + /** + * Validates a CA file using opensl_x509_parse only if it is safe to use + * + * @param string $filename + * @param LoggerInterface $logger optional logger for information about which CA files were loaded + * + * @return bool + */ + public static function validateCaFile($filename, ?LoggerInterface $logger = null) + { + static $warned = false; + + if (isset(self::$caFileValidity[$filename])) { + return self::$caFileValidity[$filename]; + } + + $contents = file_get_contents($filename); + + if (is_string($contents) && strlen($contents) > 0) { + $contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents); + if (null === $contents) { + // regex extraction failed + $isValid = false; + } else { + $isValid = (bool) openssl_x509_parse($contents); + } + } else { + $isValid = false; + } + + if ($logger) { + $logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid')); + } + + return self::$caFileValidity[$filename] = $isValid; + } + + /** + * Test if it is safe to use the PHP function openssl_x509_parse(). + * + * This checks if OpenSSL extensions is vulnerable to remote code execution + * via the exploit documented as CVE-2013-6420. + * + * @return bool + */ + public static function isOpensslParseSafe() + { + return true; + } + + /** + * Resets the static caches + * @return void + */ + public static function reset() + { + self::$caFileValidity = array(); + self::$caPath = null; + } + + /** + * @param string $name + * @return string|false + */ + private static function getEnvVariable($name) + { + if (isset($_SERVER[$name])) { + return (string) $_SERVER[$name]; + } + + if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) { + return (string) $value; + } + + return false; + } + + /** + * @param string|false $certFile + * @param LoggerInterface|null $logger + * @return bool + */ + private static function caFileUsable($certFile, ?LoggerInterface $logger = null) + { + return $certFile + && self::isFile($certFile, $logger) + && self::isReadable($certFile, $logger) + && self::validateCaFile($certFile, $logger); + } + + /** + * @param string|false $certDir + * @param LoggerInterface|null $logger + * @return bool + */ + private static function caDirUsable($certDir, ?LoggerInterface $logger = null) + { + return $certDir + && self::isDir($certDir, $logger) + && self::isReadable($certDir, $logger) + && self::glob($certDir . '/*', $logger); + } + + /** + * @param string $certFile + * @param LoggerInterface|null $logger + * @return bool + */ + private static function isFile($certFile, ?LoggerInterface $logger = null) + { + $isFile = @is_file($certFile); + if (!$isFile && $logger) { + $logger->debug(sprintf('Checked CA file %s does not exist or it is not a file.', $certFile)); + } + + return $isFile; + } + + /** + * @param string $certDir + * @param LoggerInterface|null $logger + * @return bool + */ + private static function isDir($certDir, ?LoggerInterface $logger = null) + { + $isDir = @is_dir($certDir); + if (!$isDir && $logger) { + $logger->debug(sprintf('Checked directory %s does not exist or it is not a directory.', $certDir)); + } + + return $isDir; + } + + /** + * @param string $certFileOrDir + * @param LoggerInterface|null $logger + * @return bool + */ + private static function isReadable($certFileOrDir, ?LoggerInterface $logger = null) + { + $isReadable = @is_readable($certFileOrDir); + if (!$isReadable && $logger) { + $logger->debug(sprintf('Checked file or directory %s is not readable.', $certFileOrDir)); + } + + return $isReadable; + } + + /** + * @param string $pattern + * @param LoggerInterface|null $logger + * @return bool + */ + private static function glob($pattern, ?LoggerInterface $logger = null) + { + $certs = glob($pattern); + if ($certs === false) { + if ($logger) { + $logger->debug(sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern)); + } + return false; + } + + if (count($certs) === 0) { + if ($logger) { + $logger->debug(sprintf("No CA files found for pattern: %s", $pattern)); + } + return false; + } + + return true; + } +} diff --git a/vendor/composer/class-map-generator/LICENSE b/vendor/composer/class-map-generator/LICENSE new file mode 100644 index 000000000..43bd2de01 --- /dev/null +++ b/vendor/composer/class-map-generator/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2022 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/class-map-generator/README.md b/vendor/composer/class-map-generator/README.md new file mode 100644 index 000000000..fe5ab828f --- /dev/null +++ b/vendor/composer/class-map-generator/README.md @@ -0,0 +1,65 @@ +composer/class-map-generator +============================ + +Utilities to generate class maps and scan PHP code. + +[![Continuous Integration](https://github.com/composer/class-map-generator/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/class-map-generator/actions) + +Installation +------------ + +Install the latest version with: + +```bash +composer require composer/class-map-generator +``` + + +Requirements +------------ + +* PHP 7.2 is required. + + +Basic usage +----------- + +If all you want is to scan a directory and extract a classmap with all +classes/interfaces/traits/enums mapped to their paths, you can simply use: + + +```php +use Composer\ClassMapGenerator\ClassMapGenerator; + +$map = ClassMapGenerator::createMap('path/to/scan'); +foreach ($map as $symbol => $path) { + // do your thing +} +``` + +For more advanced usage, you can instantiate a generator object and call scanPaths one or more time +then call getClassMap to get a ClassMap object containing the resulting map + eventual warnings. + +```php +use Composer\ClassMapGenerator\ClassMapGenerator; + +$generator = new ClassMapGenerator; +$generator->scanPaths('path/to/scan'); +$generator->scanPaths('path/to/scan2'); + +$classMap = $generator->getClassMap(); +$classMap->sort(); // optionally sort classes alphabetically +foreach ($classMap->getMap() as $symbol => $path) { + // do your thing +} + +foreach ($classMap->getAmbiguousClasses() as $symbol => $paths) { + // warn user about ambiguous class resolution +} +``` + + +License +------- + +composer/class-map-generator is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/class-map-generator/composer.json b/vendor/composer/class-map-generator/composer.json new file mode 100644 index 000000000..9175863f4 --- /dev/null +++ b/vendor/composer/class-map-generator/composer.json @@ -0,0 +1,48 @@ +{ + "name": "composer/class-map-generator", + "description": "Utilities to scan PHP code and generate class maps.", + "type": "library", + "license": "MIT", + "keywords": [ + "classmap" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8", + "composer/pcre": "^2.1 || ^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^8", + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "scripts": { + "test": "@php phpunit", + "phpstan": "@php phpstan analyse" + } +} diff --git a/vendor/composer/class-map-generator/src/ClassMap.php b/vendor/composer/class-map-generator/src/ClassMap.php new file mode 100644 index 000000000..72304da28 --- /dev/null +++ b/vendor/composer/class-map-generator/src/ClassMap.php @@ -0,0 +1,191 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\ClassMapGenerator; + +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class ClassMap implements \Countable +{ + /** + * @var array + */ + public $map = []; + + /** + * @var array> + */ + private $ambiguousClasses = []; + + /** + * @var array> + */ + private $psrViolations = []; + + /** + * Returns the class map, which is a list of paths indexed by class name + * + * @return array + */ + public function getMap(): array + { + return $this->map; + } + + /** + * Returns warning strings containing details about PSR-0/4 violations that were detected + * + * Violations are for ex a class which is in the wrong file/directory and thus should not be + * found using psr-0/psr-4 autoloading but was found by the ClassMapGenerator as it scans all files. + * + * This is only happening when scanning paths using psr-0/psr-4 autoload type. Classmap type + * always accepts every class as it finds it. + * + * @return string[] + */ + public function getPsrViolations(): array + { + if (\count($this->psrViolations) === 0) { + return []; + } + + return array_map(static function (array $violation): string { + return $violation['warning']; + }, array_merge(...array_values($this->psrViolations))); + } + + /** + * A map of class names to their list of ambiguous paths + * + * This occurs when the same class can be found in several files + * + * To get the path the class is being mapped to, call getClassPath + * + * By default, paths that contain test(s), fixture(s), example(s) or stub(s) are ignored + * as those are typically not problematic when they're dummy classes in the tests folder. + * If you want to get these back as well you can pass false to $duplicatesFilter. Or + * you can pass your own pattern to exclude if you need to change the default. + * + * @param non-empty-string|false $duplicatesFilter + * + * @return array> + */ + public function getAmbiguousClasses($duplicatesFilter = '{/(test|fixture|example|stub)s?/}i'): array + { + if (false === $duplicatesFilter) { + return $this->ambiguousClasses; + } + + if (true === $duplicatesFilter) { + throw new \InvalidArgumentException('$duplicatesFilter should be false or a string with a valid regex, got true.'); + } + + $ambiguousClasses = []; + foreach ($this->ambiguousClasses as $class => $paths) { + $paths = array_filter($paths, function ($path) use ($duplicatesFilter): bool { + return !Preg::isMatch($duplicatesFilter, strtr($path, '\\', '/')); + }); + if (\count($paths) > 0) { + $ambiguousClasses[$class] = array_values($paths); + } + } + + return $ambiguousClasses; + } + + /** + * Sorts the class map alphabetically by class names + */ + public function sort(): void + { + ksort($this->map); + } + + /** + * @param class-string $className + * @param non-empty-string $path + */ + public function addClass(string $className, string $path): void + { + unset($this->psrViolations[strtr($path, '\\', '/')]); + + $this->map[$className] = $path; + } + + /** + * @param class-string $className + * @return non-empty-string + */ + public function getClassPath(string $className): string + { + if (!isset($this->map[$className])) { + throw new \OutOfBoundsException('Class '.$className.' is not present in the map'); + } + + return $this->map[$className]; + } + + /** + * @param class-string $className + */ + public function hasClass(string $className): bool + { + return isset($this->map[$className]); + } + + public function addPsrViolation(string $warning, string $className, string $path): void + { + $path = rtrim(strtr($path, '\\', '/'), '/'); + + $this->psrViolations[$path][] = ['warning' => $warning, 'className' => $className]; + } + + public function clearPsrViolationsByPath(string $pathPrefix): void + { + $pathPrefix = rtrim(strtr($pathPrefix, '\\', '/'), '/'); + + foreach ($this->psrViolations as $path => $violations) { + if ($path === $pathPrefix || 0 === \strpos($path, $pathPrefix.'/')) { + unset($this->psrViolations[$path]); + } + } + } + + /** + * @param class-string $className + * @param non-empty-string $path + */ + public function addAmbiguousClass(string $className, string $path): void + { + $this->ambiguousClasses[$className][] = $path; + } + + public function count(): int + { + return \count($this->map); + } + + /** + * Get the raw psr violations + * + * This is a map of filepath to an associative array of the warning string + * and the offending class name. + * @return array> + */ + public function getRawPsrViolations(): array + { + return $this->psrViolations; + } +} diff --git a/vendor/composer/class-map-generator/src/ClassMapGenerator.php b/vendor/composer/class-map-generator/src/ClassMapGenerator.php new file mode 100644 index 000000000..fa078d245 --- /dev/null +++ b/vendor/composer/class-map-generator/src/ClassMapGenerator.php @@ -0,0 +1,356 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * This file was initially based on a version from the Symfony package. + * + * (c) Fabien Potencier + */ + +namespace Composer\ClassMapGenerator; + +use Composer\Pcre\Preg; +use Symfony\Component\Finder\Finder; +use Composer\IO\IOInterface; + +/** + * ClassMapGenerator + * + * @author Gyula Sallai + * @author Jordi Boggiano + */ +class ClassMapGenerator +{ + /** + * @var list + */ + private $extensions; + + /** + * @var FileList|null + */ + private $scannedFiles = null; + + /** + * @var ClassMap + */ + private $classMap; + + /** + * @var non-empty-string + */ + private $streamWrappersRegex; + + /** + * @param list $extensions File extensions to scan for classes in the given paths + */ + public function __construct(array $extensions = ['php', 'inc']) + { + $this->extensions = $extensions; + $this->classMap = new ClassMap; + $this->streamWrappersRegex = sprintf('{^(?:%s)://}', implode('|', array_map('preg_quote', stream_get_wrappers()))); + } + + /** + * When calling scanPaths repeatedly with paths that may overlap, calling this will ensure that the same class is never scanned twice + * + * You can provide your own FileList instance or use the default one if you pass no argument + * + * @return $this + */ + public function avoidDuplicateScans(?FileList $scannedFiles = null): self + { + $this->scannedFiles = $scannedFiles ?? new FileList; + + return $this; + } + + /** + * Iterate over all files in the given directory searching for classes + * + * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance) + * @return array A class map array + * + * @throws \RuntimeException When the path is neither an existing file nor directory + */ + public static function createMap($path): array + { + $generator = new self(); + + $generator->scanPaths($path); + + return $generator->getClassMap()->getMap(); + } + + public function getClassMap(): ClassMap + { + return $this->classMap; + } + + /** + * Iterate over all files in the given directory searching for classes + * + * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance) + * @param non-empty-string|null $excluded Regex that matches file paths to be excluded from the classmap + * @param 'classmap'|'psr-0'|'psr-4' $autoloadType Optional autoload standard to use mapping rules with the namespace instead of purely doing a classmap + * @param string|null $namespace Optional namespace prefix to filter by, only for psr-0/psr-4 autoloading + * @param array $excludedDirs Optional dirs to exclude from search relative to $path + * + * @throws \RuntimeException When the path is neither an existing file nor directory + */ + public function scanPaths($path, ?string $excluded = null, string $autoloadType = 'classmap', ?string $namespace = null, array $excludedDirs = []): void + { + if (!in_array($autoloadType, ['psr-0', 'psr-4', 'classmap'], true)) { + throw new \InvalidArgumentException('$autoloadType must be one of: "psr-0", "psr-4" or "classmap"'); + } + + if ('classmap' !== $autoloadType) { + if (!is_string($path)) { + throw new \InvalidArgumentException('$path must be a string when specifying a psr-0 or psr-4 autoload type'); + } + if (!is_string($namespace)) { + throw new \InvalidArgumentException('$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type'); + } + $basePath = $path; + } + + if (is_string($path)) { + if (is_file($path)) { + $path = [new \SplFileInfo($path)]; + } elseif (is_dir($path) || strpos($path, '*') !== false) { + $path = Finder::create() + ->files() + ->followLinks() + ->name('/\.(?:'.implode('|', array_map('preg_quote', $this->extensions)).')$/') + ->in($path) + ->exclude($excludedDirs); + } else { + throw new \RuntimeException( + 'Could not scan for classes inside "'.$path.'" which does not appear to be a file nor a folder' + ); + } + } + + $cwd = realpath(self::getCwd()); + + foreach ($path as $file) { + $filePath = $file->getPathname(); + if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), $this->extensions, true)) { + continue; + } + + $isStreamWrapperPath = Preg::isMatch($this->streamWrappersRegex, $filePath); + if (!self::isAbsolutePath($filePath) && !$isStreamWrapperPath) { + $filePath = $cwd . '/' . $filePath; + $filePath = self::normalizePath($filePath); + } else { + $filePath = Preg::replace('{(?getPathname()); + } + + $realPath = $isStreamWrapperPath + ? $filePath + : realpath($filePath); + + // fallback just in case but this really should not happen + if (false === $realPath) { + throw new \RuntimeException('realpath of '.$filePath.' failed to resolve, got false'); + } + + // if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings + // in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already + if ($this->scannedFiles !== null && $this->scannedFiles->contains($realPath)) { + continue; + } + + // check the realpath of the file against the excluded paths as the path might be a symlink and the excluded path is realpath'd so symlink are resolved + if (null !== $excluded && Preg::isMatch($excluded, strtr($realPath, '\\', '/'))) { + continue; + } + // check non-realpath of file for directories symlink in project dir + if (null !== $excluded && Preg::isMatch($excluded, strtr($filePath, '\\', '/'))) { + continue; + } + + $classes = PhpFileParser::findClasses($filePath); + if ('classmap' !== $autoloadType && isset($namespace)) { + $classes = $this->filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath); + + // if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later + if (\count($classes) > 0 && $this->scannedFiles !== null) { + $this->scannedFiles->add($realPath); + } + } elseif ($this->scannedFiles !== null) { + // classmap autoload rules always collect all classes so for these we definitely do not want to scan again + $this->scannedFiles->add($realPath); + } + + foreach ($classes as $class) { + if (!$this->classMap->hasClass($class)) { + $this->classMap->addClass($class, $filePath); + } elseif ($filePath !== $this->classMap->getClassPath($class)) { + $this->classMap->addAmbiguousClass($class, $filePath); + } + } + } + } + + /** + * Remove classes which could not have been loaded by namespace autoloaders + * + * @param array $classes found classes in given file + * @param string $filePath current file + * @param string $baseNamespace prefix of given autoload mapping + * @param 'psr-0'|'psr-4' $namespaceType + * @param string $basePath root directory of given autoload mapping + * @return array valid classes + * + * @throws \InvalidArgumentException When namespaceType is neither psr-0 nor psr-4 + */ + private function filterByNamespace(array $classes, string $filePath, string $baseNamespace, string $namespaceType, string $basePath): array + { + $validClasses = []; + $rejectedClasses = []; + + $realSubPath = substr($filePath, strlen($basePath) + 1); + $dotPosition = strrpos($realSubPath, '.'); + $realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition); + + foreach ($classes as $class) { + // transform class name to file path and validate + if ('psr-0' === $namespaceType) { + if ('' !== $baseNamespace && !str_starts_with($class, $baseNamespace)) { + $rejectedClasses[] = $class; + continue; + } + + $namespaceLength = strrpos($class, '\\'); + if (false !== $namespaceLength) { + $namespace = substr($class, 0, $namespaceLength + 1); + $className = substr($class, $namespaceLength + 1); + $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) + . str_replace('_', DIRECTORY_SEPARATOR, $className); + } else { + $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class); + } + } elseif ('psr-4' === $namespaceType) { + $subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class; + $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace); + } else { + throw new \InvalidArgumentException('$namespaceType must be "psr-0" or "psr-4"'); + } + if ($subPath === $realSubPath) { + $validClasses[] = $class; + } else { + $rejectedClasses[] = $class; + } + } + // warn only if no valid classes, else silently skip invalid + if (\count($validClasses) === 0) { + $cwd = realpath(self::getCwd()); + if ($cwd === false) { + $cwd = self::getCwd(); + } + $cwd = self::normalizePath($cwd); + $shortPath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($filePath), 1); + $shortBasePath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($basePath), 1); + + foreach ($rejectedClasses as $class) { + $this->classMap->addPsrViolation("Class $class located in $shortPath does not comply with $namespaceType autoloading standard (rule: $baseNamespace => $shortBasePath). Skipping.", $class, $filePath); + } + + return []; + } + + return $validClasses; + } + + /** + * Checks if the given path is absolute + * + * @see Composer\Util\Filesystem::isAbsolutePath + * + * @param string $path + * @return bool + */ + private static function isAbsolutePath(string $path): bool + { + return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0; + } + + /** + * Normalize a path. This replaces backslashes with slashes, removes ending + * slash and collapses redundant separators and up-level references. + * + * @see Composer\Util\Filesystem::normalizePath + * + * @param string $path Path to the file or directory + * @return string + */ + private static function normalizePath(string $path): string + { + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = ''; + + // extract windows UNC paths e.g. \\foo\bar + if (strpos($path, '//') === 0 && \strlen($path) > 2) { + $absolute = '//'; + $path = substr($path, 2); + } + + // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: + if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, \strlen($prefix)); + } + + if (strpos($path, '/') === 0) { + $absolute = '/'; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) { + array_pop($parts); + $up = !(\count($parts) === 0 || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + // ensure c: is normalized to C: + $prefix = Preg::replaceCallback('{(?:^|://)[a-z]:$}i', function (array $m) { return strtoupper((string) $m[0]); }, $prefix); + + return $prefix.$absolute.implode('/', $parts); + } + + /** + * @see Composer\Util\Platform::getCwd + */ + private static function getCwd(): string + { + $cwd = getcwd(); + + if (false === $cwd) { + throw new \RuntimeException('Could not determine the current working directory'); + } + + return $cwd; + } +} diff --git a/vendor/composer/class-map-generator/src/FileList.php b/vendor/composer/class-map-generator/src/FileList.php new file mode 100644 index 000000000..f0bfcb7e2 --- /dev/null +++ b/vendor/composer/class-map-generator/src/FileList.php @@ -0,0 +1,42 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\ClassMapGenerator; + +/** + * Contains a list of files which were scanned to generate a classmap + * + * @author Jordi Boggiano + */ +class FileList +{ + /** + * @var array + */ + public $files = []; + + /** + * @param non-empty-string $path + */ + public function add(string $path): void + { + $this->files[$path] = true; + } + + /** + * @param non-empty-string $path + */ + public function contains(string $path): bool + { + return isset($this->files[$path]); + } +} diff --git a/vendor/composer/class-map-generator/src/PhpFileCleaner.php b/vendor/composer/class-map-generator/src/PhpFileCleaner.php new file mode 100644 index 000000000..4591aa2a4 --- /dev/null +++ b/vendor/composer/class-map-generator/src/PhpFileCleaner.php @@ -0,0 +1,251 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\ClassMapGenerator; + +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + * @internal + */ +class PhpFileCleaner +{ + /** @var array */ + private static $typeConfig; + + /** @var non-empty-string */ + private static $restPattern; + + /** + * @readonly + * @var string + */ + private $contents; + + /** + * @readonly + * @var int + */ + private $len; + + /** + * @readonly + * @var int + */ + private $maxMatches; + + /** @var int */ + private $index = 0; + + /** + * @param string[] $types + */ + public static function setTypeConfig(array $types): void + { + foreach ($types as $type) { + self::$typeConfig[$type[0]] = [ + 'name' => $type, + 'length' => \strlen($type), + 'pattern' => '{.\b(?])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais', + ]; + } + + self::$restPattern = '{[^?"\'contents = $contents; + $this->len = \strlen($this->contents); + $this->maxMatches = $maxMatches; + } + + public function clean(): string + { + $clean = ''; + + while ($this->index < $this->len) { + $this->skipToPhp(); + $clean .= 'index < $this->len) { + $char = $this->contents[$this->index]; + if ($char === '?' && $this->peek('>')) { + $clean .= '?>'; + $this->index += 2; + continue 2; + } + + if ($char === '"') { + $this->skipString('"'); + $clean .= 'null'; + continue; + } + + if ($char === "'") { + $this->skipString("'"); + $clean .= 'null'; + continue; + } + + if ($char === "<" && $this->peek('<') && $this->match('{<<<[ \t]*+([\'"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*+)\\1(?:\r\n|\n|\r)}A', $match)) { + $this->index += \strlen($match[0]); + $this->skipHeredoc($match[2]); + $clean .= 'null'; + continue; + } + + if ($char === '/') { + if ($this->peek('/')) { + $this->skipToNewline(); + continue; + } + + if ($this->peek('*')) { + $this->skipComment(); + continue; + } + } + + if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) { + $type = self::$typeConfig[$char]; + if ( + \substr($this->contents, $this->index, $type['length']) === $type['name'] + && Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1) + ) { + return $clean . $match[0]; + } + } + + $this->index += 1; + if ($this->match(self::$restPattern, $match)) { + $clean .= $char . $match[0]; + $this->index += \strlen($match[0]); + } else { + $clean .= $char; + } + } + } + + return $clean; + } + + private function skipToPhp(): void + { + while ($this->index < $this->len) { + if ($this->contents[$this->index] === '<' && $this->peek('?')) { + $this->index += 2; + break; + } + + $this->index += 1; + } + } + + private function skipString(string $delimiter): void + { + $this->index += 1; + while ($this->index < $this->len) { + if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) { + $this->index += 2; + continue; + } + + if ($this->contents[$this->index] === $delimiter) { + $this->index += 1; + break; + } + + $this->index += 1; + } + } + + private function skipComment(): void + { + $this->index += 2; + while ($this->index < $this->len) { + if ($this->contents[$this->index] === '*' && $this->peek('/')) { + $this->index += 2; + break; + } + + $this->index += 1; + } + } + + private function skipToNewline(): void + { + while ($this->index < $this->len) { + if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") { + return; + } + + $this->index += 1; + } + } + + private function skipHeredoc(string $delimiter): void + { + $firstDelimiterChar = $delimiter[0]; + $delimiterLength = \strlen($delimiter); + $delimiterPattern = '{'.preg_quote($delimiter).'(?![a-zA-Z0-9_\x80-\xff])}A'; + + while ($this->index < $this->len) { + // check if we find the delimiter after some spaces/tabs + switch ($this->contents[$this->index]) { + case "\t": + case " ": + $this->index += 1; + continue 2; + case $firstDelimiterChar: + if ( + \substr($this->contents, $this->index, $delimiterLength) === $delimiter + && $this->match($delimiterPattern) + ) { + $this->index += $delimiterLength; + + return; + } + + break; + } + + // skip the rest of the line + while ($this->index < $this->len) { + $this->skipToNewline(); + + // skip newlines + while ($this->index < $this->len && ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n")) { + $this->index += 1; + } + + break; + } + } + } + + private function peek(string $char): bool + { + return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char; + } + + /** + * @param non-empty-string $regex + * @param null|array $match + * @param-out array $match + */ + private function match(string $regex, ?array &$match = null): bool + { + return Preg::isMatchStrictGroups($regex, $this->contents, $match, 0, $this->index); + } +} diff --git a/vendor/composer/class-map-generator/src/PhpFileParser.php b/vendor/composer/class-map-generator/src/PhpFileParser.php new file mode 100644 index 000000000..04d98284a --- /dev/null +++ b/vendor/composer/class-map-generator/src/PhpFileParser.php @@ -0,0 +1,163 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\ClassMapGenerator; + +use RuntimeException; +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class PhpFileParser +{ + /** + * Extract the classes in the given file + * + * @param string $path The file to check + * @throws RuntimeException + * @return list The found classes + */ + public static function findClasses(string $path): array + { + $extraTypes = self::getExtraTypes(); + + if (!function_exists('php_strip_whitespace')) { + throw new RuntimeException('Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.'); + } + + // Use @ here instead of Silencer to actively suppress 'unhelpful' output + // @link https://github.com/composer/composer/pull/4886 + $contents = @php_strip_whitespace($path); + if ('' === $contents) { + if (!file_exists($path)) { + $message = 'File at "%s" does not exist, check your classmap definitions'; + } elseif (!self::isReadable($path)) { + $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim((string) file_get_contents($path))) { + // The input file was really empty and thus contains no classes + return []; + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; + } + + $error = error_get_last(); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + + throw new RuntimeException(sprintf($message, $path)); + } + + // return early if there is no chance of matching anything in this file + Preg::matchAllStrictGroups('{\b(?:class|interface|trait'.$extraTypes.')\s}i', $contents, $matches); + if ([] === $matches[0]) { + return []; + } + + $p = new PhpFileCleaner($contents, count($matches[0])); + $contents = $p->clean(); + unset($p); + + Preg::matchAll('{ + (?: + \b(?])(?Pclass|interface|trait'.$extraTypes.') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] + ) + }ix', $contents, $matches); + + $classes = []; + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; ++$i) { + if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') { + $namespace = str_replace([' ', "\t", "\r", "\n"], '', (string) $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + assert(is_string($name)); + // skip anon classes extending/implementing + if ($name === 'extends') { + continue; + } + if ($name === 'implements') { + continue; + } + + if ($name[0] === ':') { + // This is an XHP class, https://github.com/facebook/xhp + $name = 'xhp'.substr(str_replace(['-', ':'], ['_', '__'], $name), 1); + } elseif (strtolower((string) $matches['type'][$i]) === 'enum') { + // something like: + // enum Foo: int { HERP = '123'; } + // The regex above captures the colon, which isn't part of + // the class name. + // or: + // enum Foo:int { HERP = '123'; } + // The regex above captures the colon and type, which isn't part of + // the class name. + $colonPos = strrpos($name, ':'); + if (false !== $colonPos) { + $name = substr($name, 0, $colonPos); + } + } + + /** @var class-string */ + $className = ltrim($namespace . $name, '\\'); + $classes[] = $className; + } + } + + return $classes; + } + + private static function getExtraTypes(): string + { + static $extraTypes = null; + + if (null === $extraTypes) { + $extraTypes = ''; + $extraTypesArray = []; + if (PHP_VERSION_ID >= 80100 || (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) { + $extraTypes .= '|enum'; + $extraTypesArray = ['enum']; + } + + PhpFileCleaner::setTypeConfig(array_merge(['class', 'interface', 'trait'], $extraTypesArray)); + } + + return $extraTypes; + } + + /** + * Cross-platform safe version of is_readable() + * + * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts + * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + * + * @see Composer\Util\Filesystem::isReadable + * + * @return bool + */ + private static function isReadable(string $path) + { + if (is_readable($path)) { + return true; + } + + if (is_file($path)) { + return false !== @file_get_contents($path, false, null, 0, 1); + } + + // assume false otherwise + return false; + } +} diff --git a/vendor/composer/composer/LICENSE b/vendor/composer/composer/LICENSE new file mode 100644 index 000000000..62ecfd8d0 --- /dev/null +++ b/vendor/composer/composer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/composer/composer/bin/compile b/vendor/composer/composer/bin/compile new file mode 100755 index 000000000..8eb2e624f --- /dev/null +++ b/vendor/composer/composer/bin/compile @@ -0,0 +1,44 @@ +#!/usr/bin/env php +compile(); +} catch (\Exception $e) { + echo 'Failed to compile phar: ['.get_class($e).'] '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().PHP_EOL; + exit(1); +} diff --git a/vendor/composer/composer/bin/composer b/vendor/composer/composer/bin/composer new file mode 100755 index 000000000..4901698a9 --- /dev/null +++ b/vendor/composer/composer/bin/composer @@ -0,0 +1,113 @@ +#!/usr/bin/env php +check(); +unset($xdebug); + +if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '4.0', '>=')) { + echo 'HHVM 4.0 has dropped support for Composer, please use PHP instead. Aborting.'.PHP_EOL; + exit(1); +} +if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { + echo 'The iconv OR mbstring extension is required and both are missing.' + .PHP_EOL.'Install either of them or recompile php without --disable-iconv.' + .PHP_EOL.'Aborting.'.PHP_EOL; + exit(1); +} + +if (function_exists('ini_set')) { + // check if error logging is on, but to an empty destination - for the CLI SAPI, that means stderr + $logsToSapiDefault = ('' === ini_get('error_log') && (bool) ini_get('log_errors')); + // on the CLI SAPI, ensure errors are displayed on stderr, either via display_errors or via error_log + if (PHP_SAPI === 'cli') { + @ini_set('display_errors', $logsToSapiDefault ? '0' : 'stderr'); + } + + // Set user defined memory limit + if ($memoryLimit = getenv('COMPOSER_MEMORY_LIMIT')) { + @ini_set('memory_limit', $memoryLimit); + } else { + $memoryInBytes = function ($value) { + $unit = strtolower(substr($value, -1, 1)); + $value = (int) $value; + switch($unit) { + case 'g': + $value *= 1024; + // no break (cumulative multiplier) + case 'm': + $value *= 1024; + // no break (cumulative multiplier) + case 'k': + $value *= 1024; + } + + return $value; + }; + + $memoryLimit = trim(ini_get('memory_limit')); + // Increase memory_limit if it is lower than 1.5GB + if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) { + @ini_set('memory_limit', '1536M'); + } + unset($memoryInBytes); + } + unset($memoryLimit); +} + +// Workaround PHP bug on Windows where env vars containing Unicode chars are mangled in $_SERVER +// see https://github.com/php/php-src/issues/7896 +if (PHP_VERSION_ID >= 70113 && (PHP_VERSION_ID < 80016 || (PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80103)) && Platform::isWindows()) { + foreach ($_SERVER as $serverVar => $serverVal) { + if (($serverVal = getenv($serverVar)) !== false) { + $_SERVER[$serverVar] = $serverVal; + } + } +} + +Platform::putEnv('COMPOSER_BINARY', realpath($_SERVER['argv'][0])); + +ErrorHandler::register(); + +// run the command application +$application = new Application(); +$application->run(); diff --git a/vendor/composer/composer/composer.json b/vendor/composer/composer/composer.json new file mode 100644 index 000000000..19d64b87f --- /dev/null +++ b/vendor/composer/composer/composer.json @@ -0,0 +1,113 @@ +{ + "name": "composer/composer", + "type": "library", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "keywords": [ + "package", + "dependency", + "autoload" + ], + "homepage": "https://getcomposer.org/", + "license": "MIT", + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "ext-json": "*", + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", + "composer/metadata-minifier": "^1.0", + "composer/semver": "^3.3", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "justinrainbow/json-schema": "^6.5.1", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0", + "react/promise": "^3.3", + "composer/pcre": "^2.3 || ^3.3", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "symfony/polyfill-php84": "^1.30", + "seld/signal-handler": "^2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0", + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0" + }, + "suggest": { + "ext-curl": "Provides HTTP support (will fallback to PHP streams if missing)", + "ext-openssl": "Enables access to repositories and packages over HTTPS", + "ext-zip": "Allows direct extraction of ZIP archives (unzip/7z binaries will be used instead if available)", + "ext-zlib": "Enables gzip for HTTP requests" + }, + "config": { + "platform": { + "php": "7.2.5" + }, + "platform-check": false + }, + "extra": { + "branch-alias": { + "dev-main": "2.9-dev" + }, + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer/" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Test\\": "tests/Composer/Test/" + }, + "exclude-from-classmap": [ + "tests/Composer/Test/Fixtures/", + "tests/Composer/Test/Autoload/Fixtures", + "tests/Composer/Test/Autoload/MinimumVersionSupport", + "tests/Composer/Test/Plugin/Fixtures" + ] + }, + "bin": [ + "bin/composer" + ], + "scripts": { + "compile": "@php -dphar.readonly=0 bin/compile", + "test": "@php simple-phpunit", + "phpstan": "@php vendor/bin/phpstan analyse --configuration=phpstan/config.neon" + }, + "scripts-descriptions": { + "compile": "Compile composer.phar", + "test": "Run all tests", + "phpstan": "Runs PHPStan" + }, + "support": { + "issues": "https://github.com/composer/composer/issues", + "irc": "ircs://irc.libera.chat:6697/composer", + "security": "https://github.com/composer/composer/security/policy" + } +} diff --git a/vendor/composer/composer/composer.lock b/vendor/composer/composer/composer.lock new file mode 100644 index 000000000..94d442b3d --- /dev/null +++ b/vendor/composer/composer/composer.lock @@ -0,0 +1,2582 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "bf258f8cbec69ad23a35e119a3807dc5", + "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.5.10", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63", + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.10" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-12-08T15:06:51+00:00" + }, + { + "name": "composer/class-map-generator", + "version": "1.7.1", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8f5fa3cc214230e71f54924bd0197a3bcc705eb1", + "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.7.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-12-29T13:15:25+00:00" + }, + { + "name": "composer/metadata-minifier", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-04-07T13:37:33+00:00" + }, + { + "name": "composer/pcre", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "ebb81df8f52b40172d14062ae96a06939d80a069" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/ebb81df8f52b40172d14062ae96a06939d80a069", + "reference": "ebb81df8f52b40172d14062ae96a06939d80a069", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/2.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:24:47+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.9", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.9" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2025-05-12T21:07:07+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.6.4", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7", + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "^23.2", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4" + }, + "time": "2025-12-19T15:01:32+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.2", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "time": "2025-09-14T11:18:39+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-07-11T14:55:45+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "time": "2023-09-03T09:24:00+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T11:30:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-22T13:05:35+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "63741784cd7b9967975eec610b256eed3ede022b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-28T13:32:08+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", + "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.51" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-26T15:53:37+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-10T20:33:58+00:00" + } + ], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.12.32", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8", + "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-09-30T10:16:31+00:00" + }, + { + "name": "phpstan/phpstan-deprecation-rules", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/f94d246cc143ec5a23da868f8f7e1393b50eaa82", + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.12" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "support": { + "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.1" + }, + "time": "2024-09-11T15:52:35+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "72a6721c9b64b3e4c9db55abbc38f790b318267e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/72a6721c9b64b3e4c9db55abbc38f790b318267e", + "reference": "72a6721c9b64b3e4c9db55abbc38f790b318267e", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.12" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.2" + }, + "time": "2024-12-17T17:20:49+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "b564ca479e7e735f750aaac4935af965572a7845" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b564ca479e7e735f750aaac4935af965572a7845", + "reference": "b564ca479e7e735f750aaac4935af965572a7845", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.12.4" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.2" + }, + "time": "2025-01-19T13:02:24+00:00" + }, + { + "name": "phpstan/phpstan-symfony", + "version": "1.4.16", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-symfony.git", + "reference": "18df9086a84fc28e9a231ea8e91d5aff1a0a3d6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/18df9086a84fc28e9a231ea8e91d5aff1a0a3d6f", + "reference": "18df9086a84fc28e9a231ea8e91d5aff1a0a3d6f", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.12" + }, + "conflict": { + "symfony/framework-bundle": "<3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.3.11", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^8.5.29 || ^9.5", + "psr/container": "1.0 || 1.1.1", + "symfony/config": "^5.4 || ^6.1", + "symfony/console": "^5.4 || ^6.1", + "symfony/dependency-injection": "^5.4 || ^6.1", + "symfony/form": "^5.4 || ^6.1", + "symfony/framework-bundle": "^5.4 || ^6.1", + "symfony/http-foundation": "^5.4 || ^6.1", + "symfony/messenger": "^5.4", + "symfony/polyfill-php80": "^1.24", + "symfony/serializer": "^5.4", + "symfony/service-contracts": "^2.2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lukáš Unger", + "email": "looky.msc@gmail.com", + "homepage": "https://lookyman.net" + } + ], + "description": "Symfony Framework extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-symfony/issues", + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.16" + }, + "time": "2025-09-07T06:55:28+00:00" + }, + { + "name": "symfony/phpunit-bridge", + "version": "v7.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/phpunit-bridge.git", + "reference": "ed77a629c13979e051b7000a317966474d566398" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/ed77a629c13979e051b7000a317966474d566398", + "reference": "ed77a629c13979e051b7000a317966474d566398", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "conflict": { + "phpunit/phpunit": "<7.5|9.1.2" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.4|^7.0", + "symfony/polyfill-php81": "^1.27" + }, + "bin": [ + "bin/simple-phpunit" + ], + "type": "symfony-bridge", + "extra": { + "thanks": { + "url": "https://github.com/sebastianbergmann/phpunit", + "name": "phpunit/phpunit" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Bridge\\PhpUnit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/", + "/bin/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides utilities for PHPUnit, especially user deprecation notices management", + "homepage": "https://symfony.com", + "keywords": [ + "testing" + ], + "support": { + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-12T12:18:52+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.2.5 || ^8.0", + "ext-json": "*" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "7.2.5" + }, + "plugin-api-version": "2.9.0" +} diff --git a/vendor/composer/composer/phpstan/rules.neon b/vendor/composer/composer/phpstan/rules.neon new file mode 100644 index 000000000..6dae5cfd4 --- /dev/null +++ b/vendor/composer/composer/phpstan/rules.neon @@ -0,0 +1,14 @@ +# Composer-specific PHPStan extensions +# +# These can be reused by third party packages by including 'vendor/composer/composer/phpstan/rules.neon' +# in your phpstan config + +services: + - + class: Composer\PHPStan\ConfigReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: Composer\PHPStan\RuleReasonDataReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension diff --git a/vendor/composer/composer/res/composer-lock-schema.json b/vendor/composer/composer/res/composer-lock-schema.json new file mode 100644 index 000000000..b1ef31c2b --- /dev/null +++ b/vendor/composer/composer/res/composer-lock-schema.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "title": "Composer Lock File", + "type": "object", + "required": [ "content-hash", "packages", "packages-dev" ], + "additionalProperties": true, + "properties": { + "_readme": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Informational text for humans reading the file" + }, + "content-hash": { + "type": "string", + "description": "Hash of all relevant properties of the composer.json that was used to create this lock file." + }, + "packages": { + "type": "array", + "description": "An array of packages that are required.", + "items": { + "$ref": "./composer-schema.json", + "required": ["name", "version"] + } + }, + "packages-dev": { + "type": "array", + "description": "An array of packages that are required in require-dev.", + "items": { + "$ref": "./composer-schema.json" + } + }, + "aliases": { + "type": "array", + "description": "Inline aliases defined in the root package.", + "items": { + "type": "object", + "required": [ "package", "version", "alias", "alias_normalized" ], + "properties": { + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "alias_normalized": { + "type": "string" + } + } + } + }, + "minimum-stability": { + "type": "string", + "description": "The minimum-stability used to generate this lock file." + }, + "stability-flags": { + "type": "object", + "description": "Root package stability flags changing the minimum-stability for specific packages.", + "additionalProperties": { + "type": "integer" + } + }, + "prefer-stable": { + "type": "boolean", + "description": "Whether the --prefer-stable flag was used when building this lock file." + }, + "prefer-lowest": { + "type": "boolean", + "description": "Whether the --prefer-lowest flag was used when building this lock file." + }, + "platform": { + "type": "object", + "description": "Platform requirements of the root package.", + "additionalProperties": { + "type": "string" + } + }, + "platform-dev": { + "type": "object", + "description": "Platform dev-requirements of the root package.", + "additionalProperties": { + "type": "string" + } + }, + "platform-overrides": { + "type": "object", + "description": "Platform config overrides of the root package.", + "additionalProperties": { + "type": "string" + } + }, + "plugin-api-version": { + "type": "string", + "description": "The composer-plugin-api version that was used to generate this lock file." + } + } +} diff --git a/vendor/composer/composer/res/composer-repository-schema.json b/vendor/composer/composer/res/composer-repository-schema.json new file mode 100644 index 000000000..223f63abf --- /dev/null +++ b/vendor/composer/composer/res/composer-repository-schema.json @@ -0,0 +1,204 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "title": "Composer Package Repository", + "type": "object", + "oneOf": [ + { "required": [ "packages" ] }, + { "required": [ "providers" ] }, + { "required": [ "provider-includes", "providers-url" ] }, + { "required": [ "metadata-url" ] } + ], + "properties": { + "packages": { + "type": ["object", "array"], + "description": "A hashmap of package names in the form of /.", + "additionalProperties": { "$ref": "#/definitions/versions" } + }, + "metadata-url": { + "type": "string", + "description": "Endpoint to retrieve package metadata data from, in Composer v2 format, e.g. '/p2/%package%.json'." + }, + "available-packages": { + "type": "array", + "items": { + "type": "string" + }, + "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify all the package names that your repository contains here." + }, + "available-package-patterns": { + "type": "array", + "items": { + "type": "string" + }, + "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify package name patterns containing wildcards (*) that your repository contains here." + }, + "security-advisories": { + "type": "array", + "items": { + "type": "object", + "required": ["metadata", "api-url"], + "properties": { + "metadata": { + "type": "boolean", + "description": "Whether metadata files contain security advisory data or whether it should always be queried using the API URL." + }, + "api-url": { + "type": "string", + "description": "Endpoint to call to retrieve security advisories data." + } + } + } + }, + "metadata-changes-url": { + "type": "string", + "description": "Endpoint to retrieve package metadata updates from. This should receive a timestamp since last call to be able to return new changes. e.g. '/metadata/changes.json'." + }, + "providers-api": { + "type": "string", + "description": "Endpoint to retrieve package names providing a given name from, e.g. '/providers/%package%.json'." + }, + "notify-batch": { + "type": "string", + "description": "Endpoint to call after multiple packages have been installed, e.g. '/downloads/'." + }, + "search": { + "type": "string", + "description": "Endpoint that provides search capabilities, e.g. '/search.json?q=%query%&type=%type%'." + }, + "list": { + "type": "string", + "description": "Endpoint that provides a full list of packages present in the repository. It should accept an optional `?filter=xx` query param, which can contain `*` as wildcards matching any substring. e.g. '/list.json'." + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "required": ["message", "versions"], + "properties": { + "message": { + "type": "string", + "description": "A message that will be output by Composer as a warning when this source is consulted." + }, + "versions": { + "type": "string", + "description": "A version constraint to limit to which Composer versions the warning should be shown." + } + } + } + }, + "infos": { + "type": "array", + "items": { + "type": "object", + "required": ["message", "versions"], + "properties": { + "message": { + "type": "string", + "description": "A message that will be output by Composer as info when this source is consulted." + }, + "versions": { + "type": "string", + "description": "A version constraint to limit to which Composer versions the info should be shown." + } + } + } + }, + "providers-url": { + "type": "string", + "description": "DEPRECATED: Endpoint to retrieve provider data from, e.g. '/p/%package%$%hash%.json'." + }, + "provider-includes": { + "type": "object", + "description": "DEPRECATED: A hashmap of provider listings.", + "additionalProperties": { "$ref": "#/definitions/provider" } + }, + "providers": { + "type": "object", + "description": "DEPRECATED: A hashmap of package names in the form of /.", + "additionalProperties": { "$ref": "#/definitions/provider" } + }, + "warning": { + "type": "string", + "description": "DEPRECATED: A message that will be output by Composer as a warning when this source is consulted." + }, + "warning-versions": { + "type": "string", + "description": "DEPRECATED: A version constraint to limit to which Composer versions the warning should be shown." + }, + "info": { + "type": "string", + "description": "DEPRECATED: A message that will be output by Composer as a info when this source is consulted." + }, + "info-versions": { + "type": "string", + "description": "DEPRECATED: A version constraint to limit to which Composer versions the info should be shown." + } + }, + "definitions": { + "versions": { + "type": "object", + "description": "A hashmap of versions and their metadata.", + "additionalProperties": { "$ref": "#/definitions/version" } + }, + "version": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/package" }, + { "$ref": "#/definitions/metapackage" } + ] + }, + "package-base": { + "properties": { + "name": { "type": "string" }, + "type": { "type": "string" }, + "version": { "type": "string" }, + "version_normalized": { + "type": "string", + "description": "Normalized version, optional but can save computational time on client side." + }, + "autoload": { "type": "object" }, + "require": { "type": "object" }, + "replace": { "type": "object" }, + "conflict": { "type": "object" }, + "provide": { "type": "object" }, + "time": { "type": "string" } + }, + "additionalProperties": true + }, + "package": { + "allOf": [ + { "$ref": "#/definitions/package-base" }, + { + "properties": { + "dist": { "type": "object" }, + "source": { "type": "object" } + } + }, + { "oneOf": [ + { "required": [ "name", "version", "source" ] }, + { "required": [ "name", "version", "dist" ] } + ] } + ] + }, + "metapackage": { + "allOf": [ + { "$ref": "#/definitions/package-base" }, + { + "properties": { + "type": { "type": "string", "enum": [ "metapackage" ] } + }, + "required": [ "name", "version", "type" ] + } + ] + }, + "provider": { + "type": "object", + "properties": { + "sha256": { + "type": "string", + "description": "Hash value that can be used to validate the resource." + } + } + } + } +} diff --git a/vendor/composer/composer/res/composer-schema.json b/vendor/composer/composer/res/composer-schema.json new file mode 100644 index 000000000..c4661da5a --- /dev/null +++ b/vendor/composer/composer/res/composer-schema.json @@ -0,0 +1,1422 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "title": "Composer Package", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Package name, including 'vendor-name/' prefix.", + "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$" + }, + "description": { + "type": "string", + "description": "Short package description." + }, + "license": { + "type": ["string", "array"], + "description": "License name. Or an array of license names." + }, + "type": { + "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", + "type": "string", + "pattern": "^[a-z0-9-]+$" + }, + "abandoned": { + "type": ["boolean", "string"], + "description": "Indicates whether this package has been abandoned, it can be boolean or a package name/URL pointing to a recommended alternative. Defaults to false." + }, + "version": { + "type": "string", + "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.", + "pattern": "^[vV]?\\d+(?:[.-]\\d+){0,3}[._-]?(?:(?:[sS][tT][aA][bB][lL][eE]|[bB][eE][tT][aA]|[bB]|[rR][cC]|[aA][lL][pP][hH][aA]|[aA]|[pP][aA][tT][cC][hH]|[pP][lL]|[pP])(?:(?:[.-]?\\d+)*)?)?(?:[.-]?[dD][eE][vV]|\\.x-dev)?(?:\\+.*)?$|^dev-.*$" + }, + "default-branch": { + "type": ["boolean"], + "description": "Internal use only, do not specify this in composer.json. Indicates whether this version is the default branch of the linked VCS repository. Defaults to false." + }, + "non-feature-branches": { + "type": ["array"], + "description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.", + "items": { + "type": "string" + } + }, + "keywords": { + "type": "array", + "items": { + "type": "string", + "description": "A tag/keyword that this package relates to." + } + }, + "readme": { + "type": "string", + "description": "Relative path to the readme document." + }, + "time": { + "type": "string", + "description": "Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format." + }, + "authors": { + "$ref": "#/definitions/authors" + }, + "homepage": { + "type": "string", + "description": "Homepage URL for the project.", + "format": "uri" + }, + "support": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "Email address for support.", + "format": "email" + }, + "issues": { + "type": "string", + "description": "URL to the issue tracker.", + "format": "uri" + }, + "forum": { + "type": "string", + "description": "URL to the forum.", + "format": "uri" + }, + "wiki": { + "type": "string", + "description": "URL to the wiki.", + "format": "uri" + }, + "irc": { + "type": "string", + "description": "IRC channel for support, as irc://server/channel.", + "format": "uri" + }, + "chat": { + "type": "string", + "description": "URL to the support chat.", + "format": "uri" + }, + "source": { + "type": "string", + "description": "URL to browse or download the sources.", + "format": "uri" + }, + "docs": { + "type": "string", + "description": "URL to the documentation.", + "format": "uri" + }, + "rss": { + "type": "string", + "description": "URL to the RSS feed.", + "format": "uri" + }, + "security": { + "type": "string", + "description": "URL to the vulnerability disclosure policy (VDP).", + "format": "uri" + } + } + }, + "funding": { + "type": "array", + "description": "A list of options to fund the development and maintenance of the package.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of funding or platform through which funding is possible." + }, + "url": { + "type": "string", + "description": "URL to a website with details on funding and a way to fund the package.", + "format": "uri" + } + } + } + }, + "source": { + "$ref": "#/definitions/source" + }, + "dist": { + "$ref": "#/definitions/dist" + }, + "_comment": { + "type": ["array", "string"], + "description": "A key to store comments in" + }, + "require": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that are required to run this package.", + "additionalProperties": { + "type": "string" + } + }, + "require-dev": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).", + "additionalProperties": { + "type": "string" + } + }, + "replace": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that can be replaced by this package.", + "additionalProperties": { + "type": "string" + } + }, + "conflict": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that conflict with this package.", + "additionalProperties": { + "type": "string" + } + }, + "provide": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that this package provides in addition to this package's name.", + "additionalProperties": { + "type": "string" + } + }, + "suggest": { + "type": "object", + "description": "This is an object of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).", + "additionalProperties": { + "type": "string" + } + }, + "repositories": { + "type": ["object", "array"], + "description": "A set of additional repositories where packages can be found.", + "additionalProperties": { + "anyOf": [ + { "$ref": "#/definitions/anonymous-repository" }, + { "type": "boolean", "enum": [false] } + ] + }, + "items": { + "anyOf": [ + { "$ref": "#/definitions/repository" }, + { + "type": "object", + "additionalProperties": { "type": "boolean", "enum": [false] }, + "minProperties": 1, + "maxProperties": 1 + } + ] + } + }, + "minimum-stability": { + "type": ["string"], + "description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable.", + "enum": ["dev", "alpha", "beta", "rc", "RC", "stable"] + }, + "prefer-stable": { + "type": ["boolean"], + "description": "If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages." + }, + "autoload": { + "$ref": "#/definitions/autoload" + }, + "autoload-dev": { + "type": "object", + "description": "Description of additional autoload rules for development purpose (eg. a test suite).", + "properties": { + "psr-0": { + "type": "object", + "description": "This is an object of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.", + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "psr-4": { + "type": "object", + "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "classmap": { + "type": "array", + "description": "This is an array of paths that contain classes to be included in the class-map generation process." + }, + "files": { + "type": "array", + "description": "This is an array of files that are always required on every request." + } + } + }, + "target-dir": { + "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", + "type": "string" + }, + "include-path": { + "type": ["array"], + "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", + "items": { + "type": "string" + } + }, + "bin": { + "type": ["string", "array"], + "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", + "items": { + "type": "string" + } + }, + "archive": { + "type": ["object"], + "description": "Options for creating package archives for distribution.", + "properties": { + "name": { + "type": "string", + "description": "A base name for archive." + }, + "exclude": { + "type": "array", + "description": "A list of patterns for paths to exclude or include if prefixed with an exclamation mark." + } + } + }, + "php-ext": { + "type": "object", + "description": "Settings for PHP extension packages.", + "properties": { + "extension-name": { + "type": "string", + "description": "If specified, this will be used as the name of the extension, where needed by tooling. If this is not specified, the extension name will be derived from the Composer package name (e.g. `vendor/name` would become `ext-name`). The extension name may be specified with or without the `ext-` prefix, and tools that use this must normalise this appropriately.", + "example": "ext-xdebug" + }, + "priority": { + "type": "integer", + "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.", + "minimum": 10, + "maximum": 99, + "example": 80, + "default": 80 + }, + "support-zts": { + "type": "boolean", + "description": "Does this package support Zend Thread Safety", + "example": false, + "default": true + }, + "support-nts": { + "type": "boolean", + "description": "Does this package support non-Thread Safe mode", + "example": false, + "default": true + }, + "build-path": { + "type": ["string", "null"], + "description": "If specified, this is the subdirectory that will be used to build the extension instead of the root of the project.", + "example": "my-extension-source", + "default": null + }, + "download-url-method": { + "oneOf": [ + { + "type": "string", + "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.", + "deprecated": true, + "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"], + "example": "composer-default", + "default": "composer-default" + }, + { + "type": "array", + "description": "Multiple techniques can be specified, in which case PIE will try each in turn until one succeeds. The first technique that succeeds will be used.", + "items": { + "type": "string", + "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.", + "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"], + "example": ["pre-packaged-binary", "composer-default"] + }, + "minItems": 1, + "default": ["composer-default"] + } + ] + }, + "os-families": { + "type": "array", + "minItems": 1, + "description": "An array of OS families to mark as compatible with the extension. Specifying this property will mean this package is not installable with PIE on any OS family not listed here. Must not be specified alongside os-families-exclude.", + "items": { + "type": "string", + "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"], + "description": "The name of the OS family to mark as compatible." + } + }, + "os-families-exclude": { + "type": "array", + "minItems": 1, + "description": "An array of OS families to mark as incompatible with the extension. Specifying this property will mean this package is installable on any OS family except those listed here. Must not be specified alongside os-families.", + "items": { + "type": "string", + "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"], + "description": "The name of the OS family to exclude." + } + }, + "configure-options": { + "type": "array", + "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "The name of the flag, this would typically be prefixed with `--`, for example, the value 'the-flag' would be passed as `./configure --the-flag`.", + "example": "without-xdebug-compression", + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$" + }, + "needs-value": { + "type": "boolean", + "description": "If this is set to true, the flag needs a value (e.g. --with-somelib=), otherwise it is a flag without a value (e.g. --enable-some-feature).", + "example": false, + "default": false + }, + "description": { + "type": "string", + "description": "The description of what the flag does or means.", + "example": "Disable compression through zlib" + } + } + } + } + }, + "allOf": [ + { + "not": { + "required": ["os-families", "os-families-exclude"] + } + } + ] + }, + "config": { + "type": "object", + "description": "Composer options.", + "properties": { + "platform": { + "type": "object", + "description": "This is an object of package name (keys) and version (values) that will be used to mock the platform packages on this machine, the version can be set to false to make it appear like the package is not present.", + "additionalProperties": { + "type": ["string", "boolean"] + } + }, + "allow-plugins": { + "type": ["object", "boolean"], + "description": "This is an object of {\"pattern\": true|false} with packages which are allowed to be loaded as plugins, or true to allow all, false to allow none. Defaults to {} which prompts when an unknown plugin is added.", + "additionalProperties": { + "type": ["boolean"] + } + }, + "process-timeout": { + "type": "integer", + "description": "The timeout in seconds for process executions, defaults to 300 (5mins)." + }, + "use-include-path": { + "type": "boolean", + "description": "If true, the Composer autoloader will also look for classes in the PHP include path." + }, + "use-parent-dir": { + "type": ["string", "boolean"], + "description": "When running Composer in a directory where there is no composer.json, if there is one present in a directory above Composer will by default ask you whether you want to use that directory's composer.json instead. One of: true (always use parent if needed), false (never ask or use it) or \"prompt\" (ask every time), defaults to prompt." + }, + "preferred-install": { + "type": ["string", "object"], + "description": "The install method Composer will prefer to use, defaults to auto and can be any of source, dist, auto, or an object of {\"pattern\": \"preference\"}.", + "additionalProperties": { + "type": ["string"] + } + }, + "audit": { + "type": "object", + "description": "Security audit and version blocking configuration options", + "properties": { + "ignore": { + "anyOf": [ + { + "type": "object", + "description": "A list of advisory ids, remote ids, CVE ids or package names (keys) with either explanations (string values) or detailed configuration (object values with 'apply' and 'reason' fields) for why they're ignored.", + "additionalProperties": { + "anyOf": [ + { + "type": "string", + "description": "Explanation for ignoring this advisory (applies to audit reports and version blocking)" + }, + { + "type": "object", + "description": "Detailed configuration for ignoring this advisory", + "properties": { + "apply": { + "type": "string", + "enum": ["audit", "block", "all"], + "description": "Where to apply this ignore: 'audit' (only audit reports), 'block' (only version blocking), or 'all' (both)" + }, + "reason": { + "type": "string", + "description": "Explanation for ignoring this advisory, e.g. vulnerable code path not reachable in application" + } + } + }, + { + "type": "null", + "description": "Ignore without explanation (applies to audit reports and version blocking)" + } + ] + } + }, + { + "type": "array", + "description": "A set of advisory ids, remote ids, CVE ids or package names that are reported but let the audit command pass. Applies to audit reports and version blocking.", + "items": { + "type": "string" + } + } + ] + }, + "abandoned": { + "enum": ["ignore", "report", "fail"], + "description": "Whether abandoned packages should be ignored, reported as problems or cause an audit failure. Applies only to audit reports, not to version blocking." + }, + "ignore-severity": { + "anyOf": [ + { + "type": "object", + "description": "A list of severities (keys) with either explanations (string values) or detailed configuration (object values with 'apply' fields) for why they're ignored.", + "additionalProperties": { + "anyOf": [ + { + "type": "string", + "description": "Explanation for ignoring this severity (applies to audit reports and version blocking)" + }, + { + "type": "object", + "description": "Detailed configuration for ignoring this severity", + "properties": { + "apply": { + "type": "string", + "enum": ["audit", "block", "all"], + "description": "Where to apply this ignore: 'audit' (only audit reports), 'block' (only version blocking), or 'all' (both)" + } + } + }, + { + "type": "null", + "description": "Ignore without explanation (applies to audit reports and version blocking)" + } + ] + } + }, + { + "type": "array", + "description": "A list of severities for which advisories with matching severity will be ignored, e.g. critical, high, medium, low, or none. Applies to audit reports and version blocking.", + "items": { + "type": "string" + } + } + ] + }, + "ignore-unreachable": { + "type": "boolean", + "description": "Whether repositories that are unreachable or return a non-200 status code should be ignored or not. Applies only to the composer audit command, does not affect audit report summaries in other commands or version blocking." + }, + "block-insecure": { + "type": "boolean", + "description": "Whether insecure versions should be blocked during a composer update/require command or not.", + "default": true + }, + "block-abandoned": { + "type": "boolean", + "description": "Whether abandoned packages should be blocked during a composer update/require command or not. Applies only if blocking of insecure versions is enabled.", + "default": false + }, + "ignore-abandoned": { + "anyOf": [ + { + "type": "object", + "description": "A list of abandoned package names (keys) with either explanations (string values) or detailed configuration (object values with 'apply' and 'reason' fields) for why they're ignored.", + "additionalProperties": { + "anyOf": [ + { + "type": "string", + "description": "Explanation for ignoring this abandoned package (applies to audit reports and version blocking)" + }, + { + "type": "object", + "description": "Detailed configuration for ignoring this abandoned package", + "properties": { + "apply": { + "type": "string", + "enum": ["audit", "block", "all"], + "description": "Where to apply this ignore: 'audit' (only audit reports), 'block' (only version blocking), or 'all' (both)" + }, + "reason": { + "type": "string", + "description": "Explanation for ignoring this abandoned package" + } + } + }, + { + "type": "null", + "description": "Ignore without explanation (applies to audit reports and version blocking)" + } + ] + } + }, + { + "type": "array", + "description": "A set of abandoned package names that are reported but let the audit command pass and that are not blocked during updates/requires.", + "items": { + "type": "string" + } + } + ] + } + } + }, + "notify-on-install": { + "type": "boolean", + "description": "Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour, defaults to true." + }, + "github-protocols": { + "type": "array", + "description": "A list of protocols to use for github.com clones, in priority order, defaults to [\"https\", \"ssh\", \"git\"].", + "items": { + "type": "string" + } + }, + "github-oauth": { + "type": "object", + "description": "An object of domain name => github API oauth tokens, typically {\"github.com\":\"\"}.", + "additionalProperties": { + "type": "string" + } + }, + "gitlab-oauth": { + "type": "object", + "description": "An object of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":{\"expires-at\":\"\", \"refresh-token\":\"\", \"token\":\"\"}}.", + "additionalProperties": { + "type": ["string", "object"], + "required": [ "token"], + "properties": { + "expires-at": { + "type": "integer", + "description": "The expiration date for this GitLab token" + }, + "refresh-token": { + "type": "string", + "description": "The refresh token used for GitLab authentication" + }, + "token": { + "type": "string", + "description": "The token used for GitLab authentication" + } + } + } + }, + "gitlab-token": { + "type": "object", + "description": "An object of domain name => gitlab private tokens, typically {\"gitlab.com\":\"\"}, or an object with username and token keys.", + "additionalProperties": { + "type": ["string", "object"], + "required": ["username", "token"], + "properties": { + "username": { + "type": "string", + "description": "The username used for GitLab authentication" + }, + "token": { + "type": "string", + "description": "The token used for GitLab authentication" + } + } + } + }, + "gitlab-protocol": { + "enum": ["git", "http", "https"], + "description": "A protocol to force use of when creating a repository URL for the `source` value of the package metadata. One of `git` or `http`. By default, Composer will generate a git URL for private repositories and http one for public repos." + }, + "bearer": { + "type": "object", + "description": "An object of domain name => bearer authentication token, for example {\"example.com\":\"\"}.", + "additionalProperties": { + "type": "string" + } + }, + "custom-headers": { + "type": "object", + "description": "Custom HTTP headers for specific domains.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string", + "description": "A header in format 'Header-Name: Header-Value'" + } + } + }, + "forgejo-token": { + "type": "object", + "description": "An object of domain name => forgejo username/access token, typically {\"codeberg.org\":{\"username\": \"\", \"token\": \"\"}}.", + "additionalProperties": { + "type": ["object"], + "required": ["username", "token"], + "properties": { + "username": { + "type": "string", + "description": "The username used for Forgejo authentication" + }, + "token": { + "type": "string", + "description": "The access token used for Forgejo authentication" + } + } + } + }, + "disable-tls": { + "type": "boolean", + "description": "Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. Enabling this is a security risk and is NOT recommended. The better way is to enable the php_openssl extension in php.ini." + }, + "secure-http": { + "type": "boolean", + "description": "Defaults to `true`. If set to true only HTTPS URLs are allowed to be downloaded via Composer. If you really absolutely need HTTP access to something then you can disable it, but using \"Let's Encrypt\" to get a free SSL certificate is generally a better alternative." + }, + "secure-svn-domains": { + "type": "array", + "description": "A list of domains which should be trusted/marked as using a secure Subversion/SVN transport. By default svn:// protocol is seen as insecure and will throw. This is a better/safer alternative to disabling `secure-http` altogether.", + "items": { + "type": "string" + } + }, + "cafile": { + "type": "string", + "description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically." + }, + "capath": { + "type": "string", + "description": "If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory." + }, + "http-basic": { + "type": "object", + "description": "An object of domain name => {\"username\": \"...\", \"password\": \"...\"}.", + "additionalProperties": { + "type": "object", + "required": ["username", "password"], + "properties": { + "username": { + "type": "string", + "description": "The username used for HTTP Basic authentication" + }, + "password": { + "type": "string", + "description": "The password used for HTTP Basic authentication" + } + } + } + }, + "client-certificate": { + "type": "object", + "description": "An object of domain name => {\"local_cert\": \"...\", \"local_pk\"?: \"...\", \"passphrase\"?: \"...\"} to provide client certificate.", + "additionalProperties": { + "type": "object", + "required": ["local_cert"], + "properties": { + "local_cert": { + "type": "string", + "description": "Path to a certificate (pem) or pair certificate+key (pem)" + }, + "local_pk": { + "type": "string", + "description": "Path to a private key file (pem)" + }, + "passphrase": { + "type": "string", + "description": "Passphrase for private key" + } + } + } + }, + "store-auths": { + "type": ["string", "boolean"], + "description": "What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt." + }, + "vendor-dir": { + "type": "string", + "description": "The location where all packages are installed, defaults to \"vendor\"." + }, + "bin-dir": { + "type": "string", + "description": "The location where all binaries are linked, defaults to \"vendor/bin\"." + }, + "data-dir": { + "type": "string", + "description": "The location where old phar files are stored, defaults to \"$home\" except on XDG Base Directory compliant unixes." + }, + "cache-dir": { + "type": "string", + "description": "The location where all caches are located, defaults to \"~/.composer/cache\" on *nix and \"%LOCALAPPDATA%\\Composer\" on windows." + }, + "cache-files-dir": { + "type": "string", + "description": "The location where files (zip downloads) are cached, defaults to \"{$cache-dir}/files\"." + }, + "cache-repo-dir": { + "type": "string", + "description": "The location where repo (git/hg repo clones) are cached, defaults to \"{$cache-dir}/repo\"." + }, + "cache-vcs-dir": { + "type": "string", + "description": "The location where vcs infos (git clones, github api calls, etc. when reading vcs repos) are cached, defaults to \"{$cache-dir}/vcs\"." + }, + "cache-ttl": { + "type": "integer", + "description": "The default cache time-to-live, defaults to 15552000 (6 months)." + }, + "cache-files-ttl": { + "type": "integer", + "description": "The cache time-to-live for files, defaults to the value of cache-ttl." + }, + "cache-files-maxsize": { + "type": ["string", "integer"], + "description": "The cache max size for the files cache, defaults to \"300MiB\"." + }, + "cache-read-only": { + "type": ["boolean"], + "description": "Whether to use the Composer cache in read-only mode." + }, + "bin-compat": { + "enum": ["auto", "full", "proxy", "symlink"], + "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed), can be \"full\" (compatible with both Windows and Unix-based systems) and \"proxy\" (only bash-style proxy)." + }, + "discard-changes": { + "type": ["string", "boolean"], + "description": "The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"." + }, + "autoloader-suffix": { + "type": "string", + "description": "Optional string to be used as a suffix for the generated Composer autoloader. When null a random one will be generated." + }, + "optimize-autoloader": { + "type": "boolean", + "description": "Always optimize when dumping the autoloader." + }, + "prepend-autoloader": { + "type": "boolean", + "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true." + }, + "classmap-authoritative": { + "type": "boolean", + "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false." + }, + "apcu-autoloader": { + "type": "boolean", + "description": "If true, the Composer autoloader will check for APCu and use it to cache found/not-found classes when the extension is enabled, defaults to false." + }, + "github-domains": { + "type": "array", + "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].", + "items": { + "type": "string" + } + }, + "github-expose-hostname": { + "type": "boolean", + "description": "Defaults to true. If set to false, the OAuth tokens created to access the github API will have a date instead of the machine hostname." + }, + "gitlab-domains": { + "type": "array", + "description": "A list of domains to use in gitlab mode. This is used for custom GitLab setups, defaults to [\"gitlab.com\"].", + "items": { + "type": "string" + } + }, + "forgejo-domains": { + "type": "array", + "description": "A list of domains to use in forgejo mode. This is used for custom Forgejo setups, defaults to [\"codeberg.org\"].", + "items": { + "type": "string" + } + }, + "bitbucket-oauth": { + "type": "object", + "description": "An object of domain name => {\"consumer-key\": \"...\", \"consumer-secret\": \"...\"}.", + "additionalProperties": { + "type": "object", + "required": ["consumer-key", "consumer-secret"], + "properties": { + "consumer-key": { + "type": "string", + "description": "The consumer-key used for OAuth authentication" + }, + "consumer-secret": { + "type": "string", + "description": "The consumer-secret used for OAuth authentication" + }, + "access-token": { + "type": "string", + "description": "The OAuth token retrieved from Bitbucket's API, this is written by Composer and you should not set it nor modify it." + }, + "access-token-expiration": { + "type": "integer", + "description": "The generated token's expiration timestamp, this is written by Composer and you should not set it nor modify it." + } + } + } + }, + "use-github-api": { + "type": "boolean", + "description": "Defaults to true. If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository." + }, + "archive-format": { + "type": "string", + "description": "The default archiving format when not provided on cli, defaults to \"tar\"." + }, + "archive-dir": { + "type": "string", + "description": "The default archive path when not provided on cli, defaults to \".\"." + }, + "htaccess-protect": { + "type": "boolean", + "description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories." + }, + "sort-packages": { + "type": "boolean", + "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency." + }, + "lock": { + "type": "boolean", + "description": "Defaults to true. If set to false, Composer will not create a composer.lock file." + }, + "platform-check": { + "type": ["boolean", "string"], + "enum": ["php-only", true, false], + "description": "Defaults to \"php-only\" which checks only the PHP version. Setting to true will also check the presence of required PHP extensions. If set to false, Composer will not create and require a platform_check.php file as part of the autoloader bootstrap." + }, + "bump-after-update": { + "type": ["string", "boolean"], + "enum": ["dev", "no-dev", true, false], + "description": "Defaults to false and can be any of true, false, \"dev\"` or \"no-dev\"`. If set to true, Composer will run the bump command after running the update command. If set to \"dev\" or \"no-dev\" then only the corresponding dependencies will be bumped." + }, + "allow-missing-requirements": { + "type": ["boolean"], + "description": "Defaults to false. If set to true, Composer will allow install when lock file is not up to date with the latest changes in composer.json." + }, + "update-with-minimal-changes": { + "type": ["boolean"], + "description": "Defaults to false. If set to true, Composer will only perform absolutely necessary changes to transitive dependencies during update." + } + } + }, + "extra": { + "type": ["object", "array"], + "description": "Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.", + "additionalProperties": true + }, + "scripts": { + "type": ["object"], + "description": "Script listeners that will be executed before/after some events.", + "properties": { + "pre-install-cmd": { + "type": ["array", "string"], + "description": "Occurs before the install command is executed, contains one or more Class::method callables or shell commands." + }, + "post-install-cmd": { + "type": ["array", "string"], + "description": "Occurs after the install command is executed, contains one or more Class::method callables or shell commands." + }, + "pre-update-cmd": { + "type": ["array", "string"], + "description": "Occurs before the update command is executed, contains one or more Class::method callables or shell commands." + }, + "post-update-cmd": { + "type": ["array", "string"], + "description": "Occurs after the update command is executed, contains one or more Class::method callables or shell commands." + }, + "pre-status-cmd": { + "type": ["array", "string"], + "description": "Occurs before the status command is executed, contains one or more Class::method callables or shell commands." + }, + "post-status-cmd": { + "type": ["array", "string"], + "description": "Occurs after the status command is executed, contains one or more Class::method callables or shell commands." + }, + "pre-package-install": { + "type": ["array", "string"], + "description": "Occurs before a package is installed, contains one or more Class::method callables or shell commands." + }, + "post-package-install": { + "type": ["array", "string"], + "description": "Occurs after a package is installed, contains one or more Class::method callables or shell commands." + }, + "pre-package-update": { + "type": ["array", "string"], + "description": "Occurs before a package is updated, contains one or more Class::method callables or shell commands." + }, + "post-package-update": { + "type": ["array", "string"], + "description": "Occurs after a package is updated, contains one or more Class::method callables or shell commands." + }, + "pre-package-uninstall": { + "type": ["array", "string"], + "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables or shell commands." + }, + "post-package-uninstall": { + "type": ["array", "string"], + "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables or shell commands." + }, + "pre-autoload-dump": { + "type": ["array", "string"], + "description": "Occurs before the autoloader is dumped, contains one or more Class::method callables or shell commands." + }, + "post-autoload-dump": { + "type": ["array", "string"], + "description": "Occurs after the autoloader is dumped, contains one or more Class::method callables or shell commands." + }, + "post-root-package-install": { + "type": ["array", "string"], + "description": "Occurs after the root-package is installed, contains one or more Class::method callables or shell commands." + }, + "post-create-project-cmd": { + "type": ["array", "string"], + "description": "Occurs after the create-project command is executed, contains one or more Class::method callables or shell commands." + } + } + }, + "scripts-descriptions": { + "type": ["object"], + "description": "Descriptions for custom commands, shown in console help.", + "additionalProperties": { + "type": "string" + } + }, + "scripts-aliases": { + "type": ["object"], + "description": "Aliases for custom commands.", + "additionalProperties": { + "type": "array" + } + } + }, + "definitions": { + "authors": { + "type": "array", + "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ "name"], + "properties": { + "name": { + "type": "string", + "description": "Full name of the author." + }, + "email": { + "type": "string", + "description": "Email address of the author.", + "format": "email" + }, + "homepage": { + "type": "string", + "description": "Homepage URL for the author.", + "format": "uri" + }, + "role": { + "type": "string", + "description": "Author's role in the project." + } + } + } + }, + "autoload": { + "type": "object", + "description": "Description of how the package can be autoloaded.", + "properties": { + "psr-0": { + "type": "object", + "description": "This is an object of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.", + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "psr-4": { + "type": "object", + "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "classmap": { + "type": "array", + "description": "This is an array of paths that contain classes to be included in the class-map generation process." + }, + "files": { + "type": "array", + "description": "This is an array of files that are always required on every request." + }, + "exclude-from-classmap": { + "type": "array", + "description": "This is an array of patterns to exclude from autoload classmap generation. (e.g. \"exclude-from-classmap\": [\"/test/\", \"/tests/\", \"/Tests/\"]" + } + } + }, + "repository": { + "type": "object", + "anyOf": [ + { "$ref": "#/definitions/composer-repository" }, + { "$ref": "#/definitions/vcs-repository" }, + { "$ref": "#/definitions/path-repository" }, + { "$ref": "#/definitions/artifact-repository" }, + { "$ref": "#/definitions/pear-repository" }, + { "$ref": "#/definitions/package-repository" } + ] + }, + "anonymous-repository": { + "allOf": [ + { "$ref": "#/definitions/repository" }, + { "not": { "required": ["name"] } } + ] + }, + "composer-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "name": { "type": "string" }, + "type": { "type": "string", "enum": ["composer"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "options": { + "type": "object", + "additionalProperties": true + }, + "allow_ssl_downgrade": { "type": "boolean" }, + "force-lazy-providers": { "type": "boolean" } + } + }, + "vcs-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "name": { "type": "string" }, + "type": { "type": "string", "enum": ["vcs", "github", "git", "gitlab", "bitbucket", "git-bitbucket", "hg", "fossil", "perforce", "svn", "forgejo"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "no-api": { "type": "boolean" }, + "secure-http": { "type": "boolean" }, + "svn-cache-credentials": { "type": "boolean" }, + "trunk-path": { "type": ["string", "boolean"] }, + "branches-path": { "type": ["string", "boolean"] }, + "tags-path": { "type": ["string", "boolean"] }, + "package-path": { "type": "string" }, + "depot": { "type": "string" }, + "branch": { "type": "string" }, + "unique_perforce_client_name": { "type": "string" }, + "p4user": { "type": "string" }, + "p4password": { "type": "string" } + } + }, + "path-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "name": { "type": "string" }, + "type": { "type": "string", "enum": ["path"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "options": { + "type": "object", + "properties": { + "reference": { "type": ["string"], "enum": ["none", "config", "auto"] }, + "symlink": { "type": ["boolean", "null"] }, + "relative": { "type": ["boolean"] }, + "versions": { "type": "object", "additionalProperties": { "type": "string" } } + }, + "additionalProperties": true + } + } + }, + "artifact-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "name": { "type": "string" }, + "type": { "type": "string", "enum": ["artifact"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "pear-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "name": { "type": "string" }, + "type": { "type": "string", "enum": ["pear"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "vendor-alias": { "type": "string" } + } + }, + "package-repository": { + "type": "object", + "required": ["type", "package"], + "properties": { + "name": { "type": "string" }, + "type": { "type": "string", "enum": ["package"] }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "package": { + "oneOf": [ + { "$ref": "#/definitions/inline-package" }, + { + "type": "array", + "items": { "$ref": "#/definitions/inline-package" } + } + ] + } + } + }, + "inline-package": { + "type": "object", + "required": ["name", "version"], + "properties": { + "name": { + "type": "string", + "description": "Package name, including 'vendor-name/' prefix." + }, + "type": { + "type": "string" + }, + "target-dir": { + "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", + "type": "string" + }, + "description": { + "type": "string" + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "homepage": { + "type": "string", + "format": "uri" + }, + "version": { + "type": "string" + }, + "time": { + "type": "string" + }, + "license": { + "type": [ + "string", + "array" + ] + }, + "authors": { + "$ref": "#/definitions/authors" + }, + "require": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "replace": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "conflict": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "provide": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "require-dev": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "suggest": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "extra": { + "type": ["object", "array"], + "additionalProperties": true + }, + "autoload": { + "$ref": "#/definitions/autoload" + }, + "archive": { + "type": ["object"], + "properties": { + "exclude": { + "type": "array" + } + } + }, + "bin": { + "type": ["string", "array"], + "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", + "items": { + "type": "string" + } + }, + "include-path": { + "type": ["array"], + "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", + "items": { + "type": "string" + } + }, + "source": { + "$ref": "#/definitions/source" + }, + "dist": { + "$ref": "#/definitions/dist" + } + }, + "additionalProperties": true + }, + "source": { + "type": "object", + "required": ["type", "url", "reference"], + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "mirrors": { + "type": "array" + } + } + }, + "dist": { + "type": "object", + "required": ["type", "url"], + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + }, + "mirrors": { + "type": "array" + } + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Advisory/AuditConfig.php b/vendor/composer/composer/src/Composer/Advisory/AuditConfig.php new file mode 100644 index 000000000..42a5dde80 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Advisory/AuditConfig.php @@ -0,0 +1,201 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Advisory; + +use Composer\Config; + +/** + * @readonly + * @internal + */ +class AuditConfig +{ + /** + * @var bool Whether to run audit + */ + public $audit; + + /** + * @var Auditor::FORMAT_* + */ + public $auditFormat; + + /** + * @var Auditor::ABANDONED_* + */ + public $auditAbandoned; + + /** + * @var bool Should insecure versions be blocked during a composer update/required command + */ + public $blockInsecure; + + /** + * @var bool Should abandoned packages be blocked during a composer update/required command + */ + public $blockAbandoned; + + /** + * @var bool Should repositories that are unreachable or return a non-200 status code be ignored. + */ + public $ignoreUnreachable; + + /** + * @var array List of advisory IDs to ignore during auditing => reason for ignoring + */ + public $ignoreListForAudit; + + /** + * @var array List of advisory IDs to ignore during blocking + */ + public $ignoreListForBlocking; + + /** + * @var array List of severities to ignore during auditing + */ + public $ignoreSeverityForAudit; + + /** + * @var array List of severities to ignore during blocking + */ + public $ignoreSeverityForBlocking; + + /** + * @var array List of abandoned packages to ignore during auditing + */ + public $ignoreAbandonedForAudit; + + /** + * @var array List of abandoned packages to ignore during blocking + */ + public $ignoreAbandonedForBlocking; + + /** + * @param Auditor::FORMAT_* $auditFormat + * @param Auditor::ABANDONED_* $auditAbandoned + * @param array $ignoreListForAudit + * @param array $ignoreListForBlocking + * @param array $ignoreSeverityForAudit + * @param array $ignoreSeverityForBlocking + * @param array $ignoreAbandonedForAudit + * @param array $ignoreAbandonedForBlocking + */ + public function __construct(bool $audit, string $auditFormat, string $auditAbandoned, bool $blockInsecure, bool $blockAbandoned, bool $ignoreUnreachable, array $ignoreListForAudit, array $ignoreListForBlocking, array $ignoreSeverityForAudit, array $ignoreSeverityForBlocking, array $ignoreAbandonedForAudit, array $ignoreAbandonedForBlocking) + { + $this->audit = $audit; + $this->auditFormat = $auditFormat; + $this->auditAbandoned = $auditAbandoned; + $this->blockInsecure = $blockInsecure; + $this->blockAbandoned = $blockAbandoned; + $this->ignoreUnreachable = $ignoreUnreachable; + $this->ignoreListForAudit = $ignoreListForAudit; + $this->ignoreListForBlocking = $ignoreListForBlocking; + $this->ignoreSeverityForAudit = $ignoreSeverityForAudit; + $this->ignoreSeverityForBlocking = $ignoreSeverityForBlocking; + $this->ignoreAbandonedForAudit = $ignoreAbandonedForAudit; + $this->ignoreAbandonedForBlocking = $ignoreAbandonedForBlocking; + } + + /** + * Parse ignore configuration supporting both simple and detailed formats with apply scopes + * + * Simple format: ['CVE-123', 'CVE-456'] or ['CVE-123' => 'reason'] + * Detailed format: ['CVE-123' => ['apply' => 'audit|block|all', 'reason' => '...']] + * + * @param array $config + * @return array{audit: array, block: array} + */ + private static function parseIgnoreWithApply(array $config): array + { + $forAudit = []; + $forBlock = []; + + foreach ($config as $key => $value) { + // Simple format: ['CVE-123'] + if (is_int($key) && is_string($value)) { + $id = $value; + $apply = 'all'; + $reason = null; + } + // Simple format with reason: ['CVE-123' => 'reason'] + elseif (is_string($value)) { + $id = $key; + $apply = 'all'; + $reason = $value; + } + // Detailed format: ['CVE-123' => ['apply' => '...', 'reason' => '...']] + elseif (is_array($value)) { + $id = $key; + $apply = $value['apply'] ?? 'all'; + $reason = $value['reason'] ?? null; + + // Validate apply value + if (!in_array($apply, ['audit', 'block', 'all'], true)) { + throw new \InvalidArgumentException( + "Invalid 'apply' value for '{$id}': {$apply}. Expected 'audit', 'block', or 'all'." + ); + } + } + // Simple format with null: ['CVE-123' => null] + elseif ($value === null) { + $id = $key; + $apply = 'all'; + $reason = null; + } + else { + continue; + } + + // Store in appropriate lists based on apply scope + if ($apply === 'audit' || $apply === 'all') { + $forAudit[$id] = $reason; + } + if ($apply === 'block' || $apply === 'all') { + $forBlock[$id] = $reason; + } + } + + return [ + 'audit' => $forAudit, + 'block' => $forBlock, + ]; + } + + /** + * @param Auditor::FORMAT_* $auditFormat + */ + public static function fromConfig(Config $config, bool $audit = true, string $auditFormat = Auditor::FORMAT_SUMMARY): self + { + $auditConfig = $config->get('audit'); + + // Parse ignore lists with apply scopes + $ignoreListParsed = self::parseIgnoreWithApply($auditConfig['ignore'] ?? []); + $ignoreAbandonedParsed = self::parseIgnoreWithApply($auditConfig['ignore-abandoned'] ?? []); + $ignoreSeverityParsed = self::parseIgnoreWithApply($auditConfig['ignore-severity'] ?? []); + + return new self( + $audit, + $auditFormat, + $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL, + (bool) ($auditConfig['block-insecure'] ?? true), + (bool) ($auditConfig['block-abandoned'] ?? false), + (bool) ($auditConfig['ignore-unreachable'] ?? false), + $ignoreListParsed['audit'], + $ignoreListParsed['block'], + $ignoreSeverityParsed['audit'], + $ignoreSeverityParsed['block'], + $ignoreAbandonedParsed['audit'], + $ignoreAbandonedParsed['block'] + ); + } +} diff --git a/vendor/composer/composer/src/Composer/Advisory/Auditor.php b/vendor/composer/composer/src/Composer/Advisory/Auditor.php new file mode 100644 index 000000000..e156dbb69 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Advisory/Auditor.php @@ -0,0 +1,481 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Advisory; + +use Composer\IO\ConsoleIO; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\RepositorySet; +use Composer\Util\PackageInfo; +use InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @internal + */ +class Auditor +{ + public const FORMAT_TABLE = 'table'; + + public const FORMAT_PLAIN = 'plain'; + + public const FORMAT_JSON = 'json'; + + public const FORMAT_SUMMARY = 'summary'; + + public const FORMATS = [ + self::FORMAT_TABLE, + self::FORMAT_PLAIN, + self::FORMAT_JSON, + self::FORMAT_SUMMARY, + ]; + + public const ABANDONED_IGNORE = 'ignore'; + public const ABANDONED_REPORT = 'report'; + public const ABANDONED_FAIL = 'fail'; + + /** @internal */ + public const ABANDONEDS = [ + self::ABANDONED_IGNORE, + self::ABANDONED_REPORT, + self::ABANDONED_FAIL, + ]; + + /** Values to determine the audit result. */ + public const STATUS_OK = 0; + public const STATUS_VULNERABLE = 1; + public const STATUS_ABANDONED = 2; + + /** + * @param PackageInterface[] $packages + * @param self::FORMAT_* $format The format that will be used to output audit results. + * @param bool $warningOnly If true, outputs a warning. If false, outputs an error. + * @param array $ignoreList List of advisory IDs, remote IDs, CVE IDs or package names that reported but not listed as vulnerabilities. + * @param self::ABANDONED_* $abandoned + * @param array $ignoredSeverities List of ignored severity levels + * @param array $ignoreAbandoned List of abandoned package name that reported but not listed as vulnerabilities. + * + * @return int-mask A bitmask of STATUS_* constants or 0 on success + * @throws InvalidArgumentException If no packages are passed in + */ + public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true, array $ignoreList = [], string $abandoned = self::ABANDONED_FAIL, array $ignoredSeverities = [], bool $ignoreUnreachable = false, array $ignoreAbandoned = []): int + { + $result = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY, $ignoreUnreachable); + $allAdvisories = $result['advisories']; + $unreachableRepos = $result['unreachableRepos']; + + // we need the CVE & remote IDs set to filter ignores correctly so if we have any matches using the optimized codepath above + // and ignores are set then we need to query again the full data to make sure it can be filtered + if ($format === self::FORMAT_SUMMARY && $this->needsCompleteAdvisoryLoad($allAdvisories, $ignoreList)) { + $result = $repoSet->getMatchingSecurityAdvisories($packages, false, $ignoreUnreachable); + $allAdvisories = $result['advisories']; + $unreachableRepos = array_merge($unreachableRepos, $result['unreachableRepos']); + } + ['advisories' => $advisories, 'ignoredAdvisories' => $ignoredAdvisories] = $this->processAdvisories($allAdvisories, $ignoreList, $ignoredSeverities); + + $abandonedCount = 0; + $affectedPackagesCount = count($advisories); + if ($abandoned === self::ABANDONED_IGNORE) { + $abandonedPackages = []; + } else { + $abandonedPackages = $this->filterAbandonedPackages($packages, $ignoreAbandoned); + if ($abandoned === self::ABANDONED_FAIL) { + $abandonedCount = count($abandonedPackages); + } + } + + $auditBitmask = $this->calculateBitmask(0 < $affectedPackagesCount, 0 < $abandonedCount); + + if (self::FORMAT_JSON === $format) { + $json = ['advisories' => $advisories]; + if ($ignoredAdvisories !== []) { + $json['ignored-advisories'] = $ignoredAdvisories; + } + if ($unreachableRepos !== []) { + $json['unreachable-repositories'] = $unreachableRepos; + } + $json['abandoned'] = array_reduce($abandonedPackages, static function (array $carry, CompletePackageInterface $package): array { + $carry[$package->getPrettyName()] = $package->getReplacementPackage(); + + return $carry; + }, []); + + $io->write(JsonFile::encode($json)); + + return $auditBitmask; + } + + $errorOrWarn = $warningOnly ? 'warning' : 'error'; + if ($affectedPackagesCount > 0 || count($ignoredAdvisories) > 0) { + $passes = [ + [$ignoredAdvisories, "Found %d ignored security vulnerability advisor%s affecting %d package%s%s"], + [$advisories, "<$errorOrWarn>Found %d security vulnerability advisor%s affecting %d package%s%s"], + ]; + foreach ($passes as [$advisoriesToOutput, $message]) { + [$pkgCount, $totalAdvisoryCount] = $this->countAdvisories($advisoriesToOutput); + if ($pkgCount > 0) { + $plurality = $totalAdvisoryCount === 1 ? 'y' : 'ies'; + $pkgPlurality = $pkgCount === 1 ? '' : 's'; + $punctuation = $format === 'summary' ? '.' : ':'; + $io->writeError(sprintf($message, $totalAdvisoryCount, $plurality, $pkgCount, $pkgPlurality, $punctuation)); + $this->outputAdvisories($io, $advisoriesToOutput, $format); + } + } + + if ($format === self::FORMAT_SUMMARY) { + $io->writeError('Run "composer audit" for a full list of advisories.'); + } + } else { + $io->writeError('No security vulnerability advisories found.'); + } + + if (count($unreachableRepos) > 0) { + $io->writeError('The following repositories were unreachable:'); + foreach ($unreachableRepos as $repo) { + $io->writeError(' - ' . $repo); + } + } + + if (count($abandonedPackages) > 0 && $format !== self::FORMAT_SUMMARY) { + $this->outputAbandonedPackages($io, $abandonedPackages, $format); + } + + return $auditBitmask; + } + + /** + * @param array> $advisories + * @param array $ignoreList + * @return bool + */ + public function needsCompleteAdvisoryLoad(array $advisories, array $ignoreList): bool + { + if (\count($advisories) === 0) { + return false; + } + + // no partial advisories present + if (array_all($advisories, static function (array $pkgAdvisories) { + return array_all($pkgAdvisories, static function ($advisory) { return $advisory instanceof SecurityAdvisory; }); + })) { + return false; + } + + $ignoredIds = array_keys($ignoreList); + + return array_any($ignoredIds, static function (string $id) { + return !str_starts_with($id, 'PKSA-'); + }); + } + + /** + * @param array $packages + * @param array $ignoreAbandoned + * @return array + */ + public function filterAbandonedPackages(array $packages, array $ignoreAbandoned): array + { + $filter = null; + if (\count($ignoreAbandoned) !== 0) { + $filter = BasePackage::packageNamesToRegexp(array_keys($ignoreAbandoned)); + } + + return array_filter($packages, static function (PackageInterface $pkg) use ($filter): bool { + return $pkg instanceof CompletePackageInterface && $pkg->isAbandoned() && ($filter === null || !Preg::isMatch($filter, $pkg->getName())); + }); + } + + /** + * @phpstan-param array> $allAdvisories + * @param array $ignoreList List of advisory IDs, remote IDs, CVE IDs or package names that reported but not listed as vulnerabilities. + * @param array $ignoredSeverities List of ignored severity levels + * @phpstan-return array{advisories: array>, ignoredAdvisories: array>} + */ + public function processAdvisories(array $allAdvisories, array $ignoreList, array $ignoredSeverities): array + { + if ($ignoreList === [] && $ignoredSeverities === []) { + return ['advisories' => $allAdvisories, 'ignoredAdvisories' => []]; + } + + $advisories = []; + $ignored = []; + $ignoreReason = null; + + foreach ($allAdvisories as $package => $pkgAdvisories) { + foreach ($pkgAdvisories as $advisory) { + $isActive = true; + + if (array_key_exists($package, $ignoreList)) { + $isActive = false; + $ignoreReason = $ignoreList[$package] ?? null; + } + + if (array_key_exists($advisory->advisoryId, $ignoreList)) { + $isActive = false; + $ignoreReason = $ignoreList[$advisory->advisoryId] ?? null; + } + + if ($advisory instanceof SecurityAdvisory) { + if (is_string($advisory->severity) && array_key_exists($advisory->severity, $ignoredSeverities)) { + $isActive = false; + $ignoreReason = $ignoredSeverities[$advisory->severity] ?? $advisory->severity.' severity is ignored'; + } + + if (is_string($advisory->cve) && array_key_exists($advisory->cve, $ignoreList)) { + $isActive = false; + $ignoreReason = $ignoreList[$advisory->cve] ?? null; + } + + foreach ($advisory->sources as $source) { + if (array_key_exists($source['remoteId'], $ignoreList)) { + $isActive = false; + $ignoreReason = $ignoreList[$source['remoteId']] ?? null; + break; + } + } + } + + if ($isActive) { + $advisories[$package][] = $advisory; + continue; + } + + // Partial security advisories only used in summary mode + // and in that case we do not need to cast the object. + if ($advisory instanceof SecurityAdvisory) { + $advisory = $advisory->toIgnoredAdvisory($ignoreReason); + } + + $ignored[$package][] = $advisory; + } + } + + return ['advisories' => $advisories, 'ignoredAdvisories' => $ignored]; + } + + /** + * @param array> $advisories + * @return array{int, int} Count of affected packages and total count of advisories + */ + private function countAdvisories(array $advisories): array + { + $count = 0; + foreach ($advisories as $packageAdvisories) { + $count += count($packageAdvisories); + } + + return [count($advisories), $count]; + } + + /** + * @param array> $advisories + * @param self::FORMAT_* $format The format that will be used to output audit results. + */ + private function outputAdvisories(IOInterface $io, array $advisories, string $format): void + { + switch ($format) { + case self::FORMAT_TABLE: + if (!($io instanceof ConsoleIO)) { + throw new InvalidArgumentException('Cannot use table format with ' . get_class($io)); + } + $this->outputAdvisoriesTable($io, $advisories); + + return; + case self::FORMAT_PLAIN: + $this->outputAdvisoriesPlain($io, $advisories); + + return; + case self::FORMAT_SUMMARY: + + return; + default: + throw new InvalidArgumentException('Invalid format "'.$format.'".'); + } + } + + /** + * @param array> $advisories + */ + private function outputAdvisoriesTable(ConsoleIO $io, array $advisories): void + { + foreach ($advisories as $packageAdvisories) { + foreach ($packageAdvisories as $advisory) { + $headers = [ + 'Package', + 'Severity', + 'Advisory ID', + 'CVE', + 'Title', + 'URL', + 'Affected versions', + 'Reported at', + ]; + $row = [ + $advisory->packageName, + $this->getSeverity($advisory), + $this->getAdvisoryId($advisory), + $this->getCVE($advisory), + $advisory->title, + $this->getURL($advisory), + $advisory->affectedVersions->getPrettyString(), + $advisory->reportedAt->format(DATE_ATOM), + ]; + if ($advisory instanceof IgnoredSecurityAdvisory) { + $headers[] = 'Ignore reason'; + $row[] = $advisory->ignoreReason ?? 'None specified'; + } + $io->getTable() + ->setHorizontal() + ->setHeaders($headers) + ->addRow(ConsoleIO::sanitize($row)) + ->setColumnWidth(1, 80) + ->setColumnMaxWidth(1, 80) + ->render(); + } + } + } + + /** + * @param array> $advisories + */ + private function outputAdvisoriesPlain(IOInterface $io, array $advisories): void + { + $error = []; + $firstAdvisory = true; + foreach ($advisories as $packageAdvisories) { + foreach ($packageAdvisories as $advisory) { + if (!$firstAdvisory) { + $error[] = '--------'; + } + $error[] = "Package: ".$advisory->packageName; + $error[] = "Severity: ".$this->getSeverity($advisory); + $error[] = "Advisory ID: ".$this->getAdvisoryId($advisory); + $error[] = "CVE: ".$this->getCVE($advisory); + $error[] = "Title: ".OutputFormatter::escape($advisory->title); + $error[] = "URL: ".$this->getURL($advisory); + $error[] = "Affected versions: ".OutputFormatter::escape($advisory->affectedVersions->getPrettyString()); + $error[] = "Reported at: ".$advisory->reportedAt->format(DATE_ATOM); + if ($advisory instanceof IgnoredSecurityAdvisory) { + $error[] = "Ignore reason: ".($advisory->ignoreReason ?? 'None specified'); + } + $firstAdvisory = false; + } + } + $io->writeError($error); + } + + /** + * @param array $packages + * @param self::FORMAT_PLAIN|self::FORMAT_TABLE $format + */ + private function outputAbandonedPackages(IOInterface $io, array $packages, string $format): void + { + $io->writeError(sprintf('Found %d abandoned package%s:', count($packages), count($packages) > 1 ? 's' : '')); + + if ($format === self::FORMAT_PLAIN) { + foreach ($packages as $pkg) { + $replacement = $pkg->getReplacementPackage() !== null + ? 'Use '.$pkg->getReplacementPackage().' instead' + : 'No replacement was suggested'; + $io->writeError(sprintf( + '%s is abandoned. %s.', + $this->getPackageNameWithLink($pkg), + $replacement + )); + } + + return; + } + + if (!($io instanceof ConsoleIO)) { + throw new InvalidArgumentException('Cannot use table format with ' . get_class($io)); + } + + $table = $io->getTable() + ->setHeaders(['Abandoned Package', 'Suggested Replacement']) + ->setColumnWidth(1, 80) + ->setColumnMaxWidth(1, 80); + + foreach ($packages as $pkg) { + $replacement = $pkg->getReplacementPackage() !== null ? $pkg->getReplacementPackage() : 'none'; + $table->addRow(ConsoleIO::sanitize([$this->getPackageNameWithLink($pkg), $replacement])); + } + + $table->render(); + } + + private function getPackageNameWithLink(PackageInterface $package): string + { + $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); + + return $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); + } + + private function getSeverity(SecurityAdvisory $advisory): string + { + if ($advisory->severity === null) { + return ''; + } + + return $advisory->severity; + } + + private function getAdvisoryId(SecurityAdvisory $advisory): string + { + if (str_starts_with($advisory->advisoryId, 'PKSA-')) { + return 'advisoryId.'>'.$advisory->advisoryId.''; + } + + return $advisory->advisoryId; + } + + private function getCVE(SecurityAdvisory $advisory): string + { + if ($advisory->cve === null) { + return 'NO CVE'; + } + + return ''.$advisory->cve.''; + } + + private function getURL(SecurityAdvisory $advisory): string + { + if ($advisory->link === null) { + return ''; + } + + return 'link).'>'.OutputFormatter::escape($advisory->link).''; + } + + /** + * @return int-mask + */ + private function calculateBitmask(bool $hasVulnerablePackages, bool $hasAbandonedPackages): int + { + $bitmask = self::STATUS_OK; + + if ($hasVulnerablePackages) { + $bitmask |= self::STATUS_VULNERABLE; + } + + if ($hasAbandonedPackages) { + $bitmask |= self::STATUS_ABANDONED; + } + + return $bitmask; + } +} diff --git a/vendor/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php b/vendor/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php new file mode 100644 index 000000000..8c5aba4f0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php @@ -0,0 +1,49 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Advisory; + +use Composer\Semver\Constraint\ConstraintInterface; +use DateTimeImmutable; + +class IgnoredSecurityAdvisory extends SecurityAdvisory +{ + /** + * @var string|null + * @readonly + */ + public $ignoreReason; + + /** + * @param non-empty-array $sources + */ + public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $ignoreReason = null, ?string $severity = null) + { + parent::__construct($packageName, $advisoryId, $affectedVersions, $title, $sources, $reportedAt, $cve, $link, $severity); + + $this->ignoreReason = $ignoreReason; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + if ($this->ignoreReason === null) { + unset($data['ignoreReason']); + } + + return $data; + } +} diff --git a/vendor/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php b/vendor/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php new file mode 100644 index 000000000..f9b02cdbc --- /dev/null +++ b/vendor/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php @@ -0,0 +1,84 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Advisory; + +use Composer\Pcre\Preg; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\VersionParser; +use JsonSerializable; + +class PartialSecurityAdvisory implements JsonSerializable +{ + /** + * @var string + * @readonly + */ + public $advisoryId; + + /** + * @var string + * @readonly + */ + public $packageName; + + /** + * @var ConstraintInterface + * @readonly + */ + public $affectedVersions; + + /** + * @param array $data + * @return SecurityAdvisory|PartialSecurityAdvisory + */ + public static function create(string $packageName, array $data, VersionParser $parser): self + { + try { + $constraint = $parser->parseConstraints($data['affectedVersions']); + } catch (\UnexpectedValueException $e) { + // try to keep only the essential part of the constraint to turn invalid ones like <=3.20-test2 into <=3.20 which is better than nothing + try { + $affectedVersion = Preg::replace('{(^[>=<^~]*[\d.]+).*}', '$1', $data['affectedVersions']); + $constraint = $parser->parseConstraints($affectedVersion); + } catch (\UnexpectedValueException $e) { + $constraint = new Constraint('==', '0.0.0-invalid-version'); + } + } + + if (isset($data['title'], $data['sources'], $data['reportedAt'])) { + return new SecurityAdvisory($packageName, $data['advisoryId'], $constraint, $data['title'], $data['sources'], new \DateTimeImmutable($data['reportedAt'], new \DateTimeZone('UTC')), $data['cve'] ?? null, $data['link'] ?? null, $data['severity'] ?? null); + } + + return new self($packageName, $data['advisoryId'], $constraint); + } + + public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions) + { + $this->advisoryId = $advisoryId; + $this->packageName = $packageName; + $this->affectedVersions = $affectedVersions; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = (array) $this; + $data['affectedVersions'] = $data['affectedVersions']->getPrettyString(); + + return $data; + } +} diff --git a/vendor/composer/composer/src/Composer/Advisory/SecurityAdvisory.php b/vendor/composer/composer/src/Composer/Advisory/SecurityAdvisory.php new file mode 100644 index 000000000..a3d58b462 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Advisory/SecurityAdvisory.php @@ -0,0 +1,101 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Advisory; + +use Composer\Semver\Constraint\ConstraintInterface; +use DateTimeImmutable; + +class SecurityAdvisory extends PartialSecurityAdvisory +{ + /** + * @var string + * @readonly + */ + public $title; + + /** + * @var string|null + * @readonly + */ + public $cve; + + /** + * @var string|null + * @readonly + */ + public $link; + + /** + * @var DateTimeImmutable + * @readonly + */ + public $reportedAt; + + /** + * @var non-empty-array + * @readonly + */ + public $sources; + + /** + * @var string|null + * @readonly + */ + public $severity; + + /** + * @param non-empty-array $sources + */ + public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $severity = null) + { + parent::__construct($packageName, $advisoryId, $affectedVersions); + + $this->title = $title; + $this->sources = $sources; + $this->reportedAt = $reportedAt; + $this->cve = $cve; + $this->link = $link; + $this->severity = $severity; + } + + /** + * @internal + */ + public function toIgnoredAdvisory(?string $ignoreReason): IgnoredSecurityAdvisory + { + return new IgnoredSecurityAdvisory( + $this->packageName, + $this->advisoryId, + $this->affectedVersions, + $this->title, + $this->sources, + $this->reportedAt, + $this->cve, + $this->link, + $ignoreReason, + $this->severity + ); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + $data['reportedAt'] = $data['reportedAt']->format(DATE_RFC3339); + + return $data; + } +} diff --git a/vendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php b/vendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php new file mode 100644 index 000000000..5e922eec7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php @@ -0,0 +1,1435 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +use Composer\ClassMapGenerator\ClassMap; +use Composer\ClassMapGenerator\ClassMapGenerator; +use Composer\Config; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\Installer\InstallationManager; +use Composer\IO\IOInterface; +use Composer\IO\NullIO; +use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; +use Composer\Package\RootPackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Semver\Constraint\Bound; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Script\ScriptEvents; +use Composer\Util\PackageSorter; +use Composer\Json\JsonFile; +use Composer\Package\Locker; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Igor Wiedler + * @author Jordi Boggiano + */ +class AutoloadGenerator +{ + /** + * @var EventDispatcher + */ + private $eventDispatcher; + + /** + * @var IOInterface + */ + private $io; + + /** + * @var ?bool + */ + private $devMode = null; + + /** + * @var bool + */ + private $classMapAuthoritative = false; + + /** + * @var bool + */ + private $apcu = false; + + /** + * @var string|null + */ + private $apcuPrefix; + + /** + * @var bool + */ + private $dryRun = false; + + /** + * @var bool + */ + private $runScripts = false; + + /** + * @var PlatformRequirementFilterInterface + */ + private $platformRequirementFilter; + + public function __construct(EventDispatcher $eventDispatcher, ?IOInterface $io = null) + { + $this->eventDispatcher = $eventDispatcher; + $this->io = $io ?? new NullIO(); + + $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); + } + + /** + * @return void + */ + public function setDevMode(bool $devMode = true) + { + $this->devMode = $devMode; + } + + /** + * Whether generated autoloader considers the class map authoritative. + * + * @return void + */ + public function setClassMapAuthoritative(bool $classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Whether generated autoloader considers APCu caching. + * + * @return void + */ + public function setApcu(bool $apcu, ?string $apcuPrefix = null) + { + $this->apcu = $apcu; + $this->apcuPrefix = $apcuPrefix; + } + + /** + * Whether to run scripts or not + * + * @return void + */ + public function setRunScripts(bool $runScripts = true) + { + $this->runScripts = $runScripts; + } + + /** + * Whether to run in drymode or not + */ + public function setDryRun(bool $dryRun = true): void + { + $this->dryRun = $dryRun; + } + + /** + * Whether platform requirements should be ignored. + * + * If this is set to true, the platform check file will not be generated + * If this is set to false, the platform check file will be generated with all requirements + * If this is set to string[], those packages will be ignored from the platform check file + * + * @param bool|string[] $ignorePlatformReqs + * @return void + * + * @deprecated use setPlatformRequirementFilter instead + */ + public function setIgnorePlatformRequirements($ignorePlatformReqs) + { + trigger_error('AutoloadGenerator::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED); + + $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); + } + + /** + * @return void + */ + public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter) + { + $this->platformRequirementFilter = $platformRequirementFilter; + } + + /** + * @return ClassMap + * @throws \Seld\JsonLint\ParsingException + * @throws \RuntimeException + */ + public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, string $targetDir, bool $scanPsrPackages = false, ?string $suffix = null, ?Locker $locker = null, bool $strictAmbiguous = false) + { + if ($this->classMapAuthoritative) { + // Force scanPsrPackages when classmap is authoritative + $scanPsrPackages = true; + } + + // auto-set devMode based on whether dev dependencies are installed or not + if (null === $this->devMode) { + // we assume no-dev mode if no vendor dir is present or it is too old to contain dev information + $this->devMode = false; + + $installedJson = new JsonFile($config->get('vendor-dir').'/composer/installed.json'); + if ($installedJson->exists()) { + $installedJson = $installedJson->read(); + if (isset($installedJson['dev'])) { + $this->devMode = $installedJson['dev']; + } + } + } + + if ($this->runScripts) { + // set COMPOSER_DEV_MODE in case not set yet so it is available in the dump-autoload event listeners + if (!isset($_SERVER['COMPOSER_DEV_MODE'])) { + Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0'); + } + + $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, [], [ + 'optimize' => $scanPsrPackages, + ]); + } + + $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']); + $classMapGenerator->avoidDuplicateScans(); + + $filesystem = new Filesystem(); + $filesystem->ensureDirectoryExists($config->get('vendor-dir')); + // Do not remove double realpath() calls. + // Fixes failing Windows realpath() implementation. + // See https://bugs.php.net/bug.php?id=72738 + $basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd()))); + $vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir')))); + $useGlobalIncludePath = $config->get('use-include-path'); + $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; + $targetDir = $vendorPath.'/'.$targetDir; + $filesystem->ensureDirectoryExists($targetDir); + + $vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true); + $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true); + + $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true); + $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode); + + $namespacesFile = <<getDevPackageNames(); + $packageMap = $this->buildPackageMap($installationManager, $rootPackage, $localRepo->getCanonicalPackages()); + if ($this->devMode) { + // if dev mode is enabled, then we do not filter any dev packages out so disable this entirely + $filteredDevPackages = false; + } else { + // if the list of dev package names is available we use that straight, otherwise pass true which means use legacy algo to figure them out + $filteredDevPackages = $devPackageNames ?: true; + } + $autoloads = $this->parseAutoloads($packageMap, $rootPackage, $filteredDevPackages); + + // Process the 'psr-0' base directories. + foreach ($autoloads['psr-0'] as $namespace => $paths) { + $exportedPaths = []; + foreach ($paths as $path) { + $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); + } + $exportedPrefix = var_export($namespace, true); + $namespacesFile .= " $exportedPrefix => "; + $namespacesFile .= "array(".implode(', ', $exportedPaths)."),\n"; + } + $namespacesFile .= ");\n"; + + // Process the 'psr-4' base directories. + foreach ($autoloads['psr-4'] as $namespace => $paths) { + $exportedPaths = []; + foreach ($paths as $path) { + $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); + } + $exportedPrefix = var_export($namespace, true); + $psr4File .= " $exportedPrefix => "; + $psr4File .= "array(".implode(', ', $exportedPaths)."),\n"; + } + $psr4File .= ");\n"; + + // add custom psr-0 autoloading if the root package has a target dir + $targetDirLoader = null; + $mainAutoload = $rootPackage->getAutoload(); + if ($rootPackage->getTargetDir() && !empty($mainAutoload['psr-0'])) { + $levels = substr_count($filesystem->normalizePath($rootPackage->getTargetDir()), '/') + 1; + $prefixes = implode(', ', array_map(static function ($prefix): string { + return var_export($prefix, true); + }, array_keys($mainAutoload['psr-0']))); + $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, $basePath, true); + + $targetDirLoader = <<scanPaths($dir, $this->buildExclusionRegex($dir, $excluded)); + } + + if ($scanPsrPackages) { + $namespacesToScan = []; + + // Scan the PSR-0/4 directories for class files, and add them to the class map + foreach (['psr-4', 'psr-0'] as $psrType) { + foreach ($autoloads[$psrType] as $namespace => $paths) { + $namespacesToScan[$namespace][] = ['paths' => $paths, 'type' => $psrType]; + } + } + + krsort($namespacesToScan); + + foreach ($namespacesToScan as $namespace => $groups) { + foreach ($groups as $group) { + foreach ($group['paths'] as $dir) { + $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir); + if (!is_dir($dir)) { + continue; + } + + // if the vendor dir is contained within a psr-0/psr-4 dir being scanned we exclude it + if (str_contains($vendorPath, $dir.'/')) { + $exclusionRegex = $this->buildExclusionRegex($dir, array_merge($excluded, [$vendorPath.'/'])); + } else { + $exclusionRegex = $this->buildExclusionRegex($dir, $excluded); + } + + $classMapGenerator->scanPaths($dir, $exclusionRegex, $group['type'], $namespace); + } + } + } + } + + $classMap = $classMapGenerator->getClassMap(); + if ($strictAmbiguous) { + $ambiguousClasses = $classMap->getAmbiguousClasses(false); + } else { + $ambiguousClasses = $classMap->getAmbiguousClasses(); + } + foreach ($ambiguousClasses as $className => $ambiguousPaths) { + if (count($ambiguousPaths) > 1) { + $this->io->writeError( + 'Warning: Ambiguous class resolution, "'.$className.'"'. + ' was found '. (count($ambiguousPaths) + 1) .'x: in "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.' + ); + } else { + $this->io->writeError( + 'Warning: Ambiguous class resolution, "'.$className.'"'. + ' was found in both "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.' + ); + } + } + if (\count($ambiguousClasses) > 0) { + $this->io->writeError('To resolve ambiguity in classes not under your control you can ignore them by path using exclude-from-classmap'); + } + + // output PSR violations which are not coming from the vendor dir + $classMap->clearPsrViolationsByPath($vendorPath); + foreach ($classMap->getPsrViolations() as $msg) { + $this->io->writeError("$msg"); + } + + $classMap->addClass('Composer\InstalledVersions', $vendorPath . '/composer/InstalledVersions.php'); + $classMap->sort(); + + $classmapFile = <<getMap() as $className => $path) { + $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n"; + $classmapFile .= ' '.var_export($className, true).' => '.$pathCode; + } + $classmapFile .= ");\n"; + + if ('' === $suffix) { + $suffix = null; + } + if (null === $suffix) { + $suffix = $config->get('autoloader-suffix'); + + // carry over existing autoload.php's suffix if possible and none is configured + if (null === $suffix && Filesystem::isReadable($vendorPath.'/autoload.php')) { + $content = (string) file_get_contents($vendorPath.'/autoload.php'); + if (Preg::isMatch('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) { + $suffix = $match[1]; + } + } + + if (null === $suffix) { + $suffix = $locker !== null && $locker->isLocked() ? $locker->getLockData()['content-hash'] : bin2hex(random_bytes(16)); + } + } + + if ($this->dryRun) { + return $classMap; + } + + $filesystem->filePutContentsIfModified($targetDir.'/autoload_namespaces.php', $namespacesFile); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_psr4.php', $psr4File); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_classmap.php', $classmapFile); + $includePathFilePath = $targetDir.'/include_paths.php'; + if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) { + $filesystem->filePutContentsIfModified($includePathFilePath, $includePathFileContents); + } elseif (file_exists($includePathFilePath)) { + unlink($includePathFilePath); + } + $includeFilesFilePath = $targetDir.'/autoload_files.php'; + if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) { + $filesystem->filePutContentsIfModified($includeFilesFilePath, $includeFilesFileContents); + } elseif (file_exists($includeFilesFilePath)) { + unlink($includeFilesFilePath); + } + $filesystem->filePutContentsIfModified($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath)); + $checkPlatform = $config->get('platform-check') !== false && !($this->platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter); + $platformCheckContent = null; + if ($checkPlatform) { + $platformCheckContent = $this->getPlatformCheck($packageMap, $config->get('platform-check'), $devPackageNames); + if (null === $platformCheckContent) { + $checkPlatform = false; + } + } + if ($checkPlatform) { + $filesystem->filePutContentsIfModified($targetDir.'/platform_check.php', $platformCheckContent); + } elseif (file_exists($targetDir.'/platform_check.php')) { + unlink($targetDir.'/platform_check.php'); + } + $filesystem->filePutContentsIfModified($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $checkPlatform)); + + $filesystem->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php'); + $filesystem->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE'); + + if ($this->runScripts) { + $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, [], [ + 'optimize' => $scanPsrPackages, + ]); + } + + return $classMap; + } + + /** + * @param array $excluded + * @return non-empty-string|null + */ + private function buildExclusionRegex(string $dir, array $excluded): ?string + { + if ([] === $excluded) { + return null; + } + + // filter excluded patterns here to only use those matching $dir + // exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work + // if $dir does not exist, it should anyway not find anything there so no trouble + if (file_exists($dir)) { + // transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other + $dirMatch = preg_quote(strtr(realpath($dir), '\\', '/')); + // also match against the non-realpath version for symlinks + $fs = new Filesystem(); + $absDir = $fs->isAbsolutePath($dir) ? $dir : realpath(Platform::getCwd()).'/'.$dir; + $dirMatchNormalized = preg_quote(strtr($fs->normalizePath($absDir), '\\', '/')); + $isSymlink = $dirMatch !== $dirMatchNormalized; + + foreach ($excluded as $index => $pattern) { + // extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character + $pattern = Preg::replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern); + // if the pattern is not a subset or superset of $dir, it is unrelated and we skip it + if ( + (!str_starts_with($pattern, $dirMatch) && !str_starts_with($dirMatch, $pattern)) + && ( + // only check dirMatchNormalized if it is actually different from dirMatch otherwise there is no point + !$isSymlink + || (!str_starts_with($pattern, $dirMatchNormalized) && !str_starts_with($dirMatchNormalized, $pattern)) + ) + ) { + unset($excluded[$index]); + } + } + } + + return \count($excluded) > 0 ? '{(' . implode('|', $excluded) . ')}' : null; + } + + /** + * @param PackageInterface[] $packages + * @return non-empty-array + */ + public function buildPackageMap(InstallationManager $installationManager, PackageInterface $rootPackage, array $packages) + { + // build package => install path map + $packageMap = [[$rootPackage, '']]; + + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + continue; + } + $this->validatePackage($package); + $packageMap[] = [ + $package, + $installationManager->getInstallPath($package), + ]; + } + + return $packageMap; + } + + /** + * @return void + * @throws \InvalidArgumentException Throws an exception, if the package has illegal settings. + */ + protected function validatePackage(PackageInterface $package) + { + $autoload = $package->getAutoload(); + if (!empty($autoload['psr-4']) && null !== $package->getTargetDir()) { + $name = $package->getName(); + $package->getTargetDir(); + throw new \InvalidArgumentException("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '$name'."); + } + if (!empty($autoload['psr-4'])) { + foreach ($autoload['psr-4'] as $namespace => $dirs) { + if ($namespace !== '' && '\\' !== substr($namespace, -1)) { + throw new \InvalidArgumentException("psr-4 namespaces must end with a namespace separator, '$namespace' does not, use '$namespace\\'."); + } + } + } + } + + /** + * Compiles an ordered list of namespace => path mappings + * + * @param non-empty-array $packageMap array of array(package, installDir-relative-to-composer.json or null for metapackages) + * @param RootPackageInterface $rootPackage root package instance + * @param bool|string[] $filteredDevPackages If an array, the list of packages that must be removed. If bool, whether to filter out require-dev packages + * @return array + * @phpstan-return array{ + * 'psr-0': array>, + * 'psr-4': array>, + * 'classmap': array, + * 'files': array, + * 'exclude-from-classmap': array, + * } + */ + public function parseAutoloads(array $packageMap, PackageInterface $rootPackage, $filteredDevPackages = false) + { + $rootPackageMap = array_shift($packageMap); + if (is_array($filteredDevPackages)) { + $packageMap = array_filter($packageMap, static function ($item) use ($filteredDevPackages): bool { + return !in_array($item[0]->getName(), $filteredDevPackages, true); + }); + } elseif ($filteredDevPackages) { + $packageMap = $this->filterPackageMap($packageMap, $rootPackage); + } + $sortedPackageMap = $this->sortPackageMap($packageMap); + $sortedPackageMap[] = $rootPackageMap; + $reverseSortedMap = array_reverse($sortedPackageMap); + + // reverse-sorted means root first, then dependents, then their dependents, etc. + // which makes sense to allow root to override classmap or psr-0/4 entries with higher precedence rules + $psr0 = $this->parseAutoloadsType($reverseSortedMap, 'psr-0', $rootPackage); + $psr4 = $this->parseAutoloadsType($reverseSortedMap, 'psr-4', $rootPackage); + $classmap = $this->parseAutoloadsType($reverseSortedMap, 'classmap', $rootPackage); + + // sorted (i.e. dependents first) for files to ensure that dependencies are loaded/available once a file is included + $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $rootPackage); + // using sorted here but it does not really matter as all are excluded equally + $exclude = $this->parseAutoloadsType($sortedPackageMap, 'exclude-from-classmap', $rootPackage); + + krsort($psr0); + krsort($psr4); + + return [ + 'psr-0' => $psr0, + 'psr-4' => $psr4, + 'classmap' => $classmap, + 'files' => $files, + 'exclude-from-classmap' => $exclude, + ]; + } + + /** + * Registers an autoloader based on an autoload-map returned by parseAutoloads + * + * @param array $autoloads see parseAutoloads return value + * @return ClassLoader + */ + public function createLoader(array $autoloads, ?string $vendorDir = null) + { + $loader = new ClassLoader($vendorDir); + + if (isset($autoloads['psr-0'])) { + foreach ($autoloads['psr-0'] as $namespace => $path) { + $loader->add($namespace, $path); + } + } + + if (isset($autoloads['psr-4'])) { + foreach ($autoloads['psr-4'] as $namespace => $path) { + $loader->addPsr4($namespace, $path); + } + } + + if (isset($autoloads['classmap'])) { + $excluded = []; + if (!empty($autoloads['exclude-from-classmap'])) { + $excluded = $autoloads['exclude-from-classmap']; + } + + $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']); + $classMapGenerator->avoidDuplicateScans(); + + foreach ($autoloads['classmap'] as $dir) { + try { + $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded)); + } catch (\RuntimeException $e) { + $this->io->writeError(''.$e->getMessage().''); + } + } + + $loader->addClassMap($classMapGenerator->getClassMap()->getMap()); + } + + return $loader; + } + + /** + * @param array $packageMap + * @return ?string + */ + protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode) + { + $includePaths = []; + + foreach ($packageMap as $item) { + [$package, $installPath] = $item; + + // packages that are not installed cannot autoload anything + if (null === $installPath) { + continue; + } + + if (null !== $package->getTargetDir() && strlen($package->getTargetDir()) > 0) { + $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir())); + } + + foreach ($package->getIncludePaths() as $includePath) { + $includePath = trim($includePath, '/'); + $includePaths[] = $installPath === '' ? $includePath : $installPath.'/'.$includePath; + } + } + + if (\count($includePaths) === 0) { + return null; + } + + $includePathsCode = ''; + foreach ($includePaths as $path) { + $includePathsCode .= " " . $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n"; + } + + return << $files + * @return ?string + */ + protected function getIncludeFilesFile(array $files, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode) + { + // Get the path to each file, and make sure these paths are unique. + $files = array_map( + function (string $functionFile) use ($filesystem, $basePath, $vendorPath): string { + return $this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile); + }, + $files + ); + $uniqueFiles = array_unique($files); + if (count($uniqueFiles) < count($files)) { + $this->io->writeError('The following "files" autoload rules are included multiple times, this may cause issues and should be resolved:'); + foreach (array_unique(array_diff_assoc($files, $uniqueFiles)) as $duplicateFile) { + $this->io->writeError(' - '.$duplicateFile.''); + } + } + unset($uniqueFiles); + + $filesCode = ''; + + foreach ($files as $fileIdentifier => $functionFile) { + $filesCode .= ' ' . var_export($fileIdentifier, true) . ' => ' . $functionFile . ",\n"; + } + + if (!$filesCode) { + return null; + } + + return <<isAbsolutePath($path)) { + $path = $basePath . '/' . $path; + } + $path = $filesystem->normalizePath($path); + + $baseDir = ''; + if (strpos($path.'/', $vendorPath.'/') === 0) { + $path = (string) substr($path, strlen($vendorPath)); + $baseDir = '$vendorDir . '; + } else { + $path = $filesystem->normalizePath($filesystem->findShortestPath($basePath, $path, true)); + if (!$filesystem->isAbsolutePath($path)) { + $baseDir = '$baseDir . '; + $path = '/' . $path; + } + } + + if (Preg::isMatch('{\.phar([\\\\/]|$)}', $path)) { + $baseDir = "'phar://' . " . $baseDir; + } + + return $baseDir . var_export($path, true); + } + + /** + * @param array $packageMap + * @param bool|'php-only' $checkPlatform + * @param string[] $devPackageNames + * @return ?string + */ + protected function getPlatformCheck(array $packageMap, $checkPlatform, array $devPackageNames) + { + $lowestPhpVersion = Bound::zero(); + $requiredPhp64bit = false; + $requiredExtensions = []; + $extensionProviders = []; + + foreach ($packageMap as $item) { + $package = $item[0]; + foreach (array_merge($package->getReplaces(), $package->getProvides()) as $link) { + if (Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) { + $extensionProviders[$match[1]][] = $link->getConstraint(); + } + } + } + + foreach ($packageMap as $item) { + $package = $item[0]; + // skip dev dependencies platform requirements as platform-check really should only be a production safeguard + if (in_array($package->getName(), $devPackageNames, true)) { + continue; + } + + foreach ($package->getRequires() as $link) { + if ($this->platformRequirementFilter->isIgnored($link->getTarget())) { + continue; + } + + if (in_array($link->getTarget(), ['php', 'php-64bit'], true)) { + $constraint = $link->getConstraint(); + if ($constraint->getLowerBound()->compareTo($lowestPhpVersion, '>')) { + $lowestPhpVersion = $constraint->getLowerBound(); + } + } + + if ('php-64bit' === $link->getTarget()) { + $requiredPhp64bit = true; + } + + if ($checkPlatform === true && Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) { + // skip extension checks if they have a valid provider/replacer + if (isset($extensionProviders[$match[1]])) { + foreach ($extensionProviders[$match[1]] as $provided) { + if ($provided->matches($link->getConstraint())) { + continue 2; + } + } + } + + if ($match[1] === 'zend-opcache') { + $match[1] = 'zend opcache'; + } + + $extension = var_export($match[1], true); + if ($match[1] === 'pcntl' || $match[1] === 'readline') { + $requiredExtensions[$extension] = "PHP_SAPI !== 'cli' || extension_loaded($extension) || \$missingExtensions[] = $extension;\n"; + } else { + $requiredExtensions[$extension] = "extension_loaded($extension) || \$missingExtensions[] = $extension;\n"; + } + } + } + } + + ksort($requiredExtensions); + + $formatToPhpVersionId = static function (Bound $bound): int { + if ($bound->isZero()) { + return 0; + } + + if ($bound->isPositiveInfinity()) { + return 99999; + } + + $version = str_replace('-', '.', $bound->getVersion()); + $chunks = array_map('intval', explode('.', $version)); + + return $chunks[0] * 10000 + $chunks[1] * 100 + $chunks[2]; + }; + + $formatToHumanReadable = static function (Bound $bound) { + if ($bound->isZero()) { + return 0; + } + + if ($bound->isPositiveInfinity()) { + return 99999; + } + + $version = str_replace('-', '.', $bound->getVersion()); + $chunks = explode('.', $version); + $chunks = array_slice($chunks, 0, 3); + + return implode('.', $chunks); + }; + + $requiredPhp = ''; + $requiredPhpError = ''; + if (!$lowestPhpVersion->isZero()) { + $operator = $lowestPhpVersion->isInclusive() ? '>=' : '>'; + $requiredPhp = 'PHP_VERSION_ID '.$operator.' '.$formatToPhpVersionId($lowestPhpVersion); + $requiredPhpError = '"'.$operator.' '.$formatToHumanReadable($lowestPhpVersion).'"'; + } + + if ($requiredPhp) { + $requiredPhp = <<classMapAuthoritative) { + $file .= <<<'CLASSMAPAUTHORITATIVE' + $loader->setClassMapAuthoritative(true); + +CLASSMAPAUTHORITATIVE; + } + + if ($this->apcu) { + $apcuPrefix = var_export(($this->apcuPrefix !== null ? $this->apcuPrefix : bin2hex(random_bytes(10))), true); + $file .= <<setApcuPrefix($apcuPrefix); + +APCU; + } + + if ($useGlobalIncludePath) { + $file .= <<<'INCLUDEPATH' + $loader->setUseIncludePath(true); + +INCLUDEPATH; + } + + if ($targetDirLoader) { + $file .= <<register($prependAutoloader); + + +REGISTER_LOADER; + + if ($useIncludeFiles) { + $file .= << \$file) { + \$requireFile(\$fileIdentifier, \$file); + } + + +INCLUDE_FILES; + } + + $file .= << $path) { + $loader->set($namespace, $path); + } + + $map = require $targetDir . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + /** + * @var string $vendorDir + * @var string $baseDir + */ + $classMap = require $targetDir . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $filesystem = new Filesystem(); + + $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; + $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; + $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; + $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; + + $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1); + $absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1); + $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1); + $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1); + + $initializer = ''; + $prefix = "\0Composer\Autoload\ClassLoader\0"; + $prefixLen = strlen($prefix); + if (file_exists($targetDir . '/autoload_files.php')) { + $maps = ['files' => require $targetDir . '/autoload_files.php']; + } else { + $maps = []; + } + + foreach ((array) $loader as $prop => $value) { + if (!is_array($value) || \count($value) === 0 || !str_starts_with($prop, $prefix)) { + continue; + } + $maps[substr($prop, $prefixLen)] = $value; + } + + foreach ($maps as $prop => $value) { + $value = strtr( + var_export($value, true), + [ + $absoluteVendorPathCode => $vendorPathCode, + $absoluteVendorPharPathCode => $vendorPharPathCode, + $absoluteAppBaseDirCode => $appBaseDirCode, + $absoluteAppBaseDirPharCode => $appBaseDirPharCode, + ] + ); + $value = ltrim(Preg::replace('/^ */m', ' $0$0', $value)); + $value = Preg::replace('/ +$/m', '', $value); + + $file .= sprintf(" public static $%s = %s;\n\n", $prop, $value); + if ('files' !== $prop) { + $initializer .= " \$loader->$prop = ComposerStaticInit$suffix::\$$prop;\n"; + } + } + + return $file . << $packageMap + * @param string $type one of: 'psr-0'|'psr-4'|'classmap'|'files' + * @return array|array>|array + */ + protected function parseAutoloadsType(array $packageMap, string $type, RootPackageInterface $rootPackage) + { + $autoloads = []; + + foreach ($packageMap as $item) { + [$package, $installPath] = $item; + + // packages that are not installed cannot autoload anything + if (null === $installPath) { + continue; + } + + $autoload = $package->getAutoload(); + if ($this->devMode && $package === $rootPackage) { + $autoload = array_merge_recursive($autoload, $package->getDevAutoload()); + } + + // skip misconfigured packages + if (!isset($autoload[$type]) || !is_array($autoload[$type])) { + continue; + } + if (null !== $package->getTargetDir() && $package !== $rootPackage) { + $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir())); + } + + foreach ($autoload[$type] as $namespace => $paths) { + if (in_array($type, ['psr-4', 'psr-0'], true)) { + // normalize namespaces to ensure "\" becomes "" and others do not have leading separators as they are not needed + $namespace = ltrim($namespace, '\\'); + } + foreach ((array) $paths as $path) { + if (($type === 'files' || $type === 'classmap' || $type === 'exclude-from-classmap') && $package->getTargetDir() && !Filesystem::isReadable($installPath.'/'.$path)) { + // remove target-dir from file paths of the root package + if ($package === $rootPackage) { + $targetDir = str_replace('\\', '[\\\\/]', preg_quote(str_replace(['/', '\\'], '', $package->getTargetDir()))); + $path = ltrim(Preg::replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/'); + } else { + // add target-dir from file paths that don't have it + $path = $package->getTargetDir() . '/' . $path; + } + } + + if ($type === 'exclude-from-classmap') { + // first escape user input + $path = Preg::replace('{/+}', '/', preg_quote(trim(strtr($path, '\\', '/'), '/'))); + + // add support for wildcards * and ** + $path = strtr($path, ['\\*\\*' => '.+?', '\\*' => '[^/]+?']); + + // add support for up-level relative paths + $updir = null; + $path = Preg::replaceCallback( + '{^((?:(?:\\\\\\.){1,2}+/)+)}', + static function ($matches) use (&$updir): string { + // undo preg_quote for the matched string + $updir = str_replace('\\.', '.', $matches[1]); + + return ''; + }, + $path + ); + if (empty($installPath)) { + $installPath = strtr(Platform::getCwd(), '\\', '/'); + } + + $resolvedPath = realpath($installPath . '/' . $updir); + if (false === $resolvedPath) { + continue; + } + $autoloads[] = preg_quote(strtr($resolvedPath, '\\', '/')) . '/' . $path . '($|/)'; + continue; + } + + $relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath.'/'.$path; + + if ($type === 'files') { + $autoloads[$this->getFileIdentifier($package, $path)] = $relativePath; + continue; + } + if ($type === 'classmap') { + $autoloads[] = $relativePath; + continue; + } + + $autoloads[$namespace][] = $relativePath; + } + } + } + + return $autoloads; + } + + /** + * @return string + */ + protected function getFileIdentifier(PackageInterface $package, string $path) + { + // TODO composer v3 change this to sha1 or xxh3? Possibly not worth the potential breakage though + return hash('md5', $package->getName() . ':' . $path); + } + + /** + * Filters out dev-dependencies + * + * @param array $packageMap + * @return array + */ + protected function filterPackageMap(array $packageMap, RootPackageInterface $rootPackage) + { + $packages = []; + $include = []; + $replacedBy = []; + + foreach ($packageMap as $item) { + $package = $item[0]; + $name = $package->getName(); + $packages[$name] = $package; + foreach ($package->getReplaces() as $replace) { + $replacedBy[$replace->getTarget()] = $name; + } + } + + $add = static function (PackageInterface $package) use (&$add, $packages, &$include, $replacedBy): void { + foreach ($package->getRequires() as $link) { + $target = $link->getTarget(); + if (isset($replacedBy[$target])) { + $target = $replacedBy[$target]; + } + if (!isset($include[$target])) { + $include[$target] = true; + if (isset($packages[$target])) { + $add($packages[$target]); + } + } + } + }; + $add($rootPackage); + + return array_filter( + $packageMap, + static function ($item) use ($include): bool { + $package = $item[0]; + foreach ($package->getNames() as $name) { + if (isset($include[$name])) { + return true; + } + } + + return false; + } + ); + } + + /** + * Sorts packages by dependency weight + * + * Packages of equal weight are sorted alphabetically + * + * @param array $packageMap + * @return array + */ + protected function sortPackageMap(array $packageMap) + { + $packages = []; + $paths = []; + + foreach ($packageMap as $item) { + [$package, $path] = $item; + $name = $package->getName(); + $packages[$name] = $package; + $paths[$name] = $path; + } + + $sortedPackages = PackageSorter::sortPackages($packages); + + $sortedPackageMap = []; + + foreach ($sortedPackages as $package) { + $name = $package->getName(); + $sortedPackageMap[] = [$packages[$name], $paths[$name]]; + } + + return $sortedPackageMap; + } +} + +function composerRequire(string $fileIdentifier, string $file): void +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } +} diff --git a/vendor/composer/composer/src/Composer/Autoload/ClassLoader.php b/vendor/composer/composer/src/Composer/Autoload/ClassLoader.php new file mode 100644 index 000000000..7824d8f7e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Autoload/ClassLoader.php @@ -0,0 +1,579 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php b/vendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php new file mode 100644 index 000000000..316757fe9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php @@ -0,0 +1,98 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * This file is copied from the Symfony package. + * + * (c) Fabien Potencier + */ + +namespace Composer\Autoload; + +use Composer\ClassMapGenerator\FileList; +use Composer\IO\IOInterface; + +/** + * ClassMapGenerator + * + * @author Gyula Sallai + * @author Jordi Boggiano + * + * @deprecated Since Composer 2.4.0 use the composer/class-map-generator package instead + */ +class ClassMapGenerator +{ + /** + * Generate a class map file + * + * @param \Traversable|array $dirs Directories or a single path to search in + * @param string $file The name of the class map file + */ + public static function dump(iterable $dirs, string $file): void + { + $maps = []; + + foreach ($dirs as $dir) { + $maps = array_merge($maps, static::createMap($dir)); + } + + file_put_contents($file, sprintf('|string|array<\SplFileInfo> $path The path to search in or an iterator + * @param non-empty-string|null $excluded Regex that matches file paths to be excluded from the classmap + * @param ?IOInterface $io IO object + * @param null|string $namespace Optional namespace prefix to filter by + * @param null|'psr-0'|'psr-4'|'classmap' $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules + * @param array $scannedFiles + * @return array A class map array + * @throws \RuntimeException When the path is neither an existing file nor directory + */ + public static function createMap($path, ?string $excluded = null, ?IOInterface $io = null, ?string $namespace = null, ?string $autoloadType = null, array &$scannedFiles = []): array + { + $generator = new \Composer\ClassMapGenerator\ClassMapGenerator(['php', 'inc', 'hh']); + $fileList = new FileList(); + $fileList->files = $scannedFiles; + $generator->avoidDuplicateScans($fileList); + + $generator->scanPaths($path, $excluded, $autoloadType ?? 'classmap', $namespace); + + $classMap = $generator->getClassMap(); + + $scannedFiles = $fileList->files; + + if ($io !== null) { + foreach ($classMap->getPsrViolations() as $msg) { + $io->writeError("$msg"); + } + + foreach ($classMap->getAmbiguousClasses() as $class => $paths) { + if (count($paths) > 1) { + $io->writeError( + 'Warning: Ambiguous class resolution, "'.$class.'"'. + ' was found '. (count($paths) + 1) .'x: in "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.' + ); + } else { + $io->writeError( + 'Warning: Ambiguous class resolution, "'.$class.'"'. + ' was found in both "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.' + ); + } + } + } + + return $classMap->getMap(); + } +} diff --git a/vendor/composer/composer/src/Composer/Cache.php b/vendor/composer/composer/src/Composer/Cache.php new file mode 100644 index 000000000..38071dc70 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Cache.php @@ -0,0 +1,390 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use Symfony\Component\Finder\Finder; + +/** + * Reads/writes to a filesystem cache + * + * @author Jordi Boggiano + */ +class Cache +{ + /** @var bool|null */ + private static $cacheCollected = null; + /** @var IOInterface */ + private $io; + /** @var string */ + private $root; + /** @var ?bool */ + private $enabled = null; + /** @var string */ + private $allowlist; + /** @var Filesystem */ + private $filesystem; + /** @var bool */ + private $readOnly; + + /** + * @param string $cacheDir location of the cache + * @param string $allowlist List of characters that are allowed in path names (used in a regex character class) + * @param Filesystem $filesystem optional filesystem instance + * @param bool $readOnly whether the cache is in readOnly mode + */ + public function __construct(IOInterface $io, string $cacheDir, string $allowlist = 'a-z0-9._', ?Filesystem $filesystem = null, bool $readOnly = false) + { + $this->io = $io; + $this->root = rtrim($cacheDir, '/\\') . '/'; + $this->allowlist = $allowlist; + $this->filesystem = $filesystem ?: new Filesystem(); + $this->readOnly = $readOnly; + + if (!self::isUsable($cacheDir)) { + $this->enabled = false; + } + } + + /** + * @return void + */ + public function setReadOnly(bool $readOnly) + { + $this->readOnly = $readOnly; + } + + /** + * @return bool + */ + public function isReadOnly() + { + return $this->readOnly; + } + + /** + * @return bool + */ + public static function isUsable(string $path) + { + return !Preg::isMatch('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path); + } + + /** + * @return bool + */ + public function isEnabled() + { + if ($this->enabled === null) { + $this->enabled = true; + + if ( + !$this->readOnly + && ( + (!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true)) + || !is_writable($this->root) + ) + ) { + $this->io->writeError('Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache. See also cache-read-only config if your filesystem is read-only.'); + $this->enabled = false; + } + } + + return $this->enabled; + } + + /** + * @return string + */ + public function getRoot() + { + return $this->root; + } + + /** + * @return string|false + */ + public function read(string $file) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); + + return file_get_contents($this->root . $file); + } + } + + return false; + } + + /** + * @return bool + */ + public function write(string $file, string $contents) + { + $wasEnabled = $this->enabled === true; + + if ($this->isEnabled() && !$this->readOnly) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + + $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); + + $tempFileName = $this->root . $file . bin2hex(random_bytes(5)) . '.tmp'; + try { + return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file); + } catch (\ErrorException $e) { + // If the write failed despite isEnabled checks passing earlier, rerun the isEnabled checks to + // see if they are still current and recreate the cache dir if needed. Refs https://github.com/composer/composer/issues/11076 + if ($wasEnabled) { + clearstatcache(); + $this->enabled = null; + + return $this->write($file, $contents); + } + + $this->io->writeError('Failed to write into cache: '.$e->getMessage().'', true, IOInterface::DEBUG); + if (Preg::isMatch('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { + // Remove partial file. + unlink($tempFileName); + + $message = sprintf( + 'Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$s bytes of free space available', + $tempFileName, + $m[1], + $m[2], + function_exists('disk_free_space') ? @disk_free_space(dirname($tempFileName)) : 'unknown' + ); + + $this->io->writeError($message); + + return false; + } + + throw $e; + } + } + + return false; + } + + /** + * Copy a file into the cache + * + * @return bool + */ + public function copyFrom(string $file, string $source) + { + if ($this->isEnabled() && !$this->readOnly) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + $this->filesystem->ensureDirectoryExists(dirname($this->root . $file)); + + if (!file_exists($source)) { + $this->io->writeError(''.$source.' does not exist, can not write into cache'); + } elseif ($this->io->isDebug()) { + $this->io->writeError('Writing '.$this->root . $file.' into cache from '.$source); + } + + return $this->filesystem->copy($source, $this->root . $file); + } + + return false; + } + + /** + * Copy a file out of the cache + * + * @return bool + */ + public function copyTo(string $file, string $target) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + try { + touch($this->root . $file, (int) filemtime($this->root . $file), time()); + } catch (\ErrorException $e) { + // fallback in case the above failed due to incorrect ownership + // see https://github.com/composer/composer/issues/4070 + Silencer::call('touch', $this->root . $file); + } + + $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); + + return $this->filesystem->copy($this->root . $file, $target); + } + } + + return false; + } + + /** + * @return bool + */ + public function gcIsNecessary() + { + if (self::$cacheCollected) { + return false; + } + + self::$cacheCollected = true; + if (Platform::getEnv('COMPOSER_TEST_SUITE')) { + return false; + } + + if (Platform::isInputCompletionProcess()) { + return false; + } + + return !random_int(0, 50); + } + + /** + * @return bool + */ + public function remove(string $file) + { + if ($this->isEnabled() && !$this->readOnly) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + return $this->filesystem->unlink($this->root . $file); + } + } + + return false; + } + + /** + * @return bool + */ + public function clear() + { + if ($this->isEnabled() && !$this->readOnly) { + $this->filesystem->emptyDirectory($this->root); + + return true; + } + + return false; + } + + /** + * @return int|false + * @phpstan-return int<0, max>|false + */ + public function getAge(string $file) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file) && ($mtime = filemtime($this->root . $file)) !== false) { + return abs(time() - $mtime); + } + } + + return false; + } + + /** + * @return bool + */ + public function gc(int $ttl, int $maxSize) + { + if ($this->isEnabled() && !$this->readOnly) { + $expire = new \DateTime(); + $expire->modify('-'.$ttl.' seconds'); + + $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s')); + foreach ($finder as $file) { + $this->filesystem->unlink($file->getPathname()); + } + + $totalSize = $this->filesystem->size($this->root); + if ($totalSize > $maxSize) { + $iterator = $this->getFinder()->sortByAccessedTime()->getIterator(); + while ($totalSize > $maxSize && $iterator->valid()) { + $filepath = $iterator->current()->getPathname(); + $totalSize -= $this->filesystem->size($filepath); + $this->filesystem->unlink($filepath); + $iterator->next(); + } + } + + self::$cacheCollected = true; + + return true; + } + + return false; + } + + public function gcVcsCache(int $ttl): bool + { + if ($this->isEnabled()) { + $expire = new \DateTime(); + $expire->modify('-'.$ttl.' seconds'); + + $finder = Finder::create()->in($this->root)->directories()->depth(0)->date('until '.$expire->format('Y-m-d H:i:s')); + foreach ($finder as $file) { + $this->filesystem->removeDirectory($file->getPathname()); + } + + self::$cacheCollected = true; + + return true; + } + + return false; + } + + /** + * @return string|false + */ + public function sha1(string $file) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + return hash_file('sha1', $this->root . $file); + } + } + + return false; + } + + /** + * @return string|false + */ + public function sha256(string $file) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + return hash_file('sha256', $this->root . $file); + } + } + + return false; + } + + /** + * @return Finder + */ + protected function getFinder() + { + return Finder::create()->in($this->root)->files(); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/AboutCommand.php b/vendor/composer/composer/src/Composer/Command/AboutCommand.php new file mode 100644 index 000000000..b4bd2296d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/AboutCommand.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class AboutCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('about') + ->setDescription('Shows a short information about Composer') + ->setHelp( + <<php composer.phar about +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composerVersion = Composer::getVersion(); + + $this->getIO()->write( + <<Composer - Dependency Manager for PHP - version $composerVersion +Composer is a dependency manager tracking local dependencies of your projects and libraries. +See https://getcomposer.org/ for more information. +EOT + ); + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ArchiveCommand.php b/vendor/composer/composer/src/Composer/Command/ArchiveCommand.php new file mode 100644 index 000000000..b71f4e241 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ArchiveCommand.php @@ -0,0 +1,212 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\Composer; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Repository\RepositorySet; +use Composer\Script\ScriptEvents; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Util\Filesystem; +use Composer\Util\Loop; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Creates an archive of a package for distribution. + * + * @author Nils Adermann + */ +class ArchiveCommand extends BaseCommand +{ + use CompletionTrait; + + private const FORMATS = ['tar', 'tar.gz', 'tar.bz2', 'zip']; + + protected function configure(): void + { + $this + ->setName('archive') + ->setDescription('Creates an archive of this composer package') + ->setDefinition([ + new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project', null, $this->suggestAvailablePackage()), + new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)', null, self::FORMATS), + new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'), + new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.' + .' Note that the format will be appended.'), + new InputOption('ignore-filters', null, InputOption::VALUE_NONE, 'Ignore filters when saving package'), + ]) + ->setHelp( + <<archive command creates an archive of the specified format +containing the files and directories of the Composer project or the specified +package in the specified version and writes it to the specified directory. + +php composer.phar archive [--format=zip] [--dir=/foo] [--file=filename] [package [version]] + +Read more at https://getcomposer.org/doc/03-cli.md#archive +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->tryComposer(); + $config = null; + + if ($composer) { + $config = $composer->getConfig(); + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output); + $eventDispatcher = $composer->getEventDispatcher(); + $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent); + $eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD); + } + + if (!$config) { + $config = Factory::createConfig(); + } + + $format = $input->getOption('format') ?? $config->get('archive-format'); + $dir = $input->getOption('dir') ?? $config->get('archive-dir'); + + $returnCode = $this->archive( + $this->getIO(), + $config, + $input->getArgument('package'), + $input->getArgument('version'), + $format, + $dir, + $input->getOption('file'), + $input->getOption('ignore-filters'), + $composer + ); + + if (0 === $returnCode && $composer) { + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD); + } + + return $returnCode; + } + + /** + * @throws \Exception + */ + protected function archive(IOInterface $io, Config $config, ?string $packageName, ?string $version, string $format, string $dest, ?string $fileName, bool $ignoreFilters, ?Composer $composer): int + { + if ($composer) { + $archiveManager = $composer->getArchiveManager(); + } else { + $factory = new Factory; + $process = new ProcessExecutor(); + $httpDownloader = Factory::createHttpDownloader($io, $config); + $downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader, $process); + $archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader, $process)); + } + + if ($packageName) { + $package = $this->selectPackage($io, $packageName, $version); + + if (!$package) { + return 1; + } + } else { + $package = $this->requireComposer()->getPackage(); + } + + $io->writeError('Creating the archive into "'.$dest.'".'); + $packagePath = $archiveManager->archive($package, $format, $dest, $fileName, $ignoreFilters); + $fs = new Filesystem; + $shortPath = $fs->findShortestPath(Platform::getCwd(), $packagePath, true); + + $io->writeError('Created: ', false); + $io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath : $packagePath); + + return 0; + } + + /** + * @return (BasePackage&CompletePackageInterface)|false + */ + protected function selectPackage(IOInterface $io, string $packageName, ?string $version = null) + { + $io->writeError('Searching for the specified package.'); + + if ($composer = $this->tryComposer()) { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $repo = new CompositeRepository(array_merge([$localRepo], $composer->getRepositoryManager()->getRepositories())); + $minStability = $composer->getPackage()->getMinimumStability(); + } else { + $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); + $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos))); + $repo = new CompositeRepository($defaultRepos); + $minStability = 'stable'; + } + + if ($version !== null && Preg::isMatchStrictGroups('{@(stable|RC|beta|alpha|dev)$}i', $version, $match)) { + $minStability = VersionParser::normalizeStability($match[1]); + $version = (string) substr($version, 0, -strlen($match[0])); + } + + $repoSet = new RepositorySet($minStability); + $repoSet->addRepository($repo); + $parser = new VersionParser(); + $constraint = $version !== null ? $parser->parseConstraints($version) : null; + $packages = $repoSet->findPackages(strtolower($packageName), $constraint); + + if (count($packages) > 1) { + $versionSelector = new VersionSelector($repoSet); + $package = $versionSelector->findBestCandidate(strtolower($packageName), $version, $minStability); + if ($package === false) { + $package = reset($packages); + } + + $io->writeError('Found multiple matches, selected '.$package->getPrettyString().'.'); + $io->writeError('Alternatives were '.implode(', ', array_map(static function ($p): string { + return $p->getPrettyString(); + }, $packages)).'.'); + $io->writeError('Please use a more specific constraint to pick a different package.'); + } elseif (count($packages) === 1) { + $package = reset($packages); + $io->writeError('Found an exact match '.$package->getPrettyString().'.'); + } else { + $io->writeError('Could not find a package matching '.$packageName.'.'); + + return false; + } + + if (!$package instanceof CompletePackageInterface) { + throw new \LogicException('Expected a CompletePackageInterface instance but found '.get_class($package)); + } + if (!$package instanceof BasePackage) { + throw new \LogicException('Expected a BasePackage instance but found '.get_class($package)); + } + + return $package; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/AuditCommand.php b/vendor/composer/composer/src/Composer/Command/AuditCommand.php new file mode 100644 index 000000000..564db470e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/AuditCommand.php @@ -0,0 +1,122 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Advisory\AuditConfig; +use Composer\Composer; +use Composer\Repository\RepositorySet; +use Composer\Repository\RepositoryUtils; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Package\PackageInterface; +use Composer\Repository\InstalledRepository; +use Composer\Advisory\Auditor; +use Composer\Console\Input\InputOption; + +class AuditCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('audit') + ->setDescription('Checks for security vulnerability advisories for installed packages') + ->setDefinition([ + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables auditing of require-dev packages.'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS), + new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.'), + new InputOption('abandoned', null, InputOption::VALUE_REQUIRED, 'Behavior on abandoned packages. Must be "ignore", "report", or "fail".', null, Auditor::ABANDONEDS), + new InputOption('ignore-severity', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Ignore advisories of a certain severity level.', [], ['low', 'medium', 'high', 'critical']), + new InputOption('ignore-unreachable', null, InputOption::VALUE_NONE, 'Ignore repositories that are unreachable or return a non-200 status code.'), + ]) + ->setHelp( + <<audit command checks for security vulnerability advisories for installed packages. + +If you do not want to include dev dependencies in the audit you can omit them with --no-dev + +If you want to ignore repositories that are unreachable or return a non-200 status code, use --ignore-unreachable + +Read more at https://getcomposer.org/doc/03-cli.md#audit +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + $packages = $this->getPackages($composer, $input); + + if (count($packages) === 0) { + $this->getIO()->writeError('No packages - skipping audit.'); + + return 0; + } + + $auditor = new Auditor(); + $repoSet = new RepositorySet(); + foreach ($composer->getRepositoryManager()->getRepositories() as $repo) { + $repoSet->addRepository($repo); + } + + $auditConfig = AuditConfig::fromConfig($composer->getConfig()); + + $abandoned = $input->getOption('abandoned'); + if ($abandoned !== null && !in_array($abandoned, Auditor::ABANDONEDS, true)) { + throw new \InvalidArgumentException('--abandoned must be one of '.implode(', ', Auditor::ABANDONEDS).'.'); + } + + $abandoned = $abandoned ?? $auditConfig->auditAbandoned; + + $ignoreSeverities = array_merge(array_fill_keys($input->getOption('ignore-severity'), null), $auditConfig->ignoreSeverityForAudit); + $ignoreUnreachable = $input->getOption('ignore-unreachable') || $auditConfig->ignoreUnreachable; + + return min(255, $auditor->audit( + $this->getIO(), + $repoSet, + $packages, + $this->getAuditFormat($input, 'format'), + false, + $auditConfig->ignoreListForAudit, + $abandoned, + $ignoreSeverities, + $ignoreUnreachable, + $auditConfig->ignoreAbandonedForAudit + )); + + } + + /** + * @return PackageInterface[] + */ + private function getPackages(Composer $composer, InputInterface $input): array + { + if ($input->getOption('locked')) { + if (!$composer->getLocker()->isLocked()) { + throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked'); + } + $locker = $composer->getLocker(); + + return $locker->getLockedRepository(!$input->getOption('no-dev'))->getPackages(); + } + + $rootPkg = $composer->getPackage(); + $installedRepo = new InstalledRepository([$composer->getRepositoryManager()->getLocalRepository()]); + + if ($input->getOption('no-dev')) { + return RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg); + } + + return $installedRepo->getPackages(); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/BaseCommand.php b/vendor/composer/composer/src/Composer/Command/BaseCommand.php new file mode 100644 index 000000000..a0c145208 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/BaseCommand.php @@ -0,0 +1,507 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\Config; +use Composer\Console\Application; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Composer\Factory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\IO\IOInterface; +use Composer\IO\NullIO; +use Composer\Plugin\PreCommandRunEvent; +use Composer\Package\Version\VersionParser; +use Composer\Plugin\PluginEvents; +use Composer\Advisory\Auditor; +use Composer\Advisory\AuditConfig; +use Composer\Util\Platform; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Terminal; + +/** + * Base class for Composer commands + * + * @author Ryan Weaver + * @author Konstantin Kudryashov + */ +abstract class BaseCommand extends Command +{ + /** + * @var Composer|null + */ + private $composer; + + /** + * @var IOInterface + */ + private $io; + + /** + * Gets the application instance for this command. + */ + public function getApplication(): Application + { + $application = parent::getApplication(); + if (!$application instanceof Application) { + throw new \RuntimeException('Composer commands can only work with an '.Application::class.' instance set'); + } + + return $application; + } + + /** + * @param bool $required Should be set to false, or use `requireComposer` instead + * @param bool|null $disablePlugins If null, reads --no-plugins as default + * @param bool|null $disableScripts If null, reads --no-scripts as default + * @throws \RuntimeException + * @return Composer|null + * @deprecated since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false + */ + public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null) + { + if ($required) { + return $this->requireComposer($disablePlugins, $disableScripts); + } + + return $this->tryComposer($disablePlugins, $disableScripts); + } + + /** + * Retrieves the default Composer\Composer instance or throws + * + * Use this instead of getComposer if you absolutely need an instance + * + * @param bool|null $disablePlugins If null, reads --no-plugins as default + * @param bool|null $disableScripts If null, reads --no-scripts as default + * @throws \RuntimeException + */ + public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): Composer + { + if (null === $this->composer) { + $application = parent::getApplication(); + if ($application instanceof Application) { + $this->composer = $application->getComposer(true, $disablePlugins, $disableScripts); + assert($this->composer instanceof Composer); + } else { + throw new \RuntimeException( + 'Could not create a Composer\Composer instance, you must inject '. + 'one if this command is not used with a Composer\Console\Application instance' + ); + } + } + + return $this->composer; + } + + /** + * Retrieves the default Composer\Composer instance or null + * + * Use this instead of getComposer(false) + * + * @param bool|null $disablePlugins If null, reads --no-plugins as default + * @param bool|null $disableScripts If null, reads --no-scripts as default + */ + public function tryComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer + { + if (null === $this->composer) { + $application = parent::getApplication(); + if ($application instanceof Application) { + $this->composer = $application->getComposer(false, $disablePlugins, $disableScripts); + } + } + + return $this->composer; + } + + /** + * @return void + */ + public function setComposer(Composer $composer) + { + $this->composer = $composer; + } + + /** + * Removes the cached composer instance + * + * @return void + */ + public function resetComposer() + { + $this->composer = null; + $this->getApplication()->resetComposer(); + } + + /** + * Whether or not this command is meant to call another command. + * + * This is mainly needed to avoid duplicated warnings messages. + * + * @return bool + */ + public function isProxyCommand() + { + return false; + } + + /** + * @return IOInterface + */ + public function getIO() + { + if (null === $this->io) { + $application = parent::getApplication(); + if ($application instanceof Application) { + $this->io = $application->getIO(); + } else { + $this->io = new NullIO(); + } + } + + return $this->io; + } + + /** + * @return void + */ + public function setIO(IOInterface $io) + { + $this->io = $io; + } + + /** + * @inheritdoc + * + * Backport suggested values definition from symfony/console 6.1+ + * + * TODO drop when PHP 8.1 / symfony 6.1+ can be required + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $definition = $this->getDefinition(); + $name = (string) $input->getCompletionName(); + if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() + && $definition->hasOption($name) + && ($option = $definition->getOption($name)) instanceof InputOption + ) { + $option->complete($input, $suggestions); + } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() + && $definition->hasArgument($name) + && ($argument = $definition->getArgument($name)) instanceof InputArgument + ) { + $argument->complete($input, $suggestions); + } else { + parent::complete($input, $suggestions); + } + } + + /** + * @inheritDoc + */ + protected function initialize(InputInterface $input, OutputInterface $output): void + { + // initialize a plugin-enabled Composer instance, either local or global + $disablePlugins = $input->hasParameterOption('--no-plugins'); + $disableScripts = $input->hasParameterOption('--no-scripts'); + + $application = parent::getApplication(); + if ($application instanceof Application && $application->getDisablePluginsByDefault()) { + $disablePlugins = true; + } + if ($application instanceof Application && $application->getDisableScriptsByDefault()) { + $disableScripts = true; + } + + if ($this instanceof SelfUpdateCommand) { + $disablePlugins = true; + $disableScripts = true; + } + + $composer = $this->tryComposer($disablePlugins, $disableScripts); + $io = $this->getIO(); + + if (null === $composer) { + $composer = Factory::createGlobal($this->getIO(), $disablePlugins, $disableScripts); + } + if ($composer) { + $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName()); + $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent); + } + + if (true === $input->hasParameterOption(['--no-ansi']) && $input->hasOption('no-progress')) { + $input->setOption('no-progress', true); + } + + $envOptions = [ + 'COMPOSER_NO_AUDIT' => ['no-audit'], + 'COMPOSER_NO_DEV' => ['no-dev', 'update-no-dev'], + 'COMPOSER_PREFER_STABLE' => ['prefer-stable'], + 'COMPOSER_PREFER_LOWEST' => ['prefer-lowest'], + 'COMPOSER_MINIMAL_CHANGES' => ['minimal-changes'], + 'COMPOSER_WITH_DEPENDENCIES' => ['with-dependencies'], + 'COMPOSER_WITH_ALL_DEPENDENCIES' => ['with-all-dependencies'], + 'COMPOSER_NO_SECURITY_BLOCKING' => ['no-security-blocking'], + ]; + foreach ($envOptions as $envName => $optionNames) { + foreach ($optionNames as $optionName) { + if (true === $input->hasOption($optionName)) { + if (false === $input->getOption($optionName) && (bool) Platform::getEnv($envName)) { + $input->setOption($optionName, true); + } + } + } + } + + if (true === $input->hasOption('ignore-platform-reqs')) { + if (!$input->getOption('ignore-platform-reqs') && (bool) Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQS')) { + $input->setOption('ignore-platform-reqs', true); + + $io->writeError('COMPOSER_IGNORE_PLATFORM_REQS is set. You may experience unexpected errors.'); + } + } + + if (true === $input->hasOption('ignore-platform-req') && (!$input->hasOption('ignore-platform-reqs') || !$input->getOption('ignore-platform-reqs'))) { + $ignorePlatformReqEnv = Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQ'); + if (0 === count($input->getOption('ignore-platform-req')) && is_string($ignorePlatformReqEnv) && '' !== $ignorePlatformReqEnv) { + $input->setOption('ignore-platform-req', explode(',', $ignorePlatformReqEnv)); + + $io->writeError('COMPOSER_IGNORE_PLATFORM_REQ is set to ignore '.$ignorePlatformReqEnv.'. You may experience unexpected errors.'); + } + } + + parent::initialize($input, $output); + } + + /** + * Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins + * + * @param mixed $config either a configuration array or a filename to read from, if null it will read from + * the default filename + */ + protected function createComposerInstance(InputInterface $input, IOInterface $io, $config = null, ?bool $disablePlugins = null, ?bool $disableScripts = null): Composer + { + $disablePlugins = $disablePlugins === true || $input->hasParameterOption('--no-plugins'); + $disableScripts = $disableScripts === true || $input->hasParameterOption('--no-scripts'); + + $application = parent::getApplication(); + if ($application instanceof Application && $application->getDisablePluginsByDefault()) { + $disablePlugins = true; + } + if ($application instanceof Application && $application->getDisableScriptsByDefault()) { + $disableScripts = true; + } + + return Factory::create($io, $config, $disablePlugins, $disableScripts); + } + + /** + * Returns preferSource and preferDist values based on the configuration. + * + * @return bool[] An array composed of the preferSource and preferDist values + */ + protected function getPreferredInstallOptions(Config $config, InputInterface $input, bool $keepVcsRequiresPreferSource = false) + { + $preferSource = false; + $preferDist = false; + + switch ($config->get('preferred-install')) { + case 'source': + $preferSource = true; + break; + case 'dist': + $preferDist = true; + break; + case 'auto': + default: + // noop + break; + } + + if (!$input->hasOption('prefer-dist') || !$input->hasOption('prefer-source')) { + return [$preferSource, $preferDist]; + } + + if ($input->hasOption('prefer-install') && is_string($input->getOption('prefer-install'))) { + if ($input->getOption('prefer-source')) { + throw new \InvalidArgumentException('--prefer-source can not be used together with --prefer-install'); + } + if ($input->getOption('prefer-dist')) { + throw new \InvalidArgumentException('--prefer-dist can not be used together with --prefer-install'); + } + switch ($input->getOption('prefer-install')) { + case 'dist': + $input->setOption('prefer-dist', true); + break; + case 'source': + $input->setOption('prefer-source', true); + break; + case 'auto': + $preferDist = false; + $preferSource = false; + break; + default: + throw new \UnexpectedValueException('--prefer-install accepts one of "dist", "source" or "auto", got '.$input->getOption('prefer-install')); + } + } + + if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) { + $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs')); + $preferDist = $input->getOption('prefer-dist'); + } + + return [$preferSource, $preferDist]; + } + + protected function getPlatformRequirementFilter(InputInterface $input): PlatformRequirementFilterInterface + { + if (!$input->hasOption('ignore-platform-reqs') || !$input->hasOption('ignore-platform-req')) { + throw new \LogicException('Calling getPlatformRequirementFilter from a command which does not define the --ignore-platform-req[s] flags is not permitted.'); + } + + if (true === $input->getOption('ignore-platform-reqs')) { + return PlatformRequirementFilterFactory::ignoreAll(); + } + + $ignores = $input->getOption('ignore-platform-req'); + if (count($ignores) > 0) { + return PlatformRequirementFilterFactory::fromBoolOrList($ignores); + } + + return PlatformRequirementFilterFactory::ignoreNothing(); + } + + /** + * @param array $requirements + * + * @return array + */ + protected function formatRequirements(array $requirements) + { + $requires = []; + $requirements = $this->normalizeRequirements($requirements); + foreach ($requirements as $requirement) { + if (!isset($requirement['version'])) { + throw new \UnexpectedValueException('Option '.$requirement['name'] .' is missing a version constraint, use e.g. '.$requirement['name'].':^1.0'); + } + $requires[$requirement['name']] = $requirement['version']; + } + + return $requires; + } + + /** + * @param array $requirements + * + * @return list + */ + protected function normalizeRequirements(array $requirements) + { + $parser = new VersionParser(); + + return $parser->parseNameVersionPairs($requirements); + } + + /** + * @param array $table + * + * @return void + */ + protected function renderTable(array $table, OutputInterface $output) + { + $renderer = new Table($output); + $renderer->setStyle('compact'); + $renderer->setRows($table)->render(); + } + + /** + * @return int + */ + protected function getTerminalWidth() + { + $terminal = new Terminal(); + $width = $terminal->getWidth(); + + if (Platform::isWindows()) { + $width--; + } else { + $width = max(80, $width); + } + + return $width; + } + + /** + * @internal + * @param 'format'|'audit-format' $optName + * @return Auditor::FORMAT_* + */ + protected function getAuditFormat(InputInterface $input, string $optName = 'audit-format'): string + { + if (!$input->hasOption($optName)) { + throw new \LogicException('This should not be called on a Command which has no '.$optName.' option defined.'); + } + + $val = $input->getOption($optName); + if (!in_array($val, Auditor::FORMATS, true)) { + throw new \InvalidArgumentException('--'.$optName.' must be one of '.implode(', ', Auditor::FORMATS).'.'); + } + + return $val; + } + + /** + * Creates an AuditConfig from the Config object, optionally overriding security blocking based on input options + * + * @internal + */ + protected function createAuditConfig(Config $config, InputInterface $input): AuditConfig + { + // Handle both --audit and --no-audit flags + if ($input->hasOption('audit')) { + $audit = (bool) $input->getOption('audit'); + } else { + $audit = !($input->hasOption('no-audit') && $input->getOption('no-audit')); + } + $auditFormat = $input->hasOption('audit-format') ? $this->getAuditFormat($input) : Auditor::FORMAT_SUMMARY; + + $auditConfig = AuditConfig::fromConfig($config, $audit, $auditFormat); + + if ((bool) Platform::getEnv('COMPOSER_NO_SECURITY_BLOCKING') || ($input->hasOption('no-security-blocking') && $input->getOption('no-security-blocking'))) { + $auditConfig = new AuditConfig( + $auditConfig->audit, + $auditConfig->auditFormat, + $auditConfig->auditAbandoned, + false, // blockInsecure + $auditConfig->blockAbandoned, + $auditConfig->ignoreUnreachable, + $auditConfig->ignoreListForAudit, + $auditConfig->ignoreListForBlocking, + $auditConfig->ignoreSeverityForAudit, + $auditConfig->ignoreSeverityForBlocking, + $auditConfig->ignoreAbandonedForAudit, + $auditConfig->ignoreAbandonedForBlocking + ); + } + + return $auditConfig; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/BaseConfigCommand.php b/vendor/composer/composer/src/Composer/Command/BaseConfigCommand.php new file mode 100644 index 000000000..eaa9ec6a4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/BaseConfigCommand.php @@ -0,0 +1,105 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Config; +use Composer\Config\JsonConfigSource; +use Composer\Json\JsonFile; +use Composer\Factory; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +abstract class BaseConfigCommand extends BaseCommand +{ + /** + * @var Config + */ + protected $config; + + /** + * @var JsonFile + */ + protected $configFile; + + /** + * @var JsonConfigSource + */ + protected $configSource; + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + parent::initialize($input, $output); + + if ($input->getOption('global') && null !== $input->getOption('file')) { + throw new \RuntimeException('--file and --global can not be combined'); + } + + $io = $this->getIO(); + $this->config = Factory::createConfig($io); + + // When using --global flag, set baseDir to home directory for correct absolute path resolution + if ($input->getOption('global')) { + $this->config->setBaseDir($this->config->get('home')); + } + + $configFile = $this->getComposerConfigFile($input, $this->config); + + // Create global composer.json if invoked using `composer global [config-cmd]` + if ( + ($configFile === 'composer.json' || $configFile === './composer.json') + && !file_exists($configFile) + && realpath(Platform::getCwd()) === realpath($this->config->get('home')) + ) { + file_put_contents($configFile, "{\n}\n"); + } + + $this->configFile = new JsonFile($configFile, null, $io); + $this->configSource = new JsonConfigSource($this->configFile); + + // Initialize the global file if it's not there, ignoring any warnings or notices + if ($input->getOption('global') && !$this->configFile->exists()) { + touch($this->configFile->getPath()); + $this->configFile->write(['config' => new \ArrayObject]); + Silencer::call('chmod', $this->configFile->getPath(), 0600); + } + + if (!$this->configFile->exists()) { + throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile)); + } + } + + /** + * Get the local composer.json, global config.json, or the file passed by the user + */ + protected function getComposerConfigFile(InputInterface $input, Config $config): string + { + return $input->getOption('global') + ? ($config->get('home') . '/config.json') + : ($input->getOption('file') ?? Factory::getComposerFile()) + ; + } + + /** + * Get the local auth.json or global auth.json, or if the user passed in a file to use, + * the corresponding auth.json + */ + protected function getAuthConfigFile(InputInterface $input, Config $config): string + { + return $input->getOption('global') + ? ($config->get('home') . '/auth.json') + : dirname($this->getComposerConfigFile($input, $config)) . '/auth.json' + ; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php b/vendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php new file mode 100644 index 000000000..3a291ac70 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php @@ -0,0 +1,296 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Package\Link; +use Composer\Package\Package; +use Composer\Package\PackageInterface; +use Composer\Package\CompletePackageInterface; +use Composer\Package\RootPackage; +use Composer\Repository\InstalledArrayRepository; +use Composer\Repository\CompositeRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\InstalledRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Semver\Constraint\Bound; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Composer\Package\Version\VersionParser; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Util\PackageInfo; + +/** + * Base implementation for commands mapping dependency relationships. + * + * @author Niels Keurentjes + */ +abstract class BaseDependencyCommand extends BaseCommand +{ + protected const ARGUMENT_PACKAGE = 'package'; + protected const ARGUMENT_CONSTRAINT = 'version'; + protected const OPTION_RECURSIVE = 'recursive'; + protected const OPTION_TREE = 'tree'; + + /** @var string[] */ + protected $colors; + + /** + * Execute the command. + * + * @param bool $inverted Whether to invert matching process (why-not vs why behaviour) + * @return int Exit code of the operation. + */ + protected function doExecute(InputInterface $input, OutputInterface $output, bool $inverted = false): int + { + // Emit command event on startup + $composer = $this->requireComposer(); + $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $repos = []; + + $repos[] = new RootPackageRepository(clone $composer->getPackage()); + + if ($input->getOption('locked')) { + $locker = $composer->getLocker(); + + if (!$locker->isLocked()) { + throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --locked'); + } + + $repos[] = $locker->getLockedRepository(true); + $repos[] = new PlatformRepository([], $locker->getPlatformOverrides()); + } else { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $rootPkg = $composer->getPackage(); + + if (count($localRepo->getPackages()) === 0 && (count($rootPkg->getRequires()) > 0 || count($rootPkg->getDevRequires()) > 0)) { + $output->writeln('No dependencies installed. Try running composer install or update, or use --locked.'); + + return 1; + } + + $repos[] = $localRepo; + + $platformOverrides = $composer->getConfig()->get('platform') ?: []; + $repos[] = new PlatformRepository([], $platformOverrides); + } + + $installedRepo = new InstalledRepository($repos); + + // Parse package name and constraint + $needle = $input->getArgument(self::ARGUMENT_PACKAGE); + $textConstraint = $input->hasArgument(self::ARGUMENT_CONSTRAINT) ? $input->getArgument(self::ARGUMENT_CONSTRAINT) : '*'; + + // Find packages that are or provide the requested package first + $packages = $installedRepo->findPackagesWithReplacersAndProviders($needle); + if (empty($packages)) { + throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); + } + + // If the version we ask for is not installed then we need to locate it in remote repos and add it. + // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages. + $matchedPackage = $installedRepo->findPackage($needle, $textConstraint); + if (!$matchedPackage) { + $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager())); + if ($match = $defaultRepos->findPackage($needle, $textConstraint)) { + $installedRepo->addRepository(new InstalledArrayRepository([clone $match])); + } elseif (PlatformRepository::isPlatformPackage($needle)) { + $parser = new VersionParser(); + $constraint = $parser->parseConstraints($textConstraint); + if ($constraint->getLowerBound() !== Bound::zero()) { + $tempPlatformPkg = new Package($needle, $constraint->getLowerBound()->getVersion(), $constraint->getLowerBound()->getVersion()); + $installedRepo->addRepository(new InstalledArrayRepository([$tempPlatformPkg])); + } + } else { + $this->getIO()->writeError('Package "'.$needle.'" could not be found with constraint "'.$textConstraint.'", results below will most likely be incomplete.'); + } + } elseif (PlatformRepository::isPlatformPackage($needle)) { + $extraNotice = ''; + if (($matchedPackage->getExtra()['config.platform'] ?? false) === true) { + $extraNotice = ' (version provided by config.platform)'; + } + $this->getIO()->writeError('Package "'.$needle.' '.$textConstraint.'" found in version "'.$matchedPackage->getPrettyVersion().'"'.$extraNotice.'.'); + } elseif ($inverted) { + $this->getIO()->write('Package "'.$needle.'" '.$matchedPackage->getPrettyVersion().' is already installed! To find out why, run `composer why '.$needle.'`'); + + return 0; + } + + // Include replaced packages for inverted lookups as they are then the actual starting point to consider + $needles = [$needle]; + if ($inverted) { + foreach ($packages as $package) { + $needles = array_merge($needles, array_map(static function (Link $link): string { + return $link->getTarget(); + }, $package->getReplaces())); + } + } + + // Parse constraint if one was supplied + if ('*' !== $textConstraint) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($textConstraint); + } else { + $constraint = null; + } + + // Parse rendering options + $renderTree = $input->getOption(self::OPTION_TREE); + $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE); + + $return = $inverted ? 1 : 0; + + // Resolve dependencies + $results = $installedRepo->getDependents($needles, $constraint, $inverted, $recursive); + if (empty($results)) { + $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : ''; + $this->getIO()->writeError(sprintf( + 'There is no installed package depending on "%s"%s', + $needle, + $extra + )); + $return = $inverted ? 0 : 1; + } elseif ($renderTree) { + $this->initStyles($output); + $root = $packages[0]; + $this->getIO()->write(sprintf('%s %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root instanceof CompletePackageInterface ? $root->getDescription() : '')); + $this->printTree($results); + } else { + $this->printTable($output, $results); + } + + if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT) && !PlatformRepository::isPlatformPackage($needle)) { + $composerCommand = 'update'; + + foreach ($composer->getPackage()->getRequires() as $rootRequirement) { + if ($rootRequirement->getTarget() === $needle) { + $composerCommand = 'require'; + break; + } + } + + foreach ($composer->getPackage()->getDevRequires() as $rootRequirement) { + if ($rootRequirement->getTarget() === $needle) { + $composerCommand = 'require --dev'; + break; + } + } + + $this->getIO()->writeError('Not finding what you were looking for? Try calling `composer '.$composerCommand.' "'.$needle.':'.$textConstraint.'" --dry-run` to get another view on the problem.'); + } + + return $return; + } + + /** + * Assembles and prints a bottom-up table of the dependencies. + * + * @param array{PackageInterface, Link, array|false}[] $results + */ + protected function printTable(OutputInterface $output, array $results): void + { + $table = []; + $doubles = []; + do { + $queue = []; + $rows = []; + foreach ($results as $result) { + /** + * @var PackageInterface $package + * @var Link $link + */ + [$package, $link, $children] = $result; + $unique = (string) $link; + if (isset($doubles[$unique])) { + continue; + } + $doubles[$unique] = true; + $version = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '-' : $package->getPrettyVersion(); + $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); + $nameWithLink = $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); + $rows[] = [$nameWithLink, $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())]; + if (is_array($children)) { + $queue = array_merge($queue, $children); + } + } + $results = $queue; + $table = array_merge($rows, $table); + } while (\count($results) > 0); + + $this->renderTable($table, $output); + } + + /** + * Init styles for tree + */ + protected function initStyles(OutputInterface $output): void + { + $this->colors = [ + 'green', + 'yellow', + 'cyan', + 'magenta', + 'blue', + ]; + + foreach ($this->colors as $color) { + $style = new OutputFormatterStyle($color); + $output->getFormatter()->setStyle($color, $style); + } + } + + /** + * Recursively prints a tree of the selected results. + * + * @param array{PackageInterface, Link, array|false}[] $results Results to be printed at this level. + * @param string $prefix Prefix of the current tree level. + * @param int $level Current level of recursion. + */ + protected function printTree(array $results, string $prefix = '', int $level = 1): void + { + $count = count($results); + $idx = 0; + foreach ($results as $result) { + [$package, $link, $children] = $result; + + $color = $this->colors[$level % count($this->colors)]; + $prevColor = $this->colors[($level - 1) % count($this->colors)]; + $isLast = (++$idx === $count); + $versionText = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '' : $package->getPrettyVersion(); + $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); + $nameWithLink = $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); + $packageText = rtrim(sprintf('<%s>%s %s', $color, $nameWithLink, $versionText)); + $linkText = sprintf('%s <%s>%s %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint()); + $circularWarn = $children === false ? '(circular dependency aborted here)' : ''; + $this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn))); + if (is_array($children)) { + $this->printTree($children, $prefix . ($isLast ? ' ' : '│ '), $level + 1); + } + } + } + + private function writeTreeLine(string $line): void + { + $io = $this->getIO(); + if (!$io->isDecorated()) { + $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line); + } + + $io->write($line); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/BumpCommand.php b/vendor/composer/composer/src/Composer/Command/BumpCommand.php new file mode 100644 index 000000000..3579a8db0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/BumpCommand.php @@ -0,0 +1,264 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\IO\IOInterface; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Version\VersionBumper; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Factory; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Repository\PlatformRepository; +use Composer\Util\Silencer; + +/** + * @author Jordi Boggiano + */ +final class BumpCommand extends BaseCommand +{ + private const ERROR_GENERIC = 1; + private const ERROR_LOCK_OUTDATED = 2; + + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('bump') + ->setDescription('Increases the lower limit of your composer.json requirements to the currently installed versions') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name(s) to restrict which packages are bumped.', null, $this->suggestRootRequirement()), + new InputOption('dev-only', 'D', InputOption::VALUE_NONE, 'Only bump requirements in "require-dev".'), + new InputOption('no-dev-only', 'R', InputOption::VALUE_NONE, 'Only bump requirements in "require".'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the packages to bump, but will not execute anything.'), + ]) + ->setHelp( + <<bump command increases the lower limit of your composer.json requirements +to the currently installed versions. This helps to ensure your dependencies do not +accidentally get downgraded due to some other conflict, and can slightly improve +dependency resolution performance as it limits the amount of package versions +Composer has to look at. + +Running this blindly on libraries is **NOT** recommended as it will narrow down +your allowed dependencies, which may cause dependency hell for your users. +Running it with --dev-only on libraries may be fine however as dev requirements +are local to the library and do not affect consumers of the package. + +EOT + ) + ; + } + + /** + * @throws \Seld\JsonLint\ParsingException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + return $this->doBump( + $this->getIO(), + $input->getOption('dev-only'), + $input->getOption('no-dev-only'), + $input->getOption('dry-run'), + $input->getArgument('packages') + ); + } + + /** + * @internal + * @param string[] $packagesFilter + * @throws \Seld\JsonLint\ParsingException + */ + public function doBump( + IOInterface $io, + bool $devOnly, + bool $noDevOnly, + bool $dryRun, + array $packagesFilter, + string $devOnlyFlagHint = '--dev-only' + ): int { + /** @readonly */ + $composerJsonPath = Factory::getComposerFile(); + + if (!Filesystem::isReadable($composerJsonPath)) { + $io->writeError(''.$composerJsonPath.' is not readable.'); + + return self::ERROR_GENERIC; + } + + $composerJson = new JsonFile($composerJsonPath); + $contents = file_get_contents($composerJson->getPath()); + if (false === $contents) { + $io->writeError(''.$composerJsonPath.' is not readable.'); + + return self::ERROR_GENERIC; + } + + // check for writability by writing to the file as is_writable can not be trusted on network-mounts + // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + if (!is_writable($composerJsonPath) && false === Silencer::call('file_put_contents', $composerJsonPath, $contents)) { + $io->writeError(''.$composerJsonPath.' is not writable.'); + + return self::ERROR_GENERIC; + } + unset($contents); + + $composer = $this->requireComposer(); + $hasLockfileDisabled = !$composer->getConfig()->has('lock') || $composer->getConfig()->get('lock'); + if (!$hasLockfileDisabled) { + $repo = $composer->getLocker()->getLockedRepository(true); + } elseif ($composer->getLocker()->isLocked()) { + if (!$composer->getLocker()->isFresh()) { + $io->writeError('The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.'); + + return self::ERROR_LOCK_OUTDATED; + } + + $repo = $composer->getLocker()->getLockedRepository(true); + } else { + $repo = $composer->getRepositoryManager()->getLocalRepository(); + } + + if ($composer->getPackage()->getType() !== 'project' && !$devOnly) { + $io->writeError('Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.'); + + $contents = $composerJson->read(); + if (!isset($contents['type'])) { + $io->writeError('If your package is not a library, you can explicitly specify the "type" by using "composer config type project".'); + $io->writeError('Alternatively you can use '.$devOnlyFlagHint.' to only bump dependencies within "require-dev".'); + } + unset($contents); + } + + $bumper = new VersionBumper(); + $tasks = []; + if (!$devOnly) { + $tasks['require'] = $composer->getPackage()->getRequires(); + } + if (!$noDevOnly) { + $tasks['require-dev'] = $composer->getPackage()->getDevRequires(); + } + + if (count($packagesFilter) > 0) { + // support proxied args from the update command that contain constraints together with the package names + $packagesFilter = array_map(static function ($constraint) { + return Preg::replace('{[:= ].+}', '', $constraint); + }, $packagesFilter); + $pattern = BasePackage::packageNamesToRegexp(array_unique(array_map('strtolower', $packagesFilter))); + foreach ($tasks as $key => $reqs) { + foreach ($reqs as $pkgName => $link) { + if (!Preg::isMatch($pattern, $pkgName)) { + unset($tasks[$key][$pkgName]); + } + } + } + } + + $updates = []; + foreach ($tasks as $key => $reqs) { + foreach ($reqs as $pkgName => $link) { + if (PlatformRepository::isPlatformPackage($pkgName)) { + continue; + } + $currentConstraint = $link->getPrettyConstraint(); + + $package = $repo->findPackage($pkgName, '*'); + // name must be provided or replaced + if (null === $package) { + continue; + } + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + $bumped = $bumper->bumpRequirement($link->getConstraint(), $package); + + if ($bumped === $currentConstraint) { + continue; + } + + $updates[$key][$pkgName] = $bumped; + } + } + + if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) { + $composerDefinition = $composerJson->read(); + foreach ($updates as $key => $packages) { + foreach ($packages as $package => $version) { + $composerDefinition[$key][$package] = $version; + } + } + $composerJson->write($composerDefinition); + } + + $changeCount = array_sum(array_map('count', $updates)); + if ($changeCount > 0) { + if ($dryRun) { + $io->write('' . $composerJsonPath . ' would be updated with:'); + foreach ($updates as $requireType => $packages) { + foreach ($packages as $package => $version) { + $io->write(sprintf(' - %s.%s: %s', $requireType, $package, $version)); + } + } + } else { + $io->write('' . $composerJsonPath . ' has been updated (' . $changeCount . ' changes).'); + } + } else { + $io->write('No requirements to update in '.$composerJsonPath.'.'); + } + + if (!$dryRun && $composer->getLocker()->isLocked() && $composer->getConfig()->get('lock') && $changeCount > 0) { + $composer->getLocker()->updateHash($composerJson); + } + + if ($dryRun && $changeCount > 0) { + return self::ERROR_GENERIC; + } + + return 0; + } + + /** + * @param array<'require'|'require-dev', array> $updates + */ + private function updateFileCleanly(JsonFile $json, array $updates): bool + { + $contents = file_get_contents($json->getPath()); + if (false === $contents) { + throw new \RuntimeException('Unable to read '.$json->getPath().' contents.'); + } + + $manipulator = new JsonManipulator($contents); + + foreach ($updates as $key => $packages) { + foreach ($packages as $package => $version) { + if (!$manipulator->addLink($key, $package, $version)) { + return false; + } + } + } + + if (false === file_put_contents($json->getPath(), $manipulator->getContents())) { + throw new \RuntimeException('Unable to write new '.$json->getPath().' contents.'); + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php b/vendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php new file mode 100644 index 000000000..e25210075 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php @@ -0,0 +1,214 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Package\Link; +use Composer\Semver\Constraint\Constraint; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\InstalledRepository; +use Composer\Json\JsonFile; + +class CheckPlatformReqsCommand extends BaseCommand +{ + protected function configure(): void + { + $this->setName('check-platform-reqs') + ->setDescription('Check that platform requirements are satisfied') + ->setDefinition([ + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'), + new InputOption('lock', null, InputOption::VALUE_NONE, 'Checks requirements only from the lock file, not from installed packages.'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), + ]) + ->setHelp( + <<php composer.phar check-platform-reqs + +EOT + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $requires = []; + $removePackages = []; + if ($input->getOption('lock')) { + $this->getIO()->writeError('Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements using the lock file'); + $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev')); + } else { + $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); + // fallback to lockfile if installed repo is empty + if (!$installedRepo->getPackages()) { + $this->getIO()->writeError('No vendor dir present, checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements from the lock file'); + $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev')); + } else { + if ($input->getOption('no-dev')) { + $removePackages = $installedRepo->getDevPackageNames(); + } + + $this->getIO()->writeError('Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements for packages in the vendor dir'); + } + } + if (!$input->getOption('no-dev')) { + foreach ($composer->getPackage()->getDevRequires() as $require => $link) { + $requires[$require] = [$link]; + } + } + + $installedRepo = new InstalledRepository([$installedRepo, new RootPackageRepository(clone $composer->getPackage())]); + foreach ($installedRepo->getPackages() as $package) { + if (in_array($package->getName(), $removePackages, true)) { + continue; + } + foreach ($package->getRequires() as $require => $link) { + $requires[$require][] = $link; + } + } + + ksort($requires); + + $installedRepo->addRepository(new PlatformRepository([], [])); + + $results = []; + $exitCode = 0; + + /** + * @var Link[] $links + */ + foreach ($requires as $require => $links) { + if (PlatformRepository::isPlatformPackage($require)) { + $candidates = $installedRepo->findPackagesWithReplacersAndProviders($require); + if ($candidates) { + $reqResults = []; + foreach ($candidates as $candidate) { + $candidateConstraint = null; + if ($candidate->getName() === $require) { + $candidateConstraint = new Constraint('=', $candidate->getVersion()); + $candidateConstraint->setPrettyString($candidate->getPrettyVersion()); + } else { + foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) { + if ($link->getTarget() === $require) { + $candidateConstraint = $link->getConstraint(); + break; + } + } + } + + // safety check for phpstan, but it should not be possible to get a candidate out of findPackagesWithReplacersAndProviders without a constraint matching $require + if (!$candidateConstraint) { + continue; + } + + foreach ($links as $link) { + if (!$link->getConstraint()->matches($candidateConstraint)) { + $reqResults[] = [ + $candidate->getName() === $require ? $candidate->getPrettyName() : $require, + $candidateConstraint->getPrettyString(), + $link, + 'failed', + $candidate->getName() === $require ? '' : 'provided by '.$candidate->getPrettyName().'', + ]; + + // skip to next candidate + continue 2; + } + } + + $results[] = [ + $candidate->getName() === $require ? $candidate->getPrettyName() : $require, + $candidateConstraint->getPrettyString(), + null, + 'success', + $candidate->getName() === $require ? '' : 'provided by '.$candidate->getPrettyName().'', + ]; + + // candidate matched, skip to next requirement + continue 2; + } + + // show the first error from every failed candidate + $results = array_merge($results, $reqResults); + $exitCode = max($exitCode, 1); + + continue; + } + + $results[] = [ + $require, + 'n/a', + $links[0], + 'missing', + '', + ]; + + $exitCode = max($exitCode, 2); + } + } + + $this->printTable($output, $results, $input->getOption('format')); + + return $exitCode; + } + + /** + * @param mixed[] $results + */ + protected function printTable(OutputInterface $output, array $results, string $format): void + { + $rows = []; + foreach ($results as $result) { + /** + * @var Link|null $link + */ + [$platformPackage, $version, $link, $status, $provider] = $result; + + if ('json' === $format) { + $rows[] = [ + "name" => $platformPackage, + "version" => $version, + "status" => strip_tags($status), + "failed_requirement" => $link instanceof Link ? [ + 'source' => $link->getSource(), + 'type' => $link->getDescription(), + 'target' => $link->getTarget(), + 'constraint' => $link->getPrettyConstraint(), + ] : null, + "provider" => $provider === '' ? null : strip_tags($provider), + ]; + } else { + $rows[] = [ + $platformPackage, + $version, + $link, + $link ? sprintf('%s %s %s (%s)', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()) : '', + rtrim($status.' '.$provider), + ]; + } + } + + if ('json' === $format) { + $this->getIO()->write(JsonFile::encode($rows)); + } else { + $this->renderTable($rows, $output); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ClearCacheCommand.php b/vendor/composer/composer/src/Composer/Command/ClearCacheCommand.php new file mode 100644 index 000000000..77ed517a8 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ClearCacheCommand.php @@ -0,0 +1,107 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Cache; +use Composer\Factory; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author David Neilsen + */ +class ClearCacheCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('clear-cache') + ->setAliases(['clearcache', 'cc']) + ->setDescription('Clears composer\'s internal package cache') + ->setDefinition([ + new InputOption('gc', null, InputOption::VALUE_NONE, 'Only run garbage collection, not a full cache clear'), + ]) + ->setHelp( + <<clear-cache deletes all cached packages from composer's +cache directory. + +Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->tryComposer(); + if ($composer !== null) { + $config = $composer->getConfig(); + } else { + $config = Factory::createConfig(); + } + + $io = $this->getIO(); + + $cachePaths = [ + 'cache-vcs-dir' => $config->get('cache-vcs-dir'), + 'cache-repo-dir' => $config->get('cache-repo-dir'), + 'cache-files-dir' => $config->get('cache-files-dir'), + 'cache-dir' => $config->get('cache-dir'), + ]; + + foreach ($cachePaths as $key => $cachePath) { + // only individual dirs get garbage collected + if ($key === 'cache-dir' && $input->getOption('gc')) { + continue; + } + + $cachePath = realpath($cachePath); + if (!$cachePath) { + $io->writeError("Cache directory does not exist ($key): $cachePath"); + + continue; + } + $cache = new Cache($io, $cachePath); + $cache->setReadOnly($config->get('cache-read-only')); + if (!$cache->isEnabled()) { + $io->writeError("Cache is not enabled ($key): $cachePath"); + + continue; + } + + if ($input->getOption('gc')) { + $io->writeError("Garbage-collecting cache ($key): $cachePath"); + if ($key === 'cache-files-dir') { + $cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); + } elseif ($key === 'cache-repo-dir') { + $cache->gc($config->get('cache-ttl'), 1024 * 1024 * 1024 /* 1GB, this should almost never clear anything that is not outdated */); + } elseif ($key === 'cache-vcs-dir') { + $cache->gcVcsCache($config->get('cache-ttl')); + } + } else { + $io->writeError("Clearing cache ($key): $cachePath"); + $cache->clear(); + } + } + + if ($input->getOption('gc')) { + $io->writeError('All caches garbage-collected.'); + } else { + $io->writeError('All caches cleared.'); + } + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/CompletionTrait.php b/vendor/composer/composer/src/Composer/Command/CompletionTrait.php new file mode 100644 index 000000000..444d69554 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/CompletionTrait.php @@ -0,0 +1,244 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Repository\InstalledRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RootPackageRepository; +use Symfony\Component\Console\Completion\CompletionInput; + +/** + * Adds completion to arguments and options. + * + * @internal + */ +trait CompletionTrait +{ + /** + * @see BaseCommand::requireComposer() + */ + abstract public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): Composer; + + /** + * Suggestion values for "prefer-install" option + * + * @return list + */ + private function suggestPreferInstall(): array + { + return ['dist', 'source', 'auto']; + } + + /** + * Suggest package names from root requirements. + */ + private function suggestRootRequirement(): \Closure + { + return function (CompletionInput $input): array { + $composer = $this->requireComposer(); + + return array_merge(array_keys($composer->getPackage()->getRequires()), array_keys($composer->getPackage()->getDevRequires())); + }; + } + + /** + * Suggest package names from installed. + */ + private function suggestInstalledPackage(bool $includeRootPackage = true, bool $includePlatformPackages = false): \Closure + { + return function (CompletionInput $input) use ($includeRootPackage, $includePlatformPackages): array { + $composer = $this->requireComposer(); + $installedRepos = []; + + if ($includeRootPackage) { + $installedRepos[] = new RootPackageRepository(clone $composer->getPackage()); + } + + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $installedRepos[] = $locker->getLockedRepository(true); + } else { + $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); + } + + $platformHint = []; + if ($includePlatformPackages) { + if ($locker->isLocked()) { + $platformRepo = new PlatformRepository([], $locker->getPlatformOverrides()); + } else { + $platformRepo = new PlatformRepository([], $composer->getConfig()->get('platform')); + } + if ($input->getCompletionValue() === '') { + // to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes + $hintsToFind = ['ext-' => 0, 'lib-' => 0, 'php' => 99, 'composer' => 99]; + foreach ($platformRepo->getPackages() as $pkg) { + foreach ($hintsToFind as $hintPrefix => $hintCount) { + if (str_starts_with($pkg->getName(), $hintPrefix)) { + if ($hintCount === 0 || $hintCount >= 99) { + $platformHint[] = $pkg->getName(); + $hintsToFind[$hintPrefix]++; + } elseif ($hintCount === 1) { + unset($hintsToFind[$hintPrefix]); + $platformHint[] = substr($pkg->getName(), 0, max(strlen($pkg->getName()) - 3, strlen($hintPrefix) + 1)).'...'; + } + continue 2; + } + } + } + } else { + $installedRepos[] = $platformRepo; + } + } + + $installedRepo = new InstalledRepository($installedRepos); + + return array_merge( + array_map(static function (PackageInterface $package) { + return $package->getName(); + }, $installedRepo->getPackages()), + $platformHint + ); + }; + } + + /** + * Suggest package names from installed. + */ + private function suggestInstalledPackageTypes(bool $includeRootPackage = true): \Closure + { + return function (CompletionInput $input) use ($includeRootPackage): array { + $composer = $this->requireComposer(); + $installedRepos = []; + + if ($includeRootPackage) { + $installedRepos[] = new RootPackageRepository(clone $composer->getPackage()); + } + + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $installedRepos[] = $locker->getLockedRepository(true); + } else { + $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); + } + + $installedRepo = new InstalledRepository($installedRepos); + + return array_values(array_unique( + array_map(static function (PackageInterface $package) { + return $package->getType(); + }, $installedRepo->getPackages()) + )); + }; + } + + /** + * Suggest package names available on all configured repositories. + */ + private function suggestAvailablePackage(int $max = 99): \Closure + { + return function (CompletionInput $input) use ($max): array { + if ($max < 1) { + return []; + } + + $composer = $this->requireComposer(); + $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); + + $results = []; + $showVendors = false; + if (!str_contains($input->getCompletionValue(), '/')) { + $results = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_VENDOR); + $showVendors = true; + } + + // if we get a single vendor, we expand it into its contents already + if (\count($results) <= 1) { + $results = $repos->search('^'.preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME); + $showVendors = false; + } + + $results = array_column($results, 'name'); + + if ($showVendors) { + $results = array_map(static function (string $name): string { + return $name.'/'; + }, $results); + + // sort shorter results first to avoid auto-expanding the completion to a longer string than needed + usort($results, static function (string $a, string $b) { + $lenA = \strlen($a); + $lenB = \strlen($b); + if ($lenA === $lenB) { + return $a <=> $b; + } + + return $lenA - $lenB; + }); + + $pinned = []; + + // ensure if the input is an exact match that it is always in the result set + $completionInput = $input->getCompletionValue().'/'; + if (false !== ($exactIndex = array_search($completionInput, $results, true))) { + $pinned[] = $completionInput; + array_splice($results, $exactIndex, 1); + } + + return array_merge($pinned, array_slice($results, 0, $max - \count($pinned))); + } + + return array_slice($results, 0, $max); + }; + } + + /** + * Suggest package names available on all configured repositories or + * platform packages from the ones available on the currently-running PHP + */ + private function suggestAvailablePackageInclPlatform(): \Closure + { + return function (CompletionInput $input): array { + if (Preg::isMatch('{^(ext|lib|php)(-|$)|^com}', $input->getCompletionValue())) { + $matches = $this->suggestPlatformPackage()($input); + } else { + $matches = []; + } + + return array_merge($matches, $this->suggestAvailablePackage(99 - \count($matches))($input)); + }; + } + + /** + * Suggest platform packages from the ones available on the currently-running PHP + */ + private function suggestPlatformPackage(): \Closure + { + return function (CompletionInput $input): array { + $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform')); + + $pattern = BasePackage::packageNameToRegexp($input->getCompletionValue().'*'); + + return array_filter(array_map(static function (PackageInterface $package) { + return $package->getName(); + }, $repos->getPackages()), static function (string $name) use ($pattern): bool { + return Preg::isMatch($pattern, $name); + }); + }; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ConfigCommand.php b/vendor/composer/composer/src/Composer/Command/ConfigCommand.php new file mode 100644 index 000000000..01bde448b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ConfigCommand.php @@ -0,0 +1,1143 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Advisory\Auditor; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Config; +use Composer\Config\JsonConfigSource; +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Semver\VersionParser; +use Composer\Package\BasePackage; + +/** + * @author Joshua Estes + * @author Jordi Boggiano + */ +class ConfigCommand extends BaseConfigCommand +{ + /** + * List of additional configurable package-properties + * + * @var string[] + */ + protected const CONFIGURABLE_PACKAGE_PROPERTIES = [ + 'name', + 'type', + 'description', + 'homepage', + 'version', + 'minimum-stability', + 'prefer-stable', + 'keywords', + 'license', + 'repositories', + 'suggest', + 'extra', + ]; + + /** + * @var JsonFile + */ + protected $authConfigFile; + + /** + * @var JsonConfigSource + */ + protected $authConfigSource; + + protected function configure(): void + { + $this + ->setName('config') + ->setDescription('Sets config options') + ->setDefinition([ + new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), + new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'), + new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'), + new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'), + new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), + new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), + new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'), + new InputOption('json', 'j', InputOption::VALUE_NONE, 'JSON decode the setting value, to be used with extra.* keys'), + new InputOption('merge', 'm', InputOption::VALUE_NONE, 'Merge the setting value with the current value, to be used with extra.* or audit.ignore[-abandoned] keys in combination with --json'), + new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)'), + new InputOption('source', null, InputOption::VALUE_NONE, 'Display where the config value is loaded from'), + new InputArgument('setting-key', null, 'Setting key', null, $this->suggestSettingKeys()), + new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), + ]) + ->setHelp( + <<%command.full_name% bin-dir bin/ + +To read a config setting: + + %command.full_name% bin-dir + Outputs: bin + +To edit the global config.json file: + + %command.full_name% --global + +To add a repository: + + %command.full_name% repositories.foo vcs https://bar.com + +To remove a repository (repo is a short alias for repositories): + + %command.full_name% --unset repo.foo + +To disable packagist.org: + + %command.full_name% repo.packagist.org false + +You can alter repositories in the global config.json file by passing in the +--global option. + +To add or edit suggested packages you can use: + + %command.full_name% suggest.package reason for the suggestion + +To add or edit extra properties you can use: + + %command.full_name% extra.property value + +Or to add a complex value you can use json with: + + %command.full_name% extra.property --json '{"foo":true, "bar": []}' + +To edit the file in an external editor: + + %command.full_name% --editor + +To choose your editor you can set the "EDITOR" env variable. + +To get a list of configuration values in the file: + + %command.full_name% --list + +You can always pass more than one option. As an example, if you want to edit the +global config.json file. + + %command.full_name% --editor --global + +Read more at https://getcomposer.org/doc/03-cli.md#config +EOT + ) + ; + } + + /** + * @throws \Exception + */ + protected function initialize(InputInterface $input, OutputInterface $output): void + { + parent::initialize($input, $output); + + $authConfigFile = $this->getAuthConfigFile($input, $this->config); + + $this->authConfigFile = new JsonFile($authConfigFile, null, $this->getIO()); + $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true); + + // Initialize the global file if it's not there, ignoring any warnings or notices + if ($input->getOption('global') && !$this->authConfigFile->exists()) { + touch($this->authConfigFile->getPath()); + $this->authConfigFile->write(['bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject, 'bearer' => new \ArrayObject, 'forgejo-token' => new \ArrayObject()]); + Silencer::call('chmod', $this->authConfigFile->getPath(), 0600); + } + } + + /** + * @throws \Seld\JsonLint\ParsingException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + // Open file in editor + if (true === $input->getOption('editor')) { + $editor = Platform::getEnv('EDITOR'); + if (false === $editor || '' === $editor) { + if (Platform::isWindows()) { + $editor = 'notepad'; + } else { + foreach (['editor', 'vim', 'vi', 'nano', 'pico', 'ed'] as $candidate) { + if (exec('which '.$candidate)) { + $editor = $candidate; + break; + } + } + } + } else { + $editor = escapeshellcmd($editor); + } + + $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath(); + system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`')); + + return 0; + } + + if (false === $input->getOption('global')) { + $this->config->merge($this->configFile->read(), $this->configFile->getPath()); + $this->config->merge(['config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : []], $this->authConfigFile->getPath()); + } + + $this->getIO()->loadConfiguration($this->config); + + // List the configuration of the file settings + if (true === $input->getOption('list')) { + $this->listConfiguration($this->config->all(), $this->config->raw(), $output, null, $input->getOption('source')); + + return 0; + } + + $settingKey = $input->getArgument('setting-key'); + if (!is_string($settingKey)) { + return 0; + } + + // If the user enters in a config variable, parse it and save to file + if ([] !== $input->getArgument('setting-value') && $input->getOption('unset')) { + throw new \RuntimeException('You can not combine a setting value with --unset'); + } + + // show the value if no value is provided + if ([] === $input->getArgument('setting-value') && !$input->getOption('unset')) { + $properties = self::CONFIGURABLE_PACKAGE_PROPERTIES; + $propertiesDefaults = [ + 'type' => 'library', + 'description' => '', + 'homepage' => '', + 'minimum-stability' => 'stable', + 'prefer-stable' => false, + 'keywords' => [], + 'license' => [], + 'suggest' => [], + 'extra' => [], + ]; + $rawData = $this->configFile->read(); + $data = $this->config->all(); + $source = $this->config->getSourceOfValue($settingKey); + + if (Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) { + if (!isset($matches[1])) { + $value = $data['repositories'] ?? []; + } else { + if (!isset($data['repositories'][$matches[1]])) { + throw new \InvalidArgumentException('There is no '.$matches[1].' repository defined'); + } + + $value = $data['repositories'][$matches[1]]; + } + } elseif (strpos($settingKey, '.')) { + $bits = explode('.', $settingKey); + if ($bits[0] === 'extra' || $bits[0] === 'suggest') { + $data = $rawData; + } else { + $data = $data['config']; + } + $match = false; + foreach ($bits as $bit) { + $key = isset($key) ? $key.'.'.$bit : $bit; + $match = false; + if (isset($data[$key])) { + $match = true; + $data = $data[$key]; + unset($key); + } + } + + if (!$match) { + throw new \RuntimeException($settingKey.' is not defined.'); + } + + $value = $data; + } elseif (isset($data['config'][$settingKey])) { + $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS); + // ensure we get {} output for properties which are objects + if ($value === []) { + $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH)); + if ( + isset($schema['properties']['config']['properties'][$settingKey]['type']) + && in_array('object', (array) $schema['properties']['config']['properties'][$settingKey]['type'], true) + ) { + $value = new \stdClass; + } + } + } elseif (isset($rawData[$settingKey]) && in_array($settingKey, $properties, true)) { + $value = $rawData[$settingKey]; + $source = $this->configFile->getPath(); + } elseif (isset($propertiesDefaults[$settingKey])) { + $value = $propertiesDefaults[$settingKey]; + $source = 'defaults'; + } else { + throw new \RuntimeException($settingKey.' is not defined'); + } + + if (is_array($value) || is_object($value) || is_bool($value)) { + $value = JsonFile::encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + $sourceOfConfigValue = ''; + if ($input->getOption('source')) { + $sourceOfConfigValue = ' (' . $source . ')'; + } + + $this->getIO()->write($value . $sourceOfConfigValue, true, IOInterface::QUIET); + + return 0; + } + + $values = $input->getArgument('setting-value'); // what the user is trying to add/change + + $booleanValidator = static function ($val): bool { + return in_array($val, ['true', 'false', '1', '0'], true); + }; + $booleanNormalizer = static function ($val): bool { + return $val !== 'false' && (bool) $val; + }; + + // handle config values + $uniqueConfigValues = [ + 'process-timeout' => ['is_numeric', 'intval'], + 'use-include-path' => [$booleanValidator, $booleanNormalizer], + 'use-github-api' => [$booleanValidator, $booleanNormalizer], + 'preferred-install' => [ + static function ($val): bool { + return in_array($val, ['auto', 'source', 'dist'], true); + }, + static function ($val) { + return $val; + }, + ], + 'gitlab-protocol' => [ + static function ($val): bool { + return in_array($val, ['git', 'http', 'https'], true); + }, + static function ($val) { + return $val; + }, + ], + 'store-auths' => [ + static function ($val): bool { + return in_array($val, ['true', 'false', 'prompt'], true); + }, + static function ($val) { + if ('prompt' === $val) { + return 'prompt'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'notify-on-install' => [$booleanValidator, $booleanNormalizer], + 'vendor-dir' => ['is_string', static function ($val) { + return $val; + }], + 'bin-dir' => ['is_string', static function ($val) { + return $val; + }], + 'archive-dir' => ['is_string', static function ($val) { + return $val; + }], + 'archive-format' => ['is_string', static function ($val) { + return $val; + }], + 'data-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-files-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-repo-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-vcs-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-ttl' => ['is_numeric', 'intval'], + 'cache-files-ttl' => ['is_numeric', 'intval'], + 'cache-files-maxsize' => [ + static function ($val): bool { + return Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val); + }, + static function ($val) { + return $val; + }, + ], + 'bin-compat' => [ + static function ($val): bool { + return in_array($val, ['auto', 'full', 'proxy', 'symlink']); + }, + static function ($val) { + return $val; + }, + ], + 'discard-changes' => [ + static function ($val): bool { + return in_array($val, ['stash', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('stash' === $val) { + return 'stash'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'autoloader-suffix' => ['is_string', static function ($val) { + return $val === 'null' ? null : $val; + }], + 'sort-packages' => [$booleanValidator, $booleanNormalizer], + 'optimize-autoloader' => [$booleanValidator, $booleanNormalizer], + 'classmap-authoritative' => [$booleanValidator, $booleanNormalizer], + 'apcu-autoloader' => [$booleanValidator, $booleanNormalizer], + 'prepend-autoloader' => [$booleanValidator, $booleanNormalizer], + 'update-with-minimal-changes' => [$booleanValidator, $booleanNormalizer], + 'disable-tls' => [$booleanValidator, $booleanNormalizer], + 'secure-http' => [$booleanValidator, $booleanNormalizer], + 'bump-after-update' => [ + static function ($val): bool { + return in_array($val, ['dev', 'no-dev', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('dev' === $val || 'no-dev' === $val) { + return $val; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'cafile' => [ + static function ($val): bool { + return file_exists($val) && Filesystem::isReadable($val); + }, + static function ($val) { + return $val === 'null' ? null : $val; + }, + ], + 'capath' => [ + static function ($val): bool { + return is_dir($val) && Filesystem::isReadable($val); + }, + static function ($val) { + return $val === 'null' ? null : $val; + }, + ], + 'github-expose-hostname' => [$booleanValidator, $booleanNormalizer], + 'htaccess-protect' => [$booleanValidator, $booleanNormalizer], + 'lock' => [$booleanValidator, $booleanNormalizer], + 'allow-plugins' => [$booleanValidator, $booleanNormalizer], + 'platform-check' => [ + static function ($val): bool { + return in_array($val, ['php-only', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('php-only' === $val) { + return 'php-only'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'use-parent-dir' => [ + static function ($val): bool { + return in_array($val, ['true', 'false', 'prompt'], true); + }, + static function ($val) { + if ('prompt' === $val) { + return 'prompt'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'audit.abandoned' => [ + static function ($val): bool { + return in_array($val, [Auditor::ABANDONED_IGNORE, Auditor::ABANDONED_REPORT, Auditor::ABANDONED_FAIL], true); + }, + static function ($val) { + return $val; + }, + ], + 'audit.ignore-unreachable' => [$booleanValidator, $booleanNormalizer], + 'audit.block-insecure' => [$booleanValidator, $booleanNormalizer], + 'audit.block-abandoned' => [$booleanValidator, $booleanNormalizer], + ]; + $multiConfigValues = [ + 'github-protocols' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + foreach ($vals as $val) { + if (!in_array($val, ['git', 'https', 'ssh'])) { + return 'valid protocols include: git, https, ssh'; + } + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'github-domains' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'gitlab-domains' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'audit.ignore-severity' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + foreach ($vals as $val) { + if (!in_array($val, ['low', 'medium', 'high', 'critical'], true)) { + return 'valid severities include: low, medium, high, critical'; + } + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + ]; + + // allow unsetting audit config entirely + if ($input->getOption('unset') && $settingKey === 'audit') { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) { + if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); + } + + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + if (isset($uniqueConfigValues[$settingKey])) { + $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting'); + + return 0; + } + if (isset($multiConfigValues[$settingKey])) { + $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting'); + + return 0; + } + // handle preferred-install per-package config + if (Preg::isMatch('/^preferred-install\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + [$validator] = $uniqueConfigValues['preferred-install']; + if (!$validator($values[0])) { + throw new \RuntimeException('Invalid value for '.$settingKey.'. Should be one of: auto, source, or dist'); + } + + $this->configSource->addConfigSetting($settingKey, $values[0]); + + return 0; + } + + // handle allow-plugins config setting elements true or false to add/remove + if (Preg::isMatch('{^allow-plugins\.([a-zA-Z0-9/*-]+)}', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + if (true !== $booleanValidator($values[0])) { + throw new \RuntimeException(sprintf( + '"%s" is an invalid value', + $values[0] + )); + } + + $normalizedValue = $booleanNormalizer($values[0]); + + $this->configSource->addConfigSetting($settingKey, $normalizedValue); + + return 0; + } + + // handle properties + $uniqueProps = [ + 'name' => ['is_string', static function ($val) { + return $val; + }], + 'type' => ['is_string', static function ($val) { + return $val; + }], + 'description' => ['is_string', static function ($val) { + return $val; + }], + 'homepage' => ['is_string', static function ($val) { + return $val; + }], + 'version' => ['is_string', static function ($val) { + return $val; + }], + 'minimum-stability' => [ + static function ($val): bool { + return isset(BasePackage::STABILITIES[VersionParser::normalizeStability($val)]); + }, + static function ($val): string { + return VersionParser::normalizeStability($val); + }, + ], + 'prefer-stable' => [$booleanValidator, $booleanNormalizer], + ]; + $multiProps = [ + 'keywords' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'license' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + ]; + + if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || strpos($settingKey, 'extra.') === 0)) { + throw new \InvalidArgumentException('The ' . $settingKey . ' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json'); + } + if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + if (isset($uniqueProps[$settingKey])) { + $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty'); + + return 0; + } + if (isset($multiProps[$settingKey])) { + $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty'); + + return 0; + } + + // handle repositories + if (Preg::isMatchStrictGroups('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeRepository($matches[1]); + + return 0; + } + + if (2 === count($values)) { + $this->configSource->addRepository($matches[1], [ + 'type' => $values[0], + 'url' => $values[1], + ], $input->getOption('append')); + + return 0; + } + + if (1 === count($values)) { + $value = strtolower($values[0]); + if (true === $booleanValidator($value)) { + if (false === $booleanNormalizer($value)) { + $this->configSource->addRepository($matches[1], false, $input->getOption('append')); + + return 0; + } + } else { + $value = JsonFile::parseJson($values[0]); + $this->configSource->addRepository($matches[1], $value, $input->getOption('append')); + + return 0; + } + } + + throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com'); + } + + // handle extra + if (Preg::isMatch('/^extra\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + $value = $values[0]; + if ($input->getOption('json')) { + $value = JsonFile::parseJson($value); + if ($input->getOption('merge')) { + $currentValue = $this->configFile->read(); + $bits = explode('.', $settingKey); + foreach ($bits as $bit) { + $currentValue = $currentValue[$bit] ?? null; + } + if (is_array($currentValue) && is_array($value)) { + if (array_is_list($currentValue) && array_is_list($value)) { + $value = array_merge($currentValue, $value); + } else { + $value = $value + $currentValue; + } + } + } + } + $this->configSource->addProperty($settingKey, $value); + + return 0; + } + + // handle suggest + if (Preg::isMatch('/^suggest\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + $this->configSource->addProperty($settingKey, implode(' ', $values)); + + return 0; + } + + // handle unsetting extra/suggest + if (in_array($settingKey, ['suggest', 'extra'], true) && $input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + // handle platform + if (Preg::isMatch('/^platform\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + $this->configSource->addConfigSetting($settingKey, $values[0] === 'false' ? false : $values[0]); + + return 0; + } + + // handle unsetting platform + if ($settingKey === 'platform' && $input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + // handle audit.ignore and audit.ignore-abandoned with --merge support + if (in_array($settingKey, ['audit.ignore', 'audit.ignore-abandoned'], true)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + $value = $values; + if ($input->getOption('json')) { + $value = JsonFile::parseJson($values[0]); + if (!is_array($value)) { + throw new \RuntimeException('Expected an array or object for '.$settingKey); + } + } + + if ($input->getOption('merge')) { + $currentConfig = $this->configFile->read(); + $currentValue = $currentConfig['config']['audit'][str_replace('audit.', '', $settingKey)] ?? null; + + if ($currentValue !== null && is_array($currentValue) && is_array($value)) { + if (array_is_list($currentValue) && array_is_list($value)) { + // Both are lists, merge them + $value = array_merge($currentValue, $value); + } elseif (!array_is_list($currentValue) && !array_is_list($value)) { + // Both are associative arrays (objects), merge them + $value = $value + $currentValue; + } else { + throw new \RuntimeException('Cannot merge array and object for '.$settingKey); + } + } + } + + $this->configSource->addConfigSetting($settingKey, $value); + + return 0; + } + + // handle auth + if (Preg::isMatch('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|custom-headers|bearer|forgejo-token)\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + + return 0; + } + + if ($matches[1] === 'bitbucket-oauth') { + if (2 !== count($values)) { + throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values)); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['consumer-key' => $values[0], 'consumer-secret' => $values[1]]); + } elseif ($matches[1] === 'gitlab-token' && 2 === count($values)) { + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]); + } elseif (in_array($matches[1], ['github-oauth', 'gitlab-oauth', 'gitlab-token', 'bearer'], true)) { + if (1 !== count($values)) { + throw new \RuntimeException('Too many arguments, expected only one token'); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]); + } elseif ($matches[1] === 'http-basic') { + if (2 !== count($values)) { + throw new \RuntimeException('Expected two arguments (username, password), got '.count($values)); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'password' => $values[1]]); + } elseif ($matches[1] === 'custom-headers') { + if (count($values) === 0) { + throw new \RuntimeException('Expected at least one argument (header), got none'); + } + + // Validate headers format + $formattedHeaders = []; + foreach ($values as $header) { + if (!is_string($header)) { + throw new \RuntimeException('Headers must be strings in "Header-Name: Header-Value" format'); + } + + // Check if the header is in correct "Name: Value" format + if (!Preg::isMatch('/^[^:]+:\s*.+$/', $header, $headerParts)) { + throw new \RuntimeException('Header "' . $header . '" is not in "Header-Name: Header-Value" format'); + } + + $formattedHeaders[] = $header; + } + + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $formattedHeaders); + } elseif ($matches[1] === 'forgejo-token') { + if (2 !== count($values)) { + throw new \RuntimeException('Expected two arguments (username, access token), got '.count($values)); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]); + } + + return 0; + } + + // handle script + if (Preg::isMatch('/^scripts\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]); + + return 0; + } + + // handle unsetting other top level properties + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); + } + + /** + * @param array{callable, callable} $callbacks Validator and normalizer callbacks + * @param array $values + */ + protected function handleSingleValue(string $key, array $callbacks, array $values, string $method): void + { + [$validator, $normalizer] = $callbacks; + if (1 !== count($values)) { + throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300'); + } + + if (true !== $validation = $validator($values[0])) { + throw new \RuntimeException(sprintf( + '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''), + $values[0] + )); + } + + $normalizedValue = $normalizer($values[0]); + + if ($key === 'disable-tls') { + if (!$normalizedValue && $this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); + } elseif ($normalizedValue && !$this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection disabled.'); + } + } + + call_user_func([$this->configSource, $method], $key, $normalizedValue); + } + + /** + * @param array{callable, callable} $callbacks Validator and normalizer callbacks + * @param array $values + */ + protected function handleMultiValue(string $key, array $callbacks, array $values, string $method): void + { + [$validator, $normalizer] = $callbacks; + if (true !== $validation = $validator($values)) { + throw new \RuntimeException(sprintf( + '%s is an invalid value'.($validation ? ' ('.$validation.')' : ''), + json_encode($values) + )); + } + + call_user_func([$this->configSource, $method], $key, $normalizer($values)); + } + + /** + * Display the contents of the file in a pretty formatted way + * + * @param array $contents + * @param array $rawContents + */ + protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, ?string $k = null, bool $showSource = false): void + { + $origK = $k; + $io = $this->getIO(); + foreach ($contents as $key => $value) { + if ($k === null && !in_array($key, ['config', 'repositories'])) { + continue; + } + + $rawVal = $rawContents[$key] ?? null; + + if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) { + $k .= Preg::replace('{^config\.}', '', $key . '.'); + $this->listConfiguration($value, $rawVal, $output, $k, $showSource); + $k = $origK; + + continue; + } + + if (is_array($value)) { + $value = array_map(static function ($val) { + return is_array($val) ? json_encode($val) : $val; + }, $value); + + $value = '['.implode(', ', $value).']'; + } + + if (is_bool($value)) { + $value = var_export($value, true); + } + + $source = ''; + if ($showSource) { + $source = ' (' . $this->config->getSourceOfValue($k . $key) . ')'; + } + + if (null !== $k && 0 === strpos($k, 'repositories')) { + $link = 'https://getcomposer.org/doc/05-repositories.md'; + } else { + $id = Preg::replace('{\..*$}', '', $k === '' || $k === null ? (string) $key : $k); + $id = Preg::replace('{[^a-z0-9]}i', '-', strtolower(trim($id))); + $id = Preg::replace('{-+}', '-', $id); + $link = 'https://getcomposer.org/doc/06-config.md#' . $id; + } + if (is_string($rawVal) && $rawVal !== $value) { + $io->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')' . $source, true, IOInterface::QUIET); + } else { + $io->write('[' . $k . $key . '] ' . $value . '' . $source, true, IOInterface::QUIET); + } + } + } + + /** + * Suggest setting-keys, while taking given options in account. + */ + private function suggestSettingKeys(): \Closure + { + return function (CompletionInput $input): array { + if ($input->getOption('list') || $input->getOption('editor') || $input->getOption('auth')) { + return []; + } + + // initialize configuration + $config = Factory::createConfig(); + + // load configuration + $configFile = new JsonFile($this->getComposerConfigFile($input, $config)); + if ($configFile->exists()) { + $config->merge($configFile->read(), $configFile->getPath()); + } + + // load auth-configuration + $authConfigFile = new JsonFile($this->getAuthConfigFile($input, $config)); + if ($authConfigFile->exists()) { + $config->merge(['config' => $authConfigFile->read()], $authConfigFile->getPath()); + } + + // collect all configuration setting-keys + $rawConfig = $config->raw(); + $keys = array_merge( + $this->flattenSettingKeys($rawConfig['config']), + $this->flattenSettingKeys($rawConfig['repositories'], 'repositories.') + ); + + // if unsetting … + if ($input->getOption('unset')) { + // … keep only the currently customized setting-keys … + $sources = [$configFile->getPath(), $authConfigFile->getPath()]; + $keys = array_filter( + $keys, + static function (string $key) use ($config, $sources): bool { + return in_array($config->getSourceOfValue($key), $sources, true); + } + ); + + // … else if showing or setting a value … + } else { + // … add all configurable package-properties, no matter if it exist + $keys = array_merge($keys, self::CONFIGURABLE_PACKAGE_PROPERTIES); + + // it would be nice to distinguish between showing and setting + // a value, but that makes the implementation much more complex + // and partially impossible because symfony's implementation + // does not complete arguments followed by other arguments + } + + // add all existing configurable package-properties + if ($configFile->exists()) { + $properties = array_filter( + $configFile->read(), + static function (string $key): bool { + return in_array($key, self::CONFIGURABLE_PACKAGE_PROPERTIES, true); + }, + ARRAY_FILTER_USE_KEY + ); + + $keys = array_merge( + $keys, + $this->flattenSettingKeys($properties) + ); + } + + // filter settings-keys by completion value + $completionValue = $input->getCompletionValue(); + + if ($completionValue !== '') { + $keys = array_filter( + $keys, + static function (string $key) use ($completionValue): bool { + return str_starts_with($key, $completionValue); + } + ); + } + + sort($keys); + + return array_unique($keys); + }; + } + + /** + * build a flat list of dot-separated setting-keys from given config + * + * @param array $config + * @return string[] + */ + private function flattenSettingKeys(array $config, string $prefix = ''): array + { + $keys = []; + foreach ($config as $key => $value) { + $keys[] = [$prefix . $key]; + // array-lists must not be added to completion + // sub-keys of repository-keys must not be added to completion + if (is_array($value) && !array_is_list($value) && $prefix !== 'repositories.') { + $keys[] = $this->flattenSettingKeys($value, $prefix . $key . '.'); + } + } + + return array_merge(...$keys); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/CreateProjectCommand.php b/vendor/composer/composer/src/Composer/Command/CreateProjectCommand.php new file mode 100644 index 000000000..7dd286739 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/CreateProjectCommand.php @@ -0,0 +1,493 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Config; +use Composer\Factory; +use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\Installer; +use Composer\Installer\ProjectInstaller; +use Composer\Installer\SuggestedPackagesReporter; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\Package\Version\VersionSelector; +use Composer\Package\AliasPackage; +use Composer\Pcre\Preg; +use Composer\Plugin\PluginBlockedException; +use Composer\Repository\RepositoryFactory; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\InstalledArrayRepository; +use Composer\Repository\RepositorySet; +use Composer\Script\ScriptEvents; +use Composer\Console\Input\InputArgument; +use Seld\Signal\SignalHandler; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Finder\Finder; +use Composer\Json\JsonFile; +use Composer\Config\JsonConfigSource; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Package\Version\VersionParser; +use Composer\Advisory\Auditor; + +/** + * Install a package as new project into new directory. + * + * @author Benjamin Eberlei + * @author Jordi Boggiano + * @author Tobias Munk + * @author Nils Adermann + */ +class CreateProjectCommand extends BaseCommand +{ + use CompletionTrait; + + /** + * @var SuggestedPackagesReporter + */ + protected $suggestedPackagesReporter; + + protected function configure(): void + { + $this + ->setName('create-project') + ->setDescription('Creates new project from a package into given directory') + ->setDefinition([ + new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed', null, $this->suggestAvailablePackage()), + new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'), + new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'), + new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'), + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories to look the package up, either by URL or using JSON arrays'), + new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'), + new InputOption('add-repository', null, InputOption::VALUE_NONE, 'Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), + new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), + new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'), + new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'), + new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), + new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json" or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('ask', null, InputOption::VALUE_NONE, 'Whether to ask for project directory.'), + ]) + ->setHelp( + <<create-project command creates a new project from a given +package into a new directory. If executed without params and in a directory +with a composer.json file it installs the packages for the current project. + +You can use this command to bootstrap new projects or setup a clean +version-controlled installation for developers of your project. + +php composer.phar create-project vendor/project target-directory [version] + +You can also specify the version with the package name using = or : as separator. + +php composer.phar create-project vendor/project:version target-directory + +To install unstable packages, either specify the version you want, or use the +--stability=dev (where dev can be one of RC, beta, alpha or dev). + +To setup a developer workable version you should create the project using the source +controlled code by appending the '--prefer-source' flag. + +To install a package from another repository than the default one you +can pass the '--repository=https://myrepository.org' flag. + +Read more at https://getcomposer.org/doc/03-cli.md#create-project +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $config = Factory::createConfig(); + $io = $this->getIO(); + + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input, true); + + if ($input->getOption('dev')) { + $io->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); + } + if ($input->getOption('no-custom-installers')) { + $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $input->setOption('no-plugins', true); + } + + if ($input->isInteractive() && $input->getOption('ask')) { + $package = $input->getArgument('package'); + if (null === $package) { + throw new \RuntimeException('Not enough arguments (missing: "package").'); + } + $parts = explode("/", strtolower($package), 2); + $input->setArgument('directory', $io->ask('New project directory ['.array_pop($parts).']: ')); + } + + return $this->installProject( + $io, + $config, + $input, + $input->getArgument('package'), + $input->getArgument('directory'), + $input->getArgument('version'), + $input->getOption('stability'), + $preferSource, + $preferDist, + !$input->getOption('no-dev'), + \count($input->getOption('repository')) > 0 ? $input->getOption('repository') : $input->getOption('repository-url'), + $input->getOption('no-plugins'), + $input->getOption('no-scripts'), + $input->getOption('no-progress'), + $input->getOption('no-install'), + $this->getPlatformRequirementFilter($input), + !$input->getOption('no-secure-http'), + $input->getOption('add-repository') + ); + } + + /** + * @param string|array|null $repositories + * + * @throws \Exception + */ + public function installProject(IOInterface $io, Config $config, InputInterface $input, ?string $packageName = null, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $noInstall = false, ?PlatformRequirementFilterInterface $platformRequirementFilter = null, bool $secureHttp = true, bool $addRepository = false): int + { + $oldCwd = Platform::getCwd(); + + if ($repositories !== null && !is_array($repositories)) { + $repositories = (array) $repositories; + } + + $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); + + // we need to manually load the configuration to pass the auth credentials to the io interface! + $io->loadConfiguration($config); + + $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io); + + if ($packageName !== null) { + $installedFromVcs = $this->installRootPackage($input, $io, $config, $packageName, $platformRequirementFilter, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositories, $disablePlugins, $disableScripts, $noProgress, $secureHttp); + } else { + $installedFromVcs = false; + } + + if ($repositories !== null && $addRepository && is_file('composer.lock')) { + unlink('composer.lock'); + } + + $composer = $this->createComposerInstance($input, $io, null, $disablePlugins, $disableScripts); + + // add the repository to the composer.json and use it for the install run later + if ($repositories !== null && $addRepository) { + foreach ($repositories as $index => $repo) { + $repoConfig = RepositoryFactory::configFromString($io, $composer->getConfig(), $repo, true); + $composerJsonRepositoriesConfig = $composer->getConfig()->getRepositories(); + $name = RepositoryFactory::generateRepositoryName($index, $repoConfig, $composerJsonRepositoriesConfig); + $configSource = new JsonConfigSource(new JsonFile('composer.json')); + + if ( + (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) + || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) + ) { + $configSource->addRepository('packagist.org', false); + } else { + $configSource->addRepository($name, $repoConfig, false); + } + + $composer = $this->createComposerInstance($input, $io, null, $disablePlugins); + } + } + + $process = $composer->getLoop()->getProcessExecutor(); + $fs = new Filesystem($process); + + // dispatch event + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages); + + // use the new config including the newly installed project + $config = $composer->getConfig(); + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); + + // install dependencies of the created project + if ($noInstall === false) { + $composer->getInstallationManager()->setOutputProgress(!$noProgress); + + $installer = Installer::create($io, $composer); + $installer->setPreferSource($preferSource) + ->setPreferDist($preferDist) + ->setDevMode($installDevPackages) + ->setPlatformRequirementFilter($platformRequirementFilter) + ->setSuggestedPackagesReporter($this->suggestedPackagesReporter) + ->setOptimizeAutoloader($config->get('optimize-autoloader')) + ->setClassMapAuthoritative($config->get('classmap-authoritative')) + ->setApcuAutoloader($config->get('apcu-autoloader')) + ->setAuditConfig($this->createAuditConfig($config, $input)); + + if (!$composer->getLocker()->isLocked()) { + $installer->setUpdate(true); + } + + if ($disablePlugins) { + $installer->disablePlugins(); + } + + try { + $status = $installer->run(); + if (0 !== $status) { + return $status; + } + } catch (PluginBlockedException $e) { + $io->writeError('Hint: To allow running the config command recommended below before dependencies are installed, run create-project with --no-install.'); + $io->writeError('You can then cd into '.getcwd().', configure allow-plugins, and finally run a composer install to complete the process.'); + throw $e; + } + } + + $hasVcs = $installedFromVcs; + if ( + !$input->getOption('keep-vcs') + && $installedFromVcs + && ( + $input->getOption('remove-vcs') + || !$io->isInteractive() + || $io->askConfirmation('Do you want to remove the existing VCS (.git, .svn..) history? [y,n]? ') + ) + ) { + $finder = new Finder(); + $finder->depth(0)->directories()->in(Platform::getCwd())->ignoreVCS(false)->ignoreDotFiles(false); + foreach (['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg', '.fslckout', '_FOSSIL_'] as $vcsName) { + $finder->name($vcsName); + } + + try { + $dirs = iterator_to_array($finder); + unset($finder); + foreach ($dirs as $dir) { + if (!$fs->removeDirectory((string) $dir)) { + throw new \RuntimeException('Could not remove '.$dir); + } + } + } catch (\Exception $e) { + $io->writeError('An error occurred while removing the VCS metadata: '.$e->getMessage().''); + } + + $hasVcs = false; + } + + // rewriting self.version dependencies with explicit version numbers if the package's vcs metadata is gone + if (!$hasVcs) { + $package = $composer->getPackage(); + $configSource = new JsonConfigSource(new JsonFile('composer.json')); + foreach (BasePackage::$supportedLinkTypes as $type => $meta) { + foreach ($package->{'get'.$meta['method']}() as $link) { + if ($link->getPrettyConstraint() === 'self.version') { + $configSource->addLink($type, $link->getTarget(), $package->getPrettyVersion()); + } + } + } + } + + // dispatch event + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages); + + chdir($oldCwd); + + return 0; + } + + /** + * @param array|null $repositories + * + * @throws \Exception + */ + protected function installRootPackage(InputInterface $input, IOInterface $io, Config $config, string $packageName, PlatformRequirementFilterInterface $platformRequirementFilter, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, ?array $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $secureHttp = true): bool + { + $parser = new VersionParser(); + $requirements = $parser->parseNameVersionPairs([$packageName]); + $name = strtolower($requirements[0]['name']); + if (!$packageVersion && isset($requirements[0]['version'])) { + $packageVersion = $requirements[0]['version']; + } + + // if no directory was specified, use the 2nd part of the package name + if (null === $directory) { + $parts = explode("/", $name, 2); + $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . array_pop($parts); + } + $directory = rtrim($directory, '/\\'); + + $process = new ProcessExecutor($io); + $fs = new Filesystem($process); + if (!$fs->isAbsolutePath($directory)) { + $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . $directory; + } + if ('' === $directory) { + throw new \UnexpectedValueException('Got an empty target directory, something went wrong'); + } + + // set the base dir to ensure $config->all() below resolves the correct absolute paths to vendor-dir etc + $config->setBaseDir($directory); + if (!$secureHttp) { + $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND); + } + + $io->writeError('Creating a "' . $packageName . '" project at "' . $fs->findShortestPath(Platform::getCwd(), $directory, true) . '"'); + + if (file_exists($directory)) { + if (!is_dir($directory)) { + throw new \InvalidArgumentException('Cannot create project directory at "'.$directory.'", it exists as a file.'); + } + if (!$fs->isDirEmpty($directory)) { + throw new \InvalidArgumentException('Project directory "'.$directory.'" is not empty.'); + } + } + + if (null === $stability) { + if (null === $packageVersion) { + $stability = 'stable'; + } elseif (Preg::isMatchStrictGroups('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::STABILITIES)).')$}i', $packageVersion, $match)) { + $stability = $match[1]; + } else { + $stability = VersionParser::parseStability($packageVersion); + } + } + + $stability = VersionParser::normalizeStability($stability); + + if (!isset(BasePackage::STABILITIES[$stability])) { + throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::STABILITIES))); + } + + $composer = $this->createComposerInstance($input, $io, $config->all(), $disablePlugins, $disableScripts); + $config = $composer->getConfig(); + // set the base dir here again on the new config instance, as otherwise in case the vendor dir is defined in an env var for example it would still override the value set above by $config->all() + $config->setBaseDir($directory); + $rm = $composer->getRepositoryManager(); + + $repositorySet = new RepositorySet($stability); + if (null === $repositories) { + $repositorySet->addRepository(new CompositeRepository(RepositoryFactory::defaultRepos($io, $config, $rm))); + } else { + foreach ($repositories as $repo) { + $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true); + if ( + (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) + || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) + ) { + continue; + } + + // disable symlinking for the root package by default as that most likely makes no sense + if (($repoConfig['type'] ?? null) === 'path' && !isset($repoConfig['options']['symlink'])) { + $repoConfig['options']['symlink'] = false; + } + + $repositorySet->addRepository(RepositoryFactory::createRepo($io, $config, $repoConfig, $rm)); + } + } + + $platformOverrides = $config->get('platform'); + $platformRepo = new PlatformRepository([], $platformOverrides); + + // find the latest version if there are multiple + $versionSelector = new VersionSelector($repositorySet, $platformRepo); + $package = $versionSelector->findBestCandidate($name, $packageVersion, $stability, $platformRequirementFilter, 0, $io); + + if (!$package) { + $errorMessage = "Could not find package $name with " . ($packageVersion ? "version $packageVersion" : "stability $stability"); + if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && $versionSelector->findBestCandidate($name, $packageVersion, $stability, PlatformRequirementFilterFactory::ignoreAll())) { + throw new \InvalidArgumentException($errorMessage .' in a version installable using your PHP version, PHP extensions and Composer version.'); + } + + throw new \InvalidArgumentException($errorMessage .'.'); + } + + // handler Ctrl+C aborts gracefully + @mkdir($directory, 0777, true); + if (false !== ($realDir = realpath($directory))) { + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir) { + $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + $fs = new Filesystem(); + $fs->removeDirectory($realDir); + $handler->exitWithLastSignal(); + }); + } + + // avoid displaying 9999999-dev as version if default-branch was selected + if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $package = $package->getAliasOf(); + } + + $io->writeError('Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')'); + + if ($disablePlugins) { + $io->writeError('Plugins have been disabled.'); + } + + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + $dm = $composer->getDownloadManager(); + $dm->setPreferSource($preferSource) + ->setPreferDist($preferDist); + + $projectInstaller = new ProjectInstaller($directory, $dm, $fs); + $im = $composer->getInstallationManager(); + $im->setOutputProgress(!$noProgress); + $im->addInstaller($projectInstaller); + $im->execute(new InstalledArrayRepository(), [new InstallOperation($package)]); + $im->notifyInstalls($io); + + // collect suggestions + $this->suggestedPackagesReporter->addSuggestionsFromPackage($package); + + $installedFromVcs = 'source' === $package->getInstallationSource(); + + $io->writeError('Created project in ' . $directory . ''); + chdir($directory); + + // ensure that the env var being set does not interfere with create-project + // as it is probably not meant to be used here, so we do not use it if a composer.json can be found + // in the project + if (file_exists($directory.'/composer.json') && Platform::getEnv('COMPOSER') !== false) { + Platform::clearEnv('COMPOSER'); + } + + Platform::putEnv('COMPOSER_ROOT_VERSION', $package->getPrettyVersion()); + + // once the root project is fully initialized, we do not need to wipe everything on user abort anymore even if it happens during deps install + if (isset($signalHandler)) { + $signalHandler->unregister(); + } + + return $installedFromVcs; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/DependsCommand.php b/vendor/composer/composer/src/Composer/Command/DependsCommand.php new file mode 100644 index 000000000..07e58c1df --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/DependsCommand.php @@ -0,0 +1,58 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; + +/** + * @author Niels Keurentjes + */ +class DependsCommand extends BaseDependencyCommand +{ + use CompletionTrait; + + /** + * Configure command metadata. + */ + protected function configure(): void + { + $this + ->setName('depends') + ->setAliases(['why']) + ->setDescription('Shows which packages cause the given package to be installed') + ->setDefinition([ + new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestInstalledPackage(true, true)), + new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), + new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), + new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'), + ]) + ->setHelp( + <<php composer.phar depends composer/composer + +Read more at https://getcomposer.org/doc/03-cli.md#depends-why +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + return parent::doExecute($input, $output); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/DiagnoseCommand.php b/vendor/composer/composer/src/Composer/Command/DiagnoseCommand.php new file mode 100644 index 000000000..d123861a1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/DiagnoseCommand.php @@ -0,0 +1,969 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Advisory\Auditor; +use Composer\Composer; +use Composer\Factory; +use Composer\Config; +use Composer\Downloader\TransportException; +use Composer\IO\BufferIO; +use Composer\Json\JsonFile; +use Composer\Json\JsonValidationException; +use Composer\Package\Locker; +use Composer\Package\RootPackage; +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; +use Composer\Repository\ComposerRepository; +use Composer\Repository\FilesystemRepository; +use Composer\Repository\PlatformRepository; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Repository\RepositorySet; +use Composer\Util\ConfigValidator; +use Composer\Util\Git; +use Composer\Util\IniHelper; +use Composer\Util\ProcessExecutor; +use Composer\Util\HttpDownloader; +use Composer\Util\Platform; +use Composer\SelfUpdate\Keys; +use Composer\SelfUpdate\Versions; +use Composer\IO\NullIO; +use Composer\Package\CompletePackageInterface; +use Composer\XdebugHandler\XdebugHandler; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\ExecutableFinder; +use Composer\Util\Http\ProxyManager; +use Composer\Util\Http\RequestProxy; + +/** + * @author Jordi Boggiano + */ +class DiagnoseCommand extends BaseCommand +{ + /** @var HttpDownloader */ + protected $httpDownloader; + + /** @var ProcessExecutor */ + protected $process; + + /** @var int */ + protected $exitCode = 0; + + protected function configure(): void + { + $this + ->setName('diagnose') + ->setDescription('Diagnoses the system to identify common errors') + ->setHelp( + <<diagnose command checks common errors to help debugging problems. + +The process exit code will be 1 in case of warnings and 2 for errors. + +Read more at https://getcomposer.org/doc/03-cli.md#diagnose +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->tryComposer(); + $io = $this->getIO(); + + if ($composer) { + $config = $composer->getConfig(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + $this->process = $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io); + } else { + $config = Factory::createConfig(); + + $this->process = new ProcessExecutor($io); + } + + $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND); + $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO); + + $this->httpDownloader = Factory::createHttpDownloader($io, $config); + + if (strpos(__FILE__, 'phar:') === 0) { + $io->write('Checking pubkeys: ', false); + $this->outputResult($this->checkPubKeys($config)); + + $io->write('Checking Composer version: ', false); + $this->outputResult($this->checkVersion($config)); + } + + $io->write(sprintf('Composer version: %s', Composer::getVersion())); + + $io->write('Checking Composer and its dependencies for vulnerabilities: ', false); + $this->outputResult($this->checkComposerAudit($config)); + + $platformOverrides = $config->get('platform') ?: []; + $platformRepo = new PlatformRepository([], $platformOverrides); + $phpPkg = $platformRepo->findPackage('php', '*'); + $phpVersion = $phpPkg->getPrettyVersion(); + if ($phpPkg instanceof CompletePackageInterface && str_contains((string) $phpPkg->getDescription(), 'overridden')) { + $phpVersion .= ' - ' . $phpPkg->getDescription(); + } + + $io->write(sprintf('PHP version: %s', $phpVersion)); + + if (defined('PHP_BINARY')) { + $io->write(sprintf('PHP binary path: %s', PHP_BINARY)); + } + + $io->write('OpenSSL version: ' . (defined('OPENSSL_VERSION_TEXT') ? ''.OPENSSL_VERSION_TEXT.'' : 'missing')); + $io->write('curl version: ' . $this->getCurlVersion()); + + $finder = new ExecutableFinder; + $hasSystemUnzip = (bool) $finder->find('unzip'); + $bin7zip = ''; + if ($hasSystem7zip = (bool) $finder->find('7z', null, ['C:\Program Files\7-Zip'])) { + $bin7zip = '7z'; + } elseif (!Platform::isWindows() && $hasSystem7zip = (bool) $finder->find('7zz')) { + $bin7zip = '7zz'; + } elseif (!Platform::isWindows() && $hasSystem7zip = (bool) $finder->find('7za')) { + $bin7zip = '7za'; + } + + $io->write( + 'zip: ' . (extension_loaded('zip') ? 'extension present' : 'extension not loaded') + . ', ' . ($hasSystemUnzip ? 'unzip present' : 'unzip not available') + . ', ' . ($hasSystem7zip ? '7-Zip present ('.$bin7zip.')' : '7-Zip not available') + . (($hasSystem7zip || $hasSystemUnzip) && !function_exists('proc_open') ? ', proc_open is disabled or not present, unzip/7-z will not be usable' : '') + ); + + if ($composer) { + $io->write('Active plugins: '.implode(', ', $composer->getPluginManager()->getRegisteredPlugins())); + + $io->write('Checking composer.json: ', false); + $this->outputResult($this->checkComposerSchema()); + + if ($composer->getLocker()->isLocked()) { + $io->write('Checking composer.lock: ', false); + $this->outputResult($this->checkComposerLockSchema($composer->getLocker())); + } + } + + $io->write('Checking platform settings: ', false); + $this->outputResult($this->checkPlatform()); + + $io->write('Checking git settings: ', false); + $this->outputResult($this->checkGit()); + + $io->write('Checking http connectivity to packagist: ', false); + $this->outputResult($this->checkHttp('http', $config)); + + $io->write('Checking https connectivity to packagist: ', false); + $this->outputResult($this->checkHttp('https', $config)); + + foreach ($config->getRepositories() as $repo) { + if (($repo['type'] ?? null) === 'composer' && isset($repo['url'])) { + $composerRepo = new ComposerRepository($repo, $this->getIO(), $config, $this->httpDownloader); + $reflMethod = new \ReflectionMethod($composerRepo, 'getPackagesJsonUrl'); + if (PHP_VERSION_ID < 80100) { + $reflMethod->setAccessible(true); + } + $url = $reflMethod->invoke($composerRepo); + if (!str_starts_with($url, 'http')) { + continue; + } + if (str_starts_with($url, 'https://repo.packagist.org')) { + continue; + } + $io->write('Checking connectivity to ' . $repo['url'].': ', false); + $this->outputResult($this->checkComposerRepo($url, $config)); + } + } + + $proxyManager = ProxyManager::getInstance(); + $protos = $config->get('disable-tls') === true ? ['http'] : ['http', 'https']; + try { + foreach ($protos as $proto) { + $proxy = $proxyManager->getProxyForRequest($proto.'://repo.packagist.org'); + if ($proxy->getStatus() !== '') { + $type = $proxy->isSecure() ? 'HTTPS' : 'HTTP'; + $io->write('Checking '.$type.' proxy with '.$proto.': ', false); + $this->outputResult($this->checkHttpProxy($proxy, $proto)); + } + } + } catch (TransportException $e) { + $io->write('Checking HTTP proxy: ', false); + $status = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + $this->outputResult(is_string($status) ? $status : $e); + } + + if (count($oauth = $config->get('github-oauth')) > 0) { + foreach ($oauth as $domain => $token) { + $io->write('Checking '.$domain.' oauth access: ', false); + $this->outputResult($this->checkGithubOauth($domain, $token)); + } + } else { + $io->write('Checking github.com rate limit: ', false); + try { + $rate = $this->getGithubRateLimit('github.com'); + if (!is_array($rate)) { + $this->outputResult($rate); + } elseif (10 > $rate['remaining']) { + $io->write('WARNING'); + $io->write(sprintf( + 'GitHub has a rate limit on their API. ' + . 'You currently have %u ' + . 'out of %u requests left.' . PHP_EOL + . 'See https://developer.github.com/v3/#rate-limiting and also' . PHP_EOL + . ' https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens', + $rate['remaining'], + $rate['limit'] + )); + } else { + $this->outputResult(true); + } + } catch (\Exception $e) { + if ($e instanceof TransportException && $e->getCode() === 401) { + $this->outputResult('The oauth token for github.com seems invalid, run "composer config --global --unset github-oauth.github.com" to remove it'); + } else { + $this->outputResult($e); + } + } + } + + $io->write('Checking disk free space: ', false); + $this->outputResult($this->checkDiskSpace($config)); + + return $this->exitCode; + } + + /** + * @return string|true + */ + private function checkComposerSchema() + { + $validator = new ConfigValidator($this->getIO()); + [$errors, , $warnings] = $validator->validate(Factory::getComposerFile()); + + if ($errors || $warnings) { + $messages = [ + 'error' => $errors, + 'warning' => $warnings, + ]; + + $output = ''; + foreach ($messages as $style => $msgs) { + foreach ($msgs as $msg) { + $output .= '<' . $style . '>' . $msg . '' . PHP_EOL; + } + } + + return rtrim($output); + } + + return true; + } + + /** + * @return string|true + */ + private function checkComposerLockSchema(Locker $locker) + { + $json = $locker->getJsonFile(); + + try { + $json->validateSchema(JsonFile::LOCK_SCHEMA); + } catch (JsonValidationException $e) { + $output = ''; + foreach ($e->getErrors() as $error) { + $output .= ''.$error.''.PHP_EOL; + } + + return trim($output); + } + + return true; + } + + private function checkGit(): string + { + if (!function_exists('proc_open')) { + return 'proc_open is not available, git cannot be used'; + } + + $this->process->execute(['git', 'config', 'color.ui'], $output); + if (strtolower(trim($output)) === 'always') { + return 'Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.'; + } + + $gitVersion = Git::getVersion($this->process); + if (null === $gitVersion) { + return 'No git process found'; + } + + if (version_compare('2.24.0', $gitVersion, '>')) { + return 'Your git version ('.$gitVersion.') is too old and possibly will cause issues. Please upgrade to git 2.24 or above'; + } + + return 'OK git version '.$gitVersion.''; + } + + /** + * @return string|string[]|true + */ + private function checkHttp(string $proto, Config $config) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $result = []; + if ($proto === 'https' && $config->get('disable-tls') === true) { + $tlsWarning = 'Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.'; + } + + try { + $this->httpDownloader->get($proto . '://repo.packagist.org/packages.json'); + } catch (TransportException $e) { + $hints = HttpDownloader::getExceptionHints($e); + if (null !== $hints && count($hints) > 0) { + foreach ($hints as $hint) { + $result[] = $hint; + } + } + + $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; + } + + if (isset($tlsWarning)) { + $result[] = $tlsWarning; + } + + if (count($result) > 0) { + return $result; + } + + return true; + } + + /** + * @return string|string[]|true + */ + private function checkComposerRepo(string $url, Config $config) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $result = []; + if (str_starts_with($url, 'https://') && $config->get('disable-tls') === true) { + $tlsWarning = 'Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.'; + } + + try { + $this->httpDownloader->get($url); + } catch (TransportException $e) { + $hints = HttpDownloader::getExceptionHints($e); + if (null !== $hints && count($hints) > 0) { + foreach ($hints as $hint) { + $result[] = $hint; + } + } + + $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; + } + + if (isset($tlsWarning)) { + $result[] = $tlsWarning; + } + + if (count($result) > 0) { + return $result; + } + + return true; + } + + /** + * @return string|\Exception + */ + private function checkHttpProxy(RequestProxy $proxy, string $protocol) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + try { + $proxyStatus = $proxy->getStatus(); + + if ($proxy->isExcludedByNoProxy()) { + return 'SKIP Because repo.packagist.org is '.$proxyStatus.''; + } + + $json = $this->httpDownloader->get($protocol.'://repo.packagist.org/packages.json')->decodeJson(); + if (isset($json['provider-includes'])) { + $hash = reset($json['provider-includes']); + $hash = $hash['sha256']; + $path = str_replace('%hash%', $hash, key($json['provider-includes'])); + $provider = $this->httpDownloader->get($protocol.'://repo.packagist.org/'.$path)->getBody(); + + if (hash('sha256', $provider) !== $hash) { + return 'It seems that your proxy ('.$proxyStatus.') is modifying '.$protocol.' traffic on the fly'; + } + } + + return 'OK '.$proxyStatus.''; + } catch (\Exception $e) { + return $e; + } + } + + /** + * @return string|\Exception + */ + private function checkGithubOauth(string $domain, string $token) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); + try { + $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/'; + + $response = $this->httpDownloader->get($url, [ + 'retry-auth-failure' => false, + ]); + + $expiration = $response->getHeader('github-authentication-token-expiration'); + + if ($expiration === null) { + return 'OK does not expire'; + } + + return 'OK expires on '. $expiration .''; + } catch (\Exception $e) { + if ($e instanceof TransportException && $e->getCode() === 401) { + return 'The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it'; + } + + return $e; + } + } + + /** + * @throws TransportException + * @return mixed|string + */ + private function getGithubRateLimit(string $domain, ?string $token = null) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + if ($token) { + $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); + } + + $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit'; + $data = $this->httpDownloader->get($url, ['retry-auth-failure' => false])->decodeJson(); + + return $data['resources']['core']; + } + + /** + * @return string|true + */ + private function checkDiskSpace(Config $config) + { + if (!function_exists('disk_free_space')) { + return true; + } + + $minSpaceFree = 1024 * 1024; + if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) + || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) + ) { + return 'The disk hosting '.$dir.' is full'; + } + + return true; + } + + /** + * @return string[]|true + */ + private function checkPubKeys(Config $config) + { + $home = $config->get('home'); + $errors = []; + $io = $this->getIO(); + + if (file_exists($home.'/keys.tags.pub') && file_exists($home.'/keys.dev.pub')) { + $io->write(''); + } + + if (file_exists($home.'/keys.tags.pub')) { + $io->write('Tags Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.tags.pub')); + } else { + $errors[] = 'Missing pubkey for tags verification'; + } + + if (file_exists($home.'/keys.dev.pub')) { + $io->write('Dev Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.dev.pub')); + } else { + $errors[] = 'Missing pubkey for dev verification'; + } + + if ($errors) { + $errors[] = 'Run composer self-update --update-keys to set them up'; + } + + return $errors ?: true; + } + + /** + * @return string|\Exception|true + */ + private function checkVersion(Config $config) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $versionsUtil = new Versions($config, $this->httpDownloader); + try { + $latest = $versionsUtil->getLatest(); + } catch (\Exception $e) { + return $e; + } + + if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') { + return 'You are not running the latest '.$versionsUtil->getChannel().' version, run `composer self-update` to update ('.Composer::VERSION.' => '.$latest['version'].')'; + } + + return true; + } + + /** + * @return string|true + */ + private function checkComposerAudit(Config $config) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $auditor = new Auditor(); + $repoSet = new RepositorySet(); + $installedJson = new JsonFile(__DIR__ . '/../../../vendor/composer/installed.json'); + if (!$installedJson->exists()) { + return 'Could not find Composer\'s installed.json, this must be a non-standard Composer installation.'; + } + + $localRepo = new FilesystemRepository($installedJson); + $version = Composer::getVersion(); + $packages = $localRepo->getCanonicalPackages(); + if ($version !== '@package_version@') { + $versionParser = new VersionParser(); + $normalizedVersion = $versionParser->normalize($version); + $rootPkg = new RootPackage('composer/composer', $normalizedVersion, $version); + $packages[] = $rootPkg; + } + $repoSet->addRepository(new ComposerRepository(['type' => 'composer', 'url' => 'https://packagist.org'], new NullIO(), $config, $this->httpDownloader)); + + try { + $io = new BufferIO(); + $result = $auditor->audit($io, $repoSet, $packages, Auditor::FORMAT_TABLE, true, [], Auditor::ABANDONED_IGNORE); + } catch (\Throwable $e) { + return 'Failed performing audit: '.$e->getMessage().''; + } + + if ($result > 0) { + return 'Audit found some issues:' . PHP_EOL . $io->getOutput(); + } + + return true; + } + + private function getCurlVersion(): string + { + if (extension_loaded('curl')) { + if (!HttpDownloader::isCurlEnabled()) { + return 'disabled via disable_functions, using php streams fallback, which reduces performance'; + } + + $version = curl_version(); + $libzVersion = isset($version['libz_version']) && $version['libz_version'] !== '' ? $version['libz_version'] : 'missing'; + $brotliVersion = isset($version['brotli_version']) && $version['brotli_version'] !== '' ? $version['brotli_version'] : 'missing'; + $sslVersion = isset($version['ssl_version']) && $version['ssl_version'] !== '' ? $version['ssl_version'] : 'missing'; + $hasZstd = isset($version['features']) && defined('CURL_VERSION_ZSTD') && 0 !== ($version['features'] & CURL_VERSION_ZSTD); + $httpVersions = '1.0, 1.1'; + if (isset($version['features']) && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $version['features']) !== 0) { + $httpVersions .= ', 2'; + } + if (isset($version['features']) && \defined('CURL_VERSION_HTTP3') && ($version['features'] & CURL_VERSION_HTTP3) !== 0) { + $httpVersions .= ', 3'; + } + + return ''.$version['version'].' '. + 'libz '.$libzVersion.' '. + 'brotli '.$brotliVersion.' '. + 'zstd '.($hasZstd ? 'supported' : 'missing').' '. + 'ssl '.$sslVersion.' '. + 'HTTP '.$httpVersions.''; + } + + return 'missing, using php streams fallback, which reduces performance'; + } + + /** + * @param bool|string|string[]|\Exception $result + */ + private function outputResult($result): void + { + $io = $this->getIO(); + if (true === $result) { + $io->write('OK'); + + return; + } + + $hadError = false; + $hadWarning = false; + if ($result instanceof \Exception) { + $result = '['.get_class($result).'] '.$result->getMessage().''; + } + + if (!$result) { + // falsey results should be considered as an error, even if there is nothing to output + $hadError = true; + } else { + if (!is_array($result)) { + $result = [$result]; + } + foreach ($result as $message) { + if (false !== strpos($message, '')) { + $hadError = true; + } elseif (false !== strpos($message, '')) { + $hadWarning = true; + } + } + } + + if ($hadError) { + $io->write('FAIL'); + $this->exitCode = max($this->exitCode, 2); + } elseif ($hadWarning) { + $io->write('WARNING'); + $this->exitCode = max($this->exitCode, 1); + } + + if ($result) { + foreach ($result as $message) { + $io->write(trim($message)); + } + } + } + + /** + * @return string|true + */ + private function checkPlatform() + { + $output = ''; + $out = static function ($msg, $style) use (&$output): void { + $output .= '<'.$style.'>'.$msg.''.PHP_EOL; + }; + + // code below taken from getcomposer.org/installer, any changes should be made there and replicated here + $errors = []; + $warnings = []; + $displayIniMessage = false; + + $iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage(); + $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; + + if (!function_exists('json_decode')) { + $errors['json'] = true; + } + + if (!extension_loaded('Phar')) { + $errors['phar'] = true; + } + + if (!extension_loaded('filter')) { + $errors['filter'] = true; + } + + if (!extension_loaded('hash')) { + $errors['hash'] = true; + } + + if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { + $errors['iconv_mbstring'] = true; + } + + if (!filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) { + $errors['allow_url_fopen'] = true; + } + + if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) { + $errors['ioncube'] = ioncube_loader_version(); + } + + if (\PHP_VERSION_ID < 70205) { + $errors['php'] = PHP_VERSION; + } + + if (!extension_loaded('openssl')) { + $errors['openssl'] = true; + } + + if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) { + $warnings['openssl_version'] = true; + } + + if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) { + $warnings['apc_cli'] = true; + } + + if (!extension_loaded('zlib')) { + $warnings['zlib'] = true; + } + + ob_start(); + phpinfo(INFO_GENERAL); + $phpinfo = ob_get_clean(); + if (is_string($phpinfo) && Preg::isMatchStrictGroups('{Configure Command(?: *| *=> *)(.*?)(?:|$)}m', $phpinfo, $match)) { + $configure = $match[1]; + + if (str_contains($configure, '--enable-sigchild')) { + $warnings['sigchild'] = true; + } + + if (str_contains($configure, '--with-curlwrappers')) { + $warnings['curlwrappers'] = true; + } + } + + if (filter_var(ini_get('xdebug.profiler_enabled'), FILTER_VALIDATE_BOOLEAN)) { + $warnings['xdebug_profile'] = true; + } elseif (XdebugHandler::isXdebugActive()) { + $warnings['xdebug_loaded'] = true; + } + + if (defined('PHP_WINDOWS_VERSION_BUILD') + && (version_compare(PHP_VERSION, '7.2.23', '<') + || (version_compare(PHP_VERSION, '7.3.0', '>=') + && version_compare(PHP_VERSION, '7.3.10', '<')))) { + $warnings['onedrive'] = PHP_VERSION; + } + + if (extension_loaded('uopz') + && !(filter_var(ini_get('uopz.disable'), FILTER_VALIDATE_BOOLEAN) + || filter_var(ini_get('uopz.exit'), FILTER_VALIDATE_BOOLEAN))) { + $warnings['uopz'] = true; + } + + if (!empty($errors)) { + foreach ($errors as $error => $current) { + switch ($error) { + case 'json': + $text = PHP_EOL."The json extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-json"; + break; + + case 'phar': + $text = PHP_EOL."The phar extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-phar"; + break; + + case 'filter': + $text = PHP_EOL."The filter extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-filter"; + break; + + case 'hash': + $text = PHP_EOL."The hash extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-hash"; + break; + + case 'iconv_mbstring': + $text = PHP_EOL."The iconv OR mbstring extension is required and both are missing.".PHP_EOL; + $text .= "Install either of them or recompile php without --disable-iconv"; + break; + + case 'php': + $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 7.2.5 or higher."; + break; + + case 'allow_url_fopen': + $text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL; + $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; + $text .= " allow_url_fopen = On"; + $displayIniMessage = true; + break; + + case 'ioncube': + $text = PHP_EOL."Your ionCube Loader extension ($current) is incompatible with Phar files.".PHP_EOL; + $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:".PHP_EOL; + $text .= " zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so"; + $displayIniMessage = true; + break; + + case 'openssl': + $text = PHP_EOL."The openssl extension is missing, which means that secure HTTPS transfers are impossible.".PHP_EOL; + $text .= "If possible you should enable it or recompile php with --with-openssl"; + break; + + default: + throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown error type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $error)); + } + $out($text, 'error'); + } + + $output .= PHP_EOL; + } + + if (!empty($warnings)) { + foreach ($warnings as $warning => $current) { + switch ($warning) { + case 'apc_cli': + $text = "The apc.enable_cli setting is incorrect.".PHP_EOL; + $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; + $text .= " apc.enable_cli = Off"; + $displayIniMessage = true; + break; + + case 'zlib': + $text = 'The zlib extension is not loaded, this can slow down Composer a lot.'.PHP_EOL; + $text .= 'If possible, enable it or recompile php with --with-zlib'.PHP_EOL; + $displayIniMessage = true; + break; + + case 'sigchild': + $text = "PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL; + $text .= "Recompile it without this flag if possible, see also:".PHP_EOL; + $text .= " https://bugs.php.net/bug.php?id=22999"; + break; + + case 'curlwrappers': + $text = "PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL; + $text .= " Recompile it without this flag if possible"; + break; + + case 'openssl_version': + // Attempt to parse version number out, fallback to whole string value. + $opensslVersion = strstr(trim(strstr(OPENSSL_VERSION_TEXT, ' ')), ' ', true); + $opensslVersion = $opensslVersion ?: OPENSSL_VERSION_TEXT; + + $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1.".PHP_EOL; + $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above."; + break; + + case 'xdebug_loaded': + $text = "The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL; + $text .= " Disabling it when using Composer is recommended."; + break; + + case 'xdebug_profile': + $text = "The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL; + $text .= "Add the following to the end of your `php.ini` to disable it:".PHP_EOL; + $text .= " xdebug.profiler_enabled = 0"; + $displayIniMessage = true; + break; + + case 'onedrive': + $text = "The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.".PHP_EOL; + $text .= "Upgrade your PHP ({$current}) to use this location with Composer.".PHP_EOL; + break; + + case 'uopz': + $text = "The uopz extension ignores exit calls and may not work with all Composer commands.".PHP_EOL; + $text .= "Disabling it when using Composer is recommended."; + break; + + default: + throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown warning type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $warning)); + } + $out($text, 'comment'); + } + } + + if ($displayIniMessage) { + $out($iniMessage, 'comment'); + } + + if (in_array(Platform::getEnv('COMPOSER_IPRESOLVE'), ['4', '6'], true)) { + $warnings['ipresolve'] = true; + $out('The COMPOSER_IPRESOLVE env var is set to ' . Platform::getEnv('COMPOSER_IPRESOLVE') .' which may result in network failures below.', 'comment'); + } + + return count($warnings) === 0 && count($errors) === 0 ? true : $output; + } + + /** + * Check if allow_url_fopen is ON + * + * @return string|true + */ + private function checkConnectivity() + { + if (!ini_get('allow_url_fopen')) { + return 'SKIP Because allow_url_fopen is missing.'; + } + + return true; + } + + /** + * @return string|true + */ + private function checkConnectivityAndComposerNetworkHttpEnablement() + { + $result = $this->checkConnectivity(); + if ($result !== true) { + return $result; + } + + $result = $this->checkComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + return true; + } + + /** + * Check if Composer network is enabled for HTTP/S + * + * @return string|true + */ + private function checkComposerNetworkHttpEnablement() + { + if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { + return 'SKIP Network is disabled by COMPOSER_DISABLE_NETWORK.'; + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php b/vendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php new file mode 100644 index 000000000..6c0753947 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php @@ -0,0 +1,146 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class DumpAutoloadCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('dump-autoload') + ->setAliases(['dumpautoload']) + ->setDescription('Dumps the autoloader') + ->setDefinition([ + new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'), + new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything.'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('strict-psr', null, InputOption::VALUE_NONE, 'Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.'), + new InputOption('strict-ambiguous', null, InputOption::VALUE_NONE, 'Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.'), + ]) + ->setHelp( + <<php composer.phar dump-autoload + +Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $installationManager = $composer->getInstallationManager(); + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $package = $composer->getPackage(); + $config = $composer->getConfig(); + + $missingDependencies = false; + foreach ($localRepo->getCanonicalPackages() as $localPkg) { + $installPath = $installationManager->getInstallPath($localPkg); + if ($installPath !== null && file_exists($installPath) === false) { + $missingDependencies = true; + $this->getIO()->write('Not all dependencies are installed. Make sure to run a "composer install" to install missing dependencies'); + + break; + } + } + + $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu') || $config->get('apcu-autoloader'); + + if ($input->getOption('strict-psr') && !$optimize && !$authoritative) { + throw new \InvalidArgumentException('--strict-psr mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.'); + } + if ($input->getOption('strict-ambiguous') && !$optimize && !$authoritative) { + throw new \InvalidArgumentException('--strict-ambiguous mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.'); + } + + if ($authoritative) { + $this->getIO()->write('Generating optimized autoload files (authoritative)'); + } elseif ($optimize) { + $this->getIO()->write('Generating optimized autoload files'); + } else { + $this->getIO()->write('Generating autoload files'); + } + + $generator = $composer->getAutoloadGenerator(); + if ($input->getOption('dry-run')) { + $generator->setDryRun(true); + } + if ($input->getOption('no-dev')) { + $generator->setDevMode(false); + } + if ($input->getOption('dev')) { + if ($input->getOption('no-dev')) { + throw new \InvalidArgumentException('You can not use both --no-dev and --dev as they conflict with each other.'); + } + $generator->setDevMode(true); + } + $generator->setClassMapAuthoritative($authoritative); + $generator->setRunScripts(true); + $generator->setApcu($apcu, $apcuPrefix); + $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); + $classMap = $generator->dump( + $config, + $localRepo, + $package, + $installationManager, + 'composer', + $optimize, + null, + $composer->getLocker(), + $input->getOption('strict-ambiguous') + ); + $numberOfClasses = count($classMap); + + if ($authoritative) { + $this->getIO()->write('Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes'); + } elseif ($optimize) { + $this->getIO()->write('Generated optimized autoload files containing '. $numberOfClasses .' classes'); + } else { + $this->getIO()->write('Generated autoload files'); + } + + if ($missingDependencies || ($input->getOption('strict-psr') && count($classMap->getPsrViolations()) > 0)) { + return 1; + } + + if ($input->getOption('strict-ambiguous') && count($classMap->getAmbiguousClasses(false)) > 0) { + return 2; + } + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ExecCommand.php b/vendor/composer/composer/src/Composer/Command/ExecCommand.php new file mode 100644 index 000000000..1deda895a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ExecCommand.php @@ -0,0 +1,150 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Console\Input\InputArgument; + +/** + * @author Davey Shafik + */ +class ExecCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('exec') + ->setDescription('Executes a vendored binary/script') + ->setDefinition([ + new InputOption('list', 'l', InputOption::VALUE_NONE), + new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit', null, function () { + return $this->getBinaries(false); + }), + new InputArgument( + 'args', + InputArgument::IS_ARRAY | InputArgument::OPTIONAL, + 'Arguments to pass to the binary. Use -- to separate from composer arguments' + ), + ]) + ->setHelp( + <<getBinaries(false); + if (count($binaries) === 0) { + return; + } + + if ($input->getArgument('binary') !== null || $input->getOption('list')) { + return; + } + + $io = $this->getIO(); + /** @var int $binary */ + $binary = $io->select( + 'Binary to run: ', + $binaries, + '', + 1, + 'Invalid binary name "%s"' + ); + + $input->setArgument('binary', $binaries[$binary]); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + if ($input->getOption('list') || null === $input->getArgument('binary')) { + $bins = $this->getBinaries(true); + if ([] === $bins) { + $binDir = $composer->getConfig()->get('bin-dir'); + + throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)"); + } + + $this->getIO()->write( + <<Available binaries: +EOT + ); + + foreach ($bins as $bin) { + $this->getIO()->write( + <<- $bin +EOT + ); + } + + return 0; + } + + $binary = $input->getArgument('binary'); + + $dispatcher = $composer->getEventDispatcher(); + $dispatcher->addListener('__exec_command', $binary); + + // If the CWD was modified, we restore it to what it was initially, as it was + // most likely modified by the global command, and we want exec to run in the local working directory + // not the global one + if (getcwd() !== $this->getApplication()->getInitialWorkingDirectory() && $this->getApplication()->getInitialWorkingDirectory() !== false) { + try { + chdir($this->getApplication()->getInitialWorkingDirectory()); + } catch (\Exception $e) { + throw new \RuntimeException('Could not switch back to working directory "'.$this->getApplication()->getInitialWorkingDirectory().'"', 0, $e); + } + } + + return $dispatcher->dispatchScript('__exec_command', true, $input->getArgument('args')); + } + + /** + * @return list + */ + private function getBinaries(bool $forDisplay): array + { + $composer = $this->requireComposer(); + $binDir = $composer->getConfig()->get('bin-dir'); + $bins = glob($binDir . '/*'); + $localBins = $composer->getPackage()->getBinaries(); + if ($forDisplay) { + $localBins = array_map(static function ($e) { + return "$e (local)"; + }, $localBins); + } + + $binaries = []; + foreach (array_merge($bins, $localBins) as $bin) { + // skip .bat copies + if (isset($previousBin) && $bin === $previousBin.'.bat') { + continue; + } + + $previousBin = $bin; + $binaries[] = basename($bin); + } + + return $binaries; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/FundCommand.php b/vendor/composer/composer/src/Composer/Command/FundCommand.php new file mode 100644 index 000000000..44e355ebb --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/FundCommand.php @@ -0,0 +1,151 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Json\JsonFile; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Semver\Constraint\MatchAllConstraint; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Nicolas Grekas + * @author Jordi Boggiano + */ +class FundCommand extends BaseCommand +{ + protected function configure(): void + { + $this->setName('fund') + ->setDescription('Discover how to help fund the maintenance of your dependencies') + ->setDefinition([ + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['text', 'json']), + ]) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $repo = $composer->getRepositoryManager()->getLocalRepository(); + $remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); + $fundings = []; + + $packagesToLoad = []; + foreach ($repo->getPackages() as $package) { + if ($package instanceof AliasPackage) { + continue; + } + $packagesToLoad[$package->getName()] = new MatchAllConstraint(); + } + + // load all packages dev versions in parallel + $result = $remoteRepos->loadPackages($packagesToLoad, ['dev' => BasePackage::STABILITY_DEV], []); + + // collect funding data from default branches + foreach ($result['packages'] as $package) { + if ( + !$package instanceof AliasPackage + && $package instanceof CompletePackageInterface + && $package->isDefaultBranch() + && $package->getFunding() + && isset($packagesToLoad[$package->getName()]) + ) { + $fundings = $this->insertFundingData($fundings, $package); + unset($packagesToLoad[$package->getName()]); + } + } + + // collect funding from installed packages if none was found in the default branch above + foreach ($repo->getPackages() as $package) { + if ($package instanceof AliasPackage || !isset($packagesToLoad[$package->getName()])) { + continue; + } + + if ($package instanceof CompletePackageInterface && $package->getFunding()) { + $fundings = $this->insertFundingData($fundings, $package); + } + } + + ksort($fundings); + + $io = $this->getIO(); + + $format = $input->getOption('format'); + if (!in_array($format, ['text', 'json'])) { + $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); + + return 1; + } + + if ($fundings && $format === 'text') { + $prev = null; + + $io->write('The following packages were found in your dependencies which publish funding information:'); + + foreach ($fundings as $vendor => $links) { + $io->write(''); + $io->write(sprintf("%s", $vendor)); + foreach ($links as $url => $packages) { + $line = sprintf(' %s', implode(', ', $packages)); + + if ($prev !== $line) { + $io->write($line); + $prev = $line; + } + + $io->write(sprintf(' %s', OutputFormatter::escape($url), $url)); + } + } + + $io->write(""); + $io->write("Please consider following these links and sponsoring the work of package authors!"); + $io->write("Thank you!"); + } elseif ($format === 'json') { + $io->write(JsonFile::encode($fundings)); + } else { + $io->write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!"); + } + + return 0; + } + + /** + * @param mixed[] $fundings + * @return mixed[] + */ + private function insertFundingData(array $fundings, CompletePackageInterface $package): array + { + foreach ($package->getFunding() as $fundingOption) { + [$vendor, $packageName] = explode('/', $package->getPrettyName()); + // ignore malformed funding entries + if (empty($fundingOption['url'])) { + continue; + } + $url = $fundingOption['url']; + if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && Preg::isMatch('{^https://github.com/([^/]+)$}', $url, $match)) { + $url = 'https://github.com/sponsors/'.$match[1]; + } + $fundings[$vendor][$url][] = $packageName; + } + + return $fundings; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/GlobalCommand.php b/vendor/composer/composer/src/Composer/Command/GlobalCommand.php new file mode 100644 index 000000000..90b601797 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/GlobalCommand.php @@ -0,0 +1,170 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class GlobalCommand extends BaseCommand +{ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $application = $this->getApplication(); + if ($input->mustSuggestArgumentValuesFor('command-name')) { + $suggestions->suggestValues(array_values(array_filter( + array_map(static function (Command $command) { + return $command->isHidden() ? null : $command->getName(); + }, $application->all()), + static function (?string $cmd) { + return $cmd !== null; + } + ))); + + return; + } + + if ($application->has($commandName = $input->getArgument('command-name'))) { + $input = $this->prepareSubcommandInput($input, true); + $input = CompletionInput::fromString($input->__toString(), 2); + $command = $application->find($commandName); + $command->mergeApplicationDefinition(); + + $input->bind($command->getDefinition()); + $command->complete($input, $suggestions); + } + } + + protected function configure(): void + { + $this + ->setName('global') + ->setDescription('Allows running commands in the global composer dir ($COMPOSER_HOME)') + ->setDefinition([ + new InputArgument('command-name', InputArgument::REQUIRED, ''), + new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), + ]) + ->setHelp( + <<\AppData\Roaming\Composer on Windows +and /home//.composer on unix systems. + +If your system uses freedesktop.org standards, then it will first check +XDG_CONFIG_HOME or default to /home//.config/composer + +Note: This path may vary depending on customizations to bin-dir in +composer.json or the environmental variable COMPOSER_BIN_DIR. + +Read more at https://getcomposer.org/doc/03-cli.md#global +EOT + ) + ; + } + + /** + * @throws \Symfony\Component\Console\Exception\ExceptionInterface + */ + public function run(InputInterface $input, OutputInterface $output): int + { + // TODO remove for Symfony 6+ as it is then in the interface + if (!method_exists($input, '__toString')) { // @phpstan-ignore-line + throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); + } + + // extract real command name + $tokens = Preg::split('{\s+}', $input->__toString()); + $args = []; + foreach ($tokens as $token) { + if ($token && $token[0] !== '-') { + $args[] = $token; + if (count($args) >= 2) { + break; + } + } + } + + // show help for this command if no command was found + if (count($args) < 2) { + return parent::run($input, $output); + } + + $input = $this->prepareSubcommandInput($input); + + return $this->getApplication()->run($input, $output); + } + + private function prepareSubcommandInput(InputInterface $input, bool $quiet = false): StringInput + { + // TODO remove for Symfony 6+ as it is then in the interface + if (!method_exists($input, '__toString')) { // @phpstan-ignore-line + throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); + } + + // The COMPOSER env var should not apply to the global execution scope + if (Platform::getEnv('COMPOSER')) { + Platform::clearEnv('COMPOSER'); + } + + // change to global dir + $config = Factory::createConfig(); + $home = $config->get('home'); + + if (!is_dir($home)) { + $fs = new Filesystem(); + $fs->ensureDirectoryExists($home); + if (!is_dir($home)) { + throw new \RuntimeException('Could not create home directory'); + } + } + + try { + chdir($home); + } catch (\Exception $e) { + throw new \RuntimeException('Could not switch to home directory "'.$home.'"', 0, $e); + } + if (!$quiet) { + $this->getIO()->writeError('Changed current directory to '.$home.''); + } + + // create new input without "global" command prefix + $input = new StringInput(Preg::replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1)); + $this->getApplication()->resetComposer(); + + return $input; + } + + /** + * @inheritDoc + */ + public function isProxyCommand(): bool + { + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/HomeCommand.php b/vendor/composer/composer/src/Composer/Command/HomeCommand.php new file mode 100644 index 000000000..3547faec7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/HomeCommand.php @@ -0,0 +1,165 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Package\CompletePackageInterface; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Robert Schönthal + */ +class HomeCommand extends BaseCommand +{ + use CompletionTrait; + + /** + * @inheritDoc + */ + protected function configure(): void + { + $this + ->setName('browse') + ->setAliases(['home']) + ->setDescription('Opens the package\'s repository URL or homepage in your browser') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY, 'Package(s) to browse to.', null, $this->suggestInstalledPackage()), + new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'), + new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'), + ]) + ->setHelp( + <<initializeRepos(); + $io = $this->getIO(); + $return = 0; + + $packages = $input->getArgument('packages'); + if (count($packages) === 0) { + $io->writeError('No package specified, opening homepage for the root package'); + $packages = [$this->requireComposer()->getPackage()->getName()]; + } + + foreach ($packages as $packageName) { + $handled = false; + $packageExists = false; + foreach ($repos as $repo) { + foreach ($repo->findPackages($packageName) as $package) { + $packageExists = true; + if ($package instanceof CompletePackageInterface && $this->handlePackage($package, $input->getOption('homepage'), $input->getOption('show'))) { + $handled = true; + break 2; + } + } + } + + if (!$packageExists) { + $return = 1; + $io->writeError('Package '.$packageName.' not found'); + } + + if (!$handled) { + $return = 1; + $io->writeError(''.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.''); + } + } + + return $return; + } + + private function handlePackage(CompletePackageInterface $package, bool $showHomepage, bool $showOnly): bool + { + $support = $package->getSupport(); + $url = $support['source'] ?? $package->getSourceUrl(); + if (!$url || $showHomepage) { + $url = $package->getHomepage(); + } + + if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) { + return false; + } + + if ($showOnly) { + $this->getIO()->write(sprintf('%s', $url)); + } else { + $this->openBrowser($url); + } + + return true; + } + + /** + * opens a url in your system default browser + */ + private function openBrowser(string $url): void + { + $process = new ProcessExecutor($this->getIO()); + if (Platform::isWindows()) { + $process->execute(['start', '"web"', 'explorer', $url], $output); + + return; + } + + $linux = $process->execute(['which', 'xdg-open'], $output); + $osx = $process->execute(['which', 'open'], $output); + + if (0 === $linux) { + $process->execute(['xdg-open', $url], $output); + } elseif (0 === $osx) { + $process->execute(['open', $url], $output); + } else { + $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url); + } + } + + /** + * Initializes repositories + * + * Returns an array of repos in order they should be checked in + * + * @return RepositoryInterface[] + */ + private function initializeRepos(): array + { + $composer = $this->tryComposer(); + + if ($composer) { + return array_merge( + [new RootPackageRepository(clone $composer->getPackage())], // root package + [$composer->getRepositoryManager()->getLocalRepository()], // installed packages + $composer->getRepositoryManager()->getRepositories() // remotes + ); + } + + return RepositoryFactory::defaultReposWithDefaultManager($this->getIO()); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/InitCommand.php b/vendor/composer/composer/src/Composer/Command/InitCommand.php new file mode 100644 index 000000000..2a54da313 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/InitCommand.php @@ -0,0 +1,662 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\Json\JsonFile; +use Composer\Json\JsonValidationException; +use Composer\Package\BasePackage; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Spdx\SpdxLicenses; +use Composer\Util\Filesystem; +use Composer\Util\Silencer; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Helper\FormatterHelper; + +/** + * @author Justin Rainbow + * @author Jordi Boggiano + */ +class InitCommand extends BaseCommand +{ + use CompletionTrait; + use PackageDiscoveryTrait; + + /** @var array */ + private $gitConfig; + + /** + * @inheritDoc + */ + protected function configure(): void + { + $this + ->setName('init') + ->setDescription('Creates a basic composer.json file in current directory') + ->setDefinition([ + new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'), + new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'), + new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'), + new InputOption('type', null, InputOption::VALUE_REQUIRED, 'Type of package (e.g. library, project, metapackage, composer-plugin)'), + new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'), + new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), + new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), + new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::STABILITIES)).')'), + new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'), + new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'), + new InputOption('autoload', 'a', InputOption::VALUE_REQUIRED, 'Add PSR-4 autoload mapping. Maps your package\'s namespace to the provided directory. (Expects a relative path, e.g. src/)'), + ]) + ->setHelp( + <<init command creates a basic composer.json file +in the current directory. + +php composer.phar init + +Read more at https://getcomposer.org/doc/03-cli.md#init +EOT + ) + ; + } + + /** + * @throws \Seld\JsonLint\ParsingException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = $this->getIO(); + + $allowlist = ['name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license', 'autoload']; + $options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist)), static function ($val) { return $val !== null && $val !== []; }); + + if (isset($options['name']) && !Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $options['name'])) { + throw new \InvalidArgumentException( + 'The package name '.$options['name'].' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' + ); + } + + if (isset($options['author'])) { + $options['authors'] = $this->formatAuthors($options['author']); + unset($options['author']); + } + + $repositories = $input->getOption('repository'); + if (count($repositories) > 0) { + $config = Factory::createConfig($io); + foreach ($repositories as $repo) { + $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, true); + } + } + + if (isset($options['stability'])) { + $options['minimum-stability'] = $options['stability']; + unset($options['stability']); + } + + $options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass; + if ([] === $options['require']) { + $options['require'] = new \stdClass; + } + + if (isset($options['require-dev'])) { + $options['require-dev'] = $this->formatRequirements($options['require-dev']); + if ([] === $options['require-dev']) { + $options['require-dev'] = new \stdClass; + } + } + + // --autoload - create autoload object + $autoloadPath = null; + if (isset($options['autoload'])) { + $autoloadPath = $options['autoload']; + $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); + $options['autoload'] = (object) [ + 'psr-4' => [ + $namespace . '\\' => $autoloadPath, + ], + ]; + } + + $file = new JsonFile(Factory::getComposerFile()); + $json = JsonFile::encode($options); + + if ($input->isInteractive()) { + $io->writeError(['', $json, '']); + if (!$io->askConfirmation('Do you confirm generation [yes]? ')) { + $io->writeError('Command aborted'); + + return 1; + } + } else { + $io->writeError('Writing '.$file->getPath()); + } + + $file->write($options); + try { + $file->validateSchema(JsonFile::LAX_SCHEMA); + } catch (JsonValidationException $e) { + $io->writeError('Schema validation error, aborting'); + $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); + $io->writeError($e->getMessage() . ':' . PHP_EOL . $errors); + Silencer::call('unlink', $file->getPath()); + + return 1; + } + + // --autoload - Create src folder + if ($autoloadPath) { + $filesystem = new Filesystem(); + $filesystem->ensureDirectoryExists($autoloadPath); + + // dump-autoload only for projects without added dependencies. + if (!$this->hasDependencies($options)) { + $this->runDumpAutoloadCommand($output); + } + } + + if ($input->isInteractive() && is_dir('.git')) { + $ignoreFile = realpath('.gitignore'); + + if (false === $ignoreFile) { + $ignoreFile = realpath('.') . '/.gitignore'; + } + + if (!$this->hasVendorIgnore($ignoreFile)) { + $question = 'Would you like the vendor directory added to your .gitignore [yes]? '; + + if ($io->askConfirmation($question)) { + $this->addVendorIgnore($ignoreFile); + } + } + } + + $question = 'Would you like to install dependencies now [yes]? '; + if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question)) { + $this->updateDependencies($output); + } + + // --autoload - Show post-install configuration info + if ($autoloadPath) { + $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); + + $io->writeError('PSR-4 autoloading configured. Use "namespace '.$namespace.';" in '.$autoloadPath); + $io->writeError('Include the Composer autoloader with: require \'vendor/autoload.php\';'); + } + + return 0; + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + parent::initialize($input, $output); + + if (!$input->isInteractive()) { + if ($input->getOption('name') === null) { + $input->setOption('name', $this->getDefaultPackageName()); + } + + if ($input->getOption('author') === null) { + $input->setOption('author', $this->getDefaultAuthor()); + } + } + } + + /** + * @inheritDoc + */ + protected function interact(InputInterface $input, OutputInterface $output): void + { + $io = $this->getIO(); + /** @var FormatterHelper $formatter */ + $formatter = $this->getHelperSet()->get('formatter'); + + // initialize repos if configured + $repositories = $input->getOption('repository'); + if (count($repositories) > 0) { + $config = Factory::createConfig($io); + $io->loadConfiguration($config); + $repoManager = RepositoryFactory::manager($io, $config); + + $repos = [new PlatformRepository]; + $createDefaultPackagistRepo = true; + foreach ($repositories as $repo) { + $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true); + if ( + (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) + || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) + ) { + $createDefaultPackagistRepo = false; + continue; + } + $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig, $repoManager); + } + + if ($createDefaultPackagistRepo) { + $repos[] = RepositoryFactory::createRepo($io, $config, [ + 'type' => 'composer', + 'url' => 'https://repo.packagist.org', + ], $repoManager); + } + + $this->repos = new CompositeRepository($repos); + unset($repos, $config, $repositories); + } + + $io->writeError([ + '', + $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true), + '', + ]); + + // namespace + $io->writeError([ + '', + 'This command will guide you through creating your composer.json config.', + '', + ]); + + $name = $input->getOption('name') ?? $this->getDefaultPackageName(); + + $name = $io->askAndValidate( + 'Package name (/) ['.$name.']: ', + static function ($value) use ($name) { + if (null === $value) { + return $name; + } + + if (!Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $value)) { + throw new \InvalidArgumentException( + 'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' + ); + } + + return $value; + }, + null, + $name + ); + $input->setOption('name', $name); + + $description = $input->getOption('description') ?: null; + $description = $io->ask( + 'Description ['.$description.']: ', + $description + ); + $input->setOption('description', $description); + + $author = $input->getOption('author') ?? $this->getDefaultAuthor(); + + $author = $io->askAndValidate( + 'Author ['.(is_string($author) ? ''.$author.', ' : '') . 'n to skip]: ', + function ($value) use ($author) { + if ($value === 'n' || $value === 'no') { + return; + } + $value = $value ?: $author; + $author = $this->parseAuthorString($value ?? ''); + + if ($author['email'] === null) { + return $author['name']; + } + + return sprintf('%s <%s>', $author['name'], $author['email']); + }, + null, + $author + ); + $input->setOption('author', $author); + + $minimumStability = $input->getOption('stability') ?: null; + $minimumStability = $io->askAndValidate( + 'Minimum Stability ['.$minimumStability.']: ', + static function ($value) use ($minimumStability) { + if (null === $value) { + return $minimumStability; + } + + if (!isset(BasePackage::STABILITIES[$value])) { + throw new \InvalidArgumentException( + 'Invalid minimum stability "'.$value.'". Must be empty or one of: '. + implode(', ', array_keys(BasePackage::STABILITIES)) + ); + } + + return $value; + }, + null, + $minimumStability + ); + $input->setOption('stability', $minimumStability); + + $type = $input->getOption('type'); + $type = $io->ask( + 'Package Type (e.g. library, project, metapackage, composer-plugin) ['.$type.']: ', + $type + ); + if ($type === '' || $type === false) { + $type = null; + } + $input->setOption('type', $type); + + if (null === $license = $input->getOption('license')) { + if (!empty($_SERVER['COMPOSER_DEFAULT_LICENSE'])) { + $license = $_SERVER['COMPOSER_DEFAULT_LICENSE']; + } + } + + $license = $io->ask( + 'License ['.$license.']: ', + $license + ); + $spdx = new SpdxLicenses(); + if (null !== $license && !$spdx->validate($license) && $license !== 'proprietary') { + throw new \InvalidArgumentException('Invalid license provided: '.$license.'. Only SPDX license identifiers (https://spdx.org/licenses/) or "proprietary" are accepted.'); + } + $input->setOption('license', $license); + + $io->writeError(['', 'Define your dependencies.', '']); + + // prepare to resolve dependencies + $repos = $this->getRepos(); + $preferredStability = $minimumStability ?: 'stable'; + $platformRepo = null; + if ($repos instanceof CompositeRepository) { + foreach ($repos->getRepositories() as $candidateRepo) { + if ($candidateRepo instanceof PlatformRepository) { + $platformRepo = $candidateRepo; + break; + } + } + } + + $question = 'Would you like to define your dependencies (require) interactively [yes]? '; + $require = $input->getOption('require'); + $requirements = []; + if (count($require) > 0 || $io->askConfirmation($question)) { + $requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability); + } + $input->setOption('require', $requirements); + + $question = 'Would you like to define your dev dependencies (require-dev) interactively [yes]? '; + $requireDev = $input->getOption('require-dev'); + $devRequirements = []; + if (count($requireDev) > 0 || $io->askConfirmation($question)) { + $devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability); + } + $input->setOption('require-dev', $devRequirements); + + // --autoload - input and validation + $autoload = $input->getOption('autoload') ?: 'src/'; + $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); + $autoload = $io->askAndValidate( + 'Add PSR-4 autoload mapping? Maps namespace "'.$namespace.'" to the entered relative path. ['.$autoload.', n to skip]: ', + static function ($value) use ($autoload) { + if (null === $value) { + return $autoload; + } + + if ($value === 'n' || $value === 'no') { + return; + } + + $value = $value ?: $autoload; + + if (!Preg::isMatch('{^[^/][A-Za-z0-9\-_/]+/$}', $value)) { + throw new \InvalidArgumentException(sprintf( + 'The src folder name "%s" is invalid. Please add a relative path with tailing forward slash. [A-Za-z0-9_-/]+/', + $value + )); + } + + return $value; + }, + null, + $autoload + ); + $input->setOption('autoload', $autoload); + } + + /** + * @return array{name: string, email: string|null} + */ + private function parseAuthorString(string $author): array + { + if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P.+?)>)?$/u', $author, $match)) { + if (null !== $match['email'] && !$this->isValidEmail($match['email'])) { + throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"'); + } + + return [ + 'name' => trim($match['name']), + 'email' => $match['email'], + ]; + } + + throw new \InvalidArgumentException( + 'Invalid author string. Must be in the formats: '. + 'Jane Doe or John Smith ' + ); + } + + /** + * @return array + */ + protected function formatAuthors(string $author): array + { + $author = $this->parseAuthorString($author); + if (null === $author['email']) { + unset($author['email']); + } + + return [$author]; + } + + /** + * Extract namespace from package's vendor name. + * + * new_projects.acme-extra/package-name becomes "NewProjectsAcmeExtra\PackageName" + */ + public function namespaceFromPackageName(string $packageName): ?string + { + if (!$packageName || strpos($packageName, '/') === false) { + return null; + } + + $namespace = array_map( + static function ($part): string { + $part = Preg::replace('/[^a-z0-9]/i', ' ', $part); + $part = ucwords($part); + + return str_replace(' ', '', $part); + }, + explode('/', $packageName) + ); + + return implode('\\', $namespace); + } + + /** + * @return array + */ + protected function getGitConfig(): array + { + if (null !== $this->gitConfig) { + return $this->gitConfig; + } + + $process = new ProcessExecutor($this->getIO()); + + if (0 === $process->execute(['git', 'config', '-l'], $output)) { + $this->gitConfig = []; + Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $output, $matches); + foreach ($matches[1] as $key => $match) { + $this->gitConfig[$match] = $matches[2][$key]; + } + + return $this->gitConfig; + } + + return $this->gitConfig = []; + } + + /** + * Checks the local .gitignore file for the Composer vendor directory. + * + * Tested patterns include: + * "/$vendor" + * "$vendor" + * "$vendor/" + * "/$vendor/" + * "/$vendor/*" + * "$vendor/*" + */ + protected function hasVendorIgnore(string $ignoreFile, string $vendor = 'vendor'): bool + { + if (!file_exists($ignoreFile)) { + return false; + } + + $pattern = sprintf('{^/?%s(/\*?)?$}', preg_quote($vendor)); + + $lines = file($ignoreFile, FILE_IGNORE_NEW_LINES); + foreach ($lines as $line) { + if (Preg::isMatch($pattern, $line)) { + return true; + } + } + + return false; + } + + protected function addVendorIgnore(string $ignoreFile, string $vendor = '/vendor/'): void + { + $contents = ""; + if (file_exists($ignoreFile)) { + $contents = file_get_contents($ignoreFile); + + if (strpos($contents, "\n") !== 0) { + $contents .= "\n"; + } + } + + file_put_contents($ignoreFile, $contents . $vendor. "\n"); + } + + protected function isValidEmail(string $email): bool + { + // assume it's valid if we can't validate it + if (!function_exists('filter_var')) { + return true; + } + + return false !== filter_var($email, FILTER_VALIDATE_EMAIL); + } + + private function updateDependencies(OutputInterface $output): void + { + try { + $updateCommand = $this->getApplication()->find('update'); + $this->getApplication()->resetComposer(); + $updateCommand->run(new ArrayInput([]), $output); + } catch (\Exception $e) { + $this->getIO()->writeError('Could not update dependencies. Run `composer update` to see more information.'); + } + } + + private function runDumpAutoloadCommand(OutputInterface $output): void + { + try { + $command = $this->getApplication()->find('dump-autoload'); + $this->getApplication()->resetComposer(); + $command->run(new ArrayInput([]), $output); + } catch (\Exception $e) { + $this->getIO()->writeError('Could not run dump-autoload.'); + } + } + + /** + * @param array> $options + */ + private function hasDependencies(array $options): bool + { + $requires = (array) $options['require']; + $devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : []; + + return !empty($requires) || !empty($devRequires); + } + + private function sanitizePackageNameComponent(string $name): string + { + $name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); + $name = strtolower($name); + $name = Preg::replace('{^[_.-]+|[_.-]+$|[^a-z0-9_.-]}u', '', $name); + $name = Preg::replace('{([_.-]){2,}}u', '$1', $name); + + return $name; + } + + private function getDefaultPackageName(): string + { + $git = $this->getGitConfig(); + $cwd = realpath("."); + $name = basename($cwd); + $name = $this->sanitizePackageNameComponent($name); + + $vendor = $name; + if (!empty($_SERVER['COMPOSER_DEFAULT_VENDOR'])) { + $vendor = $_SERVER['COMPOSER_DEFAULT_VENDOR']; + } elseif (isset($git['github.user'])) { + $vendor = $git['github.user']; + } elseif (!empty($_SERVER['USERNAME'])) { + $vendor = $_SERVER['USERNAME']; + } elseif (!empty($_SERVER['USER'])) { + $vendor = $_SERVER['USER']; + } elseif (get_current_user()) { + $vendor = get_current_user(); + } + + $vendor = $this->sanitizePackageNameComponent($vendor); + + return $vendor . '/' . $name; + } + + private function getDefaultAuthor(): ?string + { + $git = $this->getGitConfig(); + + if (!empty($_SERVER['COMPOSER_DEFAULT_AUTHOR'])) { + $author_name = $_SERVER['COMPOSER_DEFAULT_AUTHOR']; + } elseif (isset($git['user.name'])) { + $author_name = $git['user.name']; + } + + if (!empty($_SERVER['COMPOSER_DEFAULT_EMAIL'])) { + $author_email = $_SERVER['COMPOSER_DEFAULT_EMAIL']; + } elseif (isset($git['user.email'])) { + $author_email = $git['user.email']; + } + + if (isset($author_name, $author_email)) { + return sprintf('%s <%s>', $author_name, $author_email); + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/InstallCommand.php b/vendor/composer/composer/src/Composer/Command/InstallCommand.php new file mode 100644 index 000000000..5042a2504 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/InstallCommand.php @@ -0,0 +1,146 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Installer; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Advisory\Auditor; +use Composer\Util\HttpDownloader; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + * @author Ryan Weaver + * @author Konstantin Kudryashov + * @author Nils Adermann + */ +class InstallCommand extends BaseCommand +{ + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('install') + ->setAliases(['i']) + ->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json') + ->setDefinition([ + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), + new InputOption('download-only', null, InputOption::VALUE_NONE, 'Download only, do not install packages.'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), + new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), + new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var). Only applies when no lock file is present.'), + new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Do not use, only defined here to catch misuse of the install command.'), + new InputOption('audit', null, InputOption::VALUE_NONE, 'Run an audit after installation is complete.'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'), + ]) + ->setHelp( + <<install command reads the composer.lock file from +the current directory, processes it, and downloads and installs all the +libraries and dependencies outlined in that file. If the file does not +exist it will look for composer.json and do the same. + +php composer.phar install + +Read more at https://getcomposer.org/doc/03-cli.md#install-i +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = $this->getIO(); + if ($input->getOption('dev')) { + $io->writeError('You are using the deprecated option "--dev". It has no effect and will break in Composer 3.'); + } + if ($input->getOption('no-suggest')) { + $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); + } + + $args = $input->getArgument('packages'); + if (count($args) > 0) { + $io->writeError('Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.'); + + return 1; + } + + if ($input->getOption('no-install')) { + $io->writeError('Invalid option "--no-install". Use "composer update --no-install" instead if you are trying to update the composer.lock file.'); + + return 1; + } + + $composer = $this->requireComposer(); + + if (!$composer->getLocker()->isLocked() && !HttpDownloader::isCurlEnabled()) { + $io->writeError('Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.'); + } + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $install = Installer::create($io, $composer); + + $config = $composer->getConfig(); + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); + + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); + + $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); + + $install + ->setDryRun($input->getOption('dry-run')) + ->setDownloadOnly($input->getOption('download-only')) + ->setVerbose($input->getOption('verbose')) + ->setPreferSource($preferSource) + ->setPreferDist($preferDist) + ->setDevMode(!$input->getOption('no-dev')) + ->setDumpAutoloader(!$input->getOption('no-autoloader')) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu, $apcuPrefix) + ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) + ->setAuditConfig($this->createAuditConfig($composer->getConfig(), $input)) + ->setErrorOnAudit($input->getOption('audit')) + ; + + if ($input->getOption('no-plugins')) { + $install->disablePlugins(); + } + + return $install->run(); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/LicensesCommand.php b/vendor/composer/composer/src/Composer/Command/LicensesCommand.php new file mode 100644 index 000000000..08e308904 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/LicensesCommand.php @@ -0,0 +1,167 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Console\Input\InputOption; +use Composer\Json\JsonFile; +use Composer\Package\CompletePackageInterface; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Repository\RepositoryUtils; +use Composer\Util\PackageInfo; +use Composer\Util\PackageSorter; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Benoît Merlet + */ +class LicensesCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('licenses') + ->setDescription('Shows information about licenses of dependencies') + ->setDefinition([ + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text, json or summary', 'text', ['text', 'json', 'summary']), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), + new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows licenses from the lock file instead of installed packages.'), + ]) + ->setHelp( + <<requireComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $root = $composer->getPackage(); + + if ($input->getOption('locked')) { + if (!$composer->getLocker()->isLocked()) { + throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked'); + } + $locker = $composer->getLocker(); + $repo = $locker->getLockedRepository(!$input->getOption('no-dev')); + $packages = $repo->getPackages(); + } else { + $repo = $composer->getRepositoryManager()->getLocalRepository(); + + if ($input->getOption('no-dev')) { + $packages = RepositoryUtils::filterRequiredPackages($repo->getPackages(), $root); + } else { + $packages = $repo->getPackages(); + } + } + + $packages = PackageSorter::sortPackagesAlphabetically($packages); + $io = $this->getIO(); + + switch ($format = $input->getOption('format')) { + case 'text': + $io->write('Name: '.$root->getPrettyName().''); + $io->write('Version: '.$root->getFullPrettyVersion().''); + $io->write('Licenses: '.(implode(', ', $root->getLicense()) ?: 'none').''); + $io->write('Dependencies:'); + $io->write(''); + + $table = new Table($output); + $table->setStyle('compact'); + $table->setHeaders(['Name', 'Version', 'Licenses']); + foreach ($packages as $package) { + $link = PackageInfo::getViewSourceOrHomepageUrl($package); + if ($link !== null) { + $name = ''.$package->getPrettyName().''; + } else { + $name = $package->getPrettyName(); + } + + $table->addRow([ + $name, + $package->getFullPrettyVersion(), + implode(', ', $package instanceof CompletePackageInterface ? $package->getLicense() : []) ?: 'none', + ]); + } + $table->render(); + break; + + case 'json': + $dependencies = []; + foreach ($packages as $package) { + $dependencies[$package->getPrettyName()] = [ + 'version' => $package->getFullPrettyVersion(), + 'license' => $package instanceof CompletePackageInterface ? $package->getLicense() : [], + ]; + } + + $io->write(JsonFile::encode([ + 'name' => $root->getPrettyName(), + 'version' => $root->getFullPrettyVersion(), + 'license' => $root->getLicense(), + 'dependencies' => $dependencies, + ])); + break; + + case 'summary': + $usedLicenses = []; + foreach ($packages as $package) { + $licenses = $package instanceof CompletePackageInterface ? $package->getLicense() : []; + if (count($licenses) === 0) { + $licenses[] = 'none'; + } + foreach ($licenses as $licenseName) { + if (!isset($usedLicenses[$licenseName])) { + $usedLicenses[$licenseName] = 0; + } + $usedLicenses[$licenseName]++; + } + } + + // Sort licenses so that the most used license will appear first + arsort($usedLicenses, SORT_NUMERIC); + + $rows = []; + foreach ($usedLicenses as $usedLicense => $numberOfDependencies) { + $rows[] = [$usedLicense, $numberOfDependencies]; + } + + $symfonyIo = new SymfonyStyle($input, $output); + $symfonyIo->table( + ['License', 'Number of dependencies'], + $rows + ); + break; + default: + throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format)); + } + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/OutdatedCommand.php b/vendor/composer/composer/src/Composer/Command/OutdatedCommand.php new file mode 100644 index 000000000..0fea6dc09 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/OutdatedCommand.php @@ -0,0 +1,135 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\ArrayInput; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class OutdatedCommand extends BaseCommand +{ + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('outdated') + ->setDescription('Shows a list of installed packages that have updates available, including their latest version') + ->setDefinition([ + new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestInstalledPackage(false)), + new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only packages that are outdated (this is the default, but present here for compat with `show`'), + new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show all installed packages with their latest versions'), + new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows updates for packages from the lock file, regardless of what is currently in vendor dir'), + new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), + new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), + new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'), + new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'), + new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'), + new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first.'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), + new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'), + ]) + ->setHelp( + <<green (=): Dependency is in the latest version and is up to date. +- yellow (~): Dependency has a new version available that includes backwards + compatibility breaks according to semver, so upgrade when you can but it + may involve work. +- red (!): Dependency has a new version that is semver-compatible and you should upgrade it. + +Read more at https://getcomposer.org/doc/03-cli.md#outdated +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $args = [ + 'command' => 'show', + '--latest' => true, + ]; + if ($input->getOption('no-interaction')) { + $args['--no-interaction'] = true; + } + if ($input->getOption('no-plugins')) { + $args['--no-plugins'] = true; + } + if ($input->getOption('no-scripts')) { + $args['--no-scripts'] = true; + } + if ($input->getOption('no-cache')) { + $args['--no-cache'] = true; + } + if (!$input->getOption('all')) { + $args['--outdated'] = true; + } + if ($input->getOption('direct')) { + $args['--direct'] = true; + } + if (null !== $input->getArgument('package')) { + $args['package'] = $input->getArgument('package'); + } + if ($input->getOption('strict')) { + $args['--strict'] = true; + } + if ($input->getOption('major-only')) { + $args['--major-only'] = true; + } + if ($input->getOption('minor-only')) { + $args['--minor-only'] = true; + } + if ($input->getOption('patch-only')) { + $args['--patch-only'] = true; + } + if ($input->getOption('locked')) { + $args['--locked'] = true; + } + if ($input->getOption('no-dev')) { + $args['--no-dev'] = true; + } + if ($input->getOption('sort-by-age')) { + $args['--sort-by-age'] = true; + } + $args['--ignore-platform-req'] = $input->getOption('ignore-platform-req'); + if ($input->getOption('ignore-platform-reqs')) { + $args['--ignore-platform-reqs'] = true; + } + $args['--format'] = $input->getOption('format'); + $args['--ignore'] = $input->getOption('ignore'); + + $input = new ArrayInput($args); + + return $this->getApplication()->run($input, $output); + } + + /** + * @inheritDoc + */ + public function isProxyCommand(): bool + { + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php b/vendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php new file mode 100644 index 000000000..0bbd2a48c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php @@ -0,0 +1,466 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Repository\RepositorySet; +use Composer\Semver\Constraint\Constraint; +use Composer\Util\Filesystem; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @internal + */ +trait PackageDiscoveryTrait +{ + /** @var ?CompositeRepository */ + private $repos; + /** @var RepositorySet[] */ + private $repositorySets; + + protected function getRepos(): CompositeRepository + { + if (null === $this->repos) { + $this->repos = new CompositeRepository(array_merge( + [new PlatformRepository], + RepositoryFactory::defaultReposWithDefaultManager($this->getIO()) + )); + } + + return $this->repos; + } + + /** + * @param key-of|null $minimumStability + */ + private function getRepositorySet(InputInterface $input, ?string $minimumStability = null): RepositorySet + { + $key = $minimumStability ?? 'default'; + + if (!isset($this->repositorySets[$key])) { + $this->repositorySets[$key] = $repositorySet = new RepositorySet($minimumStability ?? $this->getMinimumStability($input)); + $repositorySet->addRepository($this->getRepos()); + } + + return $this->repositorySets[$key]; + } + + /** + * @return key-of + */ + private function getMinimumStability(InputInterface $input): string + { + if ($input->hasOption('stability')) { // @phpstan-ignore-line as InitCommand does have this option but not all classes using this trait do + return VersionParser::normalizeStability($input->getOption('stability') ?? 'stable'); + } + + // @phpstan-ignore-next-line as RequireCommand does not have the option above so this code is reachable there + $file = Factory::getComposerFile(); + if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode((string) file_get_contents($file), true))) { + if (isset($composer['minimum-stability'])) { + return VersionParser::normalizeStability($composer['minimum-stability']); + } + } + + return 'stable'; + } + + /** + * @param array $requires + * + * @return array + * @throws \Exception + */ + final protected function determineRequirements(InputInterface $input, OutputInterface $output, array $requires = [], ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $useBestVersionConstraint = true, bool $fixed = false): array + { + if (count($requires) > 0) { + $requires = $this->normalizeRequirements($requires); + $result = []; + $io = $this->getIO(); + + foreach ($requires as $requirement) { + if (isset($requirement['version']) && Preg::isMatch('{^\d+(\.\d+)?$}', $requirement['version'])) { + $io->writeError('The "'.$requirement['version'].'" constraint for "'.$requirement['name'].'" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints'); + } + + if (!isset($requirement['version'])) { + // determine the best version automatically + [$name, $version] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $requirement['name'], $platformRepo, $preferredStability, $fixed); + + // replace package name from packagist.org + $requirement['name'] = $name; + + if ($useBestVersionConstraint) { + $requirement['version'] = $version; + $io->writeError(sprintf( + 'Using version %s for %s', + $requirement['version'], + $requirement['name'] + )); + } else { + $requirement['version'] = 'guess'; + } + } + + $result[] = $requirement['name'] . ' ' . $requirement['version']; + } + + return $result; + } + + $versionParser = new VersionParser(); + + // Collect existing packages + $composer = $this->tryComposer(); + $installedRepo = null; + if (null !== $composer) { + $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); + } + $existingPackages = []; + if (null !== $installedRepo) { + foreach ($installedRepo->getPackages() as $package) { + $existingPackages[] = $package->getName(); + } + } + unset($composer, $installedRepo); + + $io = $this->getIO(); + while (null !== $package = $io->ask('Search for a package: ')) { + $matches = $this->getRepos()->search($package); + + if (count($matches) > 0) { + // Remove existing packages from search results. + foreach ($matches as $position => $foundPackage) { + if (in_array($foundPackage['name'], $existingPackages, true)) { + unset($matches[$position]); + } + } + $matches = array_values($matches); + + $exactMatch = false; + foreach ($matches as $match) { + if ($match['name'] === $package) { + $exactMatch = true; + break; + } + } + + // no match, prompt which to pick + if (!$exactMatch) { + $providers = $this->getRepos()->getProviders($package); + if (count($providers) > 0) { + array_unshift($matches, ['name' => $package, 'description' => '']); + } + + $choices = []; + foreach ($matches as $position => $foundPackage) { + $abandoned = ''; + if (isset($foundPackage['abandoned'])) { + if (is_string($foundPackage['abandoned'])) { + $replacement = sprintf('Use %s instead', $foundPackage['abandoned']); + } else { + $replacement = 'No replacement was suggested'; + } + $abandoned = sprintf('Abandoned. %s.', $replacement); + } + + $choices[] = sprintf(' %5s %s %s', "[$position]", $foundPackage['name'], $abandoned); + } + + $io->writeError([ + '', + sprintf('Found %s packages matching %s', count($matches), $package), + '', + ]); + + $io->writeError($choices); + $io->writeError(''); + + $validator = static function (string $selection) use ($matches, $versionParser) { + if ('' === $selection) { + return false; + } + + if (is_numeric($selection) && isset($matches[(int) $selection])) { + $package = $matches[(int) $selection]; + + return $package['name']; + } + + if (Preg::isMatch('{^\s*(?P[\S/]+)(?:\s+(?P\S+))?\s*$}', $selection, $packageMatches)) { + if (isset($packageMatches['version'])) { + // parsing `acme/example ~2.3` + + // validate version constraint + $versionParser->parseConstraints($packageMatches['version']); + + return $packageMatches['name'].' '.$packageMatches['version']; + } + + // parsing `acme/example` + return $packageMatches['name']; + } + + throw new \Exception('Not a valid selection'); + }; + + $package = $io->askAndValidate( + 'Enter package # to add, or the complete package name if it is not listed: ', + $validator, + 3, + '' + ); + } + + // no constraint yet, determine the best version automatically + if (false !== $package && false === strpos($package, ' ')) { + $validator = static function (string $input) { + $input = trim($input); + + return strlen($input) > 0 ? $input : false; + }; + + $constraint = $io->askAndValidate( + 'Enter the version constraint to require (or leave blank to use the latest version): ', + $validator, + 3, + '' + ); + + if (false === $constraint) { + [, $constraint] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $package, $platformRepo, $preferredStability); + + $io->writeError(sprintf( + 'Using version %s for %s', + $constraint, + $package + )); + } + + $package .= ' '.$constraint; + } + + if (false !== $package) { + $requires[] = $package; + $existingPackages[] = explode(' ', $package)[0]; + } + } + } + + return $requires; + } + + /** + * Given a package name, this determines the best version to use in the require key. + * + * This returns a version with the ~ operator prefixed when possible. + * + * @throws \InvalidArgumentException + * @return array{string, string} name version + */ + private function findBestVersionAndNameForPackage(IOInterface $io, InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $fixed = false): array + { + // handle ignore-platform-reqs flag if present + if ($input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) { + $platformRequirementFilter = $this->getPlatformRequirementFilter($input); + } else { + $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); + } + + // find the latest version allowed in this repo set + $repoSet = $this->getRepositorySet($input); + $versionSelector = new VersionSelector($repoSet, $platformRepo); + $effectiveMinimumStability = $this->getMinimumStability($input); + + $package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, 0, $this->getIO()); + + if (false === $package) { + // platform packages can not be found in the pool in versions other than the local platform's has + // so if platform reqs are ignored we just take the user's word for it + if ($platformRequirementFilter->isIgnored($name)) { + return [$name, '*']; + } + + // Check if it is a virtual package provided by others + $providers = $repoSet->getProviders($name); + if (count($providers) > 0) { + $constraint = '*'; + if ($input->isInteractive()) { + $constraint = $this->getIO()->askAndValidate('Package "'.$name.'" does not exist but is provided by '.count($providers).' packages. Which version constraint would you like to use? [*] ', static function ($value) { + $parser = new VersionParser(); + $parser->parseConstraints($value); + + return $value; + }, 3, '*'); + } + + return [$name, $constraint]; + } + + // Check whether the package requirements were the problem + if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) { + throw new \InvalidArgumentException(sprintf( + 'Package %s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo), + $name + )); + } + // Check whether the minimum stability was the problem but the package exists + if (false !== ($package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { + // we must first verify if a valid package would be found in a lower priority repository + if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) { + throw new \InvalidArgumentException( + 'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.' + ); + } + + throw new \InvalidArgumentException(sprintf( + 'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.', + $name, + $effectiveMinimumStability + )); + } + // Check whether the PHP version was the problem for all versions + if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { + $additional = ''; + if (false === $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll())) { + $additional = PHP_EOL.PHP_EOL.'Additionally, the package was only found with a stability of "'.$candidate->getStability().'" while your minimum stability is "'.$effectiveMinimumStability.'".'; + } + + throw new \InvalidArgumentException(sprintf( + 'Could not find package %s in any version matching your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo) . '%s', + $name, + $additional + )); + } + + // Check for similar names/typos + $similar = $this->findSimilar($name); + if (count($similar) > 0) { + if (in_array($name, $similar, true)) { + throw new \InvalidArgumentException(sprintf( + "Could not find package %s. It was however found via repository search, which indicates a consistency issue with the repository.", + $name + )); + } + + if ($input->isInteractive()) { + $result = $io->select("Could not find package $name.\nPick one of these or leave empty to abort:", $similar, false, 1); + if ($result !== false) { + return $this->findBestVersionAndNameForPackage($io, $input, $similar[$result], $platformRepo, $preferredStability, $fixed); + } + } + + throw new \InvalidArgumentException(sprintf( + "Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n %s", + $name, + implode("\n ", $similar) + )); + } + + throw new \InvalidArgumentException(sprintf( + 'Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).', + $name, + $effectiveMinimumStability + )); + } + + return [ + $package->getPrettyName(), + $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package), + ]; + } + + /** + * @return array + */ + private function findSimilar(string $package): array + { + try { + if (null === $this->repos) { + throw new \LogicException('findSimilar was called before $this->repos was initialized'); + } + $results = $this->repos->search($package); + } catch (\Throwable $e) { + if ($e instanceof \LogicException) { + throw $e; + } + + // ignore search errors + return []; + } + $similarPackages = []; + + $installedRepo = $this->requireComposer()->getRepositoryManager()->getLocalRepository(); + + foreach ($results as $result) { + if (null !== $installedRepo->findPackage($result['name'], '*')) { + // Ignore installed package + continue; + } + $similarPackages[$result['name']] = levenshtein($package, $result['name']); + } + asort($similarPackages); + + return array_keys(array_slice($similarPackages, 0, 5)); + } + + private function getPlatformExceptionDetails(PackageInterface $candidate, ?PlatformRepository $platformRepo = null): string + { + $details = []; + if (null === $platformRepo) { + return ''; + } + + foreach ($candidate->getRequires() as $link) { + if (!PlatformRepository::isPlatformPackage($link->getTarget())) { + continue; + } + $platformPkg = $platformRepo->findPackage($link->getTarget(), '*'); + if (null === $platformPkg) { + if ($platformRepo->isPlatformPackageDisabled($link->getTarget())) { + $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is disabled by your platform config. Enable it again with "composer config platform.'.$link->getTarget().' --unset".'; + } else { + $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is not present.'; + } + continue; + } + if (!$link->getConstraint()->matches(new Constraint('==', $platformPkg->getVersion()))) { + $platformPkgVersion = $platformPkg->getPrettyVersion(); + $platformExtra = $platformPkg->getExtra(); + if (isset($platformExtra['config.platform']) && $platformPkg instanceof CompletePackageInterface) { + $platformPkgVersion .= ' ('.$platformPkg->getDescription().')'; + } + $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' which does not match your installed version '.$platformPkgVersion.'.'; + } + } + + if (count($details) === 0) { + return ''; + } + + return ':'.PHP_EOL.' - ' . implode(PHP_EOL.' - ', $details); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ProhibitsCommand.php b/vendor/composer/composer/src/Composer/Command/ProhibitsCommand.php new file mode 100644 index 000000000..49e06694e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ProhibitsCommand.php @@ -0,0 +1,59 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; + +/** + * @author Niels Keurentjes + */ +class ProhibitsCommand extends BaseDependencyCommand +{ + use CompletionTrait; + + /** + * Configure command metadata. + */ + protected function configure(): void + { + $this + ->setName('prohibits') + ->setAliases(['why-not']) + ->setDescription('Shows which packages prevent the given package from being installed') + ->setDefinition([ + new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestAvailablePackage()), + new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::REQUIRED, 'Version constraint, which version you expected to be installed'), + new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), + new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), + new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'), + ]) + ->setHelp( + <<php composer.phar prohibits composer/composer + +Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + return parent::doExecute($input, $output, true); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ReinstallCommand.php b/vendor/composer/composer/src/Composer/Command/ReinstallCommand.php new file mode 100644 index 000000000..cb7882a9c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ReinstallCommand.php @@ -0,0 +1,198 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Transaction; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Script\ScriptEvents; +use Composer\Util\Platform; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class ReinstallCommand extends BaseCommand +{ + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('reinstall') + ->setDescription('Uninstalls and reinstalls the given package names') + ->setDefinition([ + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter packages to reinstall by type(s)', null, $this->suggestInstalledPackageTypes(false)), + new InputArgument('packages', InputArgument::IS_ARRAY, 'List of package names to reinstall, can include a wildcard (*) to match any substring.', null, $this->suggestInstalledPackage(false)), + ]) + ->setHelp( + <<reinstall command looks up installed packages by name, +uninstalls them and reinstalls them. This lets you do a clean install +of a package if you messed with its files, or if you wish to change +the installation type using --prefer-install. + +php composer.phar reinstall acme/foo "acme/bar-*" + +Read more at https://getcomposer.org/doc/03-cli.md#reinstall +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = $this->getIO(); + + $composer = $this->requireComposer(); + + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $packagesToReinstall = []; + $packageNamesToReinstall = []; + if (\count($input->getOption('type')) > 0) { + if (\count($input->getArgument('packages')) > 0) { + throw new \InvalidArgumentException('You cannot specify package names and filter by type at the same time.'); + } + foreach ($localRepo->getCanonicalPackages() as $package) { + if (in_array($package->getType(), $input->getOption('type'), true)) { + $packagesToReinstall[] = $package; + $packageNamesToReinstall[] = $package->getName(); + } + } + } else { + if (\count($input->getArgument('packages')) === 0) { + throw new \InvalidArgumentException('You must pass one or more package names to be reinstalled.'); + } + foreach ($input->getArgument('packages') as $pattern) { + $patternRegexp = BasePackage::packageNameToRegexp($pattern); + $matched = false; + foreach ($localRepo->getCanonicalPackages() as $package) { + if (Preg::isMatch($patternRegexp, $package->getName())) { + $matched = true; + $packagesToReinstall[] = $package; + $packageNamesToReinstall[] = $package->getName(); + } + } + + if (!$matched) { + $io->writeError('Pattern "' . $pattern . '" does not match any currently installed packages.'); + } + } + } + + if (0 === \count($packagesToReinstall)) { + $io->writeError('Found no packages to reinstall, aborting.'); + + return 1; + } + + $uninstallOperations = []; + foreach ($packagesToReinstall as $package) { + $uninstallOperations[] = new UninstallOperation($package); + } + + // make sure we have a list of install operations ordered by dependency/plugins + $presentPackages = $localRepo->getPackages(); + $resultPackages = $presentPackages; + foreach ($presentPackages as $index => $package) { + if (in_array($package->getName(), $packageNamesToReinstall, true)) { + unset($presentPackages[$index]); + } + } + $transaction = new Transaction($presentPackages, $resultPackages); + $installOperations = $transaction->getOperations(); + + // reverse-sort the uninstalls based on the install order + $installOrder = []; + foreach ($installOperations as $index => $op) { + if ($op instanceof InstallOperation && !$op->getPackage() instanceof AliasPackage) { + $installOrder[$op->getPackage()->getName()] = $index; + } + } + usort($uninstallOperations, static function ($a, $b) use ($installOrder): int { + return $installOrder[$b->getPackage()->getName()] - $installOrder[$a->getPackage()->getName()]; + }); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'reinstall', $input, $output); + $eventDispatcher = $composer->getEventDispatcher(); + $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent); + + $config = $composer->getConfig(); + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); + + $installationManager = $composer->getInstallationManager(); + $downloadManager = $composer->getDownloadManager(); + $package = $composer->getPackage(); + + $installationManager->setOutputProgress(!$input->getOption('no-progress')); + if ($input->getOption('no-plugins')) { + $installationManager->disablePlugins(); + } + + $downloadManager->setPreferSource($preferSource); + $downloadManager->setPreferDist($preferDist); + + $devMode = $localRepo->getDevMode() !== null ? $localRepo->getDevMode() : true; + + Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); + $eventDispatcher->dispatchScript(ScriptEvents::PRE_INSTALL_CMD, $devMode); + + $installationManager->execute($localRepo, $uninstallOperations, $devMode); + $installationManager->execute($localRepo, $installOperations, $devMode); + + if (!$input->getOption('no-autoloader')) { + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); + + $generator = $composer->getAutoloadGenerator(); + $generator->setClassMapAuthoritative($authoritative); + $generator->setApcu($apcu, $apcuPrefix); + $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); + $generator->dump( + $config, + $localRepo, + $package, + $installationManager, + 'composer', + $optimize, + null, + $composer->getLocker() + ); + } + + $eventDispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, $devMode); + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/RemoveCommand.php b/vendor/composer/composer/src/Composer/Command/RemoveCommand.php new file mode 100644 index 000000000..e323c28f5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/RemoveCommand.php @@ -0,0 +1,315 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Config\JsonConfigSource; +use Composer\DependencyResolver\Request; +use Composer\Installer; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Json\JsonFile; +use Composer\Factory; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Package\BasePackage; +use Composer\Advisory\Auditor; + +/** + * @author Pierre du Plessis + * @author Jordi Boggiano + */ +class RemoveCommand extends BaseCommand +{ + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('remove') + ->setAliases(['rm', 'uninstall']) + ->setDescription('Removes a package from the require or require-dev') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.', null, $this->suggestRootRequirement()), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), + new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'), + new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), + new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var). (Deprecated, is now default behavior)'), + new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'), + new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'), + new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'), + new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), + new InputOption('unused', null, InputOption::VALUE_NONE, 'Remove all packages which are locked but not required by any other package.'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + ]) + ->setHelp( + <<remove command removes a package from the current +list of installed packages + +php composer.phar remove + +Read more at https://getcomposer.org/doc/03-cli.md#remove-rm +EOT + ) + ; + } + + /** + * @throws \Seld\JsonLint\ParsingException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($input->getArgument('packages') === [] && !$input->getOption('unused')) { + throw new InvalidArgumentException('Not enough arguments (missing: "packages").'); + } + + $packages = $input->getArgument('packages'); + $packages = array_map('strtolower', $packages); + + if ($input->getOption('unused')) { + $composer = $this->requireComposer(); + $locker = $composer->getLocker(); + if (!$locker->isLocked()) { + throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --unused'); + } + + $lockedPackages = $locker->getLockedRepository()->getPackages(); + + $required = []; + foreach (array_merge($composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires()) as $link) { + $required[$link->getTarget()] = true; + } + + do { + $found = false; + foreach ($lockedPackages as $index => $package) { + foreach ($package->getNames() as $name) { + if (isset($required[$name])) { + foreach ($package->getRequires() as $link) { + $required[$link->getTarget()] = true; + } + $found = true; + unset($lockedPackages[$index]); + break; + } + } + } + } while ($found); + + $unused = []; + foreach ($lockedPackages as $package) { + $unused[] = $package->getName(); + } + $packages = array_merge($packages, $unused); + + if (count($packages) === 0) { + $this->getIO()->writeError('No unused packages to remove'); + + return 0; + } + } + + $file = Factory::getComposerFile(); + + $jsonFile = new JsonFile($file); + /** @var array{require?: array, require-dev?: array} $composer */ + $composer = $jsonFile->read(); + $composerBackup = file_get_contents($jsonFile->getPath()); + + $json = new JsonConfigSource($jsonFile); + + $type = $input->getOption('dev') ? 'require-dev' : 'require'; + $altType = !$input->getOption('dev') ? 'require-dev' : 'require'; + $io = $this->getIO(); + + if ($input->getOption('update-with-dependencies')) { + $io->writeError('You are using the deprecated option "update-with-dependencies". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies.'); + } + + // make sure name checks are done case insensitively + foreach (['require', 'require-dev'] as $linkType) { + if (isset($composer[$linkType])) { + foreach ($composer[$linkType] as $name => $version) { + $composer[$linkType][strtolower($name)] = $name; + } + } + } + + $dryRun = $input->getOption('dry-run'); + $toRemove = []; + foreach ($packages as $package) { + if (isset($composer[$type][$package])) { + if ($dryRun) { + $toRemove[$type][] = $composer[$type][$package]; + } else { + $json->removeLink($type, $composer[$type][$package]); + } + } elseif (isset($composer[$altType][$package])) { + $io->writeError('' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); + if ($io->isInteractive()) { + if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ')) { + if ($dryRun) { + $toRemove[$altType][] = $composer[$altType][$package]; + } else { + $json->removeLink($altType, $composer[$altType][$package]); + } + } + } + } elseif (isset($composer[$type]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) > 0) { + foreach ($matches as $matchedPackage) { + if ($dryRun) { + $toRemove[$type][] = $matchedPackage; + } else { + $json->removeLink($type, $matchedPackage); + } + } + } elseif (isset($composer[$altType]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) > 0) { + foreach ($matches as $matchedPackage) { + $io->writeError('' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); + if ($io->isInteractive()) { + if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ')) { + if ($dryRun) { + $toRemove[$altType][] = $matchedPackage; + } else { + $json->removeLink($altType, $matchedPackage); + } + } + } + } + } else { + $io->writeError(''.$package.' is not required in your composer.json and has not been removed'); + } + } + + $io->writeError(''.$file.' has been updated'); + + if ($input->getOption('no-update')) { + return 0; + } + + if ($composer = $this->tryComposer()) { + $composer->getPluginManager()->deactivateInstalledPlugins(); + } + + // Update packages + $this->resetComposer(); + $composer = $this->requireComposer(); + + if ($dryRun) { + $rootPackage = $composer->getPackage(); + $links = [ + 'require' => $rootPackage->getRequires(), + 'require-dev' => $rootPackage->getDevRequires(), + ]; + foreach ($toRemove as $type => $names) { + foreach ($names as $name) { + unset($links[$type][$name]); + } + } + $rootPackage->setRequires($links['require']); + $rootPackage->setDevRequires($links['require-dev']); + } + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $allowPlugins = $composer->getConfig()->get('allow-plugins'); + $removedPlugins = is_array($allowPlugins) ? array_intersect(array_keys($allowPlugins), $packages) : []; + if (!$dryRun && is_array($allowPlugins) && count($removedPlugins) > 0) { + if (count($allowPlugins) === count($removedPlugins)) { + $json->removeConfigSetting('allow-plugins'); + } else { + foreach ($removedPlugins as $plugin) { + $json->removeConfigSetting('allow-plugins.'.$plugin); + } + } + } + + $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); + + $install = Installer::create($io, $composer); + + $updateDevMode = !$input->getOption('update-no-dev'); + $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); + $minimalChanges = $input->getOption('minimal-changes') || $composer->getConfig()->get('update-with-minimal-changes'); + + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; + $flags = ''; + if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; + $flags .= ' --with-all-dependencies'; + } elseif ($input->getOption('no-update-with-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; + $flags .= ' --with-dependencies'; + } + + $io->writeError('Running composer update '.implode(' ', $packages).$flags.''); + + $install + ->setVerbose($input->getOption('verbose')) + ->setDevMode($updateDevMode) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu, $apcuPrefix) + ->setUpdate(true) + ->setInstall(!$input->getOption('no-install')) + ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) + ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) + ->setDryRun($dryRun) + ->setAuditConfig($this->createAuditConfig($composer->getConfig(), $input)) + ->setMinimalUpdate($minimalChanges) + ; + + // if no lock is present, we do not do a partial update as + // this is not supported by the Installer + if ($composer->getLocker()->isLocked()) { + $install->setUpdateAllowList($packages); + } + + $status = $install->run(); + if ($status !== 0) { + $io->writeError("\n".'Removal failed, reverting '.$file.' to its original content.'); + file_put_contents($jsonFile->getPath(), $composerBackup); + } + + if (!$dryRun) { + foreach ($packages as $package) { + if ($composer->getRepositoryManager()->getLocalRepository()->findPackages($package)) { + $io->writeError('Removal failed, '.$package.' is still present, it may be required by another package. See `composer why '.$package.'`.'); + + return 2; + } + } + } + + return $status; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/RepositoryCommand.php b/vendor/composer/composer/src/Composer/Command/RepositoryCommand.php new file mode 100644 index 000000000..bcb22f2c4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/RepositoryCommand.php @@ -0,0 +1,300 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Manage repositories + * + * Examples: + * - composer repo list + * - composer repo add foo vcs https://github.com/acme/foo + * - composer repo remove foo + * - composer repo set-url foo https://git.example.org/acme/foo + * - composer repo get-url foo + * - composer repo disable packagist.org + * - composer repo enable packagist.org + */ +class RepositoryCommand extends BaseConfigCommand +{ + protected function configure(): void + { + $this + ->setName('repository') + ->setAliases(['repo']) + ->setDescription('Manages repositories') + ->setDefinition([ + new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), + new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), + new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lower priority) instead of prepending it'), + new InputOption('before', null, InputOption::VALUE_REQUIRED, 'When adding a repository, insert it before the given repository name', null, $this->suggestRepoNames()), + new InputOption('after', null, InputOption::VALUE_REQUIRED, 'When adding a repository, insert it after the given repository name', null, $this->suggestRepoNames()), + new InputArgument('action', InputArgument::OPTIONAL, 'Action to perform: list, add, remove, set-url, get-url, enable, disable', 'list', ['list', 'add', 'remove', 'set-url', 'get-url', 'enable', 'disable']), + new InputArgument('name', InputArgument::OPTIONAL, 'Repository name (or special name packagist.org for enable/disable)', null, $this->suggestRepoNames()), + new InputArgument('arg1', InputArgument::OPTIONAL, 'Type for add, or new URL for set-url, or JSON config for add', null, $this->suggestTypeForAdd()), + new InputArgument('arg2', InputArgument::OPTIONAL, 'URL for add (if not using JSON)'), + ]) + ->setHelp(<<getArgument('action')); + $name = $input->getArgument('name'); + $arg1 = $input->getArgument('arg1'); + $arg2 = $input->getArgument('arg2'); + + $this->config->merge($this->configFile->read(), $this->configFile->getPath()); + $repos = $this->config->getRepositories(); + + switch ($action) { + case 'list': + case 'ls': + case 'show': + $this->listRepositories($repos); + + return 0; + + case 'add': + if ($name === null) { + throw new \RuntimeException('You must pass a repository name. Example: composer repo add foo vcs https://example.org'); + } + if ($arg1 === null) { + throw new \RuntimeException('You must pass the type and a url, or a JSON string.'); + } + if (is_string($arg1) && Preg::isMatch('{^\s*\{}', $arg1)) { + // JSON config + $repoConfig = JsonFile::parseJson($arg1); + } else { + if ($arg2 === null) { + throw new \RuntimeException('You must pass the type and a url. Example: composer repo add foo vcs https://example.org'); + } + $repoConfig = ['type' => (string) $arg1, 'url' => (string) $arg2]; + } + + // ordering options + $before = $input->getOption('before'); + $after = $input->getOption('after'); + if ($before !== null && $after !== null) { + throw new \RuntimeException('You can not combine --before and --after'); + } + + if ($before !== null || $after !== null) { + if ($repoConfig === false) { + throw new \RuntimeException('Cannot use --before/--after with boolean repository values'); + } + + $this->configSource->insertRepository((string) $name, $repoConfig, $before ?? $after, $after !== null ? 1 : 0); + + return 0; + } + + $this->configSource->addRepository((string) $name, $repoConfig, (bool) $input->getOption('append')); + + return 0; + + case 'remove': + case 'rm': + case 'delete': + if ($name === null) { + throw new \RuntimeException('You must pass the repository name to remove.'); + } + $this->configSource->removeRepository((string) $name); + if (in_array($name, ['packagist', 'packagist.org'], true)) { + $this->configSource->addRepository('packagist.org', false); + } + + return 0; + + case 'set-url': + case 'seturl': + if ($name === null || $arg1 === null) { + throw new \RuntimeException('Usage: composer repo set-url '); + } + + $this->configSource->setRepositoryUrl($name, $arg1); + + return 0; + + case 'get-url': + case 'geturl': + if ($name === null) { + throw new \RuntimeException('Usage: composer repo get-url '); + } + if (isset($repos[$name]) && is_array($repos[$name])) { + $url = $repos[$name]['url'] ?? null; + if (!is_string($url)) { + throw new \InvalidArgumentException('The '.$name.' repository does not have a URL'); + } + $this->getIO()->write($url); + + return 0; + } + // try named-list: find entry with matching name + if (is_array($repos)) { + foreach ($repos as $val) { + if (is_array($val) && isset($val['name']) && $val['name'] === $name) { + $url = $val['url'] ?? null; + if (!is_string($url)) { + throw new \InvalidArgumentException('The '.$name.' repository does not have a URL'); + } + $this->getIO()->write($url); + + return 0; + } + } + } + + throw new \InvalidArgumentException('There is no '.$name.' repository defined'); + + case 'disable': + if ($name === null) { + throw new \RuntimeException('Usage: composer repo disable packagist.org'); + } + if (in_array($name, ['packagist', 'packagist.org'], true)) { + // special handling mirrors ConfigCommand behavior + $this->configSource->addRepository('packagist.org', false, (bool) $input->getOption('append')); + + return 0; + } + throw new \RuntimeException('Only packagist.org can be enabled/disabled using this command. Use add/remove for other repositories.'); + + case 'enable': + if ($name === null) { + throw new \RuntimeException('Usage: composer repo enable packagist.org'); + } + if (in_array($name, ['packagist', 'packagist.org'], true)) { + // Remove a false flag by setting packagist.org to true via removing the key + // Here we re-add the default by removing overrides + $this->configSource->removeRepository('packagist.org'); + + return 0; + } + throw new \RuntimeException('Only packagist.org can be enabled/disabled using this command.'); + + default: + throw new \InvalidArgumentException('Unknown action "'.$action.'". Use list, add, remove, set-url, get-url, enable, disable'); + } + } + + /** + * @param array $repos + */ + private function listRepositories(array $repos): void + { + $io = $this->getIO(); + + $packagistPresent = false; + foreach ($repos as $key => $repo) { + if (isset($repo['type'], $repo['url']) && $repo['type'] === 'composer' && str_ends_with((string) parse_url($repo['url'], PHP_URL_HOST), 'packagist.org')) { + $packagistPresent = true; + break; + } + } + if (!$packagistPresent) { + $repos[] = ['packagist.org' => false]; + } + + if ($repos === []) { + $io->write('No repositories configured'); + + return; + } + + foreach ($repos as $key => $repo) { + if ($repo === false) { + $io->write('['.$key.'] disabled'); + continue; + } + + if (is_array($repo)) { + if (1 === \count($repo) && false === current($repo)) { + $io->write('['.array_key_first($repo).'] disabled'); + continue; + } + + $name = $repo['name'] ?? $key; + $type = $repo['type'] ?? 'unknown'; + $url = $repo['url'] ?? JsonFile::encode($repo); + $io->write('['.$name.'] '.$type.' '.$url); + } + } + } + + private function suggestTypeForAdd(): \Closure + { + return static function (CompletionInput $input): array { + if ($input->getArgument('action') === 'add') { + return ['composer', 'vcs', 'artifact', 'path']; + } + + return []; + }; + } + + private function suggestRepoNames(): \Closure + { + return function (CompletionInput $input): array { + if (in_array($input->getArgument('action'), ['enable', 'disable'], true)) { + return ['packagist.org']; + } + + if (!in_array($input->getArgument('action'), ['remove', 'set-url', 'get-url'], true)) { + return []; + } + + $config = Factory::createConfig(); + $configFile = new JsonFile($this->getComposerConfigFile($input, $config)); + + $data = $configFile->read(); + $repos = []; + + foreach (($data['repositories'] ?? []) as $repo) { + if (isset($repo['name'])) { + $repos[] = $repo['name']; + } + } + + sort($repos); + + return $repos; + }; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/RequireCommand.php b/vendor/composer/composer/src/Composer/Command/RequireCommand.php new file mode 100644 index 000000000..9c4907612 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/RequireCommand.php @@ -0,0 +1,639 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\DependencyResolver\Request; +use Composer\Package\AliasPackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Loader\RootPackageLoader; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Repository\RepositorySet; +use Composer\Util\Filesystem; +use Composer\Util\PackageSorter; +use Seld\Signal\SignalHandler; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Factory; +use Composer\Installer; +use Composer\Installer\InstallerEvents; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Package\Version\VersionParser; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\BasePackage; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\IO\IOInterface; +use Composer\Advisory\Auditor; +use Composer\Util\Silencer; + +/** + * @author Jérémy Romey + * @author Jordi Boggiano + */ +class RequireCommand extends BaseCommand +{ + use CompletionTrait; + use PackageDiscoveryTrait; + + /** @var bool */ + private $newlyCreated; + /** @var bool */ + private $firstRequire; + /** @var JsonFile */ + private $json; + /** @var string */ + private $file; + /** @var string */ + private $composerBackup; + /** @var string file name */ + private $lock; + /** @var ?string contents before modification if the lock file exists */ + private $lockBackup; + /** @var bool */ + private $dependencyResolutionCompleted = false; + + protected function configure(): void + { + $this + ->setName('require') + ->setAliases(['r']) + ->setDescription('Adds required packages to your composer.json and installs them') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'), + new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), + new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'), + new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), + new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'), + new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'), + new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-dependencies'), + new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), + new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), + new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), + new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + ]) + ->setHelp( + <<file = Factory::getComposerFile(); + $io = $this->getIO(); + + if ($input->getOption('no-suggest')) { + $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); + } + + $this->newlyCreated = !file_exists($this->file); + if ($this->newlyCreated && !file_put_contents($this->file, "{\n}\n")) { + $io->writeError(''.$this->file.' could not be created.'); + + return 1; + } + if (!Filesystem::isReadable($this->file)) { + $io->writeError(''.$this->file.' is not readable.'); + + return 1; + } + + if (filesize($this->file) === 0) { + file_put_contents($this->file, "{\n}\n"); + } + + $this->json = new JsonFile($this->file); + $this->lock = Factory::getLockFile($this->file); + $this->composerBackup = file_get_contents($this->json->getPath()); + $this->lockBackup = file_exists($this->lock) ? file_get_contents($this->lock) : null; + + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) { + $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + $this->revertComposerFile(); + $handler->exitWithLastSignal(); + }); + + // check for writability by writing to the file as is_writable can not be trusted on network-mounts + // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + if (!is_writable($this->file) && false === Silencer::call('file_put_contents', $this->file, $this->composerBackup)) { + $io->writeError(''.$this->file.' is not writable.'); + + return 1; + } + + if ($input->getOption('fixed') === true) { + $config = $this->json->read(); + + $packageType = empty($config['type']) ? 'library' : $config['type']; + + /** + * @see https://github.com/composer/composer/pull/8313#issuecomment-532637955 + */ + if ($packageType !== 'project' && !$input->getOption('dev')) { + $io->writeError('The "--fixed" option is only allowed for packages with a "project" type or for dev dependencies to prevent possible misuses.'); + + if (!isset($config['type'])) { + $io->writeError('If your package is not a library, you can explicitly specify the "type" by using "composer config type project".'); + } + + return 1; + } + } + + $composer = $this->requireComposer(); + $repos = $composer->getRepositoryManager()->getRepositories(); + + $platformOverrides = $composer->getConfig()->get('platform'); + // initialize $this->repos as it is used by the PackageDiscoveryTrait + $this->repos = new CompositeRepository(array_merge( + [$platformRepo = new PlatformRepository([], $platformOverrides)], + $repos + )); + + if ($composer->getPackage()->getPreferStable()) { + $preferredStability = 'stable'; + } else { + $preferredStability = $composer->getPackage()->getMinimumStability(); + } + + try { + $requirements = $this->determineRequirements( + $input, + $output, + $input->getArgument('packages'), + $platformRepo, + $preferredStability, + $input->getOption('no-update'), // if there is no update, we need to use the best possible version constraint directly as we cannot rely on the solver to guess the best constraint + $input->getOption('fixed') + ); + } catch (\Exception $e) { + if ($this->newlyCreated) { + $this->revertComposerFile(); + + throw new \RuntimeException('No composer.json present in the current directory ('.$this->file.'), this may be the cause of the following exception.', 0, $e); + } + + throw $e; + } + + $requirements = $this->formatRequirements($requirements); + + if (!$input->getOption('dev') && $io->isInteractive() && !$composer->isGlobal()) { + $devPackages = []; + $devTags = ['dev', 'testing', 'static analysis']; + $currentRequiresByKey = $this->getPackagesByRequireKey(); + foreach ($requirements as $name => $version) { + // skip packages which are already in the composer.json as those have already been decided + if (isset($currentRequiresByKey[$name])) { + continue; + } + + $pkg = PackageSorter::getMostCurrentVersion($this->getRepos()->findPackages($name)); + if ($pkg instanceof CompletePackageInterface) { + $pkgDevTags = array_intersect($devTags, array_map('strtolower', $pkg->getKeywords())); + if (count($pkgDevTags) > 0) { + $devPackages[] = $pkgDevTags; + } + } + } + + if (count($devPackages) === count($requirements)) { + $plural = count($requirements) > 1 ? 's' : ''; + $plural2 = count($requirements) > 1 ? 'are' : 'is'; + $plural3 = count($requirements) > 1 ? 'they are' : 'it is'; + $pkgDevTags = array_unique(array_merge(...$devPackages)); + $io->warning('The package'.$plural.' you required '.$plural2.' recommended to be placed in require-dev (because '.$plural3.' tagged as "'.implode('", "', $pkgDevTags).'") but you did not use --dev.'); + if ($io->askConfirmation('Do you want to re-run the command with --dev? [yes]? ')) { + $input->setOption('dev', true); + } + } + + unset($devPackages, $pkgDevTags); + } + + $requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; + $removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; + + // check which requirements need the version guessed + $requirementsToGuess = []; + foreach ($requirements as $package => $constraint) { + if ($constraint === 'guess') { + $requirements[$package] = '*'; + $requirementsToGuess[] = $package; + } + } + + // validate requirements format + $versionParser = new VersionParser(); + foreach ($requirements as $package => $constraint) { + if (strtolower($package) === $composer->getPackage()->getName()) { + $io->writeError(sprintf('Root package \'%s\' cannot require itself in its composer.json', $package)); + + return 1; + } + if ($constraint === 'self.version') { + continue; + } + $versionParser->parseConstraints($constraint); + } + + $inconsistentRequireKeys = $this->getInconsistentRequireKeys($requirements, $requireKey); + if (count($inconsistentRequireKeys) > 0) { + foreach ($inconsistentRequireKeys as $package) { + $io->warning(sprintf( + '%s is currently present in the %s key and you ran the command %s the --dev flag, which will move it to the %s key.', + $package, + $removeKey, + $input->getOption('dev') ? 'with' : 'without', + $requireKey + )); + } + + if ($io->isInteractive()) { + if (!$io->askConfirmation(sprintf('Do you want to move %s? [no]? ', count($inconsistentRequireKeys) > 1 ? 'these requirements' : 'this requirement'), false)) { + if (!$io->askConfirmation(sprintf('Do you want to re-run the command %s --dev? [yes]? ', $input->getOption('dev') ? 'without' : 'with'), true)) { + return 0; + } + + $input->setOption('dev', true); + [$requireKey, $removeKey] = [$removeKey, $requireKey]; + } + } + } + + $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages'); + + $this->firstRequire = $this->newlyCreated; + if (!$this->firstRequire) { + $composerDefinition = $this->json->read(); + if (count($composerDefinition['require'] ?? []) === 0 && count($composerDefinition['require-dev'] ?? []) === 0) { + $this->firstRequire = true; + } + } + + if (!$input->getOption('dry-run')) { + $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); + } + + $io->writeError(''.$this->file.' has been '.($this->newlyCreated ? 'created' : 'updated').''); + + if ($input->getOption('no-update')) { + return 0; + } + + $composer->getPluginManager()->deactivateInstalledPlugins(); + + try { + $result = $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey); + if ($result === 0 && count($requirementsToGuess) > 0) { + $result = $this->updateRequirementsAfterResolution($requirementsToGuess, $requireKey, $removeKey, $sortPackages, $input->getOption('dry-run'), $input->getOption('fixed')); + } + + return $result; + } catch (\Exception $e) { + if (!$this->dependencyResolutionCompleted) { + $this->revertComposerFile(); + } + throw $e; + } finally { + if ($input->getOption('dry-run') && $this->newlyCreated) { + @unlink($this->json->getPath()); + } + + $signalHandler->unregister(); + } + } + + /** + * @param array $newRequirements + * @return string[] + */ + private function getInconsistentRequireKeys(array $newRequirements, string $requireKey): array + { + $requireKeys = $this->getPackagesByRequireKey(); + $inconsistentRequirements = []; + foreach ($requireKeys as $package => $packageRequireKey) { + if (!isset($newRequirements[$package])) { + continue; + } + if ($requireKey !== $packageRequireKey) { + $inconsistentRequirements[] = $package; + } + } + + return $inconsistentRequirements; + } + + /** + * @return array + */ + private function getPackagesByRequireKey(): array + { + $composerDefinition = $this->json->read(); + $require = []; + $requireDev = []; + + if (isset($composerDefinition['require'])) { + $require = $composerDefinition['require']; + } + + if (isset($composerDefinition['require-dev'])) { + $requireDev = $composerDefinition['require-dev']; + } + + return array_merge( + array_fill_keys(array_keys($require), 'require'), + array_fill_keys(array_keys($requireDev), 'require-dev') + ); + } + + /** + * @param array $requirements + * @param 'require'|'require-dev' $requireKey + * @param 'require'|'require-dev' $removeKey + * @throws \Exception + */ + private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements, string $requireKey, string $removeKey): int + { + // Update packages + $this->resetComposer(); + $composer = $this->requireComposer(); + + $this->dependencyResolutionCompleted = false; + $composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, function (): void { + $this->dependencyResolutionCompleted = true; + }, 10000); + + if ($input->getOption('dry-run')) { + $rootPackage = $composer->getPackage(); + $links = [ + 'require' => $rootPackage->getRequires(), + 'require-dev' => $rootPackage->getDevRequires(), + ]; + $loader = new ArrayLoader(); + $newLinks = $loader->parseLinks($rootPackage->getName(), $rootPackage->getPrettyVersion(), BasePackage::$supportedLinkTypes[$requireKey]['method'], $requirements); + $links[$requireKey] = array_merge($links[$requireKey], $newLinks); + foreach ($requirements as $package => $constraint) { + unset($links[$removeKey][$package]); + } + $rootPackage->setRequires($links['require']); + $rootPackage->setDevRequires($links['require-dev']); + + // extract stability flags & references as they weren't present when loading the unmodified composer.json + $references = $rootPackage->getReferences(); + $references = RootPackageLoader::extractReferences($requirements, $references); + $rootPackage->setReferences($references); + $stabilityFlags = $rootPackage->getStabilityFlags(); + $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $rootPackage->getMinimumStability(), $stabilityFlags); + $rootPackage->setStabilityFlags($stabilityFlags); + unset($stabilityFlags, $references); + } + + $updateDevMode = !$input->getOption('update-no-dev'); + $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); + $minimalChanges = $input->getOption('minimal-changes') || $composer->getConfig()->get('update-with-minimal-changes'); + + $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; + $flags = ''; + if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; + $flags .= ' --with-all-dependencies'; + } elseif ($input->getOption('update-with-dependencies') || $input->getOption('with-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; + $flags .= ' --with-dependencies'; + } + + $io->writeError('Running composer update '.implode(' ', array_keys($requirements)).$flags.''); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); + + $install = Installer::create($io, $composer); + + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($composer->getConfig(), $input); + + $install + ->setDryRun($input->getOption('dry-run')) + ->setVerbose($input->getOption('verbose')) + ->setPreferSource($preferSource) + ->setPreferDist($preferDist) + ->setDevMode($updateDevMode) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu, $apcuPrefix) + ->setUpdate(true) + ->setInstall(!$input->getOption('no-install')) + ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) + ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) + ->setPreferStable($input->getOption('prefer-stable')) + ->setPreferLowest($input->getOption('prefer-lowest')) + ->setAuditConfig($this->createAuditConfig($composer->getConfig(), $input)) + ->setMinimalUpdate($minimalChanges) + ; + + // if no lock is present, or the file is brand new, we do not do a + // partial update as this is not supported by the Installer + if (!$this->firstRequire && $composer->getLocker()->isLocked()) { + $install->setUpdateAllowList(array_keys($requirements)); + } + + $status = $install->run(); + if ($status !== 0 && $status !== Installer::ERROR_AUDIT_FAILED) { + if ($status === Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED) { + foreach ($this->normalizeRequirements($input->getArgument('packages')) as $req) { + if (!isset($req['version'])) { + $io->writeError('You can also try re-running composer require with an explicit version constraint, e.g. "composer require '.$req['name'].':*" to figure out if any version is installable, or "composer require '.$req['name'].':^2.1" if you know which you need.'); + break; + } + } + } + $this->revertComposerFile(); + } + + return $status; + } + + /** + * @param list $requirementsToUpdate + */ + private function updateRequirementsAfterResolution(array $requirementsToUpdate, string $requireKey, string $removeKey, bool $sortPackages, bool $dryRun, bool $fixed): int + { + $composer = $this->requireComposer(); + $locker = $composer->getLocker(); + $requirements = []; + $versionSelector = new VersionSelector(new RepositorySet()); + $repo = $locker->isLocked() ? $composer->getLocker()->getLockedRepository(true) : $composer->getRepositoryManager()->getLocalRepository(); + foreach ($requirementsToUpdate as $packageName) { + $package = $repo->findPackage($packageName, '*'); + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + if (!$package instanceof PackageInterface) { + continue; + } + + if ($fixed) { + $requirements[$packageName] = $package->getPrettyVersion(); + } else { + $requirements[$packageName] = $versionSelector->findRecommendedRequireVersion($package); + } + $this->getIO()->writeError(sprintf( + 'Using version %s for %s', + $requirements[$packageName], + $packageName + )); + + if (Preg::isMatch('{^dev-(?!main$|master$|trunk$|latest$)}', $requirements[$packageName])) { + $this->getIO()->warning('Version '.$requirements[$packageName].' looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state'); + if ($this->getIO()->isInteractive() && !$this->getIO()->askConfirmation('Are you sure you want to use this constraint (y) or would you rather abort (n) the whole operation [y,n]? ')) { + $this->revertComposerFile(); + + return 1; + } + } + } + + if (!$dryRun) { + $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); + if ($locker->isLocked() && $composer->getConfig()->get('lock')) { + $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $composer->getPackage()->getMinimumStability(), []); + $locker->updateHash($this->json, static function (array $lockData) use ($stabilityFlags) { + foreach ($stabilityFlags as $packageName => $flag) { + $lockData['stability-flags'][$packageName] = $flag; + } + + return $lockData; + }); + } + } + + return 0; + } + + /** + * @param array $new + */ + private function updateFile(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): void + { + if ($this->updateFileCleanly($json, $new, $requireKey, $removeKey, $sortPackages)) { + return; + } + + $composerDefinition = $this->json->read(); + foreach ($new as $package => $version) { + $composerDefinition[$requireKey][$package] = $version; + unset($composerDefinition[$removeKey][$package]); + if (isset($composerDefinition[$removeKey]) && count($composerDefinition[$removeKey]) === 0) { + unset($composerDefinition[$removeKey]); + } + } + $this->json->write($composerDefinition); + } + + /** + * @param array $new + */ + private function updateFileCleanly(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): bool + { + $contents = file_get_contents($json->getPath()); + + $manipulator = new JsonManipulator($contents); + + foreach ($new as $package => $constraint) { + if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) { + return false; + } + if (!$manipulator->removeSubNode($removeKey, $package)) { + return false; + } + } + + $manipulator->removeMainKeyIfEmpty($removeKey); + + file_put_contents($json->getPath(), $manipulator->getContents()); + + return true; + } + + protected function interact(InputInterface $input, OutputInterface $output): void + { + } + + private function revertComposerFile(): void + { + $io = $this->getIO(); + + if ($this->newlyCreated) { + $io->writeError("\n".'Installation failed, deleting '.$this->file.'.'); + unlink($this->json->getPath()); + if (file_exists($this->lock)) { + unlink($this->lock); + } + } else { + $msg = ' to its '; + if ($this->lockBackup) { + $msg = ' and '.$this->lock.' to their '; + } + $io->writeError("\n".'Installation failed, reverting '.$this->file.$msg.'original content.'); + file_put_contents($this->json->getPath(), $this->composerBackup); + if ($this->lockBackup) { + file_put_contents($this->lock, $this->lockBackup); + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Command/RunScriptCommand.php b/vendor/composer/composer/src/Composer/Command/RunScriptCommand.php new file mode 100644 index 000000000..e7b6d820a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/RunScriptCommand.php @@ -0,0 +1,186 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Script\Event as ScriptEvent; +use Composer\Script\ScriptEvents; +use Composer\Util\ProcessExecutor; +use Composer\Util\Platform; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Fabien Potencier + */ +class RunScriptCommand extends BaseCommand +{ + /** + * @var string[] Array with command events + */ + protected $scriptEvents = [ + ScriptEvents::PRE_INSTALL_CMD, + ScriptEvents::POST_INSTALL_CMD, + ScriptEvents::PRE_UPDATE_CMD, + ScriptEvents::POST_UPDATE_CMD, + ScriptEvents::PRE_STATUS_CMD, + ScriptEvents::POST_STATUS_CMD, + ScriptEvents::POST_ROOT_PACKAGE_INSTALL, + ScriptEvents::POST_CREATE_PROJECT_CMD, + ScriptEvents::PRE_ARCHIVE_CMD, + ScriptEvents::POST_ARCHIVE_CMD, + ScriptEvents::PRE_AUTOLOAD_DUMP, + ScriptEvents::POST_AUTOLOAD_DUMP, + ]; + + protected function configure(): void + { + $this + ->setName('run-script') + ->setAliases(['run']) + ->setDescription('Runs the scripts defined in composer.json') + ->setDefinition([ + new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.', null, function () { + return array_map(static function ($script) { return $script['name']; }, $this->getScripts()); + }), + new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), + new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), + new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'), + ]) + ->setHelp( + <<run-script command runs scripts defined in composer.json: + +php composer.phar run-script post-update-cmd + +Read more at https://getcomposer.org/doc/03-cli.md#run-script-run +EOT + ) + ; + } + + protected function interact(InputInterface $input, OutputInterface $output): void + { + $scripts = $this->getScripts(); + if (count($scripts) === 0) { + return; + } + + if ($input->getArgument('script') !== null || $input->getOption('list')) { + return; + } + + $options = []; + foreach ($scripts as $script) { + $options[$script['name']] = $script['description']; + } + $io = $this->getIO(); + $script = $io->select( + 'Script to run: ', + $options, + '', + 1, + 'Invalid script name "%s"' + ); + + $input->setArgument('script', $script); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($input->getOption('list')) { + return $this->listScripts($output); + } + + $script = $input->getArgument('script'); + if ($script === null) { + throw new \RuntimeException('Missing required argument "script"'); + } + + if (!in_array($script, $this->scriptEvents)) { + if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { + throw new \InvalidArgumentException(sprintf('Script "%s" cannot be run with this command', $script)); + } + } + + $composer = $this->requireComposer(); + $devMode = $input->getOption('dev') || !$input->getOption('no-dev'); + $event = new ScriptEvent($script, $composer, $this->getIO(), $devMode); + $hasListeners = $composer->getEventDispatcher()->hasEventListeners($event); + if (!$hasListeners) { + throw new \InvalidArgumentException(sprintf('Script "%s" is not defined in this package', $script)); + } + + $args = $input->getArgument('args'); + + if (null !== $timeout = $input->getOption('timeout')) { + if (!ctype_digit($timeout)) { + throw new \RuntimeException('Timeout value must be numeric and positive if defined, or 0 for forever'); + } + // Override global timeout set before in Composer by environment or config + ProcessExecutor::setTimeout((int) $timeout); + } + + Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); + + return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args); + } + + protected function listScripts(OutputInterface $output): int + { + $scripts = $this->getScripts(); + if (count($scripts) === 0) { + return 0; + } + + $io = $this->getIO(); + $io->writeError('scripts:'); + $table = []; + foreach ($scripts as $script) { + $table[] = [' '.$script['name'], $script['description']]; + } + + $this->renderTable($table, $output); + + return 0; + } + + /** + * @return list + */ + private function getScripts(): array + { + $scripts = $this->requireComposer()->getPackage()->getScripts(); + if (count($scripts) === 0) { + return []; + } + + $result = []; + foreach ($scripts as $name => $script) { + $description = ''; + try { + $cmd = $this->getApplication()->find($name); + $description = $cmd->getDescription(); + } catch (\Symfony\Component\Console\Exception\CommandNotFoundException $e) { + // ignore scripts that have no command associated, like native Composer script listeners + } + $result[] = ['name' => $name, 'description' => $description]; + } + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php b/vendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php new file mode 100644 index 000000000..d178210c2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php @@ -0,0 +1,94 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Pcre\Preg; +use Composer\Util\Platform; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class ScriptAliasCommand extends BaseCommand +{ + /** @var string */ + private $script; + /** @var string */ + private $description; + /** @var string[] */ + private $aliases; + + /** + * @param string[] $aliases + */ + public function __construct(string $script, ?string $description, array $aliases = []) + { + $this->script = $script; + $this->description = $description ?? 'Runs the '.$script.' script as defined in composer.json'; + $this->aliases = $aliases; + + foreach ($this->aliases as $alias) { + if (!is_string($alias)) { + throw new \InvalidArgumentException('"scripts-aliases" element array values should contain only strings'); + } + } + + $this->ignoreValidationErrors(); + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setName($this->script) + ->setDescription($this->description) + ->setAliases($this->aliases) + ->setDefinition([ + new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), + new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), + ]) + ->setHelp( + <<run-script command runs scripts defined in composer.json: + +php composer.phar run-script post-update-cmd + +Read more at https://getcomposer.org/doc/03-cli.md#run-script-run +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $args = $input->getArguments(); + + // TODO remove for Symfony 6+ as it is then in the interface + if (!method_exists($input, '__toString')) { // @phpstan-ignore-line + throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); + } + + $devMode = $input->getOption('dev') || !$input->getOption('no-dev'); + + Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); + + return $composer->getEventDispatcher()->dispatchScript($this->script, $devMode, $args['args'], ['script-alias-input' => Preg::replace('{^\S+ ?}', '', $input->__toString(), 1)]); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/SearchCommand.php b/vendor/composer/composer/src/Composer/Command/SearchCommand.php new file mode 100644 index 000000000..c4bdaba42 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/SearchCommand.php @@ -0,0 +1,126 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Json\JsonFile; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; + +/** + * @author Robert Schönthal + */ +class SearchCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('search') + ->setDescription('Searches for packages') + ->setDefinition([ + new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in package names'), + new InputOption('only-vendor', 'O', InputOption::VALUE_NONE, 'Search only for vendor / organization names, returns only "vendor" as result'), + new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), + new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'), + ]) + ->setHelp( + <<php composer.phar search symfony composer + +Read more at https://getcomposer.org/doc/03-cli.md#search +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + // init repos + $platformRepo = new PlatformRepository; + $io = $this->getIO(); + + $format = $input->getOption('format'); + if (!in_array($format, ['text', 'json'])) { + $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); + + return 1; + } + + if (!($composer = $this->tryComposer())) { + $composer = $this->createComposerInstance($input, $this->getIO(), []); + } + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $installedRepo = new CompositeRepository([$localRepo, $platformRepo]); + $repos = new CompositeRepository(array_merge([$installedRepo], $composer->getRepositoryManager()->getRepositories())); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $mode = RepositoryInterface::SEARCH_FULLTEXT; + if ($input->getOption('only-name') === true) { + if ($input->getOption('only-vendor') === true) { + throw new \InvalidArgumentException('--only-name and --only-vendor cannot be used together'); + } + $mode = RepositoryInterface::SEARCH_NAME; + } elseif ($input->getOption('only-vendor') === true) { + $mode = RepositoryInterface::SEARCH_VENDOR; + } + + $type = $input->getOption('type'); + + $query = implode(' ', $input->getArgument('tokens')); + if ($mode !== RepositoryInterface::SEARCH_FULLTEXT) { + $query = preg_quote($query); + } + + $results = $repos->search($query, $mode, $type); + + if (\count($results) > 0 && $format === 'text') { + $width = $this->getTerminalWidth(); + + $nameLength = 0; + foreach ($results as $result) { + $nameLength = max(strlen($result['name']), $nameLength); + } + $nameLength += 1; + foreach ($results as $result) { + $description = $result['description'] ?? ''; + $warning = !empty($result['abandoned']) ? '! Abandoned ! ' : ''; + $remaining = $width - $nameLength - strlen($warning) - 2; + if (strlen($description) > $remaining) { + $description = substr($description, 0, $remaining - 3) . '...'; + } + + $link = $result['url'] ?? null; + if ($link !== null) { + $io->write(''.$result['name'].''. str_repeat(' ', $nameLength - strlen($result['name'])) . $warning . $description); + } else { + $io->write(str_pad($result['name'], $nameLength, ' ') . $warning . $description); + } + } + } elseif ($format === 'json') { + $io->write(JsonFile::encode($results)); + } + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php b/vendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php new file mode 100644 index 000000000..a1c24396f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php @@ -0,0 +1,670 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\Factory; +use Composer\Config; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\SelfUpdate\Keys; +use Composer\SelfUpdate\Versions; +use Composer\IO\IOInterface; +use Composer\Downloader\FilesystemException; +use Composer\Downloader\TransportException; +use Phar; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Finder\Finder; + +/** + * @author Igor Wiedler + * @author Kevin Ran + * @author Jordi Boggiano + */ +class SelfUpdateCommand extends BaseCommand +{ + private const HOMEPAGE = 'getcomposer.org'; + private const OLD_INSTALL_EXT = '-old.phar'; + + protected function configure(): void + { + $this + ->setName('self-update') + ->setAliases(['selfupdate']) + ->setDescription('Updates composer.phar to the latest version') + ->setDefinition([ + new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'), + new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'), + new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'), + new InputOption('stable', null, InputOption::VALUE_NONE, 'Force an update to the stable channel'), + new InputOption('preview', null, InputOption::VALUE_NONE, 'Force an update to the preview channel'), + new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'), + new InputOption('1', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 1.x versions'), + new InputOption('2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.x versions'), + new InputOption('2.2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.2.x LTS versions'), + new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'), + ]) + ->setHelp( + <<self-update command checks getcomposer.org for newer +versions of composer and if found, installs the latest. + +php composer.phar self-update + +Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate +EOT + ) + ; + } + + /** + * @throws FilesystemException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + if (strpos(__FILE__, 'phar:') !== 0) { + if (str_contains(strtr(__DIR__, '\\', '/'), 'vendor/composer/composer')) { + $projDir = dirname(__DIR__, 6); + $output->writeln('This instance of Composer does not have the self-update command.'); + $output->writeln('You are running Composer installed as a package in your current project ("'.$projDir.'").'); + $output->writeln('To update Composer, download a composer.phar from https://getcomposer.org and then run `composer.phar update composer/composer` in your project.'); + } else { + $output->writeln('This instance of Composer does not have the self-update command.'); + $output->writeln('This could be due to a number of reasons, such as Composer being installed as a system package on your OS, or Composer being installed as a package in the current project.'); + } + + return 1; + } + + if ($_SERVER['argv'][0] === 'Standard input code') { + return 1; + } + + // trigger autoloading of a few classes which may be needed when verifying/swapping the phar file + // to ensure we do not try to load them from the new phar, see https://github.com/composer/composer/issues/10252 + class_exists('Composer\Util\Platform'); + class_exists('Composer\Downloader\FilesystemException'); + + $config = Factory::createConfig(); + + if ($config->get('disable-tls') === true) { + $baseUrl = 'http://' . self::HOMEPAGE; + } else { + $baseUrl = 'https://' . self::HOMEPAGE; + } + + $io = $this->getIO(); + $httpDownloader = Factory::createHttpDownloader($io, $config); + + $versionsUtil = new Versions($config, $httpDownloader); + + // switch channel if requested + $requestedChannel = null; + foreach (Versions::CHANNELS as $channel) { + if ($input->getOption($channel)) { + $requestedChannel = $channel; + $versionsUtil->setChannel($channel, $io); + break; + } + } + + if ($input->getOption('set-channel-only')) { + return 0; + } + + $cacheDir = $config->get('cache-dir'); + $rollbackDir = $config->get('data-dir'); + $home = $config->get('home'); + $localFilename = Phar::running(false); + if ('' === $localFilename) { + throw new \RuntimeException('Could not determine the location of the composer.phar file as it appears you are not running this code from a phar archive.'); + } + + if ($input->getOption('update-keys')) { + $this->fetchKeys($io, $config); + + return 0; + } + + // ensure composer.phar location is accessible + if (!file_exists($localFilename)) { + throw new FilesystemException('Composer update failed: the "'.$localFilename.'" is not accessible'); + } + + // check if current dir is writable and if not try the cache dir from settings + $tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir; + + // check for permissions in local filesystem before start connection process + if (!is_writable($tmpDir)) { + throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written'); + } + + // check if composer is running as the same user that owns the directory root, only if POSIX is defined and callable + if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { + $composerUser = posix_getpwuid(posix_geteuid()); + $homeDirOwnerId = fileowner($home); + if (is_array($composerUser) && $homeDirOwnerId !== false) { + $homeOwner = posix_getpwuid($homeDirOwnerId); + if (is_array($homeOwner) && $composerUser['name'] !== $homeOwner['name']) { + $io->writeError('You are running Composer as "'.$composerUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"'); + } + } + } + + if ($input->getOption('rollback')) { + return $this->rollback($output, $rollbackDir, $localFilename); + } + + if ($input->getArgument('command') === 'self' && $input->getArgument('version') === 'update') { + $input->setArgument('version', null); + } + + $latest = $versionsUtil->getLatest(); + $latestStable = $versionsUtil->getLatest('stable'); + try { + $latestPreview = $versionsUtil->getLatest('preview'); + } catch (\UnexpectedValueException $e) { + $latestPreview = $latestStable; + } + $latestVersion = $latest['version']; + $updateVersion = $input->getArgument('version') ?? $latestVersion; + $currentMajorVersion = Preg::replace('{^(\d+).*}', '$1', Composer::getVersion()); + $updateMajorVersion = Preg::replace('{^(\d+).*}', '$1', $updateVersion); + $previewMajorVersion = Preg::replace('{^(\d+).*}', '$1', $latestPreview['version']); + + if ($versionsUtil->getChannel() === 'stable' && null === $input->getArgument('version')) { + // if requesting stable channel and no specific version, avoid automatically upgrading to the next major + // simply output a warning that the next major stable is available and let users upgrade to it manually + if ($currentMajorVersion < $updateMajorVersion) { + $skippedVersion = $updateVersion; + + $versionsUtil->setChannel($currentMajorVersion); + + $latest = $versionsUtil->getLatest(); + $latestStable = $versionsUtil->getLatest('stable'); + $latestVersion = $latest['version']; + $updateVersion = $latestVersion; + + $io->writeError('A new stable major version of Composer is available ('.$skippedVersion.'), run "composer self-update --'.$updateMajorVersion.'" to update to it. See also https://getcomposer.org/'.$updateMajorVersion.''); + } elseif ($currentMajorVersion < $previewMajorVersion) { + // promote next major version if available in preview + $io->writeError('A preview release of the next major version of Composer is available ('.$latestPreview['version'].'), run "composer self-update --preview" to give it a try. See also https://github.com/composer/composer/releases for changelogs.'); + } + } + + $effectiveChannel = $requestedChannel === null ? $versionsUtil->getChannel() : $requestedChannel; + if (is_numeric($effectiveChannel) && strpos($latestStable['version'], $effectiveChannel) !== 0) { + $io->writeError('Warning: You forced the install of '.$latestVersion.' via --'.$effectiveChannel.', but '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.'); + } + if (isset($latest['eol'])) { + $io->writeError('Warning: Version '.$latestVersion.' is EOL / End of Life. '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.'); + } + + if (Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) { + $io->writeError('You can not update to a specific SHA-1 as those phars are not available for download'); + + return 1; + } + + $channelString = $versionsUtil->getChannel(); + if (is_numeric($channelString)) { + $channelString .= '.x'; + } + + if (Composer::VERSION === $updateVersion) { + $io->writeError( + sprintf( + 'You are already using the latest available Composer version %s (%s channel).', + $updateVersion, + $channelString + ) + ); + + // remove all backups except for the most recent, if any + if ($input->getOption('clean-backups')) { + $this->cleanBackups($rollbackDir, $this->getLastBackupVersion($rollbackDir)); + } + + return 0; + } + + $tempFilename = $tmpDir . '/' . basename($localFilename, '.phar').'-temp'.random_int(0, 10000000).'.phar'; + $backupFile = sprintf( + '%s/%s-%s%s', + $rollbackDir, + strtr(Composer::RELEASE_DATE, ' :', '_-'), + Preg::replace('{^([0-9a-f]{7})[0-9a-f]{33}$}', '$1', Composer::VERSION), + self::OLD_INSTALL_EXT + ); + + $updatingToTag = !Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion); + + $io->write(sprintf("Upgrading to version %s (%s channel).", $updateVersion, $channelString)); + $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar'); + try { + $signature = $httpDownloader->get($remoteFilename.'.sig')->getBody(); + } catch (TransportException $e) { + if ($e->getStatusCode() === 404) { + throw new \InvalidArgumentException('Version "'.$updateVersion.'" could not be found.', 0, $e); + } + throw $e; + } + $io->writeError(' ', false); + $httpDownloader->copy($remoteFilename, $tempFilename); + $io->writeError(''); + + if (!file_exists($tempFilename) || null === $signature || '' === $signature) { + $io->writeError('The download of the new composer version failed for an unexpected reason'); + + return 1; + } + + // verify phar signature + if (!extension_loaded('openssl') && $config->get('disable-tls')) { + $io->writeError('Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls'); + } else { + if (!extension_loaded('openssl')) { + throw new \RuntimeException('The openssl extension is required for phar signatures to be verified but it is not available. ' + . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); + } + + $sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub'); + if (!file_exists($sigFile)) { + file_put_contents( + $home.'/keys.dev.pub', + <<getOption('clean-backups')) { + $this->cleanBackups($rollbackDir); + } + + if (!$this->setLocalPhar($localFilename, $tempFilename, $backupFile)) { + @unlink($tempFilename); + + return 1; + } + + if (file_exists($backupFile)) { + $io->writeError(sprintf( + 'Use composer self-update --rollback to return to version %s', + Composer::VERSION + )); + } else { + $io->writeError('A backup of the current version could not be written to '.$backupFile.', no rollback possible'); + } + + return 0; + } + + /** + * @throws \Exception + */ + protected function fetchKeys(IOInterface $io, Config $config): void + { + if (!$io->isInteractive()) { + throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively'); + } + + $io->write('Open https://composer.github.io/pubkeys.html to find the latest keys'); + + $validator = static function ($value): string { + $value = (string) $value; + if (!Preg::isMatch('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) { + throw new \UnexpectedValueException('Invalid input'); + } + + return trim($value)."\n"; + }; + + $devKey = ''; + while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) { + $devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator); + while ($line = $io->ask('', '')) { + $devKey .= trim($line)."\n"; + if (trim($line) === '-----END PUBLIC KEY-----') { + break; + } + } + } + file_put_contents($keyPath = $config->get('home').'/keys.dev.pub', $match[0]); + $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); + + $tagsKey = ''; + while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) { + $tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator); + while ($line = $io->ask('', '')) { + $tagsKey .= trim($line)."\n"; + if (trim($line) === '-----END PUBLIC KEY-----') { + break; + } + } + } + file_put_contents($keyPath = $config->get('home').'/keys.tags.pub', $match[0]); + $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); + + $io->write('Public keys stored in '.$config->get('home')); + } + + /** + * @throws FilesystemException + */ + protected function rollback(OutputInterface $output, string $rollbackDir, string $localFilename): int + { + $rollbackVersion = $this->getLastBackupVersion($rollbackDir); + if (null === $rollbackVersion) { + throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"'); + } + + $oldFile = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT; + + if (!is_file($oldFile)) { + throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be found'); + } + if (!Filesystem::isReadable($oldFile)) { + throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be read'); + } + + $io = $this->getIO(); + $io->writeError(sprintf("Rolling back to version %s.", $rollbackVersion)); + if (!$this->setLocalPhar($localFilename, $oldFile)) { + return 1; + } + + return 0; + } + + /** + * Checks if the downloaded/rollback phar is valid then moves it + * + * @param string $localFilename The composer.phar location + * @param string $newFilename The downloaded or backup phar + * @param string $backupTarget The filename to use for the backup + * @throws FilesystemException If the file cannot be moved + * @return bool Whether the phar is valid and has been moved + */ + protected function setLocalPhar(string $localFilename, string $newFilename, ?string $backupTarget = null): bool + { + $io = $this->getIO(); + $perms = @fileperms($localFilename); + if ($perms !== false) { + @chmod($newFilename, $perms); + } + + // check phar validity + if (!$this->validatePhar($newFilename, $error)) { + $io->writeError('The '.($backupTarget !== null ? 'update' : 'backup').' file is corrupted ('.$error.')'); + + if ($backupTarget !== null) { + $io->writeError('Please re-run the self-update command to try again.'); + } + + return false; + } + + // copy current file into backups dir + if ($backupTarget !== null) { + @copy($localFilename, $backupTarget); + } + + try { + if (Platform::isWindows()) { + // use copy to apply permissions from the destination directory + // as rename uses source permissions and may block other users + copy($newFilename, $localFilename); + @unlink($newFilename); + } else { + rename($newFilename, $localFilename); + } + + return true; + } catch (\Exception $e) { + // see if we can run this operation as an Admin on Windows + if (!is_writable(dirname($localFilename)) + && $io->isInteractive() + && $this->isWindowsNonAdminUser()) { + return $this->tryAsWindowsAdmin($localFilename, $newFilename); + } + + @unlink($newFilename); + $action = 'Composer '.($backupTarget !== null ? 'update' : 'rollback'); + throw new FilesystemException($action.' failed: "'.$localFilename.'" could not be written.'.PHP_EOL.$e->getMessage()); + } + } + + protected function cleanBackups(string $rollbackDir, ?string $except = null): void + { + $finder = $this->getOldInstallationFinder($rollbackDir); + $io = $this->getIO(); + $fs = new Filesystem; + + foreach ($finder as $file) { + if ($file->getBasename(self::OLD_INSTALL_EXT) === $except) { + continue; + } + $file = (string) $file; + $io->writeError('Removing: '.$file.''); + $fs->remove($file); + } + } + + protected function getLastBackupVersion(string $rollbackDir): ?string + { + $finder = $this->getOldInstallationFinder($rollbackDir); + $finder->sortByName(); + $files = iterator_to_array($finder); + + if (count($files) > 0) { + return end($files)->getBasename(self::OLD_INSTALL_EXT); + } + + return null; + } + + protected function getOldInstallationFinder(string $rollbackDir): Finder + { + return Finder::create() + ->depth(0) + ->files() + ->name('*' . self::OLD_INSTALL_EXT) + ->in($rollbackDir); + } + + /** + * Validates the downloaded/backup phar file + * + * @param string $pharFile The downloaded or backup phar + * @param null|string $error Set by method on failure + * + * Code taken from getcomposer.org/installer. Any changes should be made + * there and replicated here + * + * @throws \Exception + * @return bool If the operation succeeded + */ + protected function validatePhar(string $pharFile, ?string &$error): bool + { + if ((bool) ini_get('phar.readonly')) { + return true; + } + + try { + // Test the phar validity + $phar = new Phar($pharFile); + // Free the variable to unlock the file + unset($phar); + $result = true; + } catch (\Exception $e) { + if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) { + throw $e; + } + $error = $e->getMessage(); + $result = false; + } + + return $result; + } + + /** + * Returns true if this is a non-admin Windows user account + */ + protected function isWindowsNonAdminUser(): bool + { + if (!Platform::isWindows()) { + return false; + } + + // fltmc.exe manages filter drivers and errors without admin privileges + exec('fltmc.exe filters', $output, $exitCode); + + return $exitCode !== 0; + } + + /** + * Invokes a UAC prompt to update composer.phar as an admin + * + * Uses either sudo.exe or VBScript to elevate and run cmd.exe move. + * + * @param string $localFilename The composer.phar location + * @param string $newFilename The downloaded or backup phar + * @return bool Whether composer.phar has been updated + */ + protected function tryAsWindowsAdmin(string $localFilename, string $newFilename): bool + { + $io = $this->getIO(); + + $io->writeError('Unable to write "'.$localFilename.'". Access is denied.'); + $helpMessage = 'Please run the self-update command as an Administrator.'; + $question = 'Complete this operation with Administrator privileges [Y,n]? '; + + if (!$io->askConfirmation($question, true)) { + $io->writeError('Operation cancelled. '.$helpMessage.''); + + return false; + } + + $tmpFile = tempnam(sys_get_temp_dir(), ''); + if (false === $tmpFile) { + $io->writeError('Operation failed. '.$helpMessage.''); + + return false; + } + + exec('sudo config 2> NUL', $output, $exitCode); + $usingSudo = $exitCode === 0; + + $script = $usingSudo ? $tmpFile.'.bat' : $tmpFile.'.vbs'; + rename($tmpFile, $script); + + $checksum = hash_file('sha256', $newFilename); + + // cmd's internal move is fussy about backslashes + $source = str_replace('/', '\\', $newFilename); + $destination = str_replace('/', '\\', $localFilename); + + if ($usingSudo) { + $code = sprintf('move "%s" "%s"', $source, $destination); + } else { + $code = <<writeError('Operation succeeded.'); + } else { + $io->writeError('Operation failed. '.$helpMessage.''); + } + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ShowCommand.php b/vendor/composer/composer/src/Composer/Command/ShowCommand.php new file mode 100644 index 000000000..6c068ec69 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ShowCommand.php @@ -0,0 +1,1572 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\DependencyResolver\DefaultPolicy; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Link; +use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Repository\InstalledArrayRepository; +use Composer\Repository\ComposerRepository; +use Composer\Repository\CompositeRepository; +use Composer\Repository\FilterRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Repository\InstalledRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositorySet; +use Composer\Repository\RepositoryUtils; +use Composer\Repository\RootPackageRepository; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Semver; +use Composer\Spdx\SpdxLicenses; +use Composer\Util\PackageInfo; +use DateTimeInterface; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Robert Schönthal + * @author Jordi Boggiano + * @author Jérémy Romey + * @author Mihai Plasoianu + * + * @phpstan-import-type AutoloadRules from PackageInterface + * @phpstan-type JsonStructure array|AutoloadRules> + */ +class ShowCommand extends BaseCommand +{ + use CompletionTrait; + + /** @var VersionParser */ + protected $versionParser; + /** @var string[] */ + protected $colors; + + /** @var ?RepositorySet */ + private $repositorySet; + + protected function configure(): void + { + $this + ->setName('show') + ->setAliases(['info']) + ->setDescription('Shows information about packages') + ->setDefinition([ + new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestPackageBasedOnMode()), + new InputArgument('version', InputArgument::OPTIONAL, 'Version or version constraint to inspect'), + new InputOption('all', null, InputOption::VALUE_NONE, 'List all packages'), + new InputOption('locked', null, InputOption::VALUE_NONE, 'List all locked packages'), + new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only (enabled by default, only present for BC).'), + new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'), + new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'), + new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'), + new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'), + new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'), + new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'), + new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'), + new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'), + new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)), + new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'), + new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'), + new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'), + new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first. Use with the --latest or --outdated option.'), + new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), + new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'), + ]) + ->setHelp( + <<getOption('available') || $input->getOption('all')) { + return $this->suggestAvailablePackageInclPlatform()($input); + } + + if ($input->getOption('platform')) { + return $this->suggestPlatformPackage()($input); + } + + return $this->suggestInstalledPackage(false)($input); + }; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->versionParser = new VersionParser; + if ($input->getOption('tree')) { + $this->initStyles($output); + } + + $composer = $this->tryComposer(); + $io = $this->getIO(); + + if ($input->getOption('installed') && !$input->getOption('self')) { + $io->writeError('You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.'); + } + + if ($input->getOption('outdated')) { + $input->setOption('latest', true); + } elseif (count($input->getOption('ignore')) > 0) { + $io->writeError('You are using the option "ignore" for action other than "outdated", it will be ignored.'); + } + + if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) { + $io->writeError('The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)'); + + return 1; + } + + if ($input->getOption('tree') && ($input->getOption('all') || $input->getOption('available'))) { + $io->writeError('The --tree (-t) option is not usable in combination with --all or --available (-a)'); + + return 1; + } + + if (count(array_filter([$input->getOption('patch-only'), $input->getOption('minor-only'), $input->getOption('major-only')])) > 1) { + $io->writeError('Only one of --major-only, --minor-only or --patch-only can be used at once'); + + return 1; + } + + if ($input->getOption('tree') && $input->getOption('latest')) { + $io->writeError('The --tree (-t) option is not usable in combination with --latest (-l)'); + + return 1; + } + + if ($input->getOption('tree') && $input->getOption('path')) { + $io->writeError('The --tree (-t) option is not usable in combination with --path (-P)'); + + return 1; + } + + $format = $input->getOption('format'); + if (!in_array($format, ['text', 'json'])) { + $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); + + return 1; + } + + $platformReqFilter = $this->getPlatformRequirementFilter($input); + + // init repos + $platformOverrides = []; + if ($composer) { + $platformOverrides = $composer->getConfig()->get('platform'); + } + $platformRepo = new PlatformRepository([], $platformOverrides); + $lockedRepo = null; + + if ($input->getOption('self') && !$input->getOption('installed') && !$input->getOption('locked')) { + $package = clone $this->requireComposer()->getPackage(); + if ($input->getOption('name-only')) { + $io->write($package->getName()); + + return 0; + } + if ($input->getArgument('package')) { + throw new \InvalidArgumentException('You cannot use --self together with a package name'); + } + $repos = $installedRepo = new InstalledRepository([new RootPackageRepository($package)]); + } elseif ($input->getOption('platform')) { + $repos = $installedRepo = new InstalledRepository([$platformRepo]); + } elseif ($input->getOption('available')) { + $installedRepo = new InstalledRepository([$platformRepo]); + if ($composer) { + $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); + $installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository()); + } else { + $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); + $repos = new CompositeRepository($defaultRepos); + $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); + } + } elseif ($input->getOption('all') && $composer) { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $lockedRepo = $locker->getLockedRepository(true); + $installedRepo = new InstalledRepository([$lockedRepo, $localRepo, $platformRepo]); + } else { + $installedRepo = new InstalledRepository([$localRepo, $platformRepo]); + } + $repos = new CompositeRepository(array_merge([new FilterRepository($installedRepo, ['canonical' => false])], $composer->getRepositoryManager()->getRepositories())); + } elseif ($input->getOption('all')) { + $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); + $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); + $installedRepo = new InstalledRepository([$platformRepo]); + $repos = new CompositeRepository(array_merge([$installedRepo], $defaultRepos)); + } elseif ($input->getOption('locked')) { + if (!$composer || !$composer->getLocker()->isLocked()) { + throw new \UnexpectedValueException('A valid composer.json and composer.lock files is required to run this command with --locked'); + } + $locker = $composer->getLocker(); + $lockedRepo = $locker->getLockedRepository(!$input->getOption('no-dev')); + if ($input->getOption('self')) { + $lockedRepo->addPackage(clone $composer->getPackage()); + } + $repos = $installedRepo = new InstalledRepository([$lockedRepo]); + } else { + // --installed / default case + if (!$composer) { + $composer = $this->requireComposer(); + } + $rootPkg = $composer->getPackage(); + + $rootRepo = new InstalledArrayRepository(); + if ($input->getOption('self')) { + $rootRepo = new RootPackageRepository(clone $rootPkg); + } + if ($input->getOption('no-dev')) { + $packages = RepositoryUtils::filterRequiredPackages($composer->getRepositoryManager()->getLocalRepository()->getPackages(), $rootPkg); + $repos = $installedRepo = new InstalledRepository([$rootRepo, new InstalledArrayRepository(array_map(static function ($pkg): PackageInterface { + return clone $pkg; + }, $packages))]); + } else { + $repos = $installedRepo = new InstalledRepository([$rootRepo, $composer->getRepositoryManager()->getLocalRepository()]); + } + + if (!$installedRepo->getPackages()) { + $hasNonPlatformReqs = static function (array $reqs): bool { + return (bool) array_filter(array_keys($reqs), static function (string $name) { + return !PlatformRepository::isPlatformPackage($name); + }); + }; + + if ($hasNonPlatformReqs($rootPkg->getRequires()) || $hasNonPlatformReqs($rootPkg->getDevRequires())) { + $io->writeError('No dependencies installed. Try running composer install or update.'); + } + } + } + + if ($composer) { + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + } + + if ($input->getOption('latest') && null === $composer) { + $io->writeError('No composer.json found in the current directory, disabling "latest" option'); + $input->setOption('latest', false); + } + + $packageFilter = $input->getArgument('package'); + + // show single package or single version + if (isset($package)) { + $versions = [$package->getPrettyVersion() => $package->getVersion()]; + } elseif (null !== $packageFilter && !str_contains($packageFilter, '*')) { + [$package, $versions] = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version')); + + if (isset($package) && $input->getOption('direct')) { + if (!in_array($package->getName(), $this->getRootRequires(), true)) { + throw new \InvalidArgumentException('Package "' . $package->getName() . '" is installed but not a direct dependent of the root package.'); + } + } + + if (!isset($package)) { + $options = $input->getOptions(); + $hint = ''; + if ($input->getOption('locked')) { + $hint .= ' in lock file'; + } + if (isset($options['working-dir'])) { + $hint .= ' in ' . $options['working-dir'] . '/composer.json'; + } + if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) { + $hint .= ', try using --platform (-p) to show platform packages'; + } + if (!$input->getOption('all') && !$input->getOption('available')) { + $hint .= ', try using --available (-a) to show all available packages'; + } + + throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.'); + } + } + + if (isset($package)) { + assert(isset($versions)); + + $exitCode = 0; + if ($input->getOption('tree')) { + $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos); + + if ('json' === $format) { + $io->write(JsonFile::encode(['installed' => [$arrayTree]])); + } else { + $this->displayPackageTree([$arrayTree]); + } + + return $exitCode; + } + + $latestPackage = null; + if ($input->getOption('latest')) { + $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('major-only'), $input->getOption('minor-only'), $input->getOption('patch-only'), $platformReqFilter); + } + if ( + $input->getOption('outdated') + && $input->getOption('strict') + && null !== $latestPackage + && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() + && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned()) + ) { + $exitCode = 1; + } + if ($input->getOption('path')) { + $io->write($package->getName(), false); + $path = $composer->getInstallationManager()->getInstallPath($package); + if (is_string($path)) { + $io->write(' ' . strtok(realpath($path), "\r\n")); + } else { + $io->write(' null'); + } + + return $exitCode; + } + + if ('json' === $format) { + $this->printPackageInfoAsJson($package, $versions, $installedRepo, $latestPackage ?: null); + } else { + $this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null); + } + + return $exitCode; + } + + // show tree view if requested + if ($input->getOption('tree')) { + $rootRequires = $this->getRootRequires(); + $packages = $installedRepo->getPackages(); + usort($packages, static function (BasePackage $a, BasePackage $b): int { + return strcmp((string) $a, (string) $b); + }); + $arrayTree = []; + foreach ($packages as $package) { + if (in_array($package->getName(), $rootRequires, true)) { + $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos); + } + } + + if ('json' === $format) { + $io->write(JsonFile::encode(['installed' => $arrayTree])); + } else { + $this->displayPackageTree($arrayTree); + } + + return 0; + } + + // list packages + /** @var array> $packages */ + $packages = []; + $packageFilterRegex = null; + if (null !== $packageFilter) { + $packageFilterRegex = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i'; + } + + $packageListFilter = null; + if ($input->getOption('direct')) { + $packageListFilter = $this->getRootRequires(); + } + + if ($input->getOption('path') && null === $composer) { + $io->writeError('No composer.json found in the current directory, disabling "path" option'); + $input->setOption('path', false); + } + + foreach (RepositoryUtils::flattenRepositories($repos) as $repo) { + if ($repo === $platformRepo) { + $type = 'platform'; + } elseif ($lockedRepo !== null && $repo === $lockedRepo) { + $type = 'locked'; + } elseif ($repo === $installedRepo || in_array($repo, $installedRepo->getRepositories(), true)) { + $type = 'installed'; + } else { + $type = 'available'; + } + if ($repo instanceof ComposerRepository) { + foreach ($repo->getPackageNames($packageFilter) as $name) { + $packages[$type][$name] = $name; + } + } else { + foreach ($repo->getPackages() as $package) { + if (!isset($packages[$type][$package->getName()]) + || !is_object($packages[$type][$package->getName()]) + || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<') + ) { + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + if (!$packageFilterRegex || Preg::isMatch($packageFilterRegex, $package->getName())) { + if (null === $packageListFilter || in_array($package->getName(), $packageListFilter, true)) { + $packages[$type][$package->getName()] = $package; + } + } + } + } + if ($repo === $platformRepo) { + foreach ($platformRepo->getDisabledPackages() as $name => $package) { + $packages[$type][$name] = $package; + } + } + } + } + + $showAllTypes = $input->getOption('all'); + $showLatest = $input->getOption('latest'); + $showMajorOnly = $input->getOption('major-only'); + $showMinorOnly = $input->getOption('minor-only'); + $showPatchOnly = $input->getOption('patch-only'); + $ignoredPackagesRegex = BasePackage::packageNamesToRegexp(array_map('strtolower', $input->getOption('ignore'))); + $indent = $showAllTypes ? ' ' : ''; + /** @var PackageInterface[] $latestPackages */ + $latestPackages = []; + $exitCode = 0; + $viewData = []; + $viewMetaData = []; + + $writeVersion = false; + $writeDescription = false; + + foreach (['platform' => true, 'locked' => true, 'available' => false, 'installed' => true] as $type => $showVersion) { + if (isset($packages[$type])) { + ksort($packages[$type]); + + $nameLength = $versionLength = $latestLength = $releaseDateLength = 0; + + if ($showLatest && $showVersion) { + foreach ($packages[$type] as $package) { + if (is_object($package) && !Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName())) { + $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMajorOnly, $showMinorOnly, $showPatchOnly, $platformReqFilter); + if ($latestPackage === null) { + continue; + } + + $latestPackages[$package->getPrettyName()] = $latestPackage; + } + } + } + + $writePath = !$input->getOption('name-only') && $input->getOption('path'); + $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion; + $writeLatest = $writeVersion && $showLatest; + $writeDescription = !$input->getOption('name-only') && !$input->getOption('path'); + $writeReleaseDate = $writeLatest && ($input->getOption('sort-by-age') || $format === 'json'); + + $hasOutdatedPackages = false; + + if ($input->getOption('sort-by-age')) { + usort($packages[$type], static function ($a, $b) { + if (is_object($a) && is_object($b)) { + return $a->getReleaseDate() <=> $b->getReleaseDate(); + } + + return 0; + }); + } + + $viewData[$type] = []; + foreach ($packages[$type] as $package) { + $packageViewData = []; + if (is_object($package)) { + $latestPackage = null; + if ($showLatest && isset($latestPackages[$package->getPrettyName()])) { + $latestPackage = $latestPackages[$package->getPrettyName()]; + } + + // Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code + $packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned()); + // When using --major-only, and no bigger version than current major is found then it is considered up to date + $packageIsUpToDate = $packageIsUpToDate || ($latestPackage === null && $showMajorOnly); + $packageIsIgnored = Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName()); + if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) { + continue; + } + + if ($input->getOption('outdated') || $input->getOption('strict')) { + $hasOutdatedPackages = true; + } + + $packageViewData['name'] = $package->getPrettyName(); + $packageViewData['direct-dependency'] = in_array($package->getName(), $this->getRootRequires(), true); + if ($format !== 'json' || true !== $input->getOption('name-only')) { + $packageViewData['homepage'] = $package instanceof CompletePackageInterface ? $package->getHomepage() : null; + $packageViewData['source'] = PackageInfo::getViewSourceUrl($package); + } + $nameLength = max($nameLength, strlen($packageViewData['name'])); + if ($writeVersion) { + $packageViewData['version'] = $package->getFullPrettyVersion(); + if ($format === 'text') { + $packageViewData['version'] = ltrim($packageViewData['version'], 'v'); + } + $versionLength = max($versionLength, strlen($packageViewData['version'])); + } + if ($writeReleaseDate) { + if ($package->getReleaseDate() !== null) { + $packageViewData['release-age'] = str_replace(' ago', ' old', $this->getRelativeTime($package->getReleaseDate())); + if (!str_contains($packageViewData['release-age'], ' old')) { + $packageViewData['release-age'] = 'from '.$packageViewData['release-age']; + } + $releaseDateLength = max($releaseDateLength, strlen($packageViewData['release-age'])); + $packageViewData['release-date'] = $package->getReleaseDate()->format(DateTimeInterface::ATOM); + } else { + $packageViewData['release-age'] = ''; + $packageViewData['release-date'] = ''; + } + } + if ($writeLatest && $latestPackage) { + $packageViewData['latest'] = $latestPackage->getFullPrettyVersion(); + if ($format === 'text') { + $packageViewData['latest'] = ltrim($packageViewData['latest'], 'v'); + } + $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package); + $latestLength = max($latestLength, strlen($packageViewData['latest'])); + + if ($latestPackage->getReleaseDate() !== null) { + $packageViewData['latest-release-date'] = $latestPackage->getReleaseDate()->format(DateTimeInterface::ATOM); + } else { + $packageViewData['latest-release-date'] = ''; + } + } elseif ($writeLatest) { + $packageViewData['latest'] = '[none matched]'; + $packageViewData['latest-status'] = 'up-to-date'; + $latestLength = max($latestLength, strlen($packageViewData['latest'])); + } + if ($writeDescription && $package instanceof CompletePackageInterface) { + $packageViewData['description'] = $package->getDescription(); + } + if ($writePath) { + $path = $composer->getInstallationManager()->getInstallPath($package); + if (is_string($path)) { + $packageViewData['path'] = strtok(realpath($path), "\r\n"); + } else { + $packageViewData['path'] = null; + } + } + + $packageIsAbandoned = false; + if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { + $replacementPackageName = $latestPackage->getReplacementPackage(); + $replacement = $replacementPackageName !== null + ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead' + : 'No replacement was suggested'; + $packageWarning = sprintf( + 'Package %s is abandoned, you should avoid using it. %s.', + $package->getPrettyName(), + $replacement + ); + $packageViewData['warning'] = $packageWarning; + $packageIsAbandoned = $replacementPackageName ?? true; + } + + $packageViewData['abandoned'] = $packageIsAbandoned; + } else { + $packageViewData['name'] = $package; + $nameLength = max($nameLength, strlen($package)); + } + $viewData[$type][] = $packageViewData; + } + $viewMetaData[$type] = [ + 'nameLength' => $nameLength, + 'versionLength' => $versionLength, + 'latestLength' => $latestLength, + 'releaseDateLength' => $releaseDateLength, + 'writeLatest' => $writeLatest, + 'writeReleaseDate' => $writeReleaseDate, + ]; + if ($input->getOption('strict') && $hasOutdatedPackages) { + $exitCode = 1; + break; + } + } + } + + if ('json' === $format) { + $io->write(JsonFile::encode($viewData)); + } else { + if ($input->getOption('latest') && array_filter($viewData)) { + if (!$io->isDecorated()) { + $io->writeError('Legend:'); + $io->writeError('! patch or minor release available - update recommended'); + $io->writeError('~ major release available - update possible'); + if (!$input->getOption('outdated')) { + $io->writeError('= up to date version'); + } + } else { + $io->writeError('Color legend:'); + $io->writeError('- patch or minor release available - update recommended'); + $io->writeError('- major release available - update possible'); + if (!$input->getOption('outdated')) { + $io->writeError('- up to date version'); + } + } + } + + $width = $this->getTerminalWidth(); + + foreach ($viewData as $type => $packages) { + $nameLength = $viewMetaData[$type]['nameLength']; + $versionLength = $viewMetaData[$type]['versionLength']; + $latestLength = $viewMetaData[$type]['latestLength']; + $releaseDateLength = $viewMetaData[$type]['releaseDateLength']; + $writeLatest = $viewMetaData[$type]['writeLatest']; + $writeReleaseDate = $viewMetaData[$type]['writeReleaseDate']; + + $versionFits = $nameLength + $versionLength + 3 <= $width; + $latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width; + $releaseDateFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 3 <= $width; + $descriptionFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 24 <= $width; + + if ($latestFits && !$io->isDecorated()) { + $latestLength += 2; + } + + if ($showAllTypes) { + if ('available' === $type) { + $io->write('' . $type . ':'); + } else { + $io->write('' . $type . ':'); + } + } + + if ($writeLatest && !$input->getOption('direct')) { + $directDeps = []; + $transitiveDeps = []; + foreach ($packages as $pkg) { + if ($pkg['direct-dependency'] ?? false) { + $directDeps[] = $pkg; + } else { + $transitiveDeps[] = $pkg; + } + } + + $io->writeError(''); + $io->writeError('Direct dependencies required in composer.json:'); + if (\count($directDeps) > 0) { + $this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); + } else { + $io->writeError('Everything up to date'); + } + $io->writeError(''); + $io->writeError('Transitive dependencies not required in composer.json:'); + if (\count($transitiveDeps) > 0) { + $this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); + } else { + $io->writeError('Everything up to date'); + } + } else { + if ($writeLatest && \count($packages) === 0) { + $io->writeError('All your direct dependencies are up to date'); + } else { + $this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); + } + } + + if ($showAllTypes) { + $io->write(''); + } + } + } + + return $exitCode; + } + + /** + * @param array $packages + */ + private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength, bool $writeReleaseDate, int $releaseDateLength): void + { + $padName = $writeVersion || $writeLatest || $writeReleaseDate || $writeDescription; + $padVersion = $writeLatest || $writeReleaseDate || $writeDescription; + $padLatest = $writeDescription || $writeReleaseDate; + $padReleaseDate = $writeDescription; + foreach ($packages as $package) { + $link = $package['source'] ?? $package['homepage'] ?? ''; + if ($link !== '') { + $io->write($indent . ''.$package['name'].''. str_repeat(' ', ($padName ? $nameLength - strlen($package['name']) : 0)), false); + } else { + $io->write($indent . str_pad($package['name'], ($padName ? $nameLength : 0), ' '), false); + } + if (isset($package['version']) && $writeVersion) { + $io->write(' ' . str_pad($package['version'], ($padVersion ? $versionLength : 0), ' '), false); + } + if (isset($package['latest']) && isset($package['latest-status']) && $writeLatest) { + $latestVersion = $package['latest']; + $updateStatus = $package['latest-status']; + $style = $this->updateStatusToVersionStyle($updateStatus); + if (!$io->isDecorated()) { + $latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion; + } + $io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '', false); + if ($writeReleaseDate && isset($package['release-age'])) { + $io->write(' '.str_pad($package['release-age'], ($padReleaseDate ? $releaseDateLength : 0), ' '), false); + } + } + if (isset($package['description']) && $writeDescription) { + $description = (string) strtok($package['description'], "\r\n"); + + // Compute remaining width available for the description. + $remaining = $width - $nameLength - $versionLength - $releaseDateLength - 4; + if ($writeLatest) { + $remaining -= $latestLength; + } + + // If nothing fits, clear the description. + if ($remaining <= 0) { + $description = ''; + } elseif (extension_loaded('mbstring')) { + // Use mb_strwidth/mb_strimwidth to measure and trim by display width + // (CJK characters count as width 2). mb_strimwidth counts the trim + // marker ('...') in the width parameter, so pass $remaining directly. + if (mb_strwidth($description, 'UTF-8') > $remaining) { + $description = mb_strimwidth($description, 0, $remaining, '...', 'UTF-8'); + } + } else { + // Fallback when mbstring is not available: do a conservative byte-based cut. + // Ensure cut length is non-negative and leave room for the ellipsis. + $cut = max(0, $remaining - 3); + if (strlen($description) > $cut) { + $description = substr($description, 0, $cut) . '...'; + } + } + + $io->write(' ' . $description, false); + } + if (array_key_exists('path', $package)) { + $io->write(' '.(is_string($package['path']) ? $package['path'] : 'null'), false); + } + $io->write(''); + if (isset($package['warning'])) { + $io->write('' . $package['warning'] . ''); + } + } + } + + /** + * @return string[] + */ + protected function getRootRequires(): array + { + $composer = $this->tryComposer(); + if ($composer === null) { + return []; + } + + $rootPackage = $composer->getPackage(); + + return array_map( + 'strtolower', + array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires())) + ); + } + + /** + * @return array|string|string[] + */ + protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package) + { + return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package)); + } + + /** + * finds a package by name and version if provided + * + * @param ConstraintInterface|string $version + * @throws \InvalidArgumentException + * @return array{CompletePackageInterface|null, array} + */ + protected function getPackage(InstalledRepository $installedRepo, RepositoryInterface $repos, string $name, $version = null): array + { + $name = strtolower($name); + $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version; + + $policy = new DefaultPolicy(); + $repositorySet = new RepositorySet('dev'); + $repositorySet->allowInstalledRepositories(); + $repositorySet->addRepository($repos); + + $matchedPackage = null; + $versions = []; + if (PlatformRepository::isPlatformPackage($name)) { + $pool = $repositorySet->createPoolWithAllPackages(); + } else { + $pool = $repositorySet->createPoolForPackage($name); + } + $matches = $pool->whatProvides($name, $constraint); + $literals = []; + foreach ($matches as $package) { + // avoid showing the 9999999-dev alias if the default branch has no branch-alias set + if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $package = $package->getAliasOf(); + } + + // select an exact match if it is in the installed repo and no specific version was required + if (null === $version && $installedRepo->hasPackage($package)) { + $matchedPackage = $package; + } + + $versions[$package->getPrettyVersion()] = $package->getVersion(); + $literals[] = $package->getId(); + } + + // select preferred package according to policy rules + if (null === $matchedPackage && \count($literals) > 0) { + $preferred = $policy->selectPreferredPackages($pool, $literals); + $matchedPackage = $pool->literalToPackage($preferred[0]); + } + + if ($matchedPackage !== null && !$matchedPackage instanceof CompletePackageInterface) { + throw new \LogicException('ShowCommand::getPackage can only work with CompletePackageInterface, but got '.get_class($matchedPackage)); + } + + return [$matchedPackage, $versions]; + } + + /** + * Prints package info. + * + * @param array $versions + */ + protected function printPackageInfo(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void + { + $io = $this->getIO(); + + $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null); + $this->printLinks($package, Link::TYPE_REQUIRE); + $this->printLinks($package, Link::TYPE_DEV_REQUIRE, 'requires (dev)'); + + if ($package->getSuggests()) { + $io->write("\nsuggests"); + foreach ($package->getSuggests() as $suggested => $reason) { + $io->write($suggested . ' ' . $reason . ''); + } + } + + $this->printLinks($package, Link::TYPE_PROVIDE); + $this->printLinks($package, Link::TYPE_CONFLICT); + $this->printLinks($package, Link::TYPE_REPLACE); + } + + /** + * Prints package metadata. + * + * @param array $versions + */ + protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void + { + $isInstalledPackage = !PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package); + + $io = $this->getIO(); + $io->write('name : ' . $package->getPrettyName()); + $io->write('descrip. : ' . $package->getDescription()); + $io->write('keywords : ' . implode(', ', $package->getKeywords() ?: [])); + $this->printVersions($package, $versions, $installedRepo); + if ($isInstalledPackage && $package->getReleaseDate() !== null) { + $io->write('released : ' . $package->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($package->getReleaseDate())); + } + if ($latestPackage) { + $style = $this->getVersionStyle($latestPackage, $package); + $releasedTime = $latestPackage->getReleaseDate() === null ? '' : ' released ' . $latestPackage->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($latestPackage->getReleaseDate()); + $io->write('latest : <'.$style.'>' . $latestPackage->getPrettyVersion() . '' . $releasedTime); + } else { + $latestPackage = $package; + } + $io->write('type : ' . $package->getType()); + $this->printLicenses($package); + $io->write('homepage : ' . $package->getHomepage()); + $io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); + $io->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); + if ($isInstalledPackage) { + $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package); + if (is_string($path)) { + $io->write('path : ' . realpath($path)); + } else { + $io->write('path : null'); + } + } + $io->write('names : ' . implode(', ', $package->getNames())); + + if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { + $replacement = ($latestPackage->getReplacementPackage() !== null) + ? ' The author suggests using the ' . $latestPackage->getReplacementPackage(). ' package instead.' + : null; + + $io->writeError( + sprintf('Attention: This package is abandoned and no longer maintained.%s', $replacement) + ); + } + + if ($package->getSupport()) { + $io->write("\nsupport"); + foreach ($package->getSupport() as $type => $value) { + $io->write('' . $type . ' : '.$value); + } + } + + if (\count($package->getAutoload()) > 0) { + $io->write("\nautoload"); + $autoloadConfig = $package->getAutoload(); + foreach ($autoloadConfig as $type => $autoloads) { + $io->write('' . $type . ''); + + if ($type === 'psr-0' || $type === 'psr-4') { + foreach ($autoloads as $name => $path) { + $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); + } + } elseif ($type === 'classmap') { + $io->write(implode(', ', $autoloadConfig[$type])); + } + } + if ($package->getIncludePaths()) { + $io->write('include-path'); + $io->write(implode(', ', $package->getIncludePaths())); + } + } + } + + /** + * Prints all available versions of this package and highlights the installed one if any. + * + * @param array $versions + */ + protected function printVersions(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo): void + { + $versions = array_keys($versions); + $versions = Semver::rsort($versions); + + // highlight installed version + if ($installedPackages = $installedRepo->findPackages($package->getName())) { + foreach ($installedPackages as $installedPackage) { + $installedVersion = $installedPackage->getPrettyVersion(); + $key = array_search($installedVersion, $versions); + if (false !== $key) { + $versions[$key] = '* ' . $installedVersion . ''; + } + } + } + + $versions = implode(', ', $versions); + + $this->getIO()->write('versions : ' . $versions); + } + + /** + * print link objects + */ + protected function printLinks(CompletePackageInterface $package, string $linkType, ?string $title = null): void + { + $title = $title ?: $linkType; + $io = $this->getIO(); + if ($links = $package->{'get'.ucfirst($linkType)}()) { + $io->write("\n" . $title . ""); + + foreach ($links as $link) { + $io->write($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); + } + } + } + + /** + * Prints the licenses of a package with metadata + */ + protected function printLicenses(CompletePackageInterface $package): void + { + $spdxLicenses = new SpdxLicenses(); + + $licenses = $package->getLicense(); + $io = $this->getIO(); + + foreach ($licenses as $licenseId) { + $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url + + if (!$license) { + $out = $licenseId; + } else { + // is license OSI approved? + if ($license[1] === true) { + $out = sprintf('%s (%s) (OSI approved) %s', $license[0], $licenseId, $license[2]); + } else { + $out = sprintf('%s (%s) %s', $license[0], $licenseId, $license[2]); + } + } + + $io->write('license : ' . $out); + } + } + + /** + * Prints package info in JSON format. + * + * @param array $versions + */ + protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void + { + $json = [ + 'name' => $package->getPrettyName(), + 'description' => $package->getDescription(), + 'keywords' => $package->getKeywords() ?: [], + 'type' => $package->getType(), + 'homepage' => $package->getHomepage(), + 'names' => $package->getNames(), + ]; + + $json = $this->appendVersions($json, $versions); + $json = $this->appendLicenses($json, $package); + + if ($latestPackage) { + $json['latest'] = $latestPackage->getPrettyVersion(); + } else { + $latestPackage = $package; + } + + if (null !== $package->getSourceType()) { + $json['source'] = [ + 'type' => $package->getSourceType(), + 'url' => $package->getSourceUrl(), + 'reference' => $package->getSourceReference(), + ]; + } + + if (null !== $package->getDistType()) { + $json['dist'] = [ + 'type' => $package->getDistType(), + 'url' => $package->getDistUrl(), + 'reference' => $package->getDistReference(), + ]; + } + + if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) { + $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package); + if (is_string($path)) { + $path = realpath($path); + if ($path !== false) { + $json['path'] = $path; + } + } else { + $json['path'] = null; + } + + if ($package->getReleaseDate() !== null) { + $json['released'] = $package->getReleaseDate()->format(DATE_ATOM); + } + } + + if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { + $json['replacement'] = $latestPackage->getReplacementPackage(); + } + + if ($package->getSuggests()) { + $json['suggests'] = $package->getSuggests(); + } + + if ($package->getSupport()) { + $json['support'] = $package->getSupport(); + } + + $json = $this->appendAutoload($json, $package); + + if ($package->getIncludePaths()) { + $json['include_path'] = $package->getIncludePaths(); + } + + $json = $this->appendLinks($json, $package); + + $this->getIO()->write(JsonFile::encode($json)); + } + + /** + * @param JsonStructure $json + * @param array $versions + * @return JsonStructure + */ + private function appendVersions(array $json, array $versions): array + { + uasort($versions, 'version_compare'); + $versions = array_keys(array_reverse($versions)); + $json['versions'] = $versions; + + return $json; + } + + /** + * @param JsonStructure $json + * @return JsonStructure + */ + private function appendLicenses(array $json, CompletePackageInterface $package): array + { + if ($licenses = $package->getLicense()) { + $spdxLicenses = new SpdxLicenses(); + + $json['licenses'] = array_map(static function ($licenseId) use ($spdxLicenses) { + $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url + + if (!$license) { + return $licenseId; + } + + return [ + 'name' => $license[0], + 'osi' => $licenseId, + 'url' => $license[2], + ]; + }, $licenses); + } + + return $json; + } + + /** + * @param JsonStructure $json + * @return JsonStructure + */ + private function appendAutoload(array $json, CompletePackageInterface $package): array + { + if (\count($package->getAutoload()) > 0) { + $autoload = []; + + foreach ($package->getAutoload() as $type => $autoloads) { + if ($type === 'psr-0' || $type === 'psr-4') { + $psr = []; + + foreach ($autoloads as $name => $path) { + if (!$path) { + $path = '.'; + } + + $psr[$name ?: '*'] = $path; + } + + $autoload[$type] = $psr; + } elseif ($type === 'classmap') { + $autoload['classmap'] = $autoloads; + } + } + + $json['autoload'] = $autoload; + } + + return $json; + } + + /** + * @param JsonStructure $json + * @return JsonStructure + */ + private function appendLinks(array $json, CompletePackageInterface $package): array + { + foreach (Link::$TYPES as $linkType) { + $json = $this->appendLink($json, $package, $linkType); + } + + return $json; + } + + /** + * @param JsonStructure $json + * @return JsonStructure + */ + private function appendLink(array $json, CompletePackageInterface $package, string $linkType): array + { + $links = $package->{'get' . ucfirst($linkType)}(); + + if ($links) { + $json[$linkType] = []; + + foreach ($links as $link) { + $json[$linkType][$link->getTarget()] = $link->getPrettyConstraint(); + } + } + + return $json; + } + + /** + * Init styles for tree + */ + protected function initStyles(OutputInterface $output): void + { + $this->colors = [ + 'green', + 'yellow', + 'cyan', + 'magenta', + 'blue', + ]; + + foreach ($this->colors as $color) { + $style = new OutputFormatterStyle($color); + $output->getFormatter()->setStyle($color, $style); + } + } + + /** + * Display the tree + * + * @param array> $arrayTree + */ + protected function displayPackageTree(array $arrayTree): void + { + $io = $this->getIO(); + foreach ($arrayTree as $package) { + $io->write(sprintf('%s', $package['name']), false); + $io->write(' ' . $package['version'], false); + if (isset($package['description'])) { + $io->write(' ' . strtok($package['description'], "\r\n")); + } else { + // output newline + $io->write(''); + } + + if (isset($package['requires'])) { + $requires = $package['requires']; + $treeBar = '├'; + $j = 0; + $total = count($requires); + foreach ($requires as $require) { + $requireName = $require['name']; + $j++; + if ($j === $total) { + $treeBar = '└'; + } + $level = 1; + $color = $this->colors[$level]; + $info = sprintf( + '%s──<%s>%s %s', + $treeBar, + $color, + $requireName, + $color, + $require['version'] + ); + $this->writeTreeLine($info); + + $treeBar = str_replace('└', ' ', $treeBar); + $packagesInTree = [$package['name'], $requireName]; + + $this->displayTree($require, $packagesInTree, $treeBar, $level + 1); + } + } + } + } + + /** + * Generate the package tree + * + * @return array>|string|null> + */ + protected function generatePackageTree( + PackageInterface $package, + InstalledRepository $installedRepo, + RepositoryInterface $remoteRepos + ): array { + $requires = $package->getRequires(); + ksort($requires); + $children = []; + foreach ($requires as $requireName => $require) { + $packagesInTree = [$package->getName(), $requireName]; + + $treeChildDesc = [ + 'name' => $requireName, + 'version' => $require->getPrettyConstraint(), + ]; + + $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $packagesInTree); + + if ($deepChildren) { + $treeChildDesc['requires'] = $deepChildren; + } + + $children[] = $treeChildDesc; + } + $tree = [ + 'name' => $package->getPrettyName(), + 'version' => $package->getPrettyVersion(), + 'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : '', + ]; + + if ($children) { + $tree['requires'] = $children; + } + + return $tree; + } + + /** + * Display a package tree + * + * @param array>|string|null>|string $package + * @param array $packagesInTree + */ + protected function displayTree( + $package, + array $packagesInTree, + string $previousTreeBar = '├', + int $level = 1 + ): void { + $previousTreeBar = str_replace('├', '│', $previousTreeBar); + if (is_array($package) && isset($package['requires'])) { + $requires = $package['requires']; + $treeBar = $previousTreeBar . ' ├'; + $i = 0; + $total = count($requires); + foreach ($requires as $require) { + $currentTree = $packagesInTree; + $i++; + if ($i === $total) { + $treeBar = $previousTreeBar . ' └'; + } + $colorIdent = $level % count($this->colors); + $color = $this->colors[$colorIdent]; + + assert(is_string($require['name'])); + assert(is_string($require['version'])); + + $circularWarn = in_array( + $require['name'], + $currentTree, + true + ) ? '(circular dependency aborted here)' : ''; + $info = rtrim(sprintf( + '%s──<%s>%s %s %s', + $treeBar, + $color, + $require['name'], + $color, + $require['version'], + $circularWarn + )); + $this->writeTreeLine($info); + + $treeBar = str_replace('└', ' ', $treeBar); + + $currentTree[] = $require['name']; + $this->displayTree($require, $currentTree, $treeBar, $level + 1); + } + } + } + + /** + * Display a package tree + * + * @param string[] $packagesInTree + * @return array>|string>> + */ + protected function addTree( + string $name, + Link $link, + InstalledRepository $installedRepo, + RepositoryInterface $remoteRepos, + array $packagesInTree + ): array { + $children = []; + [$package] = $this->getPackage( + $installedRepo, + $remoteRepos, + $name, + $link->getPrettyConstraint() === 'self.version' ? $link->getConstraint() : $link->getPrettyConstraint() + ); + if (is_object($package)) { + $requires = $package->getRequires(); + ksort($requires); + foreach ($requires as $requireName => $require) { + $currentTree = $packagesInTree; + + $treeChildDesc = [ + 'name' => $requireName, + 'version' => $require->getPrettyConstraint(), + ]; + + if (!in_array($requireName, $currentTree, true)) { + $currentTree[] = $requireName; + $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $currentTree); + if ($deepChildren) { + $treeChildDesc['requires'] = $deepChildren; + } + } + + $children[] = $treeChildDesc; + } + } + + return $children; + } + + private function updateStatusToVersionStyle(string $updateStatus): string + { + // 'up-to-date' is printed green + // 'semver-safe-update' is printed red + // 'update-possible' is printed yellow + return str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['info', 'highlight', 'comment'], $updateStatus); + } + + private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package): string + { + if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) { + return 'up-to-date'; + } + + $constraint = $package->getVersion(); + if (0 !== strpos($constraint, 'dev-')) { + $constraint = '^'.$constraint; + } + if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) { + // it needs an immediate semver-compliant upgrade + return 'semver-safe-update'; + } + + // it needs an upgrade but has potential BC breaks so is not urgent + return 'update-possible'; + } + + private function writeTreeLine(string $line): void + { + $io = $this->getIO(); + if (!$io->isDecorated()) { + $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line); + } + + $io->write($line); + } + + /** + * Given a package, this finds the latest package matching it + */ + private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, bool $majorOnly, bool $minorOnly, bool $patchOnly, PlatformRequirementFilterInterface $platformReqFilter): ?PackageInterface + { + // find the latest version allowed in this repo set + $name = $package->getName(); + $versionSelector = new VersionSelector($this->getRepositorySet($composer), $platformRepo); + $stability = $composer->getPackage()->getMinimumStability(); + $flags = $composer->getPackage()->getStabilityFlags(); + if (isset($flags[$name])) { + $stability = array_search($flags[$name], BasePackage::STABILITIES, true); + } + + $bestStability = $stability; + if ($composer->getPackage()->getPreferStable()) { + $bestStability = $package->getStability(); + } + + $targetVersion = null; + if (0 === strpos($package->getVersion(), 'dev-')) { + $targetVersion = $package->getVersion(); + + // dev-x branches are considered to be on the latest major version always, do not look up for a new commit as that is deemed a minor upgrade (albeit risky) + if ($majorOnly) { + return null; + } + } + + if ($targetVersion === null) { + if ($majorOnly && Preg::isMatch('{^(?P(?:0\.)+)?(?P\d+)\.}', $package->getVersion(), $match)) { + $targetVersion = '>='.$match['zero_major'].(((int) $match['first_meaningful']) + 1).',<9999999-dev'; + } + + if ($minorOnly) { + $targetVersion = '^'.$package->getVersion(); + } + + if ($patchOnly) { + $trimmedVersion = Preg::replace('{(\.0)+$}D', '', $package->getVersion()); + $partsNeeded = substr($trimmedVersion, 0, 1) === '0' ? 4 : 3; + while (substr_count($trimmedVersion, '.') + 1 < $partsNeeded) { + $trimmedVersion .= '.0'; + } + $targetVersion = '~'.$trimmedVersion; + } + } + + if ($this->getIO()->isVerbose()) { + $showWarnings = true; + } else { + $showWarnings = static function (PackageInterface $candidate) use ($package): bool { + if (str_starts_with($candidate->getVersion(), 'dev-') || str_starts_with($package->getVersion(), 'dev-')) { + return false; + } + + return version_compare($candidate->getVersion(), $package->getVersion(), '<='); + }; + } + $candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter, 0, $this->getIO(), $showWarnings); + while ($candidate instanceof AliasPackage) { + $candidate = $candidate->getAliasOf(); + } + + return $candidate !== false ? $candidate : null; + } + + private function getRepositorySet(Composer $composer): RepositorySet + { + if (!$this->repositorySet) { + $this->repositorySet = new RepositorySet($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); + $this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); + } + + return $this->repositorySet; + } + + private function getRelativeTime(DateTimeInterface $releaseDate): string + { + if ($releaseDate->format('Ymd') === date('Ymd')) { + return 'today'; + } + + $diff = $releaseDate->diff(new \DateTimeImmutable()); + if ($diff->days < 7) { + return 'this week'; + } + + if ($diff->days < 14) { + return 'last week'; + } + + if ($diff->m < 1 && $diff->days < 31) { + return floor($diff->days / 7) . ' weeks ago'; + } + + if ($diff->y < 1) { + return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago'; + } + + return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago'; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/StatusCommand.php b/vendor/composer/composer/src/Composer/Command/StatusCommand.php new file mode 100644 index 000000000..5d90b3105 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/StatusCommand.php @@ -0,0 +1,221 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Downloader\ChangeReportInterface; +use Composer\Downloader\DvcsDownloaderInterface; +use Composer\Downloader\VcsCapableDownloaderInterface; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Script\ScriptEvents; +use Composer\Util\ProcessExecutor; + +/** + * @author Tiago Ribeiro + * @author Rui Marinho + */ +class StatusCommand extends BaseCommand +{ + private const EXIT_CODE_ERRORS = 1; + private const EXIT_CODE_UNPUSHED_CHANGES = 2; + private const EXIT_CODE_VERSION_CHANGES = 4; + + /** + * @throws \Symfony\Component\Console\Exception\InvalidArgumentException + */ + protected function configure(): void + { + $this + ->setName('status') + ->setDescription('Shows a list of locally modified packages') + ->setDefinition([ + new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'), + ]) + ->setHelp( + <<requireComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + // Dispatch pre-status-command + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true); + + $exitCode = $this->doExecute($input); + + // Dispatch post-status-command + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true); + + return $exitCode; + } + + private function doExecute(InputInterface $input): int + { + // init repos + $composer = $this->requireComposer(); + + $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); + + $dm = $composer->getDownloadManager(); + $im = $composer->getInstallationManager(); + + $errors = []; + $io = $this->getIO(); + $unpushedChanges = []; + $vcsVersionChanges = []; + + $parser = new VersionParser; + $guesser = new VersionGuesser($composer->getConfig(), $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io), $parser, $io); + $dumper = new ArrayDumper; + + // list packages + foreach ($installedRepo->getCanonicalPackages() as $package) { + $downloader = $dm->getDownloaderForPackage($package); + $targetDir = $im->getInstallPath($package); + if ($targetDir === null) { + continue; + } + + if ($downloader instanceof ChangeReportInterface) { + if (is_link($targetDir)) { + $errors[$targetDir] = $targetDir . ' is a symbolic link.'; + } + + if (null !== ($changes = $downloader->getLocalChanges($package, $targetDir))) { + $errors[$targetDir] = $changes; + } + } + + if ($downloader instanceof VcsCapableDownloaderInterface) { + if ($downloader->getVcsReference($package, $targetDir)) { + switch ($package->getInstallationSource()) { + case 'source': + $previousRef = $package->getSourceReference(); + break; + case 'dist': + $previousRef = $package->getDistReference(); + break; + default: + $previousRef = null; + } + + $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir); + + if ($previousRef && $currentVersion && $currentVersion['commit'] !== $previousRef && $currentVersion['pretty_version'] !== $previousRef) { + $vcsVersionChanges[$targetDir] = [ + 'previous' => [ + 'version' => $package->getPrettyVersion(), + 'ref' => $previousRef, + ], + 'current' => [ + 'version' => $currentVersion['pretty_version'], + 'ref' => $currentVersion['commit'], + ], + ]; + } + } + } + + if ($downloader instanceof DvcsDownloaderInterface) { + if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) { + $unpushedChanges[$targetDir] = $unpushed; + } + } + } + + // output errors/warnings + if (!$errors && !$unpushedChanges && !$vcsVersionChanges) { + $io->writeError('No local changes'); + + return 0; + } + + if ($errors) { + $io->writeError('You have changes in the following dependencies:'); + + foreach ($errors as $path => $changes) { + if ($input->getOption('verbose')) { + $indentedChanges = implode("\n", array_map(static function ($line): string { + return ' ' . ltrim($line); + }, explode("\n", $changes))); + $io->write(''.$path.':'); + $io->write($indentedChanges); + } else { + $io->write($path); + } + } + } + + if ($unpushedChanges) { + $io->writeError('You have unpushed changes on the current branch in the following dependencies:'); + + foreach ($unpushedChanges as $path => $changes) { + if ($input->getOption('verbose')) { + $indentedChanges = implode("\n", array_map(static function ($line): string { + return ' ' . ltrim($line); + }, explode("\n", $changes))); + $io->write(''.$path.':'); + $io->write($indentedChanges); + } else { + $io->write($path); + } + } + } + + if ($vcsVersionChanges) { + $io->writeError('You have version variations in the following dependencies:'); + + foreach ($vcsVersionChanges as $path => $changes) { + if ($input->getOption('verbose')) { + // If we don't can't find a version, use the ref instead. + $currentVersion = $changes['current']['version'] ?: $changes['current']['ref']; + $previousVersion = $changes['previous']['version'] ?: $changes['previous']['ref']; + + if ($io->isVeryVerbose()) { + // Output the ref regardless of whether or not it's being used as the version + $currentVersion .= sprintf(' (%s)', $changes['current']['ref']); + $previousVersion .= sprintf(' (%s)', $changes['previous']['ref']); + } + + $io->write(''.$path.':'); + $io->write(sprintf(' From %s to %s', $previousVersion, $currentVersion)); + } else { + $io->write($path); + } + } + } + + if (($errors || $unpushedChanges || $vcsVersionChanges) && !$input->getOption('verbose')) { + $io->writeError('Use --verbose (-v) to see a list of files'); + } + + return ($errors ? self::EXIT_CODE_ERRORS : 0) + ($unpushedChanges ? self::EXIT_CODE_UNPUSHED_CHANGES : 0) + ($vcsVersionChanges ? self::EXIT_CODE_VERSION_CHANGES : 0); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/SuggestsCommand.php b/vendor/composer/composer/src/Composer/Command/SuggestsCommand.php new file mode 100644 index 000000000..df63b3eb4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/SuggestsCommand.php @@ -0,0 +1,103 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Repository\PlatformRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\InstalledRepository; +use Composer\Installer\SuggestedPackagesReporter; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class SuggestsCommand extends BaseCommand +{ + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('suggests') + ->setDescription('Shows package suggestions') + ->setDefinition([ + new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package (default)'), + new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'), + new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show suggestions from all dependencies, including transitive ones'), + new InputOption('list', null, InputOption::VALUE_NONE, 'Show only list of suggested package names'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.', null, $this->suggestInstalledPackage()), + ]) + ->setHelp( + <<%command.name% command shows a sorted list of suggested packages. + +Read more at https://getcomposer.org/doc/03-cli.md#suggests +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $installedRepos = [ + new RootPackageRepository(clone $composer->getPackage()), + ]; + + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $installedRepos[] = new PlatformRepository([], $locker->getPlatformOverrides()); + $installedRepos[] = $locker->getLockedRepository(!$input->getOption('no-dev')); + } else { + $installedRepos[] = new PlatformRepository([], $composer->getConfig()->get('platform')); + $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); + } + + $installedRepo = new InstalledRepository($installedRepos); + $reporter = new SuggestedPackagesReporter($this->getIO()); + + $filter = $input->getArgument('packages'); + $packages = $installedRepo->getPackages(); + $packages[] = $composer->getPackage(); + foreach ($packages as $package) { + if (!empty($filter) && !in_array($package->getName(), $filter)) { + continue; + } + + $reporter->addSuggestionsFromPackage($package); + } + + // Determine output mode, default is by-package + $mode = SuggestedPackagesReporter::MODE_BY_PACKAGE; + + // if by-suggestion is given we override the default + if ($input->getOption('by-suggestion')) { + $mode = SuggestedPackagesReporter::MODE_BY_SUGGESTION; + } + // unless by-package is also present then we enable both + if ($input->getOption('by-package')) { + $mode |= SuggestedPackagesReporter::MODE_BY_PACKAGE; + } + // list is exclusive and overrides everything else + if ($input->getOption('list')) { + $mode = SuggestedPackagesReporter::MODE_LIST; + } + + $reporter->output($mode, $installedRepo, empty($filter) && !$input->getOption('all') ? $composer->getPackage() : null); + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/UpdateCommand.php b/vendor/composer/composer/src/Composer/Command/UpdateCommand.php new file mode 100644 index 000000000..5d8172d05 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/UpdateCommand.php @@ -0,0 +1,387 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\DependencyResolver\Request; +use Composer\Installer; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\Package\Loader\RootPackageLoader; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Package\Version\VersionParser; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositorySet; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Intervals; +use Composer\Util\HttpDownloader; +use Composer\Advisory\Auditor; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + * @author Nils Adermann + */ +class UpdateCommand extends BaseCommand +{ + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('update') + ->setAliases(['u', 'upgrade']) + ->setDescription('Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.', null, $this->suggestInstalledPackage(false)), + new InputOption('with', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0'), + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), + new InputOption('lock', null, InputOption::VALUE_NONE, 'Overwrites the lock file hash to suppress warning about the lock file being out of date without updating package versions. Package metadata like mirrors and URLs are updated if they changed.'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), + new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'), + new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), + new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('with-dependencies', 'w', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, except those which are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'), + new InputOption('with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, including those which are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'), + new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), + new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), + new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'Only perform absolutely necessary changes to dependencies. If packages cannot be kept at their currently locked version they are updated. For partial updates the allow-listed packages are always updated fully. (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), + new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Only allow patch version updates for currently installed dependencies.'), + new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), + new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'), + new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']), + ]) + ->setHelp( + <<update command reads the composer.json file from the +current directory, processes it, and updates, removes or installs all the +dependencies. + +php composer.phar update + +To limit the update operation to a few packages, you can list the package(s) +you want to update as such: + +php composer.phar update vendor/package1 foo/mypackage [...] + +You may also use an asterisk (*) pattern to limit the update operation to package(s) +from a specific vendor: + +php composer.phar update vendor/package1 foo/* [...] + +To run an update with more restrictive constraints you can use: + +php composer.phar update --with vendor/package:1.0.* + +To run a partial update with more restrictive constraints you can use the shorthand: + +php composer.phar update vendor/package:1.0.* + +To select packages names interactively with auto-completion use -i. + +Read more at https://getcomposer.org/doc/03-cli.md#update-u-upgrade +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = $this->getIO(); + if ($input->getOption('dev')) { + $io->writeError('You are using the deprecated option "--dev". It has no effect and will break in Composer 3.'); + } + if ($input->getOption('no-suggest')) { + $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); + } + + $composer = $this->requireComposer(); + + if (!HttpDownloader::isCurlEnabled()) { + $io->writeError('Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.'); + } + + $packages = $input->getArgument('packages'); + $reqs = $this->formatRequirements($input->getOption('with')); + + // extract --with shorthands from the allowlist + if (count($packages) > 0) { + $allowlistPackagesWithRequirements = array_filter($packages, static function ($pkg): bool { + return Preg::isMatch('{\S+[ =:]\S+}', $pkg); + }); + foreach ($this->formatRequirements($allowlistPackagesWithRequirements) as $package => $constraint) { + $reqs[$package] = $constraint; + } + + // replace the foo/bar:req by foo/bar in the allowlist + foreach ($allowlistPackagesWithRequirements as $package) { + $packageName = Preg::replace('{^([^ =:]+)[ =:].*$}', '$1', $package); + $index = array_search($package, $packages); + $packages[$index] = $packageName; + } + } + + $rootPackage = $composer->getPackage(); + $rootPackage->setReferences(RootPackageLoader::extractReferences($reqs, $rootPackage->getReferences())); + $rootPackage->setStabilityFlags(RootPackageLoader::extractStabilityFlags($reqs, $rootPackage->getMinimumStability(), $rootPackage->getStabilityFlags())); + + $parser = new VersionParser; + $temporaryConstraints = []; + $rootRequirements = array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()); + foreach ($reqs as $package => $constraint) { + $package = strtolower($package); + $parsedConstraint = $parser->parseConstraints($constraint); + $temporaryConstraints[$package] = $parsedConstraint; + if (isset($rootRequirements[$package]) && !Intervals::haveIntersections($parsedConstraint, $rootRequirements[$package]->getConstraint())) { + $io->writeError('The temporary constraint "'.$constraint.'" for "'.$package.'" must be a subset of the constraint in your composer.json ('.$rootRequirements[$package]->getPrettyConstraint().')'); + $io->write('Run `composer require '.$package.'` or `composer require '.$package.':'.$constraint.'` instead to replace the constraint'); + + return self::FAILURE; + } + } + + if ($input->getOption('patch-only')) { + if (!$composer->getLocker()->isLocked()) { + throw new \InvalidArgumentException('patch-only can only be used with a lock file present'); + } + foreach ($composer->getLocker()->getLockedRepository(true)->getCanonicalPackages() as $package) { + if ($package->isDev()) { + continue; + } + if (!Preg::isMatch('{^(\d+\.\d+\.\d+)}', $package->getVersion(), $match)) { + continue; + } + $constraint = $parser->parseConstraints('~'.$match[1]); + if (isset($temporaryConstraints[$package->getName()])) { + $temporaryConstraints[$package->getName()] = MultiConstraint::create([$temporaryConstraints[$package->getName()], $constraint], true); + } else { + $temporaryConstraints[$package->getName()] = $constraint; + } + } + } + + if ($input->getOption('interactive')) { + $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages); + } + + if ($input->getOption('root-reqs')) { + $requires = array_keys($rootPackage->getRequires()); + if (!$input->getOption('no-dev')) { + $requires = array_merge($requires, array_keys($rootPackage->getDevRequires())); + } + + if (!empty($packages)) { + $packages = array_intersect($packages, $requires); + } else { + $packages = $requires; + } + } + + // the arguments lock/nothing/mirrors are not package names but trigger a mirror update instead + // they are further mutually exclusive with listing actual package names + $filteredPackages = array_filter($packages, static function ($package): bool { + return !in_array($package, ['lock', 'nothing', 'mirrors'], true); + }); + $updateMirrors = $input->getOption('lock') || count($filteredPackages) !== count($packages); + $packages = $filteredPackages; + + if ($updateMirrors && !empty($packages)) { + $io->writeError('You cannot simultaneously update only a selection of packages and regenerate the lock file metadata.'); + + return -1; + } + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); + + $install = Installer::create($io, $composer); + + $config = $composer->getConfig(); + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); + + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); + $minimalChanges = $input->getOption('minimal-changes') || $config->get('update-with-minimal-changes'); + + $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; + if ($input->getOption('with-all-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; + } elseif ($input->getOption('with-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; + } + + $install + ->setDryRun($input->getOption('dry-run')) + ->setVerbose($input->getOption('verbose')) + ->setPreferSource($preferSource) + ->setPreferDist($preferDist) + ->setDevMode(!$input->getOption('no-dev')) + ->setDumpAutoloader(!$input->getOption('no-autoloader')) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu, $apcuPrefix) + ->setUpdate(true) + ->setInstall(!$input->getOption('no-install')) + ->setUpdateMirrors($updateMirrors) + ->setUpdateAllowList($packages) + ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) + ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) + ->setPreferStable($input->getOption('prefer-stable')) + ->setPreferLowest($input->getOption('prefer-lowest')) + ->setTemporaryConstraints($temporaryConstraints) + ->setAuditConfig($this->createAuditConfig($composer->getConfig(), $input)) + ->setMinimalUpdate($minimalChanges) + ; + + if ($input->getOption('no-plugins')) { + $install->disablePlugins(); + } + + $result = $install->run(); + + if ($result === 0 && !$input->getOption('lock')) { + $bumpAfterUpdate = $input->getOption('bump-after-update'); + if (false === $bumpAfterUpdate) { + $bumpAfterUpdate = $composer->getConfig()->get('bump-after-update'); + } + + if (false !== $bumpAfterUpdate) { + $io->writeError('Bumping dependencies'); + $bumpCommand = new BumpCommand(); + $bumpCommand->setComposer($composer); + $result = $bumpCommand->doBump( + $io, + $bumpAfterUpdate === 'dev', + $bumpAfterUpdate === 'no-dev', + $input->getOption('dry-run'), + $input->getArgument('packages'), + '--bump-after-update=dev' + ); + } + } + + return $result; + } + + /** + * @param array $packages + * @return array + */ + private function getPackagesInteractively(IOInterface $io, InputInterface $input, OutputInterface $output, Composer $composer, array $packages): array + { + if (!$input->isInteractive()) { + throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.'); + } + + $platformReqFilter = $this->getPlatformRequirementFilter($input); + $stabilityFlags = $composer->getPackage()->getStabilityFlags(); + $requires = array_merge( + $composer->getPackage()->getRequires(), + $composer->getPackage()->getDevRequires() + ); + + $filter = \count($packages) > 0 ? BasePackage::packageNamesToRegexp($packages) : null; + + $io->writeError('Loading packages that can be updated...'); + $autocompleterValues = []; + $installedPackages = $composer->getLocker()->isLocked() ? $composer->getLocker()->getLockedRepository(true)->getPackages() : $composer->getRepositoryManager()->getLocalRepository()->getPackages(); + $versionSelector = $this->createVersionSelector($composer); + foreach ($installedPackages as $package) { + if ($filter !== null && !Preg::isMatch($filter, $package->getName())) { + continue; + } + $currentVersion = $package->getPrettyVersion(); + $constraint = isset($requires[$package->getName()]) ? $requires[$package->getName()]->getPrettyConstraint() : null; + $stability = isset($stabilityFlags[$package->getName()]) ? (string) array_search($stabilityFlags[$package->getName()], BasePackage::STABILITIES, true) : $composer->getPackage()->getMinimumStability(); + $latestVersion = $versionSelector->findBestCandidate($package->getName(), $constraint, $stability, $platformReqFilter); + if ($latestVersion !== false && ($package->getVersion() !== $latestVersion->getVersion() || $latestVersion->isDev())) { + $autocompleterValues[$package->getName()] = '' . $currentVersion . ' => ' . $latestVersion->getPrettyVersion() . ''; + } + } + if (0 === \count($installedPackages)) { + foreach ($requires as $req => $constraint) { + if (PlatformRepository::isPlatformPackage($req)) { + continue; + } + $autocompleterValues[$req] = ''; + } + } + + if (0 === \count($autocompleterValues)) { + throw new \RuntimeException('Could not find any package with new versions available'); + } + + $packages = $io->select( + 'Select packages: (Select more than one value separated by comma) ', + $autocompleterValues, + false, + 1, + 'No package named "%s" is installed.', + true + ); + + $table = new Table($output); + $table->setHeaders(['Selected packages']); + foreach ($packages as $package) { + $table->addRow([$package]); + } + $table->render(); + + if ($io->askConfirmation(sprintf( + 'Would you like to continue and update the above package%s [yes]? ', + 1 === count($packages) ? '' : 's' + ))) { + return $packages; + } + + throw new \RuntimeException('Installation aborted.'); + } + + private function createVersionSelector(Composer $composer): VersionSelector + { + $repositorySet = new RepositorySet(); + $repositorySet->addRepository(new CompositeRepository(array_filter($composer->getRepositoryManager()->getRepositories(), static function (RepositoryInterface $repository) { + return !$repository instanceof PlatformRepository; + }))); + + return new VersionSelector($repositorySet); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ValidateCommand.php b/vendor/composer/composer/src/Composer/Command/ValidateCommand.php new file mode 100644 index 000000000..3cdb9e384 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ValidateCommand.php @@ -0,0 +1,216 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Package\Loader\ValidatingArrayLoader; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Util\ConfigValidator; +use Composer\Util\Filesystem; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * ValidateCommand + * + * @author Robert Schönthal + * @author Jordi Boggiano + */ +class ValidateCommand extends BaseCommand +{ + /** + * configure + */ + protected function configure(): void + { + $this + ->setName('validate') + ->setDescription('Validates a composer.json and composer.lock') + ->setDefinition([ + new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'), + new InputOption('check-lock', null, InputOption::VALUE_NONE, 'Check if lock file is up to date (even when config.lock is false)'), + new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'), + new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), + new InputOption('no-check-version', null, InputOption::VALUE_NONE, 'Do not report a warning if the version field is present'), + new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'), + new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'), + new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file'), + ]) + ->setHelp( + <<getArgument('file') ?? Factory::getComposerFile(); + $io = $this->getIO(); + + if (!file_exists($file)) { + $io->writeError('' . $file . ' not found.'); + + return 3; + } + if (!Filesystem::isReadable($file)) { + $io->writeError('' . $file . ' is not readable.'); + + return 3; + } + + $validator = new ConfigValidator($io); + $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL; + $checkPublish = !$input->getOption('no-check-publish'); + $checkLock = !$input->getOption('no-check-lock'); + $checkVersion = $input->getOption('no-check-version') ? 0 : ConfigValidator::CHECK_VERSION; + $isStrict = $input->getOption('strict'); + [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion); + + $lockErrors = []; + $composer = $this->createComposerInstance($input, $io, $file); + // config.lock = false ~= implicit --no-check-lock; --check-lock overrides + $checkLock = ($checkLock && $composer->getConfig()->get('lock')) || $input->getOption('check-lock'); + $locker = $composer->getLocker(); + if ($locker->isLocked() && !$locker->isFresh()) { + $lockErrors[] = '- The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update` or `composer update `.'; + } + + if ($locker->isLocked()) { + $lockErrors = array_merge($lockErrors, $locker->getMissingRequirementInfo($composer->getPackage(), true)); + } + + $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true); + + // $errors include publish and lock errors when exists + $exitCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0); + + if ($input->getOption('with-dependencies')) { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + foreach ($localRepo->getPackages() as $package) { + $path = $composer->getInstallationManager()->getInstallPath($package); + if (null === $path) { + continue; + } + $file = $path . '/composer.json'; + if (is_dir($path) && file_exists($file)) { + [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion); + + $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors); + + // $errors include publish errors when exists + $depCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0); + $exitCode = max($depCode, $exitCode); + } + } + } + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'validate', $input, $output); + $eventCode = $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + return max($eventCode, $exitCode); + } + + /** + * @param string[] $errors + * @param string[] $warnings + * @param string[] $publishErrors + * @param string[] $lockErrors + */ + private function outputResult(IOInterface $io, string $name, array &$errors, array &$warnings, bool $checkPublish = false, array $publishErrors = [], bool $checkLock = false, array $lockErrors = [], bool $printSchemaUrl = false): void + { + $doPrintSchemaUrl = false; + + if (\count($errors) > 0) { + $io->writeError('' . $name . ' is invalid, the following errors/warnings were found:'); + } elseif (\count($publishErrors) > 0 && $checkPublish) { + $io->writeError('' . $name . ' is valid for simple usage with Composer but has'); + $io->writeError('strict errors that make it unable to be published as a package'); + $doPrintSchemaUrl = $printSchemaUrl; + } elseif (\count($warnings) > 0) { + $io->writeError('' . $name . ' is valid, but with a few warnings'); + $doPrintSchemaUrl = $printSchemaUrl; + } elseif (\count($lockErrors) > 0) { + $io->write('' . $name . ' is valid but your composer.lock has some '.($checkLock ? 'errors' : 'warnings').''); + } else { + $io->write('' . $name . ' is valid'); + } + + if ($doPrintSchemaUrl) { + $io->writeError('See https://getcomposer.org/doc/04-schema.md for details on the schema'); + } + + if (\count($errors) > 0) { + $errors = array_map(static function ($err): string { + return '- ' . $err; + }, $errors); + array_unshift($errors, '# General errors'); + } + if (\count($warnings) > 0) { + $warnings = array_map(static function ($err): string { + return '- ' . $err; + }, $warnings); + array_unshift($warnings, '# General warnings'); + } + + // Avoid setting the exit code to 1 in case --strict and --no-check-publish/--no-check-lock are combined + $extraWarnings = []; + + // If checking publish errors, display them as errors, otherwise just show them as warnings + if (\count($publishErrors) > 0 && $checkPublish) { + $publishErrors = array_map(static function ($err): string { + return '- ' . $err; + }, $publishErrors); + + array_unshift($publishErrors, '# Publish errors'); + $errors = array_merge($errors, $publishErrors); + } + + // If checking lock errors, display them as errors, otherwise just show them as warnings + if (\count($lockErrors) > 0) { + if ($checkLock) { + array_unshift($lockErrors, '# Lock file errors'); + $errors = array_merge($errors, $lockErrors); + } else { + array_unshift($lockErrors, '# Lock file warnings'); + $extraWarnings = array_merge($extraWarnings, $lockErrors); + } + } + + $messages = [ + 'error' => $errors, + 'warning' => array_merge($warnings, $extraWarnings), + ]; + + foreach ($messages as $style => $msgs) { + foreach ($msgs as $msg) { + if (strpos($msg, '#') === 0) { + $io->writeError('<' . $style . '>' . $msg . ''); + } else { + $io->writeError($msg); + } + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Compiler.php b/vendor/composer/composer/src/Composer/Compiler.php new file mode 100644 index 000000000..2580544b3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Compiler.php @@ -0,0 +1,330 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Json\JsonFile; +use Composer\CaBundle\CaBundle; +use Composer\Pcre\Preg; +use Composer\Util\Git; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Finder\Finder; +use Seld\PharUtils\Timestamps; +use Seld\PharUtils\Linter; + +/** + * The Compiler class compiles composer into a phar + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class Compiler +{ + /** @var string */ + private $version; + /** @var string */ + private $branchAliasVersion = ''; + /** @var \DateTime */ + private $versionDate; + + /** + * Compiles composer into a single phar file + * + * @param string $pharFile The full path to the file to create + * + * @throws \RuntimeException + */ + public function compile(string $pharFile = 'composer.phar'): void + { + if (file_exists($pharFile)) { + unlink($pharFile); + } + + $process = new ProcessExecutor(); + + $command = Git::buildRevListCommand($process, ['-n1', '--format=%H', 'HEAD']); + if (0 !== $process->execute($command, $output, dirname(__DIR__, 2))) { + throw new \RuntimeException('Can\'t run git rev-list. You must ensure to run compile from composer git repository clone and that git binary is available.'); + } + $this->version = trim(Git::parseRevListOutput($output, $process)); + + $command = Git::buildRevListCommand($process, ['-n1', '--format=%ci', 'HEAD']); + if (0 !== $process->execute($command, $output, dirname(__DIR__, 2))) { + throw new \RuntimeException('Can\'t run git rev-list. You must ensure to run compile from composer git repository clone and that git binary is available.'); + } + + $this->versionDate = new \DateTime(trim(Git::parseRevListOutput($output, $process))); + $this->versionDate->setTimezone(new \DateTimeZone('UTC')); + + if (0 === $process->execute(['git', 'describe', '--tags', '--exact-match', 'HEAD'], $output, dirname(__DIR__, 2))) { + $this->version = trim($output); + } else { + // get branch-alias defined in composer.json for dev-main (if any) + $localConfig = __DIR__.'/../../composer.json'; + $file = new JsonFile($localConfig); + $localConfig = $file->read(); + if (isset($localConfig['extra']['branch-alias']['dev-main'])) { + $this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-main']; + } + } + + if ('' === $this->version) { + throw new \UnexpectedValueException('Version detection failed'); + } + + $phar = new \Phar($pharFile, 0, 'composer.phar'); + $phar->setSignatureAlgorithm(\Phar::SHA512); + + $phar->startBuffering(); + + $finderSort = static function ($a, $b): int { + return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/')); + }; + + // Add Composer sources + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->name('*.php') + ->notName('Compiler.php') + ->notName('ClassLoader.php') + ->notName('InstalledVersions.php') + ->in(__DIR__.'/..') + ->sort($finderSort) + ; + foreach ($finder as $file) { + $this->addFile($phar, $file); + } + // Add runtime utilities separately to make sure they retains the docblocks as these will get copied into projects + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/Autoload/ClassLoader.php'), false); + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/InstalledVersions.php'), false); + + // Add Composer resources + $finder = new Finder(); + $finder->files() + ->in(__DIR__.'/../../res') + ->sort($finderSort) + ; + foreach ($finder as $file) { + $this->addFile($phar, $file, false); + } + + // Add vendor files + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->notPath('/\/(composer\.(?:json|lock)|[A-Z]+\.md(?:own)?|\.gitignore|appveyor.yml|phpunit\.xml\.dist|phpstan\.neon\.dist|phpstan-config\.neon|phpstan-baseline\.neon|UPGRADE.*\.(?:md|txt))$/') + ->notPath('/bin\/(jsonlint|validate-json|simple-phpunit|phpstan|phpstan\.phar)(\.bat)?$/') + ->notPath('justinrainbow/json-schema/demo/') + ->notPath('justinrainbow/json-schema/dist/') + ->notPath('composer/pcre/extension.neon') + ->notPath('composer/LICENSE') + ->exclude('Tests') + ->exclude('tests') + ->exclude('docs') + ->in(__DIR__.'/../../vendor/') + ->sort($finderSort) + ; + + $extraFiles = []; + foreach ([ + __DIR__ . '/../../vendor/composer/installed.json', + __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-exceptions.json', + __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-licenses.json', + CaBundle::getBundledCaBundlePath(), + __DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe', + __DIR__ . '/../../vendor/symfony/console/Resources/completion.bash', + ] as $file) { + $extraFiles[$file] = realpath($file); + if (!file_exists($file)) { + throw new \RuntimeException('Extra file listed is missing from the filesystem: '.$file); + } + } + $unexpectedFiles = []; + + foreach ($finder as $file) { + if (false !== ($index = array_search($file->getRealPath(), $extraFiles, true))) { + unset($extraFiles[$index]); + } elseif (!Preg::isMatch('{(^LICENSE(?:\.txt)?$|\.php$)}', $file->getFilename())) { + $unexpectedFiles[] = (string) $file; + } + + if (Preg::isMatch('{\.php[\d.]*$}', $file->getFilename())) { + $this->addFile($phar, $file); + } else { + $this->addFile($phar, $file, false); + } + } + + if (count($extraFiles) > 0) { + throw new \RuntimeException('These files were expected but not added to the phar, they might be excluded or gone from the source package:'.PHP_EOL.var_export($extraFiles, true)); + } + if (count($unexpectedFiles) > 0) { + throw new \RuntimeException('These files were unexpectedly added to the phar, make sure they are excluded or listed in $extraFiles:'.PHP_EOL.var_export($unexpectedFiles, true)); + } + + // Add bin/composer + $this->addComposerBin($phar); + + // Stubs + $phar->setStub($this->getStub()); + + $phar->stopBuffering(); + + // disabled for interoperability with systems without gzip ext + // $phar->compressFiles(\Phar::GZ); + + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../LICENSE'), false); + + unset($phar); + + // re-sign the phar with reproducible timestamp / signature + $util = new Timestamps($pharFile); + $util->updateTimestamps($this->versionDate); + $util->save($pharFile, \Phar::SHA512); + + Linter::lint($pharFile, [ + 'vendor/symfony/console/Attribute/AsCommand.php', + 'vendor/symfony/polyfill-intl-grapheme/bootstrap80.php', + 'vendor/symfony/polyfill-intl-normalizer/bootstrap80.php', + 'vendor/symfony/polyfill-mbstring/bootstrap80.php', + 'vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'vendor/symfony/service-contracts/Attribute/SubscribedService.php', + 'vendor/symfony/polyfill-php84/Resources/stubs/Deprecated.php', + 'vendor/symfony/polyfill-php84/bootstrap82.php', + ]); + } + + private function getRelativeFilePath(\SplFileInfo $file): string + { + $realPath = $file->getRealPath(); + $pathPrefix = dirname(__DIR__, 2).DIRECTORY_SEPARATOR; + + $pos = strpos($realPath, $pathPrefix); + $relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; + + return strtr($relativePath, '\\', '/'); + } + + private function addFile(\Phar $phar, \SplFileInfo $file, bool $strip = true): void + { + $path = $this->getRelativeFilePath($file); + $content = file_get_contents((string) $file); + if ($strip) { + $content = $this->stripWhitespace($content); + } elseif ('LICENSE' === $file->getFilename()) { + $content = "\n".$content."\n"; + } + + if ($path === 'src/Composer/Composer.php') { + $content = strtr( + $content, + [ + '@package_version@' => $this->version, + '@package_branch_alias_version@' => $this->branchAliasVersion, + '@release_date@' => $this->versionDate->format('Y-m-d H:i:s'), + ] + ); + $content = Preg::replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content); + } + + $phar->addFromString($path, $content); + } + + private function addComposerBin(\Phar $phar): void + { + $content = file_get_contents(__DIR__.'/../../bin/composer'); + $content = Preg::replace('{^#!/usr/bin/env php\s*}', '', $content); + $phar->addFromString('bin/composer', $content); + } + + /** + * Removes whitespace from a PHP source string while preserving line numbers. + * + * @param string $source A PHP string + * @return string The PHP string with the whitespace removed + */ + private function stripWhitespace(string $source): string + { + if (!function_exists('token_get_all')) { + return $source; + } + + $output = ''; + foreach (token_get_all($source) as $token) { + if (is_string($token)) { + $output .= $token; + } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) { + $output .= str_repeat("\n", substr_count($token[1], "\n")); + } elseif (T_WHITESPACE === $token[0]) { + // reduce wide spaces + $whitespace = Preg::replace('{[ \t]+}', ' ', $token[1]); + // normalize newlines to \n + $whitespace = Preg::replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); + // trim leading spaces + $whitespace = Preg::replace('{\n +}', "\n", $whitespace); + $output .= $whitespace; + } else { + $output .= $token[1]; + } + } + + return $output; + } + + private function getStub(): string + { + $stub = <<<'EOF' +#!/usr/bin/env php + + * Jordi Boggiano + * + * For the full copyright and license information, please view + * the license that is located at the bottom of this file. + */ + +// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264 +if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) { + if (version_compare(phpversion('apc'), '3.0.12', '>=')) { + ini_set('apc.cache_by_default', 0); + } else { + fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL); + fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL); + } +} + +if (!class_exists('Phar')) { + echo 'PHP\'s phar extension is missing. Composer requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL; + exit(1); +} + +Phar::mapPhar('composer.phar'); + +EOF; + + // add warning once the phar is older than 60 days + if (Preg::isMatch('{^[a-f0-9]+$}', $this->version)) { + $warningTime = ((int) $this->versionDate->format('U')) + 60 * 86400; + $stub .= "define('COMPOSER_DEV_WARNING_TIME', $warningTime);\n"; + } + + return $stub . <<<'EOF' +require 'phar://composer.phar/bin/composer'; + +__HALT_COMPILER(); +EOF; + } +} diff --git a/vendor/composer/composer/src/Composer/Composer.php b/vendor/composer/composer/src/Composer/Composer.php new file mode 100644 index 000000000..3e8f92e0d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Composer.php @@ -0,0 +1,159 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Package\Locker; +use Composer\Pcre\Preg; +use Composer\Plugin\PluginManager; +use Composer\Downloader\DownloadManager; +use Composer\Autoload\AutoloadGenerator; +use Composer\Package\Archiver\ArchiveManager; + +/** + * @author Jordi Boggiano + * @author Konstantin Kudryashiv + * @author Nils Adermann + */ +class Composer extends PartialComposer +{ + /* + * Examples of the following constants in the various configurations they can be in + * + * You are probably better off using Composer::getVersion() though as that will always return something usable + * + * releases (phar): + * const VERSION = '1.8.2'; + * const BRANCH_ALIAS_VERSION = ''; + * const RELEASE_DATE = '2019-01-29 15:00:53'; + * const SOURCE_VERSION = ''; + * + * snapshot builds (phar): + * const VERSION = 'd3873a05650e168251067d9648845c220c50e2d7'; + * const BRANCH_ALIAS_VERSION = '1.9-dev'; + * const RELEASE_DATE = '2019-02-20 07:43:56'; + * const SOURCE_VERSION = ''; + * + * source (git clone): + * const VERSION = '@package_version@'; + * const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + * const RELEASE_DATE = '@release_date@'; + * const SOURCE_VERSION = '1.8-dev+source'; + * + * @see getVersion() + */ + public const VERSION = '2.9.5'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2026-01-29 11:40:53'; + public const SOURCE_VERSION = ''; + + /** + * Version number of the internal composer-runtime-api package + * + * This is used to version features available to projects at runtime + * like the platform-check file, the Composer\InstalledVersions class + * and possibly others in the future. + * + * @var string + */ + public const RUNTIME_API_VERSION = '2.2.2'; + + public static function getVersion(): string + { + // no replacement done, this must be a source checkout + if (self::VERSION === '@package_version'.'@') { + return self::SOURCE_VERSION; + } + + // we have a branch alias and version is a commit id, this must be a snapshot build + if (self::BRANCH_ALIAS_VERSION !== '' && Preg::isMatch('{^[a-f0-9]{40}$}', self::VERSION)) { + return self::BRANCH_ALIAS_VERSION.'+'.self::VERSION; + } + + return self::VERSION; + } + + /** + * @var Locker + */ + private $locker; + + /** + * @var DownloadManager + */ + private $downloadManager; + + /** + * @var PluginManager + */ + private $pluginManager; + + /** + * @var AutoloadGenerator + */ + private $autoloadGenerator; + + /** + * @var ArchiveManager + */ + private $archiveManager; + + public function setLocker(Locker $locker): void + { + $this->locker = $locker; + } + + public function getLocker(): Locker + { + return $this->locker; + } + + public function setDownloadManager(DownloadManager $manager): void + { + $this->downloadManager = $manager; + } + + public function getDownloadManager(): DownloadManager + { + return $this->downloadManager; + } + + public function setArchiveManager(ArchiveManager $manager): void + { + $this->archiveManager = $manager; + } + + public function getArchiveManager(): ArchiveManager + { + return $this->archiveManager; + } + + public function setPluginManager(PluginManager $manager): void + { + $this->pluginManager = $manager; + } + + public function getPluginManager(): PluginManager + { + return $this->pluginManager; + } + + public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator): void + { + $this->autoloadGenerator = $autoloadGenerator; + } + + public function getAutoloadGenerator(): AutoloadGenerator + { + return $this->autoloadGenerator; + } +} diff --git a/vendor/composer/composer/src/Composer/Config.php b/vendor/composer/composer/src/Composer/Config.php new file mode 100644 index 000000000..3c965e6cf --- /dev/null +++ b/vendor/composer/composer/src/Composer/Config.php @@ -0,0 +1,681 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Advisory\Auditor; +use Composer\Config\ConfigSourceInterface; +use Composer\Downloader\TransportException; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; + +/** + * @author Jordi Boggiano + */ +class Config +{ + public const SOURCE_DEFAULT = 'default'; + public const SOURCE_COMMAND = 'command'; + public const SOURCE_UNKNOWN = 'unknown'; + + public const RELATIVE_PATHS = 1; + + /** @var array */ + public static $defaultConfig = [ + 'process-timeout' => 300, + 'use-include-path' => false, + 'allow-plugins' => [], + 'use-parent-dir' => 'prompt', + 'preferred-install' => 'dist', + 'audit' => ['ignore' => [], 'abandoned' => Auditor::ABANDONED_FAIL], + 'notify-on-install' => true, + 'github-protocols' => ['https', 'ssh', 'git'], + 'gitlab-protocol' => null, + 'vendor-dir' => 'vendor', + 'bin-dir' => '{$vendor-dir}/bin', + 'cache-dir' => '{$home}/cache', + 'data-dir' => '{$home}', + 'cache-files-dir' => '{$cache-dir}/files', + 'cache-repo-dir' => '{$cache-dir}/repo', + 'cache-vcs-dir' => '{$cache-dir}/vcs', + 'cache-ttl' => 15552000, // 6 months + 'cache-files-ttl' => null, // fallback to cache-ttl + 'cache-files-maxsize' => '300MiB', + 'cache-read-only' => false, + 'bin-compat' => 'auto', + 'discard-changes' => false, + 'autoloader-suffix' => null, + 'sort-packages' => false, + 'optimize-autoloader' => false, + 'classmap-authoritative' => false, + 'apcu-autoloader' => false, + 'prepend-autoloader' => true, + 'update-with-minimal-changes' => false, + 'github-domains' => ['github.com'], + 'bitbucket-expose-hostname' => true, + 'disable-tls' => false, + 'secure-http' => true, + 'secure-svn-domains' => [], + 'cafile' => null, + 'capath' => null, + 'github-expose-hostname' => true, + 'gitlab-domains' => ['gitlab.com'], + 'store-auths' => 'prompt', + 'platform' => [], + 'archive-format' => 'tar', + 'archive-dir' => '.', + 'htaccess-protect' => true, + 'use-github-api' => true, + 'lock' => true, + 'platform-check' => 'php-only', + 'bitbucket-oauth' => [], + 'github-oauth' => [], + 'gitlab-oauth' => [], + 'gitlab-token' => [], + 'http-basic' => [], + 'bearer' => [], + 'custom-headers' => [], + 'bump-after-update' => false, + 'allow-missing-requirements' => false, + 'client-certificate' => [], + 'forgejo-domains' => ['codeberg.org'], + 'forgejo-token' => [], + ]; + + /** @var array */ + public static $defaultRepositories = [ + 'packagist.org' => [ + 'type' => 'composer', + 'url' => 'https://repo.packagist.org', + ], + ]; + + /** @var array */ + private $config; + /** @var ?non-empty-string */ + private $baseDir; + /** @var array */ + private $repositories; + /** @var ConfigSourceInterface */ + private $configSource; + /** @var ConfigSourceInterface */ + private $authConfigSource; + /** @var ConfigSourceInterface|null */ + private $localAuthConfigSource = null; + /** @var bool */ + private $useEnvironment; + /** @var array */ + private $warnedHosts = []; + /** @var array */ + private $sslVerifyWarnedHosts = []; + /** @var array */ + private $sourceOfConfigValue = []; + + /** + * @param bool $useEnvironment Use COMPOSER_ environment variables to replace config settings + * @param ?string $baseDir Optional base directory of the config + */ + public function __construct(bool $useEnvironment = true, ?string $baseDir = null) + { + // load defaults + $this->config = static::$defaultConfig; + + $this->repositories = static::$defaultRepositories; + $this->useEnvironment = $useEnvironment; + $this->baseDir = is_string($baseDir) && '' !== $baseDir ? $baseDir : null; + + foreach ($this->config as $configKey => $configValue) { + $this->setSourceOfConfigValue($configValue, $configKey, self::SOURCE_DEFAULT); + } + + foreach ($this->repositories as $configKey => $configValue) { + $this->setSourceOfConfigValue($configValue, 'repositories.' . $configKey, self::SOURCE_DEFAULT); + } + } + + /** + * Changing this can break path resolution for relative config paths so do not call this without knowing what you are doing + * + * The $baseDir should be an absolute path and without trailing slash + * + * @param non-empty-string|null $baseDir + */ + public function setBaseDir(?string $baseDir): void + { + $this->baseDir = $baseDir; + } + + public function setConfigSource(ConfigSourceInterface $source): void + { + $this->configSource = $source; + } + + public function getConfigSource(): ConfigSourceInterface + { + return $this->configSource; + } + + public function setAuthConfigSource(ConfigSourceInterface $source): void + { + $this->authConfigSource = $source; + } + + public function getAuthConfigSource(): ConfigSourceInterface + { + return $this->authConfigSource; + } + + public function setLocalAuthConfigSource(ConfigSourceInterface $source): void + { + $this->localAuthConfigSource = $source; + } + + public function getLocalAuthConfigSource(): ?ConfigSourceInterface + { + return $this->localAuthConfigSource; + } + + /** + * Merges new config values with the existing ones (overriding) + * + * @param array{config?: array, repositories?: array} $config + */ + public function merge(array $config, string $source = self::SOURCE_UNKNOWN): void + { + // override defaults with given config + if (!empty($config['config']) && is_array($config['config'])) { + foreach ($config['config'] as $key => $val) { + if (in_array($key, ['bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic', 'bearer', 'client-certificate', 'forgejo-token'], true) && isset($this->config[$key])) { + $this->config[$key] = array_merge($this->config[$key], $val); + $this->setSourceOfConfigValue($val, $key, $source); + } elseif (in_array($key, ['allow-plugins'], true) && isset($this->config[$key]) && is_array($this->config[$key]) && is_array($val)) { + // merging $val first to get the local config on top of the global one, then appending the global config, + // then merging local one again to make sure the values from local win over global ones for keys present in both + $this->config[$key] = array_merge($val, $this->config[$key], $val); + $this->setSourceOfConfigValue($val, $key, $source); + } elseif (in_array($key, ['gitlab-domains', 'github-domains'], true) && isset($this->config[$key])) { + $this->config[$key] = array_unique(array_merge($this->config[$key], $val)); + $this->setSourceOfConfigValue($val, $key, $source); + } elseif ('preferred-install' === $key && isset($this->config[$key])) { + if (is_array($val) || is_array($this->config[$key])) { + if (is_string($val)) { + $val = ['*' => $val]; + } + if (is_string($this->config[$key])) { + $this->config[$key] = ['*' => $this->config[$key]]; + $this->sourceOfConfigValue[$key . '*'] = $source; + } + $this->config[$key] = array_merge($this->config[$key], $val); + $this->setSourceOfConfigValue($val, $key, $source); + // the full match pattern needs to be last + if (isset($this->config[$key]['*'])) { + $wildcard = $this->config[$key]['*']; + unset($this->config[$key]['*']); + $this->config[$key]['*'] = $wildcard; + } + } else { + $this->config[$key] = $val; + $this->setSourceOfConfigValue($val, $key, $source); + } + } elseif ('audit' === $key) { + $currentIgnores = $this->config['audit']['ignore']; + $this->config[$key] = array_merge($this->config['audit'], $val); + $this->setSourceOfConfigValue($val, $key, $source); + $this->config['audit']['ignore'] = array_merge($currentIgnores, $val['ignore'] ?? []); + } else { + $this->config[$key] = $val; + $this->setSourceOfConfigValue($val, $key, $source); + } + } + } + + if (!empty($config['repositories']) && is_array($config['repositories'])) { + $this->repositories = array_reverse($this->repositories, true); + $newRepos = array_reverse($config['repositories'], true); + foreach ($newRepos as $name => $repository) { + // disable a repository by name + // this is a code path, that will be used less as the next check will be preferred + if (false === $repository) { + $this->disableRepoByName((string) $name); + continue; + } + + // disable a repository with an anonymous {"name": false} repo + if (is_array($repository) && 1 === count($repository) && false === current($repository)) { + $this->disableRepoByName((string) key($repository)); + continue; + } + + // auto-deactivate the default packagist.org repo if it gets redefined + if (isset($repository['type'], $repository['url']) && $repository['type'] === 'composer' && Preg::isMatch('{^https?://(?:[a-z0-9-.]+\.)?packagist.org(/|$)}', $repository['url'])) { + $this->disableRepoByName('packagist.org'); + } + + // store repo + if (is_int($name)) { + if (!isset($this->repositories[$name])) { + $this->repositories[$name] = $repository; + } else { + $this->repositories[] = $repository; + } + $this->setSourceOfConfigValue($repository, 'repositories.' . array_search($repository, $this->repositories, true), $source); + } else { + if ($name === 'packagist') { // BC support for default "packagist" named repo + $this->repositories[$name . '.org'] = $repository; + $this->setSourceOfConfigValue($repository, 'repositories.' . $name . '.org', $source); + } else { + $this->repositories[$name] = $repository; + $this->setSourceOfConfigValue($repository, 'repositories.' . $name, $source); + } + } + } + $this->repositories = array_reverse($this->repositories, true); + } + } + + /** + * @return array + */ + public function getRepositories(): array + { + return $this->repositories; + } + + /** + * Returns a setting + * + * @param int $flags Options (see class constants) + * @throws \RuntimeException + * + * @return mixed + */ + public function get(string $key, int $flags = 0) + { + switch ($key) { + // strings/paths with env var and {$refs} support + case 'vendor-dir': + case 'bin-dir': + case 'process-timeout': + case 'data-dir': + case 'cache-dir': + case 'cache-files-dir': + case 'cache-repo-dir': + case 'cache-vcs-dir': + case 'cafile': + case 'capath': + // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config + $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); + + $val = $this->getComposerEnv($env); + if ($val !== false) { + $this->setSourceOfConfigValue($val, $key, $env); + } + + if ($key === 'process-timeout') { + return max(0, false !== $val ? (int) $val : $this->config[$key]); + } + + $val = rtrim((string) $this->process(false !== $val ? $val : $this->config[$key], $flags), '/\\'); + $val = Platform::expandPath($val); + + if (substr($key, -4) !== '-dir') { + return $val; + } + + return (($flags & self::RELATIVE_PATHS) === self::RELATIVE_PATHS) ? $val : $this->realpath($val); + + // booleans with env var support + case 'cache-read-only': + case 'htaccess-protect': + // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config + $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); + + $val = $this->getComposerEnv($env); + if (false === $val) { + $val = $this->config[$key]; + } else { + $this->setSourceOfConfigValue($val, $key, $env); + } + + return $val !== 'false' && (bool) $val; + + // booleans without env var support + case 'disable-tls': + case 'secure-http': + case 'use-github-api': + case 'lock': + // special case for secure-http + if ($key === 'secure-http' && $this->get('disable-tls') === true) { + return false; + } + + return $this->config[$key] !== 'false' && (bool) $this->config[$key]; + + // ints without env var support + case 'cache-ttl': + return max(0, (int) $this->config[$key]); + + // numbers with kb/mb/gb support, without env var support + case 'cache-files-maxsize': + if (!Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', (string) $this->config[$key], $matches)) { + throw new \RuntimeException( + "Could not parse the value of '$key': {$this->config[$key]}" + ); + } + $size = (float) $matches[1]; + if (isset($matches[2])) { + switch (strtolower($matches[2])) { + case 'g': + $size *= 1024; + // intentional fallthrough + // no break + case 'm': + $size *= 1024; + // intentional fallthrough + // no break + case 'k': + $size *= 1024; + break; + } + } + + return max(0, (int) $size); + + // special cases below + case 'cache-files-ttl': + if (isset($this->config[$key])) { + return max(0, (int) $this->config[$key]); + } + + return $this->get('cache-ttl'); + + case 'home': + return rtrim($this->process(Platform::expandPath($this->config[$key]), $flags), '/\\'); + + case 'bin-compat': + $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key]; + + if (!in_array($value, ['auto', 'full', 'proxy', 'symlink'])) { + throw new \RuntimeException( + "Invalid value for 'bin-compat': {$value}. Expected auto, full or proxy" + ); + } + + if ($value === 'symlink') { + trigger_error('config.bin-compat "symlink" is deprecated since Composer 2.2, use auto, full (for Windows compatibility) or proxy instead.', E_USER_DEPRECATED); + } + + return $value; + + case 'discard-changes': + $env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES'); + if ($env !== false) { + if (!in_array($env, ['stash', 'true', 'false', '1', '0'], true)) { + throw new \RuntimeException( + "Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash" + ); + } + if ('stash' === $env) { + return 'stash'; + } + + // convert string value to bool + return $env !== 'false' && (bool) $env; + } + + if (!in_array($this->config[$key], [true, false, 'stash'], true)) { + throw new \RuntimeException( + "Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash" + ); + } + + return $this->config[$key]; + + case 'github-protocols': + $protos = $this->config['github-protocols']; + if ($this->config['secure-http'] && false !== ($index = array_search('git', $protos))) { + unset($protos[$index]); + } + if (reset($protos) === 'http') { + throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"'); + } + + return $protos; + + case 'autoloader-suffix': + if ($this->config[$key] === '') { // we need to guarantee null or non-empty-string + return null; + } + + return $this->process($this->config[$key], $flags); + + case 'audit': + $result = $this->config[$key]; + $abandonedEnv = $this->getComposerEnv('COMPOSER_AUDIT_ABANDONED'); + if (false !== $abandonedEnv) { + if (!in_array($abandonedEnv, $validChoices = Auditor::ABANDONEDS, true)) { + throw new \RuntimeException( + "Invalid value for COMPOSER_AUDIT_ABANDONED: {$abandonedEnv}. Expected one of ".implode(', ', Auditor::ABANDONEDS)."." + ); + } + $result['abandoned'] = $abandonedEnv; + } + + $blockAbandonedEnv = $this->getComposerEnv('COMPOSER_SECURITY_BLOCKING_ABANDONED'); + if (false !== $blockAbandonedEnv) { + if (!in_array($blockAbandonedEnv, ['0', '1'], true)) { + throw new \RuntimeException( + "Invalid value for COMPOSER_SECURITY_BLOCKING_ABANDONED: {$blockAbandonedEnv}. Expected 0 or 1." + ); + } + $result['block-abandoned'] = (bool) (int) $blockAbandonedEnv; + } + + return $result; + + default: + if (!isset($this->config[$key])) { + return null; + } + + return $this->process($this->config[$key], $flags); + } + } + + /** + * @return array + */ + public function all(int $flags = 0): array + { + $all = [ + 'repositories' => $this->getRepositories(), + ]; + foreach (array_keys($this->config) as $key) { + $all['config'][$key] = $this->get($key, $flags); + } + + return $all; + } + + public function getSourceOfValue(string $key): string + { + $this->get($key); + + return $this->sourceOfConfigValue[$key] ?? self::SOURCE_UNKNOWN; + } + + /** + * @param mixed $configValue + */ + private function setSourceOfConfigValue($configValue, string $path, string $source): void + { + $this->sourceOfConfigValue[$path] = $source; + + if (is_array($configValue)) { + foreach ($configValue as $key => $value) { + $this->setSourceOfConfigValue($value, $path . '.' . $key, $source); + } + } + } + + /** + * @return array + */ + public function raw(): array + { + return [ + 'repositories' => $this->getRepositories(), + 'config' => $this->config, + ]; + } + + /** + * Checks whether a setting exists + */ + public function has(string $key): bool + { + return array_key_exists($key, $this->config); + } + + /** + * Replaces {$refs} inside a config string + * + * @param string|mixed $value a config string that can contain {$refs-to-other-config} + * @param int $flags Options (see class constants) + * + * @return string|mixed + */ + private function process($value, int $flags) + { + if (!is_string($value)) { + return $value; + } + + return Preg::replaceCallback('#\{\$(.+)\}#', function ($match) use ($flags) { + return $this->get($match[1], $flags); + }, $value); + } + + /** + * Turns relative paths in absolute paths without realpath() + * + * Since the dirs might not exist yet we can not call realpath or it will fail. + */ + private function realpath(string $path): string + { + if (Preg::isMatch('{^(?:/|[a-z]:|[a-z0-9.]+://|\\\\\\\\)}i', $path)) { + return $path; + } + + return $this->baseDir !== null ? $this->baseDir . '/' . $path : $path; + } + + /** + * Reads the value of a Composer environment variable + * + * This should be used to read COMPOSER_ environment variables + * that overload config values. + * + * @param non-empty-string $var + * + * @return string|false + */ + private function getComposerEnv(string $var) + { + if ($this->useEnvironment) { + return Platform::getEnv($var); + } + + return false; + } + + private function disableRepoByName(string $name): void + { + if (isset($this->repositories[$name])) { + unset($this->repositories[$name]); + } elseif ($name === 'packagist') { // BC support for default "packagist" named repo + unset($this->repositories['packagist.org']); + } + } + + /** + * Validates that the passed URL is allowed to be used by current config, or throws an exception. + * + * @param mixed[] $repoOptions + */ + public function prohibitUrlByConfig(string $url, ?IOInterface $io = null, array $repoOptions = []): void + { + // Return right away if the URL is malformed or custom (see issue #5173), but only for non-HTTP(S) URLs + if (false === filter_var($url, FILTER_VALIDATE_URL) && !Preg::isMatch('{^https?://}', $url)) { + return; + } + + // Extract scheme and throw exception on known insecure protocols + $scheme = parse_url($url, PHP_URL_SCHEME); + $hostname = parse_url($url, PHP_URL_HOST); + if (in_array($scheme, ['http', 'git', 'ftp', 'svn'])) { + if ($this->get('secure-http')) { + if ($scheme === 'svn') { + if (in_array($hostname, $this->get('secure-svn-domains'), true)) { + return; + } + + throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-svn-domains for details."); + } + + throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-http for details."); + } + if ($io !== null) { + if (is_string($hostname)) { + if (!isset($this->warnedHosts[$hostname])) { + $io->writeError("Warning: Accessing $hostname over $scheme which is an insecure protocol."); + } + $this->warnedHosts[$hostname] = true; + } + } + } + + if ($io !== null && is_string($hostname) && !isset($this->sslVerifyWarnedHosts[$hostname])) { + $warning = null; + if (isset($repoOptions['ssl']['verify_peer']) && !(bool) $repoOptions['ssl']['verify_peer']) { + $warning = 'verify_peer'; + } + + if (isset($repoOptions['ssl']['verify_peer_name']) && !(bool) $repoOptions['ssl']['verify_peer_name']) { + $warning = $warning === null ? 'verify_peer_name' : $warning . ' and verify_peer_name'; + } + + if ($warning !== null) { + $io->writeError("Warning: Accessing $hostname with $warning disabled."); + $this->sslVerifyWarnedHosts[$hostname] = true; + } + } + } + + /** + * Used by long-running custom scripts in composer.json + * + * "scripts": { + * "watch": [ + * "Composer\\Config::disableProcessTimeout", + * "vendor/bin/long-running-script --watch" + * ] + * } + */ + public static function disableProcessTimeout(): void + { + // Override global timeout set earlier by environment or config + ProcessExecutor::setTimeout(0); + } +} diff --git a/vendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php b/vendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php new file mode 100644 index 000000000..371ca6c21 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php @@ -0,0 +1,99 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Config; + +/** + * Configuration Source Interface + * + * @author Jordi Boggiano + * @author Beau Simensen + */ +interface ConfigSourceInterface +{ + /** + * Add a repository + * + * @param string $name Name + * @param mixed[]|false $config Configuration + * @param bool $append Whether the repo should be appended (true) or prepended (false) + */ + public function addRepository(string $name, $config, bool $append = true): void; + + /** + * Inserts a repository before/after another repository by name + * + * @param string $name Name + * @param mixed[]|false $config Configuration + * @param string $referenceName The referenced repository to search for and insert next to + * @param int $offset The offset to use for insert in reference to the looked-up repository + */ + public function insertRepository(string $name, $config, string $referenceName, int $offset = 0): void; + + /** + * Changes the URL of the referenced repository by name + */ + public function setRepositoryUrl(string $name, string $url): void; + + /** + * Remove a repository + */ + public function removeRepository(string $name): void; + + /** + * Add a config setting + * + * @param string $name Name + * @param mixed $value Value + */ + public function addConfigSetting(string $name, $value): void; + + /** + * Remove a config setting + */ + public function removeConfigSetting(string $name): void; + + /** + * Add a property + * + * @param string $name Name + * @param string|string[] $value Value + */ + public function addProperty(string $name, $value): void; + + /** + * Remove a property + */ + public function removeProperty(string $name): void; + + /** + * Add a package link + * + * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) + * @param string $name Name + * @param string $value Value + */ + public function addLink(string $type, string $name, string $value): void; + + /** + * Remove a package link + * + * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) + * @param string $name Name + */ + public function removeLink(string $type, string $name): void; + + /** + * Gives a user-friendly name to this source (file path or so) + */ + public function getName(): string; +} diff --git a/vendor/composer/composer/src/Composer/Config/JsonConfigSource.php b/vendor/composer/composer/src/Composer/Config/JsonConfigSource.php new file mode 100644 index 000000000..8cedbc47f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Config/JsonConfigSource.php @@ -0,0 +1,428 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Config; + +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Json\JsonValidationException; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Silencer; + +/** + * JSON Configuration Source + * + * @author Jordi Boggiano + * @author Beau Simensen + */ +class JsonConfigSource implements ConfigSourceInterface +{ + /** + * @var JsonFile + */ + private $file; + + /** + * @var bool + */ + private $authConfig; + + /** + * Constructor + */ + public function __construct(JsonFile $file, bool $authConfig = false) + { + $this->file = $file; + $this->authConfig = $authConfig; + } + + /** + * @inheritDoc + */ + public function getName(): string + { + return $this->file->getPath(); + } + + /** + * @inheritDoc + */ + public function addRepository(string $name, $config, bool $append = true): void + { + $this->manipulateJson('addRepository', static function (&$config, $repo, $repoConfig) use ($append): void { + if (!array_is_list($config['repositories'] ?? [])) { + $list = []; + + foreach ($config['repositories'] as $repositoryIndex => $repository) { + if (is_string($repositoryIndex) && is_array($repository)) { + // convert to list entry with name + if (!isset($repository['name'])) { + $repository = ['name' => $repositoryIndex] + $repository; + } + $list[] = $repository; + } elseif (is_string($repositoryIndex)) { + // keep boolean entries (e.g. 'packagist.org' => false) + $list[] = [$repositoryIndex => $repository]; + } else { + $list[] = $repository; + } + } + + $config['repositories'] = $list; + } + + if ($repoConfig === false) { + if (isset($config['repositories'])) { + foreach ($config['repositories'] as &$repository) { + if (($repository['name'] ?? null) === $repo) { + $repository = [$repo => $repoConfig]; + + return; + } + + if ($repository === [$repo => false]) { + return; + } + } + + unset($repository); + } else { + $config['repositories'] = []; + } + + $config['repositories'][] = [$repo => $repoConfig]; + + return; + } + + if (is_array($repoConfig) && $repo !== '' && !isset($repoConfig['name'])) { + $repoConfig = ['name' => $repo] + $repoConfig; + } + + // ensure uniqueness by removing any existing entries which use the same name + $config['repositories'] = array_values(array_filter($config['repositories'] ?? [], static function ($val) use ($repo) { + return !isset($val['name']) || $val['name'] !== $repo || $val !== [$repo => false]; + })); + + if ($append) { + $config['repositories'][] = $repoConfig; + } else { + array_unshift($config['repositories'], $repoConfig); + } + }, $name, $config, $append); + } + + /** + * @inheritDoc + */ + public function insertRepository(string $name, $config, string $referenceName, int $offset = 0): void + { + $this->manipulateJson('insertRepository', static function (&$config, string $name, $repoConfig, string $referenceName, int $offset): void { + if (!array_is_list($config['repositories'] ?? [])) { + $list = []; + + foreach ($config['repositories'] as $repositoryIndex => $repository) { + if (is_string($repositoryIndex) && is_array($repository)) { + // convert to list entry with name + if (!isset($repository['name'])) { + $repository = ['name' => $repositoryIndex] + $repository; + } + $list[] = $repository; + } elseif (is_string($repositoryIndex)) { + // keep boolean entries (e.g. 'packagist.org' => false) + $list[] = [$repositoryIndex => $repository]; + } else { + $list[] = $repository; + } + } + + $config['repositories'] = $list; + } + + // ensure uniqueness by removing any existing entries which use the same name + $config['repositories'] = array_values(array_filter($config['repositories'] ?? [], static function ($val) use ($name) { + return !isset($val['name']) || $val['name'] !== $name || $val !== [$name => false]; + })); + + $indexToInsert = null; + + foreach ($config['repositories'] as $repositoryIndex => $repository) { + if (($repository['name'] ?? null) === $referenceName) { + $indexToInsert = $repositoryIndex; + break; + } + + if ([$referenceName => false] === $repository) { + $indexToInsert = $repositoryIndex; + break; + } + } + + if ($indexToInsert === null) { + throw new \RuntimeException(sprintf('The referenced repository "%s" does not exist.', $referenceName)); + } + + if (is_array($repoConfig) && $name !== '' && !isset($repoConfig['name'])) { + $repoConfig = ['name' => $name] + $repoConfig; + } + + array_splice($config['repositories'], $indexToInsert + $offset, 0, [$repoConfig]); + }, $name, $config, $referenceName, $offset); + } + + /** + * @inheritDoc + */ + public function setRepositoryUrl(string $name, string $url): void + { + $this->manipulateJson('setRepositoryUrl', static function (&$config, $name, $url): void { + foreach ($config['repositories'] ?? [] as $index => $repository) { + if ($name === $index) { + $config['repositories'][$index]['url'] = $url; + + return; + } + + if ($name === ($repository['name'] ?? null)) { + $config['repositories'][$index]['url'] = $url; + + return; + } + } + }, $name, $url); + } + + /** + * @inheritDoc + */ + public function removeRepository(string $name): void + { + $this->manipulateJson('removeRepository', static function (&$config, $repo): void { + if (isset($config['repositories'][$repo])) { + unset($config['repositories'][$repo]); + } else { + $config['repositories'] = array_values(array_filter($config['repositories'] ?? [], static function ($val) use ($repo) { + return !isset($val['name']) || $val['name'] !== $repo || $val !== [$repo => false]; + })); + } + + if ([] === $config['repositories']) { + unset($config['repositories']); + } + }, $name); + } + + /** + * @inheritDoc + */ + public function addConfigSetting(string $name, $value): void + { + $authConfig = $this->authConfig; + $this->manipulateJson('addConfigSetting', static function (&$config, $key, $val) use ($authConfig): void { + if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|custom-headers|forgejo-token|platform)\.}', $key)) { + [$key, $host] = explode('.', $key, 2); + if ($authConfig) { + $config[$key][$host] = $val; + } else { + $config['config'][$key][$host] = $val; + } + } else { + $config['config'][$key] = $val; + } + }, $name, $value); + } + + /** + * @inheritDoc + */ + public function removeConfigSetting(string $name): void + { + $authConfig = $this->authConfig; + $this->manipulateJson('removeConfigSetting', static function (&$config, $key) use ($authConfig): void { + if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|custom-headers|forgejo-token|platform)\.}', $key)) { + [$key, $host] = explode('.', $key, 2); + if ($authConfig) { + unset($config[$key][$host]); + } else { + unset($config['config'][$key][$host]); + } + } else { + unset($config['config'][$key]); + } + }, $name); + } + + /** + * @inheritDoc + */ + public function addProperty(string $name, $value): void + { + $this->manipulateJson('addProperty', static function (&$config, $key, $val): void { + if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) { + $bits = explode('.', $key); + $last = array_pop($bits); + $arr = &$config[reset($bits)]; + foreach ($bits as $bit) { + if (!isset($arr[$bit])) { + $arr[$bit] = []; + } + $arr = &$arr[$bit]; + } + $arr[$last] = $val; + } else { + $config[$key] = $val; + } + }, $name, $value); + } + + /** + * @inheritDoc + */ + public function removeProperty(string $name): void + { + $this->manipulateJson('removeProperty', static function (&$config, $key): void { + if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0 || stripos($key, 'autoload.') === 0 || stripos($key, 'autoload-dev.') === 0) { + $bits = explode('.', $key); + $last = array_pop($bits); + $arr = &$config[reset($bits)]; + foreach ($bits as $bit) { + if (!isset($arr[$bit])) { + return; + } + $arr = &$arr[$bit]; + } + unset($arr[$last]); + } else { + unset($config[$key]); + } + }, $name); + } + + /** + * @inheritDoc + */ + public function addLink(string $type, string $name, string $value): void + { + $this->manipulateJson('addLink', static function (&$config, $type, $name, $value): void { + $config[$type][$name] = $value; + }, $type, $name, $value); + } + + /** + * @inheritDoc + */ + public function removeLink(string $type, string $name): void + { + $this->manipulateJson('removeSubNode', static function (&$config, $type, $name): void { + unset($config[$type][$name]); + }, $type, $name); + $this->manipulateJson('removeMainKeyIfEmpty', static function (&$config, $type): void { + if (0 === count($config[$type])) { + unset($config[$type]); + } + }, $type); + } + + /** + * @param mixed ...$args + */ + private function manipulateJson(string $method, callable $fallback, ...$args): void + { + if ($this->file->exists()) { + if (!is_writable($this->file->getPath())) { + throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath())); + } + + if (!Filesystem::isReadable($this->file->getPath())) { + throw new \RuntimeException(sprintf('The file "%s" is not readable.', $this->file->getPath())); + } + + $contents = file_get_contents($this->file->getPath()); + } elseif ($this->authConfig) { + $contents = "{\n}\n"; + } else { + $contents = "{\n \"config\": {\n }\n}\n"; + } + + $manipulator = new JsonManipulator($contents); + + $newFile = !$this->file->exists(); + + // override manipulator method for auth config files + if ($this->authConfig && $method === 'addConfigSetting') { + $method = 'addSubNode'; + [$mainNode, $name] = explode('.', $args[0], 2); + $args = [$mainNode, $name, $args[1]]; + } elseif ($this->authConfig && $method === 'removeConfigSetting') { + $method = 'removeSubNode'; + [$mainNode, $name] = explode('.', $args[0], 2); + $args = [$mainNode, $name]; + } + + // try to update cleanly + if (call_user_func_array([$manipulator, $method], $args)) { + file_put_contents($this->file->getPath(), $manipulator->getContents()); + } else { + // on failed clean update, call the fallback and rewrite the whole file + $config = $this->file->read(); + $this->arrayUnshiftRef($args, $config); + $fallback(...$args); + // avoid ending up with arrays for keys that should be objects + foreach (['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev', 'scripts', 'scripts-descriptions', 'scripts-aliases', 'support'] as $prop) { + if (isset($config[$prop]) && $config[$prop] === []) { + $config[$prop] = new \stdClass; + } + } + foreach (['psr-0', 'psr-4'] as $prop) { + if (isset($config['autoload'][$prop]) && $config['autoload'][$prop] === []) { + $config['autoload'][$prop] = new \stdClass; + } + if (isset($config['autoload-dev'][$prop]) && $config['autoload-dev'][$prop] === []) { + $config['autoload-dev'][$prop] = new \stdClass; + } + } + foreach (['platform', 'http-basic', 'bearer', 'gitlab-token', 'gitlab-oauth', 'github-oauth', 'custom-headers', 'forgejo-token', 'preferred-install'] as $prop) { + if (isset($config['config'][$prop]) && $config['config'][$prop] === []) { + $config['config'][$prop] = new \stdClass; + } + } + $this->file->write($config); + } + + try { + $this->file->validateSchema(JsonFile::LAX_SCHEMA); + } catch (JsonValidationException $e) { + // restore contents to the original state + file_put_contents($this->file->getPath(), $contents); + throw new \RuntimeException('Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json). '.PHP_EOL.implode(PHP_EOL, $e->getErrors()), 0, $e); + } + + if ($newFile) { + Silencer::call('chmod', $this->file->getPath(), 0600); + } + } + + /** + * Prepend a reference to an element to the beginning of an array. + * + * @param mixed[] $array + * @param mixed $value + */ + private function arrayUnshiftRef(array &$array, &$value): int + { + $return = array_unshift($array, ''); + $array[0] = &$value; + + return $return; + } +} diff --git a/vendor/composer/composer/src/Composer/Console/Application.php b/vendor/composer/composer/src/Composer/Console/Application.php new file mode 100644 index 000000000..ce0d645f6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/Application.php @@ -0,0 +1,775 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console; + +use Composer\Installer; +use Composer\IO\NullIO; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use LogicException; +use RuntimeException; +use Symfony\Component\Console\Application as BaseApplication; +use Symfony\Component\Console\Command\Command as SymfonyCommand; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Seld\JsonLint\ParsingException; +use Composer\Command; +use Composer\Composer; +use Composer\Factory; +use Composer\Downloader\TransportException; +use Composer\IO\IOInterface; +use Composer\IO\ConsoleIO; +use Composer\Json\JsonValidationException; +use Composer\Util\ErrorHandler; +use Composer\Util\HttpDownloader; +use Composer\EventDispatcher\ScriptExecutionException; +use Composer\Exception\NoSslException; +use Composer\XdebugHandler\XdebugHandler; +use Symfony\Component\Process\Exception\ProcessTimedOutException; + +/** + * The console application that handles the commands + * + * @author Ryan Weaver + * @author Jordi Boggiano + * @author François Pluchino + */ +class Application extends BaseApplication +{ + /** + * @var ?Composer + */ + protected $composer; + + /** + * @var IOInterface + */ + protected $io; + + /** @var string */ + private static $logo = ' ______ + / ____/___ ____ ___ ____ ____ ________ _____ + / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ +/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / +\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ + /_/ +'; + + /** @var bool */ + private $hasPluginCommands = false; + /** @var bool */ + private $disablePluginsByDefault = false; + /** @var bool */ + private $disableScriptsByDefault = false; + + /** + * @var string|false Store the initial working directory at startup time + */ + private $initialWorkingDirectory; + + public function __construct(string $name = 'Composer', string $version = '') + { + if (method_exists($this, 'setCatchErrors')) { + $this->setCatchErrors(true); + } + + static $shutdownRegistered = false; + if ($version === '') { + $version = Composer::getVersion(); + } + if (function_exists('ini_set') && extension_loaded('xdebug')) { + ini_set('xdebug.show_exception_trace', '0'); + ini_set('xdebug.scream', '0'); + } + + if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { + date_default_timezone_set(Silencer::call('date_default_timezone_get')); + } + + $this->io = new NullIO(); + + if (!$shutdownRegistered) { + $shutdownRegistered = true; + + register_shutdown_function(static function (): void { + $lastError = error_get_last(); + + if ($lastError && $lastError['message'] && + (strpos($lastError['message'], 'Allowed memory') !== false /*Zend PHP out of memory error*/ || + strpos($lastError['message'], 'exceeded memory') !== false /*HHVM out of memory errors*/)) { + echo "\n". 'Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.'; + } + }); + } + + $this->initialWorkingDirectory = getcwd(); + + parent::__construct($name, $version); + } + + public function __destruct() + { + } + + public function run(?InputInterface $input = null, ?OutputInterface $output = null): int + { + if (null === $output) { + $output = Factory::createOutput(); + } + + return parent::run($input, $output); + } + + public function doRun(InputInterface $input, OutputInterface $output): int + { + $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); + $this->disableScriptsByDefault = $input->hasParameterOption('--no-scripts'); + + static $stdin = null; + if (null === $stdin) { + $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); + } + if (Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') !== '1' && (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === false || !Platform::isTty($stdin))) { + $input->setInteractive(false); + } + + $io = $this->io = new ConsoleIO($input, $output, new HelperSet([ + new QuestionHelper(), + ])); + + // Register error handler again to pass it the IO instance + ErrorHandler::register($io); + + if ($input->hasParameterOption('--no-cache')) { + $io->writeError('Disabling cache usage', true, IOInterface::DEBUG); + Platform::putEnv('COMPOSER_CACHE_DIR', Platform::isWindows() ? 'nul' : '/dev/null'); + } + + // switch working dir + $newWorkDir = $this->getNewWorkingDir($input); + if (null !== $newWorkDir) { + $oldWorkingDir = Platform::getCwd(true); + chdir($newWorkDir); + $this->initialWorkingDirectory = getcwd(); + $cwd = Platform::getCwd(true); + $io->writeError('Changed CWD to ' . ($cwd !== '' ? $cwd : $newWorkDir), true, IOInterface::DEBUG); + } + + // determine command name to be executed without including plugin commands + $commandName = ''; + if ($name = $this->getCommandNameBeforeBinding($input)) { + try { + $commandName = $this->find($name)->getName(); + } catch (CommandNotFoundException $e) { + // we'll check command validity again later after plugins are loaded + $commandName = false; + } catch (\InvalidArgumentException $e) { + } + } + + // prompt user for dir change if no composer.json is present in current dir + if ( + null === $newWorkDir + // do not prompt for commands that can function without composer.json + && !in_array($commandName, ['', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'], true) + && !file_exists(Factory::getComposerFile()) + // if use-parent-dir is disabled we should not prompt + && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== false + // config --file ... should not prompt + && ($commandName !== 'config' || ($input->hasParameterOption('--file', true) === false && $input->hasParameterOption('-f', true) === false)) + // calling a command's help should not prompt + && $input->hasParameterOption('--help', true) === false + && $input->hasParameterOption('-h', true) === false + ) { + $dir = dirname(Platform::getCwd(true)); + $home = realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/'); + + // abort when we reach the home dir or top of the filesystem + while (dirname($dir) !== $dir && $dir !== $home) { + if (file_exists($dir.'/'.Factory::getComposerFile())) { + if ($useParentDirIfNoJsonAvailable !== true && !$io->isInteractive()) { + $io->writeError('No composer.json in current directory, to use the one at '.$dir.' run interactively or set config.use-parent-dir to true'); + break; + } + if ($useParentDirIfNoJsonAvailable === true || $io->askConfirmation('No composer.json in current directory, do you want to use the one at '.$dir.'? [y,n]? ')) { + if ($useParentDirIfNoJsonAvailable === true) { + $io->writeError('No composer.json in current directory, changing working directory to '.$dir.''); + } else { + $io->writeError('Always want to use the parent dir? Use "composer config --global use-parent-dir true" to change the default.'); + } + $oldWorkingDir = Platform::getCwd(true); + chdir($dir); + } + break; + } + $dir = dirname($dir); + } + unset($dir, $home); + } + + $needsSudoCheck = !Platform::isWindows() + && function_exists('exec') + && !Platform::getEnv('COMPOSER_ALLOW_SUPERUSER') + && !Platform::isDocker(); + $isNonAllowedRoot = false; + + // Clobber sudo credentials if COMPOSER_ALLOW_SUPERUSER is not set before loading plugins + if ($needsSudoCheck) { + $isNonAllowedRoot = $this->isRunningAsRoot(); + + if ($isNonAllowedRoot) { + if ($uid = (int) Platform::getEnv('SUDO_UID')) { + // Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on + // ref. https://github.com/composer/composer/issues/5119 + Silencer::call('exec', "sudo -u \\#{$uid} sudo -K > /dev/null 2>&1"); + } + } + + // Silently clobber any remaining sudo leases on the current user as well to avoid privilege escalations + Silencer::call('exec', 'sudo -K > /dev/null 2>&1'); + } + + // avoid loading plugins/initializing the Composer instance earlier than necessary if no plugin command is needed + // if showing the version, we never need plugin commands + $mayNeedPluginCommand = false === $input->hasParameterOption(['--version', '-V']) + && ( + // not a composer command, so try loading plugin ones + false === $commandName + // list command requires plugin commands to show them + || in_array($commandName, ['', 'list', 'help'], true) + // autocompletion requires plugin commands but if we are running as root without COMPOSER_ALLOW_SUPERUSER + // we'd rather not autocomplete plugins than abort autocompletion entirely, so we avoid loading plugins in this case + || ($commandName === '_complete' && !$isNonAllowedRoot) + ); + + if ($mayNeedPluginCommand && !$this->disablePluginsByDefault && !$this->hasPluginCommands) { + // at this point plugins are needed, so if we are running as root and it is not allowed we need to prompt + // if interactive, and abort otherwise + if ($isNonAllowedRoot) { + $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); + + if ($io->isInteractive() && $io->askConfirmation('Continue as root/super user [yes]? ')) { + // avoid a second prompt later + $isNonAllowedRoot = false; + } else { + $io->writeError('Aborting as no plugin should be loaded if running as super user is not explicitly allowed'); + + return 1; + } + } + + try { + foreach ($this->getPluginCommands() as $command) { + if ($this->has($command->getName())) { + $io->writeError('Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped'); + } else { + // Compatibility layer for symfony/console <7.4 + // @phpstan-ignore method.notFound, function.alreadyNarrowedType + method_exists($this, 'addCommand') ? $this->addCommand($command) : $this->add($command); + } + } + } catch (NoSslException $e) { + // suppress these as they are not relevant at this point + } catch (ParsingException $e) { + $details = $e->getDetails(); + + $file = realpath(Factory::getComposerFile()); + + $line = null; + if ($details && isset($details['line'])) { + $line = $details['line']; + } + + $ghe = new GithubActionError($this->io); + $ghe->emit($e->getMessage(), $file, $line); + + throw $e; + } + + $this->hasPluginCommands = true; + } + + if (!$this->disablePluginsByDefault && $isNonAllowedRoot && !$io->isInteractive()) { + $io->writeError('Composer plugins have been disabled for safety in this non-interactive session.'); + $io->writeError('Set COMPOSER_ALLOW_SUPERUSER=1 if you want to allow plugins to run as root/super user.'); + $this->disablePluginsByDefault = true; + } + + // determine command name to be executed incl plugin commands, and check if it's a proxy command + $isProxyCommand = false; + if ($name = $this->getCommandNameBeforeBinding($input)) { + try { + $command = $this->find($name); + $commandName = $command->getName(); + $isProxyCommand = ($command instanceof Command\BaseCommand && $command->isProxyCommand()); + } catch (\InvalidArgumentException $e) { + } + } + + if (!$isProxyCommand) { + $io->writeError(sprintf( + 'Running %s (%s) with %s on %s', + Composer::getVersion(), + Composer::RELEASE_DATE, + defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION, + function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS' + ), true, IOInterface::DEBUG); + + if (\PHP_VERSION_ID < 70205) { + $io->writeError('Composer supports PHP 7.2.5 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.'. Upgrading is strongly recommended but you can use Composer 2.2.x LTS as a fallback.'); + } + + if (XdebugHandler::isXdebugActive() && !Platform::getEnv('COMPOSER_DISABLE_XDEBUG_WARN')) { + $io->writeError('Composer is operating slower than normal because you have Xdebug enabled. See https://getcomposer.org/xdebug'); + } + + if (defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && time() > COMPOSER_DEV_WARNING_TIME) { + $io->writeError(sprintf('Warning: This development build of Composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF'])); + } + + if ($isNonAllowedRoot) { + if ($commandName !== 'self-update' && $commandName !== 'selfupdate' && $commandName !== '_complete') { + $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); + + if ($io->isInteractive()) { + if (!$io->askConfirmation('Continue as root/super user [yes]? ')) { + return 1; + } + } + } + } + + // Check system temp folder for usability as it can cause weird runtime issues otherwise + Silencer::call(static function () use ($io): void { + $pid = function_exists('getmypid') ? getmypid() . '-' : ''; + $tempfile = sys_get_temp_dir() . '/temp-' . $pid . bin2hex(random_bytes(5)); + if (!(file_put_contents($tempfile, __FILE__) && (file_get_contents($tempfile) === __FILE__) && unlink($tempfile) && !file_exists($tempfile))) { + $io->writeError(sprintf('PHP temp directory (%s) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini', sys_get_temp_dir())); + } + }); + + // add non-standard scripts as own commands + $file = Factory::getComposerFile(); + if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode(file_get_contents($file), true))) { + if (isset($composer['scripts']) && is_array($composer['scripts'])) { + foreach ($composer['scripts'] as $script => $dummy) { + if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { + if ($this->has($script)) { + $io->writeError('A script named '.$script.' would override a Composer command and has been skipped'); + } else { + $description = null; + + if (isset($composer['scripts-descriptions'][$script])) { + $description = $composer['scripts-descriptions'][$script]; + } + + $aliases = $composer['scripts-aliases'][$script] ?? []; + + //if the command is not an array of commands, and points to a valid Command subclass, import its details directly + if (is_string($dummy) && class_exists($dummy) && is_subclass_of($dummy, SymfonyCommand::class)) { + $cmd = new $dummy($script); + + //makes sure the command is find()'able by the name defined in composer.json, and the name isn't overridden in its configure() + if ($cmd->getName() !== '' && $cmd->getName() !== null && $cmd->getName() !== $script) { + $io->writeError('The script named '.$script.' in composer.json has a mismatched name in its class definition. For consistency, either use the same name, or do not define one inside the class.'); + $cmd->setName($script); //override it with the defined script name + } + + if ($cmd->getDescription() === '' && is_string($description)) { + $cmd->setDescription($description); + } + } else { + //fallback to usual aliasing behavior + $cmd = new Command\ScriptAliasCommand($script, $description, $aliases); + } + + // Compatibility layer for symfony/console <7.4 + // @phpstan-ignore method.notFound, function.alreadyNarrowedType + method_exists($this, 'addCommand') ? $this->addCommand($cmd) : $this->add($cmd); + } + } + } + } + } + } + + try { + if ($input->hasParameterOption('--profile')) { + $startTime = microtime(true); + $this->io->enableDebugging($startTime); + } + + $result = parent::doRun($input, $output); + + if (true === $input->hasParameterOption(['--version', '-V'], true)) { + $io->writeError(sprintf('PHP version %s (%s)', \PHP_VERSION, \PHP_BINARY)); + $io->writeError('Run the "diagnose" command to get more detailed diagnostics output.'); + } + + // chdir back to $oldWorkingDir if set + if (isset($oldWorkingDir) && '' !== $oldWorkingDir) { + Silencer::call('chdir', $oldWorkingDir); + } + + if (isset($startTime)) { + $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); + } + + return $result; + } catch (ScriptExecutionException $e) { + if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { + $io->writeError('Plugins have been disabled automatically as you are running as root, this may be the cause of the script failure.', true, IOInterface::QUIET); + $io->writeError('See also https://getcomposer.org/root', true, IOInterface::QUIET); + } + + return $e->getCode(); + } catch (\Throwable $e) { + $ghe = new GithubActionError($this->io); + $ghe->emit($e->getMessage()); + + $this->hintCommonErrors($e, $output); + + // symfony/console <6.4 does not handle \Error subtypes so we have to renderThrowable ourselves + // instead of rethrowing those for consumption by the parent class + // can be removed when Composer supports PHP 8.1+ + if (!method_exists($this, 'setCatchErrors') && !$e instanceof \Exception) { + if ($output instanceof ConsoleOutputInterface) { + $this->renderThrowable($e, $output->getErrorOutput()); + } else { + $this->renderThrowable($e, $output); + } + + return max(1, $e->getCode()); + } + + // override TransportException's code for the purpose of parent::run() using it as process exit code + // as http error codes are all beyond the 255 range of permitted exit codes + if ($e instanceof TransportException) { + $reflProp = new \ReflectionProperty($e, 'code'); + (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true); + $reflProp->setValue($e, Installer::ERROR_TRANSPORT_EXCEPTION); + } + + throw $e; + } finally { + restore_error_handler(); + } + } + + /** + * @throws RuntimeException + */ + private function getNewWorkingDir(InputInterface $input): ?string + { + /** @var string|null $workingDir */ + $workingDir = $input->getParameterOption(['--working-dir', '-d'], null, true); + if (null !== $workingDir && !is_dir($workingDir)) { + throw new RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.'); + } + + return $workingDir; + } + + private function hintCommonErrors(\Throwable $exception, OutputInterface $output): void + { + $io = $this->getIO(); + + if ((get_class($exception) === LogicException::class || $exception instanceof \Error) && $output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + } + + Silencer::suppress(); + try { + $composer = $this->getComposer(false, true); + if (null !== $composer && function_exists('disk_free_space')) { + $config = $composer->getConfig(); + + $minSpaceFree = 100 * 1024 * 1024; + if ((($df = disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) + || (($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) + || (($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree) + ) { + $io->writeError('The disk hosting '.$dir.' has less than 100MiB of free space, this may be the cause of the following exception', true, IOInterface::QUIET); + } + } + } catch (\Exception $e) { + } + Silencer::restore(); + + if ($exception instanceof TransportException && str_contains($exception->getMessage(), 'Unable to use a proxy')) { + $io->writeError('The following exception indicates your proxy is misconfigured', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md for details', true, IOInterface::QUIET); + } + + if (Platform::isWindows() && $exception instanceof TransportException && str_contains($exception->getMessage(), 'unable to get local issuer certificate')) { + $avastDetect = glob('C:\Program Files\Avast*'); + if (is_array($avastDetect) && count($avastDetect) !== 0) { + $io->writeError('The following exception indicates a possible issue with the Avast Firewall', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/local-issuer for details', true, IOInterface::QUIET); + } else { + $io->writeError('The following exception indicates a possible issue with a Firewall/Antivirus', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/local-issuer for details', true, IOInterface::QUIET); + } + } + + if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { + $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details', true, IOInterface::QUIET); + } + + if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) { + $io->writeError('The following exception is caused by a lack of memory or swap, or not having swap configured', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details', true, IOInterface::QUIET); + } + + if ($exception instanceof ProcessTimedOutException) { + $io->writeError('The following exception is caused by a process timeout', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/06-config.md#process-timeout for details', true, IOInterface::QUIET); + } + + if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { + $io->writeError('Plugins have been disabled automatically as you are running as root, this may be the cause of the following exception. See also https://getcomposer.org/root', true, IOInterface::QUIET); + } elseif ($exception instanceof CommandNotFoundException && $this->getDisablePluginsByDefault()) { + $io->writeError('Plugins have been disabled, which may be why some commands are missing, unless you made a typo', true, IOInterface::QUIET); + } + + $hints = HttpDownloader::getExceptionHints($exception); + if (null !== $hints && count($hints) > 0) { + foreach ($hints as $hint) { + $io->writeError($hint, true, IOInterface::QUIET); + } + } + } + + /** + * @throws JsonValidationException + * @throws \InvalidArgumentException + * @return ?Composer If $required is true then the return value is guaranteed + */ + public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer + { + if (null === $disablePlugins) { + $disablePlugins = $this->disablePluginsByDefault; + } + if (null === $disableScripts) { + $disableScripts = $this->disableScriptsByDefault; + } + + if (null === $this->composer) { + try { + $this->composer = Factory::create(Platform::isInputCompletionProcess() ? new NullIO() : $this->io, null, $disablePlugins, $disableScripts); + } catch (\InvalidArgumentException $e) { + if ($required) { + $this->io->writeError($e->getMessage()); + if ($this->areExceptionsCaught()) { + exit(1); + } + throw $e; + } + } catch (JsonValidationException $e) { + if ($required) { + throw $e; + } + } catch (RuntimeException $e) { + if ($required) { + throw $e; + } + } + } + + return $this->composer; + } + + /** + * Removes the cached composer instance + */ + public function resetComposer(): void + { + $this->composer = null; + if (method_exists($this->getIO(), 'resetAuthentications')) { + $this->getIO()->resetAuthentications(); + } + } + + public function getIO(): IOInterface + { + return $this->io; + } + + public function getHelp(): string + { + return self::$logo . parent::getHelp(); + } + + /** + * Initializes all the composer commands. + * @return SymfonyCommand[] + */ + protected function getDefaultCommands(): array + { + return array_merge(parent::getDefaultCommands(), [ + new Command\AboutCommand(), + new Command\ConfigCommand(), + new Command\DependsCommand(), + new Command\ProhibitsCommand(), + new Command\InitCommand(), + new Command\InstallCommand(), + new Command\CreateProjectCommand(), + new Command\UpdateCommand(), + new Command\SearchCommand(), + new Command\ValidateCommand(), + new Command\AuditCommand(), + new Command\ShowCommand(), + new Command\SuggestsCommand(), + new Command\RequireCommand(), + new Command\DumpAutoloadCommand(), + new Command\StatusCommand(), + new Command\ArchiveCommand(), + new Command\DiagnoseCommand(), + new Command\RunScriptCommand(), + new Command\LicensesCommand(), + new Command\GlobalCommand(), + new Command\ClearCacheCommand(), + new Command\RemoveCommand(), + new Command\HomeCommand(), + new Command\ExecCommand(), + new Command\OutdatedCommand(), + new Command\CheckPlatformReqsCommand(), + new Command\FundCommand(), + new Command\ReinstallCommand(), + new Command\BumpCommand(), + new Command\RepositoryCommand(), + new Command\SelfUpdateCommand(), + ]); + } + + /** + * This ensures we can find the correct command name even if a global input option is present before it + * + * e.g. "composer -d foo bar" should detect bar as the command name, and not foo + */ + private function getCommandNameBeforeBinding(InputInterface $input): ?string + { + $input = clone $input; + try { + // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. + $input->bind($this->getDefinition()); + } catch (ExceptionInterface $e) { + // Errors must be ignored, full binding/validation happens later when the command is known. + } + + return $input->getFirstArgument(); + } + + public function getLongVersion(): string + { + $branchAliasString = ''; + if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') { + $branchAliasString = sprintf(' (%s)', Composer::BRANCH_ALIAS_VERSION); + } + + return sprintf( + '%s version %s%s %s', + $this->getName(), + $this->getVersion(), + $branchAliasString, + Composer::RELEASE_DATE + ); + } + + protected function getDefaultInputDefinition(): InputDefinition + { + $definition = parent::getDefaultInputDefinition(); + $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); + $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.')); + $definition->addOption(new InputOption('--no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.')); + $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); + $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache')); + + return $definition; + } + + /** + * @return Command\BaseCommand[] + */ + private function getPluginCommands(): array + { + $commands = []; + + $composer = $this->getComposer(false, false); + if (null === $composer) { + $composer = Factory::createGlobal($this->io, $this->disablePluginsByDefault, $this->disableScriptsByDefault); + } + + if (null !== $composer) { + $pm = $composer->getPluginManager(); + foreach ($pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', ['composer' => $composer, 'io' => $this->io]) as $capability) { + $newCommands = $capability->getCommands(); + if (!is_array($newCommands)) { + throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' failed to return an array from getCommands'); + } + foreach ($newCommands as $command) { + if (!$command instanceof Command\BaseCommand) { + throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' returned an invalid value, we expected an array of Composer\Command\BaseCommand objects'); + } + } + $commands = array_merge($commands, $newCommands); + } + } + + return $commands; + } + + /** + * Get the working directory at startup time + * + * @return string|false + */ + public function getInitialWorkingDirectory() + { + return $this->initialWorkingDirectory; + } + + public function getDisablePluginsByDefault(): bool + { + return $this->disablePluginsByDefault; + } + + public function getDisableScriptsByDefault(): bool + { + return $this->disableScriptsByDefault; + } + + /** + * @return 'prompt'|bool + */ + private function getUseParentDirConfigValue() + { + $config = Factory::createConfig($this->io); + + return $config->get('use-parent-dir'); + } + + private function isRunningAsRoot(): bool + { + return function_exists('posix_getuid') && posix_getuid() === 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Console/GithubActionError.php b/vendor/composer/composer/src/Composer/Console/GithubActionError.php new file mode 100644 index 000000000..8a19a1945 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/GithubActionError.php @@ -0,0 +1,68 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console; + +use Composer\IO\IOInterface; +use Composer\Util\Platform; + +final class GithubActionError +{ + /** + * @var IOInterface + */ + protected $io; + + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + public function emit(string $message, ?string $file = null, ?int $line = null): void + { + if (Platform::getEnv('GITHUB_ACTIONS') && !Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) { + $message = $this->escapeData($message); + + if ($file && $line) { + $file = $this->escapeProperty($file); + $this->io->write("::error file=". $file .",line=". $line ."::". $message); + } elseif ($file) { + $file = $this->escapeProperty($file); + $this->io->write("::error file=". $file ."::". $message); + } else { + $this->io->write("::error ::". $message); + } + } + } + + private function escapeData(string $data): string + { + // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L80-L85 + $data = str_replace("%", '%25', $data); + $data = str_replace("\r", '%0D', $data); + $data = str_replace("\n", '%0A', $data); + + return $data; + } + + private function escapeProperty(string $property): string + { + // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L87-L94 + $property = str_replace("%", '%25', $property); + $property = str_replace("\r", '%0D', $property); + $property = str_replace("\n", '%0A', $property); + $property = str_replace(":", '%3A', $property); + $property = str_replace(",", '%2C', $property); + + return $property; + } +} diff --git a/vendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php b/vendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php new file mode 100644 index 000000000..af638d605 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php @@ -0,0 +1,104 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console; + +use Closure; +use Composer\Pcre\Preg; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +/** + * @author Jordi Boggiano + */ +class HtmlOutputFormatter extends OutputFormatter +{ + /** @var array */ + private static $availableForegroundColors = [ + 30 => 'black', + 31 => 'red', + 32 => 'green', + 33 => 'yellow', + 34 => 'blue', + 35 => 'magenta', + 36 => 'cyan', + 37 => 'white', + ]; + /** @var array */ + private static $availableBackgroundColors = [ + 40 => 'black', + 41 => 'red', + 42 => 'green', + 43 => 'yellow', + 44 => 'blue', + 45 => 'magenta', + 46 => 'cyan', + 47 => 'white', + ]; + /** @var array */ + private static $availableOptions = [ + 1 => 'bold', + 4 => 'underscore', + //5 => 'blink', + //7 => 'reverse', + //8 => 'conceal' + ]; + + /** + * @param array $styles Array of "name => FormatterStyle" instances + */ + public function __construct(array $styles = []) + { + parent::__construct(true, $styles); + } + + public function format(?string $message): ?string + { + $formatted = parent::format($message); + + if ($formatted === null) { + return null; + } + + $clearEscapeCodes = '(?:39|49|0|22|24|25|27|28)'; + + return Preg::replaceCallback("{\033\[([0-9;]+)m(.*?)\033\[(?:".$clearEscapeCodes.";)*?".$clearEscapeCodes."m}s", Closure::fromCallable([$this, 'formatHtml']), $formatted); + } + + /** + * @param array $matches + */ + private function formatHtml(array $matches): string + { + assert(is_string($matches[1])); + $out = ''.$matches[2].''; + } +} diff --git a/vendor/composer/composer/src/Composer/Console/Input/InputArgument.php b/vendor/composer/composer/src/Composer/Console/Input/InputArgument.php new file mode 100644 index 000000000..19aff8c33 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/Input/InputArgument.php @@ -0,0 +1,69 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console\Input; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputArgument as BaseInputArgument; + +/** + * Backport suggested values definition from symfony/console 6.1+ + * + * @author Jérôme Tamarelle + * + * @internal + * + * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required + */ +class InputArgument extends BaseInputArgument +{ + /** + * @var list|\Closure(CompletionInput,CompletionSuggestions):list + */ + private $suggestedValues; + + /** + * @param string $name The argument name + * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param string $description A description text + * @param string|bool|int|float|string[]|null $default The default value (for self::OPTIONAL mode only) + * @param list|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function __construct(string $name, ?int $mode = null, string $description = '', $default = null, $suggestedValues = []) + { + parent::__construct($name, $mode, $description, $default); + + $this->suggestedValues = $suggestedValues; + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType + throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); + } + if ([] !== $values) { + $suggestions->suggestValues($values); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Console/Input/InputOption.php b/vendor/composer/composer/src/Composer/Console/Input/InputOption.php new file mode 100644 index 000000000..b5ff333cd --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/Input/InputOption.php @@ -0,0 +1,72 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console\Input; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputOption as BaseInputOption; + +/** + * Backport suggested values definition from symfony/console 6.1+ + * + * @author Jérôme Tamarelle + * + * @internal + * + * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required + */ +class InputOption extends BaseInputOption +{ + /** + * @var list|\Closure(CompletionInput,CompletionSuggestions):list + */ + private $suggestedValues; + + /** + * @param string|string[]|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the VALUE_* constants + * @param string|bool|int|float|string[]|null $default The default value (must be null for self::VALUE_NONE) + * @param list|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completionnull for self::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null, $suggestedValues = []) + { + parent::__construct($name, $shortcut, $mode, $description, $default); + + $this->suggestedValues = $suggestedValues; + + if ([] !== $suggestedValues && !$this->acceptValue()) { + throw new LogicException('Cannot set suggested values if the option does not accept a value.'); + } + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType + throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); + } + if ([] !== $values) { + $suggestions->suggestValues($values); + } + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Decisions.php b/vendor/composer/composer/src/Composer/DependencyResolver/Decisions.php new file mode 100644 index 000000000..599110750 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Decisions.php @@ -0,0 +1,233 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * Stores decisions on installing, removing or keeping packages + * + * @author Nils Adermann + * @implements \Iterator + */ +class Decisions implements \Iterator, \Countable +{ + public const DECISION_LITERAL = 0; + public const DECISION_REASON = 1; + + /** @var Pool */ + protected $pool; + /** @var array */ + protected $decisionMap; + /** + * @var array + */ + protected $decisionQueue = []; + + public function __construct(Pool $pool) + { + $this->pool = $pool; + $this->decisionMap = []; + } + + public function decide(int $literal, int $level, Rule $why): void + { + $this->addDecision($literal, $level); + $this->decisionQueue[] = [ + self::DECISION_LITERAL => $literal, + self::DECISION_REASON => $why, + ]; + } + + public function satisfy(int $literal): bool + { + $packageId = abs($literal); + + return ( + $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 || + $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 + ); + } + + public function conflict(int $literal): bool + { + $packageId = abs($literal); + + return ( + (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) || + (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0) + ); + } + + public function decided(int $literalOrPackageId): bool + { + return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) !== 0; + } + + public function undecided(int $literalOrPackageId): bool + { + return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) === 0; + } + + public function decidedInstall(int $literalOrPackageId): bool + { + $packageId = abs($literalOrPackageId); + + return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0; + } + + public function decisionLevel(int $literalOrPackageId): int + { + $packageId = abs($literalOrPackageId); + if (isset($this->decisionMap[$packageId])) { + return abs($this->decisionMap[$packageId]); + } + + return 0; + } + + public function decisionRule(int $literalOrPackageId): Rule + { + $packageId = abs($literalOrPackageId); + + foreach ($this->decisionQueue as $decision) { + if ($packageId === abs($decision[self::DECISION_LITERAL])) { + return $decision[self::DECISION_REASON]; + } + } + + throw new \LogicException('Did not find a decision rule using '.$literalOrPackageId); + } + + /** + * @return array{0: int, 1: Rule} a literal and decision reason + */ + public function atOffset(int $queueOffset): array + { + return $this->decisionQueue[$queueOffset]; + } + + public function validOffset(int $queueOffset): bool + { + return $queueOffset >= 0 && $queueOffset < \count($this->decisionQueue); + } + + public function lastReason(): Rule + { + return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_REASON]; + } + + public function lastLiteral(): int + { + return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_LITERAL]; + } + + public function reset(): void + { + while ($decision = array_pop($this->decisionQueue)) { + $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0; + } + } + + /** + * @param int<-1, max> $offset + */ + public function resetToOffset(int $offset): void + { + while (\count($this->decisionQueue) > $offset + 1) { + $decision = array_pop($this->decisionQueue); + $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0; + } + } + + public function revertLast(): void + { + $this->decisionMap[abs($this->lastLiteral())] = 0; + array_pop($this->decisionQueue); + } + + public function count(): int + { + return \count($this->decisionQueue); + } + + public function rewind(): void + { + end($this->decisionQueue); + } + + /** + * @return array{0: int, 1: Rule}|false + */ + #[\ReturnTypeWillChange] + public function current() + { + return current($this->decisionQueue); + } + + public function key(): ?int + { + return key($this->decisionQueue); + } + + public function next(): void + { + prev($this->decisionQueue); + } + + public function valid(): bool + { + return false !== current($this->decisionQueue); + } + + public function isEmpty(): bool + { + return \count($this->decisionQueue) === 0; + } + + protected function addDecision(int $literal, int $level): void + { + $packageId = abs($literal); + + $previousDecision = $this->decisionMap[$packageId] ?? 0; + if ($previousDecision !== 0) { + $literalString = $this->pool->literalToPrettyString($literal, []); + $package = $this->pool->literalToPackage($literal); + throw new SolverBugException( + "Trying to decide $literalString on level $level, even though $package was previously decided as ".$previousDecision."." + ); + } + + if ($literal > 0) { + $this->decisionMap[$packageId] = $level; + } else { + $this->decisionMap[$packageId] = -$level; + } + } + + public function toString(?Pool $pool = null): string + { + $decisionMap = $this->decisionMap; + ksort($decisionMap); + $str = '['; + foreach ($decisionMap as $packageId => $level) { + $str .= ($pool !== null ? $pool->literalToPackage($packageId) : $packageId).':'.$level.','; + } + $str .= ']'; + + return $str; + } + + public function __toString(): string + { + return $this->toString(); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php b/vendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php new file mode 100644 index 000000000..e2984b90f --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php @@ -0,0 +1,297 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Semver\CompilingMatcher; +use Composer\Semver\Constraint\Constraint; +use Composer\Util\Platform; + +/** + * @author Nils Adermann + * @author Jordi Boggiano + */ +class DefaultPolicy implements PolicyInterface +{ + /** @var bool */ + private $preferStable; + /** @var bool */ + private $preferLowest; + /** @var bool */ + private $preferDevOverPrerelease; + /** @var array|null */ + private $preferredVersions; + /** @var array>> */ + private $preferredPackageResultCachePerPool; + /** @var array> */ + private $sortingCachePerPool; + + /** + * @param array|null $preferredVersions Must be an array of package name => normalized version + */ + public function __construct(bool $preferStable = false, bool $preferLowest = false, ?array $preferredVersions = null) + { + $this->preferStable = $preferStable; + $this->preferLowest = $preferLowest; + $this->preferredVersions = $preferredVersions; + $this->preferDevOverPrerelease = (bool) Platform::getEnv('COMPOSER_PREFER_DEV_OVER_PRERELEASE'); + } + + /** + * @param string $operator One of Constraint::STR_OP_* + * + * @phpstan-param Constraint::STR_OP_* $operator + */ + public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool + { + if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) { + if ($this->preferLowest && $this->preferDevOverPrerelease && 'stable' !== $stabA && 'stable' !== $stabB) { + // When COMPOSER_PREFER_DEV_OVER_PRERELEASE is set and no stable version has been + // released, "dev" should be considered more stable than "alpha", "beta" or "RC"; + // this allows testing lowest versions with potential fixes applied + $stabA = 'dev' === $stabA ? 'stable' : $stabA; + $stabB = 'dev' === $stabB ? 'stable' : $stabB; + } + + return BasePackage::STABILITIES[$stabA] < BasePackage::STABILITIES[$stabB]; + } + + // dev versions need to be compared as branches via matchSpecific's special treatment, the rest can be optimized with compiling matcher + if (($a->isDev() && str_starts_with($a->getVersion(), 'dev-')) || ($b->isDev() && str_starts_with($b->getVersion(), 'dev-'))) { + $constraint = new Constraint($operator, $b->getVersion()); + $version = new Constraint('==', $a->getVersion()); + + return $constraint->matchSpecific($version, true); + } + + return CompilingMatcher::match(new Constraint($operator, $b->getVersion()), Constraint::OP_EQ, $a->getVersion()); + } + + /** + * @param non-empty-list $literals + * @return non-empty-list + */ + public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array + { + sort($literals); + $resultCacheKey = implode(',', $literals).$requiredPackage; + $poolId = spl_object_id($pool); + + if (isset($this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey])) { + return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey]; + } + + $packages = $this->groupLiteralsByName($pool, $literals); + + foreach ($packages as &$nameLiterals) { + usort($nameLiterals, function ($a, $b) use ($pool, $requiredPackage, $poolId): int { + $cacheKey = 'i'.$a.'.'.$b.$requiredPackage; // i prefix -> ignoreReplace = true + + if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) { + return $this->sortingCachePerPool[$poolId][$cacheKey]; + } + + return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true); + }); + } + + foreach ($packages as &$sortedLiterals) { + $sortedLiterals = $this->pruneToBestVersion($pool, $sortedLiterals); + $sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals); + } + + $selected = array_merge(...array_values($packages)); + + // now sort the result across all packages to respect replaces across packages + usort($selected, function ($a, $b) use ($pool, $requiredPackage, $poolId): int { + $cacheKey = $a.'.'.$b.$requiredPackage; // no i prefix -> ignoreReplace = false + + if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) { + return $this->sortingCachePerPool[$poolId][$cacheKey]; + } + + return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage); + }); + + return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey] = $selected; + } + + /** + * @param non-empty-list $literals + * @return non-empty-array> + */ + protected function groupLiteralsByName(Pool $pool, array $literals): array + { + $packages = []; + foreach ($literals as $literal) { + $packageName = $pool->literalToPackage($literal)->getName(); + + if (!isset($packages[$packageName])) { + $packages[$packageName] = []; + } + $packages[$packageName][] = $literal; + } + + return $packages; + } + + /** + * @protected + */ + public function compareByPriority(Pool $pool, BasePackage $a, BasePackage $b, ?string $requiredPackage = null, bool $ignoreReplace = false): int + { + // prefer aliases to the original package + if ($a->getName() === $b->getName()) { + $aAliased = $a instanceof AliasPackage; + $bAliased = $b instanceof AliasPackage; + if ($aAliased && !$bAliased) { + return -1; // use a + } + if (!$aAliased && $bAliased) { + return 1; // use b + } + } + + if (!$ignoreReplace) { + // return original, not replaced + if ($this->replaces($a, $b)) { + return 1; // use b + } + if ($this->replaces($b, $a)) { + return -1; // use a + } + + // for replacers not replacing each other, put a higher prio on replacing + // packages with the same vendor as the required package + if ($requiredPackage !== null && false !== ($pos = strpos($requiredPackage, '/'))) { + $requiredVendor = substr($requiredPackage, 0, $pos); + + $aIsSameVendor = strpos($a->getName(), $requiredVendor) === 0; + $bIsSameVendor = strpos($b->getName(), $requiredVendor) === 0; + + if ($bIsSameVendor !== $aIsSameVendor) { + return $aIsSameVendor ? -1 : 1; + } + } + } + + // priority equal, sort by package id to make reproducible + if ($a->id === $b->id) { + return 0; + } + + return ($a->id < $b->id) ? -1 : 1; + } + + /** + * Checks if source replaces a package with the same name as target. + * + * Replace constraints are ignored. This method should only be used for + * prioritisation, not for actual constraint verification. + */ + protected function replaces(BasePackage $source, BasePackage $target): bool + { + foreach ($source->getReplaces() as $link) { + if ($link->getTarget() === $target->getName() + // && (null === $link->getConstraint() || + // $link->getConstraint()->matches(new Constraint('==', $target->getVersion())))) { + ) { + return true; + } + } + + return false; + } + + /** + * @param list $literals + * @return list + */ + protected function pruneToBestVersion(Pool $pool, array $literals): array + { + if ($this->preferredVersions !== null) { + $name = $pool->literalToPackage($literals[0])->getName(); + if (isset($this->preferredVersions[$name])) { + $preferredVersion = $this->preferredVersions[$name]; + $bestLiterals = []; + foreach ($literals as $literal) { + if ($pool->literalToPackage($literal)->getVersion() === $preferredVersion) { + $bestLiterals[] = $literal; + } + } + if (\count($bestLiterals) > 0) { + return $bestLiterals; + } + } + } + + $operator = $this->preferLowest ? '<' : '>'; + $bestLiterals = [$literals[0]]; + $bestPackage = $pool->literalToPackage($literals[0]); + foreach ($literals as $i => $literal) { + if (0 === $i) { + continue; + } + + $package = $pool->literalToPackage($literal); + + if ($this->versionCompare($package, $bestPackage, $operator)) { + $bestPackage = $package; + $bestLiterals = [$literal]; + } elseif ($this->versionCompare($package, $bestPackage, '==')) { + $bestLiterals[] = $literal; + } + } + + return $bestLiterals; + } + + /** + * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones + * + * If no package is a local alias, nothing happens + * + * @param list $literals + * @return list + */ + protected function pruneRemoteAliases(Pool $pool, array $literals): array + { + $hasLocalAlias = false; + + foreach ($literals as $literal) { + $package = $pool->literalToPackage($literal); + + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + $hasLocalAlias = true; + break; + } + } + + if (!$hasLocalAlias) { + return $literals; + } + + $selected = []; + foreach ($literals as $literal) { + $package = $pool->literalToPackage($literal); + + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + $selected[] = $literal; + } + } + + return $selected; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php b/vendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php new file mode 100644 index 000000000..64dd7a215 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php @@ -0,0 +1,93 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + */ +class GenericRule extends Rule +{ + /** @var list */ + protected $literals; + + /** + * @param list $literals + */ + public function __construct(array $literals, $reason, $reasonData) + { + parent::__construct($reason, $reasonData); + + // sort all packages ascending by id + sort($literals); + + $this->literals = $literals; + } + + /** + * @return list + */ + public function getLiterals(): array + { + return $this->literals; + } + + /** + * @inheritDoc + */ + public function getHash() + { + $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', implode(',', $this->literals), true)); + if (false === $data) { + throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals)); + } + + return $data['hash']; + } + + /** + * Checks if this rule is equal to another one + * + * Ignores whether either of the rules is disabled. + * + * @param Rule $rule The rule to check against + * @return bool Whether the rules are equal + */ + public function equals(Rule $rule): bool + { + return $this->literals === $rule->getLiterals(); + } + + public function isAssertion(): bool + { + return 1 === \count($this->literals); + } + + /** + * Formats a rule as a string of the format (Literal1|Literal2|...) + */ + public function __toString(): string + { + $result = $this->isDisabled() ? 'disabled(' : '('; + + foreach ($this->literals as $i => $literal) { + if ($i !== 0) { + $result .= '|'; + } + $result .= $literal; + } + + $result .= ')'; + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php b/vendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php new file mode 100644 index 000000000..06d987797 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php @@ -0,0 +1,31 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Repository\RepositoryInterface; + +/** + * @author Nils Adermann + * @internal + */ +class LocalRepoTransaction extends Transaction +{ + public function __construct(RepositoryInterface $lockedRepository, InstalledRepositoryInterface $localRepository) + { + parent::__construct( + $localRepository->getPackages(), + $lockedRepository->getPackages() + ); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php b/vendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php new file mode 100644 index 000000000..d77a21139 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php @@ -0,0 +1,198 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Package; +use Composer\Pcre\Preg; + +/** + * @author Nils Adermann + * @internal + */ +class LockTransaction extends Transaction +{ + /** + * packages in current lock file, platform repo or otherwise present + * + * Indexed by spl_object_hash + * + * @var array + */ + protected $presentMap; + + /** + * Packages which cannot be mapped, platform repo, root package, other fixed repos + * + * Indexed by package id + * + * @var array + */ + protected $unlockableMap; + + /** + * @var array{dev: BasePackage[], non-dev: BasePackage[], all: BasePackage[]} + */ + protected $resultPackages; + + /** + * @param array $presentMap + * @param array $unlockableMap + */ + public function __construct(Pool $pool, array $presentMap, array $unlockableMap, Decisions $decisions) + { + $this->presentMap = $presentMap; + $this->unlockableMap = $unlockableMap; + + $this->setResultPackages($pool, $decisions); + parent::__construct($this->presentMap, $this->resultPackages['all']); + } + + // TODO make this a bit prettier instead of the two text indexes? + + public function setResultPackages(Pool $pool, Decisions $decisions): void + { + $this->resultPackages = ['all' => [], 'non-dev' => [], 'dev' => []]; + foreach ($decisions as $i => $decision) { + $literal = $decision[Decisions::DECISION_LITERAL]; + + if ($literal > 0) { + $package = $pool->literalToPackage($literal); + + $this->resultPackages['all'][] = $package; + if (!isset($this->unlockableMap[$package->id])) { + $this->resultPackages['non-dev'][] = $package; + } + } + } + } + + public function setNonDevPackages(LockTransaction $extractionResult): void + { + $packages = $extractionResult->getNewLockPackages(false); + + $this->resultPackages['dev'] = $this->resultPackages['non-dev']; + $this->resultPackages['non-dev'] = []; + + foreach ($packages as $package) { + foreach ($this->resultPackages['dev'] as $i => $resultPackage) { + // TODO this comparison is probably insufficient, aliases, what about modified versions? I guess they aren't possible? + if ($package->getName() === $resultPackage->getName()) { + $this->resultPackages['non-dev'][] = $resultPackage; + unset($this->resultPackages['dev'][$i]); + } + } + } + } + + // TODO additionalFixedRepository needs to be looked at here as well? + /** + * @return BasePackage[] + */ + public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): array + { + $packages = []; + foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) { + if ($package instanceof AliasPackage) { + continue; + } + + // if we're just updating mirrors we need to reset everything to the same as currently "present" packages' references to keep the lock file as-is + if ($updateMirrors === true && !array_key_exists(spl_object_hash($package), $this->presentMap)) { + $package = $this->updateMirrorAndUrls($package); + } + + $packages[] = $package; + } + + return $packages; + } + + /** + * Try to return the original package from presentMap with updated URLs/mirrors + * + * If the type of source/dist changed, then we do not update those and keep them as they were + */ + private function updateMirrorAndUrls(BasePackage $package): BasePackage + { + foreach ($this->presentMap as $presentPackage) { + if ($package->getName() !== $presentPackage->getName()) { + continue; + } + + if ($package->getVersion() !== $presentPackage->getVersion()) { + continue; + } + + if ($presentPackage->getSourceReference() === null) { + continue; + } + + if ($presentPackage->getSourceType() !== $package->getSourceType()) { + continue; + } + + if ($presentPackage instanceof Package) { + $presentPackage->setSourceUrl($package->getSourceUrl()); + $presentPackage->setSourceMirrors($package->getSourceMirrors()); + } + + // if the dist type changed, we only update the source url/mirrors + if ($presentPackage->getDistType() !== $package->getDistType()) { + return $presentPackage; + } + + // update dist url if it is in a known format + if ( + $package->getDistUrl() !== null + && $presentPackage->getDistReference() !== null + && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl()) + ) { + $presentPackage->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $presentPackage->getDistReference(), $package->getDistUrl())); + } + $presentPackage->setDistMirrors($package->getDistMirrors()); + + return $presentPackage; + } + + return $package; + } + + /** + * Checks which of the given aliases from composer.json are actually in use for the lock file + * @param list $aliases + * @return list + */ + public function getAliases(array $aliases): array + { + $usedAliases = []; + + foreach ($this->resultPackages['all'] as $package) { + if ($package instanceof AliasPackage) { + foreach ($aliases as $index => $alias) { + if ($alias['package'] === $package->getName()) { + $usedAliases[] = $alias; + unset($aliases[$index]); + } + } + } + } + + usort($usedAliases, static function ($a, $b): int { + return strcmp($a['package'], $b['package']); + }); + + return $usedAliases; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php b/vendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php new file mode 100644 index 000000000..a61094755 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php @@ -0,0 +1,113 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + * + * MultiConflictRule([A, B, C]) acts as Rule([-A, -B]), Rule([-A, -C]), Rule([-B, -C]) + */ +class MultiConflictRule extends Rule +{ + /** @var non-empty-list */ + protected $literals; + + /** + * @param non-empty-list $literals + */ + public function __construct(array $literals, $reason, $reasonData) + { + parent::__construct($reason, $reasonData); + + if (\count($literals) < 3) { + throw new \RuntimeException("multi conflict rule requires at least 3 literals"); + } + + // sort all packages ascending by id + sort($literals); + + $this->literals = $literals; + } + + /** + * @return non-empty-list + */ + public function getLiterals(): array + { + return $this->literals; + } + + /** + * @inheritDoc + */ + public function getHash() + { + $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', 'c:'.implode(',', $this->literals), true)); + if (false === $data) { + throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals)); + } + + return $data['hash']; + } + + /** + * Checks if this rule is equal to another one + * + * Ignores whether either of the rules is disabled. + * + * @param Rule $rule The rule to check against + * @return bool Whether the rules are equal + */ + public function equals(Rule $rule): bool + { + if ($rule instanceof MultiConflictRule) { + return $this->literals === $rule->getLiterals(); + } + + return false; + } + + public function isAssertion(): bool + { + return false; + } + + /** + * @return never + * @throws \RuntimeException + */ + public function disable(): void + { + throw new \RuntimeException("Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation."); + } + + /** + * Formats a rule as a string of the format (Literal1|Literal2|...) + */ + public function __toString(): string + { + // TODO multi conflict? + $result = $this->isDisabled() ? 'disabled(multi(' : '(multi('; + + foreach ($this->literals as $i => $literal) { + if ($i !== 0) { + $result .= '|'; + } + $result .= $literal; + } + + $result .= '))'; + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php new file mode 100644 index 000000000..6aa24f49d --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\PackageInterface; + +/** + * Solver install operation. + * + * @author Konstantin Kudryashov + */ +class InstallOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'install'; + + /** + * @var PackageInterface + */ + protected $package; + + public function __construct(PackageInterface $package) + { + $this->package = $package; + } + + /** + * Returns package instance. + */ + public function getPackage(): PackageInterface + { + return $this->package; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return self::format($this->package, $lock); + } + + public static function format(PackageInterface $package, bool $lock = false): string + { + return ($lock ? 'Locking ' : 'Installing ').''.$package->getPrettyName().' ('.$package->getFullPrettyVersion().')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php new file mode 100644 index 000000000..5deac9632 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\AliasPackage; + +/** + * Solver install operation. + * + * @author Nils Adermann + */ +class MarkAliasInstalledOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'markAliasInstalled'; + + /** + * @var AliasPackage + */ + protected $package; + + public function __construct(AliasPackage $package) + { + $this->package = $package; + } + + /** + * Returns package instance. + */ + public function getPackage(): AliasPackage + { + return $this->package; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getFullPrettyVersion().')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php new file mode 100644 index 000000000..9988f6ca7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\AliasPackage; + +/** + * Solver install operation. + * + * @author Nils Adermann + */ +class MarkAliasUninstalledOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'markAliasUninstalled'; + + /** + * @var AliasPackage + */ + protected $package; + + public function __construct(AliasPackage $package) + { + $this->package = $package; + } + + /** + * Returns package instance. + */ + public function getPackage(): AliasPackage + { + return $this->package; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getFullPrettyVersion().')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php new file mode 100644 index 000000000..45e6acd72 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +/** + * Solver operation interface. + * + * @author Konstantin Kudryashov + */ +interface OperationInterface +{ + /** + * Returns operation type. + * + * @return string + */ + public function getOperationType(); + + /** + * Serializes the operation in a human readable format + * + * @param bool $lock Whether this is an operation on the lock file + * @return string + */ + public function show(bool $lock); + + /** + * Serializes the operation in a human readable format + * + * @return string + */ + public function __toString(); +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php new file mode 100644 index 000000000..66f0da50f --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php @@ -0,0 +1,42 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +/** + * Abstract operation class. + * + * @author Aleksandr Bezpiatov + */ +abstract class SolverOperation implements OperationInterface +{ + /** + * @abstract must be redefined by extending classes + */ + protected const TYPE = ''; + + /** + * Returns operation type. + */ + public function getOperationType(): string + { + return static::TYPE; + } + + /** + * @inheritDoc + */ + public function __toString() + { + return $this->show(false); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php new file mode 100644 index 000000000..f6f5a4735 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\PackageInterface; + +/** + * Solver uninstall operation. + * + * @author Konstantin Kudryashov + */ +class UninstallOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'uninstall'; + + /** + * @var PackageInterface + */ + protected $package; + + public function __construct(PackageInterface $package) + { + $this->package = $package; + } + + /** + * Returns package instance. + */ + public function getPackage(): PackageInterface + { + return $this->package; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return self::format($this->package, $lock); + } + + public static function format(PackageInterface $package, bool $lock = false): string + { + return 'Removing '.$package->getPrettyName().' ('.$package->getFullPrettyVersion().')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php new file mode 100644 index 000000000..48010fb1a --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php @@ -0,0 +1,88 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; + +/** + * Solver update operation. + * + * @author Konstantin Kudryashov + */ +class UpdateOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'update'; + + /** + * @var PackageInterface + */ + protected $initialPackage; + + /** + * @var PackageInterface + */ + protected $targetPackage; + + /** + * @param PackageInterface $initial initial package + * @param PackageInterface $target target package (updated) + */ + public function __construct(PackageInterface $initial, PackageInterface $target) + { + $this->initialPackage = $initial; + $this->targetPackage = $target; + } + + /** + * Returns initial package. + */ + public function getInitialPackage(): PackageInterface + { + return $this->initialPackage; + } + + /** + * Returns target package. + */ + public function getTargetPackage(): PackageInterface + { + return $this->targetPackage; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return self::format($this->initialPackage, $this->targetPackage, $lock); + } + + public static function format(PackageInterface $initialPackage, PackageInterface $targetPackage, bool $lock = false): string + { + $fromVersion = $initialPackage->getFullPrettyVersion(); + $toVersion = $targetPackage->getFullPrettyVersion(); + + if ($fromVersion === $toVersion && $initialPackage->getSourceReference() !== $targetPackage->getSourceReference()) { + $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF); + $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF); + } elseif ($fromVersion === $toVersion && $initialPackage->getDistReference() !== $targetPackage->getDistReference()) { + $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF); + $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF); + } + + $actionName = VersionParser::isUpgrade($initialPackage->getVersion(), $targetPackage->getVersion()) ? 'Upgrading' : 'Downgrading'; + + return $actionName.' '.$initialPackage->getPrettyName().' ('.$fromVersion.' => '.$toVersion.')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php b/vendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php new file mode 100644 index 000000000..b4511d091 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php @@ -0,0 +1,33 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\PackageInterface; +use Composer\Semver\Constraint\Constraint; + +/** + * @author Nils Adermann + */ +interface PolicyInterface +{ + /** + * @phpstan-param Constraint::STR_OP_* $operator + */ + public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool; + + /** + * @param non-empty-list $literals + * @return non-empty-list + */ + public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array; +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Pool.php b/vendor/composer/composer/src/Composer/DependencyResolver/Pool.php new file mode 100644 index 000000000..a22111275 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Pool.php @@ -0,0 +1,345 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\Advisory\SecurityAdvisory; +use Composer\Package\BasePackage; +use Composer\Package\Version\VersionParser; +use Composer\Semver\CompilingMatcher; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; + +/** + * A package pool contains all packages for dependency resolution + * + * @author Nils Adermann + * @author Jordi Boggiano + */ +class Pool implements \Countable +{ + /** @var BasePackage[] */ + protected $packages = []; + /** @var array */ + protected $packageByName = []; + /** @var VersionParser */ + protected $versionParser; + /** @var array> */ + protected $providerCache = []; + /** @var BasePackage[] */ + protected $unacceptableFixedOrLockedPackages; + /** @var array> Map of package name => normalized version => pretty version */ + protected $removedVersions = []; + /** @var array> Map of package object hash => removed normalized versions => removed pretty version */ + protected $removedVersionsByPackage = []; + /** @var array>> Map of package name => normalized version => security advisories */ + private $securityRemovedVersions = []; + /** @var array> Map of package name => normalized version => pretty version */ + private $abandonedRemovedVersions = []; + + /** + * @param BasePackage[] $packages + * @param BasePackage[] $unacceptableFixedOrLockedPackages + * @param array> $removedVersions + * @param array> $removedVersionsByPackage + * @param array>> $securityRemovedVersions + * @param array> $abandonedRemovedVersions + */ + public function __construct(array $packages = [], array $unacceptableFixedOrLockedPackages = [], array $removedVersions = [], array $removedVersionsByPackage = [], array $securityRemovedVersions = [], array $abandonedRemovedVersions = []) + { + $this->versionParser = new VersionParser; + $this->setPackages($packages); + $this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages; + $this->removedVersions = $removedVersions; + $this->removedVersionsByPackage = $removedVersionsByPackage; + $this->securityRemovedVersions = $securityRemovedVersions; + $this->abandonedRemovedVersions = $abandonedRemovedVersions; + } + + /** + * @return array + */ + public function getRemovedVersions(string $name, ConstraintInterface $constraint): array + { + if (!isset($this->removedVersions[$name])) { + return []; + } + + $result = []; + foreach ($this->removedVersions[$name] as $version => $prettyVersion) { + if ($constraint->matches(new Constraint('==', $version))) { + $result[$version] = $prettyVersion; + } + } + + return $result; + } + + /** + * @return array> + */ + public function getAllRemovedVersions(): array + { + return $this->removedVersions; + } + + /** + * @return array + */ + public function getRemovedVersionsByPackage(string $objectHash): array + { + if (!isset($this->removedVersionsByPackage[$objectHash])) { + return []; + } + + return $this->removedVersionsByPackage[$objectHash]; + } + + /** + * @return array> + */ + public function getAllRemovedVersionsByPackage(): array + { + return $this->removedVersionsByPackage; + } + + public function isSecurityRemovedPackageVersion(string $packageName, ?ConstraintInterface $constraint): bool + { + foreach ($this->securityRemovedVersions[$packageName] ?? [] as $version => $packageWithSecurityAdvisories) { + if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) { + return true; + } + } + + return false; + } + + /** + * @return string[] + */ + public function getSecurityAdvisoryIdentifiersForPackageVersion(string $packageName, ?ConstraintInterface $constraint): array + { + foreach ($this->securityRemovedVersions[$packageName] ?? [] as $version => $packageWithSecurityAdvisories) { + if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) { + return array_map(static function ($advisory) { + return $advisory->advisoryId; + }, $packageWithSecurityAdvisories); + } + } + + return []; + } + + public function isAbandonedRemovedPackageVersion(string $packageName, ?ConstraintInterface $constraint): bool + { + foreach ($this->abandonedRemovedVersions[$packageName] ?? [] as $version => $prettyVersion) { + if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) { + return true; + } + } + + return false; + } + + /** + * @return array>> + */ + public function getAllSecurityRemovedPackageVersions(): array + { + return $this->securityRemovedVersions; + } + + /** + * @return array> + */ + public function getAllAbandonedRemovedPackageVersions(): array + { + return $this->abandonedRemovedVersions; + } + + /** + * @param BasePackage[] $packages + */ + private function setPackages(array $packages): void + { + $id = 1; + + foreach ($packages as $package) { + $this->packages[] = $package; + + $package->id = $id++; + + foreach ($package->getNames() as $provided) { + $this->packageByName[$provided][] = $package; + } + } + } + + /** + * @return BasePackage[] + */ + public function getPackages(): array + { + return $this->packages; + } + + /** + * Retrieves the package object for a given package id. + */ + public function packageById(int $id): BasePackage + { + return $this->packages[$id - 1]; + } + + /** + * Returns how many packages have been loaded into the pool + */ + public function count(): int + { + return \count($this->packages); + } + + /** + * Searches all packages providing the given package name and match the constraint + * + * @param string $name The package name to be searched for + * @param ?ConstraintInterface $constraint A constraint that all returned + * packages must match or null to return all + * @return BasePackage[] A set of packages + */ + public function whatProvides(string $name, ?ConstraintInterface $constraint = null): array + { + $key = (string) $constraint; + if (isset($this->providerCache[$name][$key])) { + return $this->providerCache[$name][$key]; + } + + return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint); + } + + /** + * @param string $name The package name to be searched for + * @param ?ConstraintInterface $constraint A constraint that all returned + * packages must match or null to return all + * @return BasePackage[] + */ + private function computeWhatProvides(string $name, ?ConstraintInterface $constraint = null): array + { + if (!isset($this->packageByName[$name])) { + return []; + } + + $matches = []; + + foreach ($this->packageByName[$name] as $candidate) { + if ($this->match($candidate, $name, $constraint)) { + $matches[] = $candidate; + } + } + + return $matches; + } + + public function literalToPackage(int $literal): BasePackage + { + $packageId = abs($literal); + + return $this->packageById($packageId); + } + + /** + * @param array $installedMap + */ + public function literalToPrettyString(int $literal, array $installedMap): string + { + $package = $this->literalToPackage($literal); + + if (isset($installedMap[$package->id])) { + $prefix = ($literal > 0 ? 'keep' : 'remove'); + } else { + $prefix = ($literal > 0 ? 'install' : 'don\'t install'); + } + + return $prefix.' '.$package->getPrettyString(); + } + + /** + * Checks if the package matches the given constraint directly or through + * provided or replaced packages + * + * @param string $name Name of the package to be matched + */ + public function match(BasePackage $candidate, string $name, ?ConstraintInterface $constraint = null): bool + { + $candidateName = $candidate->getName(); + $candidateVersion = $candidate->getVersion(); + + if ($candidateName === $name) { + return $constraint === null || CompilingMatcher::match($constraint, Constraint::OP_EQ, $candidateVersion); + } + + $provides = $candidate->getProvides(); + $replaces = $candidate->getReplaces(); + + // aliases create multiple replaces/provides for one target so they can not use the shortcut below + if (isset($replaces[0]) || isset($provides[0])) { + foreach ($provides as $link) { + if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { + return true; + } + } + + foreach ($replaces as $link) { + if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { + return true; + } + } + + return false; + } + + if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) { + return true; + } + + if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) { + return true; + } + + return false; + } + + public function isUnacceptableFixedOrLockedPackage(BasePackage $package): bool + { + return \in_array($package, $this->unacceptableFixedOrLockedPackages, true); + } + + /** + * @return BasePackage[] + */ + public function getUnacceptableFixedOrLockedPackages(): array + { + return $this->unacceptableFixedOrLockedPackages; + } + + public function __toString(): string + { + $str = "Pool:\n"; + + foreach ($this->packages as $package) { + $str .= '- '.str_pad((string) $package->id, 6, ' ', STR_PAD_LEFT).': '.$package->getName()."\n"; + } + + return $str; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php b/vendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php new file mode 100644 index 000000000..1929919fd --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php @@ -0,0 +1,849 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\EventDispatcher\EventDispatcher; +use Composer\IO\IOInterface; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\PackageInterface; +use Composer\Package\Version\StabilityFilter; +use Composer\Pcre\Preg; +use Composer\Plugin\PluginEvents; +use Composer\Plugin\PrePoolCreateEvent; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RootPackageRepository; +use Composer\Semver\CompilingMatcher; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Intervals; + +/** + * @author Nils Adermann + */ +class PoolBuilder +{ + private const LOAD_BATCH_SIZE = 50; + + /** + * @var int[] + * @phpstan-var array, BasePackage::STABILITY_*> + */ + private $acceptableStabilities; + /** + * @var int[] + * @phpstan-var array + */ + private $stabilityFlags; + /** + * @var array[] + * @phpstan-var array> + */ + private $rootAliases; + /** + * @var string[] + * @phpstan-var array + */ + private $rootReferences; + /** + * @var array + */ + private $temporaryConstraints; + /** + * @var ?EventDispatcher + */ + private $eventDispatcher; + /** + * @var PoolOptimizer|null + */ + private $poolOptimizer; + /** + * @var IOInterface + */ + private $io; + /** + * @var array[] + * @phpstan-var array + */ + private $aliasMap = []; + /** + * @var ConstraintInterface[] + * @phpstan-var array + */ + private $packagesToLoad = []; + /** + * @var ConstraintInterface[] + * @phpstan-var array + */ + private $loadedPackages = []; + /** + * @var array[] + * @phpstan-var array>> + */ + private $loadedPerRepo = []; + /** + * @var array + */ + private $packages = []; + /** + * @var BasePackage[] + */ + private $unacceptableFixedOrLockedPackages = []; + /** @var array */ + private $updateAllowList = []; + /** @var array> */ + private $skippedLoad = []; + /** @var list */ + private $ignoredTypes = []; + /** @var list|null */ + private $allowedTypes = null; + + /** + * If provided, only these package names are loaded + * + * This is a special-use functionality of the Request class to optimize the pool creation process + * when only a minimal subset of packages is needed and we do not need their dependencies. + * + * @var array|null + */ + private $restrictedPackagesList = null; + + /** + * Keeps a list of dependencies which are locked but were auto-unlocked as they are path repositories + * + * This half-unlocked state means the package itself will update but the UPDATE_LISTED_WITH_TRANSITIVE_DEPS* + * flags will not apply until the package really gets unlocked in some other way than being a path repo + * + * @var array + */ + private $pathRepoUnlocked = []; + + /** + * Keeps a list of dependencies which are root requirements, and as such + * have already their maximum required range loaded and can not be + * extended by markPackageNameForLoading + * + * Packages get cleared from this list if they get unlocked as in that case + * we need to actually load them + * + * @var array + */ + private $maxExtendedReqs = []; + /** + * @var array + * @phpstan-var array + */ + private $updateAllowWarned = []; + + /** @var int */ + private $indexCounter = 0; + + /** @var ?SecurityAdvisoryPoolFilter */ + private $securityAdvisoryPoolFilter; + + /** + * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value + * @phpstan-param array, BasePackage::STABILITY_*> $acceptableStabilities + * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array $stabilityFlags + * @param array[] $rootAliases + * @phpstan-param array> $rootAliases + * @param string[] $rootReferences an array of package name => source reference + * @phpstan-param array $rootReferences + * @param array $temporaryConstraints Runtime temporary constraints that will be used to filter packages + */ + public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $temporaryConstraints = [], ?SecurityAdvisoryPoolFilter $securityAdvisoryPoolFilter = null) + { + $this->acceptableStabilities = $acceptableStabilities; + $this->stabilityFlags = $stabilityFlags; + $this->rootAliases = $rootAliases; + $this->rootReferences = $rootReferences; + $this->eventDispatcher = $eventDispatcher; + $this->poolOptimizer = $poolOptimizer; + $this->io = $io; + $this->temporaryConstraints = $temporaryConstraints; + $this->securityAdvisoryPoolFilter = $securityAdvisoryPoolFilter; + } + + /** + * Packages of those types are ignored + * + * @param list $types + */ + public function setIgnoredTypes(array $types): void + { + $this->ignoredTypes = $types; + } + + /** + * Only packages of those types are allowed if set to non-null + * + * @param list|null $types + */ + public function setAllowedTypes(?array $types): void + { + $this->allowedTypes = $types; + } + + /** + * @param RepositoryInterface[] $repositories + */ + public function buildPool(array $repositories, Request $request): Pool + { + $this->restrictedPackagesList = $request->getRestrictedPackages() !== null ? array_flip($request->getRestrictedPackages()) : null; + + if (\count($request->getUpdateAllowList()) > 0) { + $this->updateAllowList = $request->getUpdateAllowList(); + $this->warnAboutNonMatchingUpdateAllowList($request); + + if (null === $request->getLockedRepository()) { + throw new \LogicException('No lock repo present and yet a partial update was requested.'); + } + + foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) { + if (!$this->isUpdateAllowed($lockedPackage)) { + // remember which packages we skipped loading remote content for in this partial update + $this->skippedLoad[$lockedPackage->getName()][] = $lockedPackage; + foreach ($lockedPackage->getReplaces() as $link) { + $this->skippedLoad[$link->getTarget()][] = $lockedPackage; + } + + // Path repo packages are never loaded from lock, to force them to always remain in sync + // unless symlinking is disabled in which case we probably should rather treat them like + // regular packages. We mark them specially so they can be reloaded fully including update propagation + // if they do get unlocked, but by default they are unlocked without update propagation. + if ($lockedPackage->getDistType() === 'path') { + $transportOptions = $lockedPackage->getTransportOptions(); + if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) { + $this->pathRepoUnlocked[$lockedPackage->getName()] = true; + continue; + } + } + + $request->lockPackage($lockedPackage); + } + } + } + + foreach ($request->getFixedOrLockedPackages() as $package) { + // using MatchAllConstraint here because fixed packages do not need to retrigger + // loading any packages + $this->loadedPackages[$package->getName()] = new MatchAllConstraint(); + + // replace means conflict, so if a fixed package replaces a name, no need to load that one, packages would conflict anyways + foreach ($package->getReplaces() as $link) { + $this->loadedPackages[$link->getTarget()] = new MatchAllConstraint(); + } + + // TODO in how far can we do the above for conflicts? It's more tricky cause conflicts can be limited to + // specific versions while replace is a conflict with all versions of the name + + if ( + $package->getRepository() instanceof RootPackageRepository + || $package->getRepository() instanceof PlatformRepository + || StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $package->getNames(), $package->getStability()) + ) { + $this->loadPackage($request, $repositories, $package, false); + } else { + $this->unacceptableFixedOrLockedPackages[] = $package; + } + } + + foreach ($request->getRequires() as $packageName => $constraint) { + // fixed and locked packages have already been added, so if a root require needs one of them, no need to do anything + if (isset($this->loadedPackages[$packageName])) { + continue; + } + + $this->packagesToLoad[$packageName] = $constraint; + $this->maxExtendedReqs[$packageName] = true; + } + + // clean up packagesToLoad for anything we manually marked loaded above + foreach ($this->packagesToLoad as $name => $constraint) { + if (isset($this->loadedPackages[$name])) { + unset($this->packagesToLoad[$name]); + } + } + + while (\count($this->packagesToLoad) > 0) { + $this->loadPackagesMarkedForLoading($request, $repositories); + } + + if (\count($this->temporaryConstraints) > 0) { + foreach ($this->packages as $i => $package) { + // we check all alias related packages at once, so no need to check individual aliases + if ($package instanceof AliasPackage) { + continue; + } + + foreach ($package->getNames() as $packageName) { + if (!isset($this->temporaryConstraints[$packageName])) { + continue; + } + + $constraint = $this->temporaryConstraints[$packageName]; + $packageAndAliases = [$i => $package]; + if (isset($this->aliasMap[spl_object_hash($package)])) { + $packageAndAliases += $this->aliasMap[spl_object_hash($package)]; + } + + $found = false; + foreach ($packageAndAliases as $packageOrAlias) { + if (CompilingMatcher::match($constraint, Constraint::OP_EQ, $packageOrAlias->getVersion())) { + $found = true; + } + } + + if (!$found) { + foreach ($packageAndAliases as $index => $packageOrAlias) { + unset($this->packages[$index]); + } + } + } + } + } + + if ($this->eventDispatcher !== null) { + $prePoolCreateEvent = new PrePoolCreateEvent( + PluginEvents::PRE_POOL_CREATE, + $repositories, + $request, + $this->acceptableStabilities, + $this->stabilityFlags, + $this->rootAliases, + $this->rootReferences, + $this->packages, + $this->unacceptableFixedOrLockedPackages + ); + $this->eventDispatcher->dispatch($prePoolCreateEvent->getName(), $prePoolCreateEvent); + $this->packages = $prePoolCreateEvent->getPackages(); + $this->unacceptableFixedOrLockedPackages = $prePoolCreateEvent->getUnacceptableFixedPackages(); + } + + $pool = new Pool($this->packages, $this->unacceptableFixedOrLockedPackages); + + $this->aliasMap = []; + $this->packagesToLoad = []; + $this->loadedPackages = []; + $this->loadedPerRepo = []; + $this->packages = []; + $this->unacceptableFixedOrLockedPackages = []; + $this->maxExtendedReqs = []; + $this->skippedLoad = []; + $this->indexCounter = 0; + + $this->io->debug('Built pool.'); + + // filter vulnerable packages before optimizing the pool otherwise we may end up with inconsistent state where the optimizer took away versions + // that were not vulnerable and now suddenly the vulnerable ones are removed and we are missing some versions to make it solvable + $pool = $this->runSecurityAdvisoryFilter($pool, $repositories, $request); + $pool = $this->runOptimizer($request, $pool); + + Intervals::clear(); + + return $pool; + } + + private function markPackageNameForLoading(Request $request, string $name, ConstraintInterface $constraint): void + { + // Skip platform requires at this stage + if (PlatformRepository::isPlatformPackage($name)) { + return; + } + + // Root require (which was not unlocked) already loaded the maximum range so no + // need to check anything here + if (isset($this->maxExtendedReqs[$name])) { + return; + } + + // Root requires can not be overruled by dependencies so there is no point in + // extending the loaded constraint for those. + // This is triggered when loading a root require which was locked but got unlocked, then + // we make sure that we load at most the intervals covered by the root constraint. + $rootRequires = $request->getRequires(); + if (isset($rootRequires[$name]) && !Intervals::isSubsetOf($constraint, $rootRequires[$name])) { + $constraint = $rootRequires[$name]; + } + + // Not yet loaded or already marked for a reload, set the constraint to be loaded + if (!isset($this->loadedPackages[$name])) { + // Maybe it was already marked before but not loaded yet. In that case + // we have to extend the constraint (we don't check if they are identical because + // MultiConstraint::create() will optimize anyway) + if (isset($this->packagesToLoad[$name])) { + // Already marked for loading and this does not expand the constraint to be loaded, nothing to do + if (Intervals::isSubsetOf($constraint, $this->packagesToLoad[$name])) { + return; + } + + // extend the constraint to be loaded + $constraint = Intervals::compactConstraint(MultiConstraint::create([$this->packagesToLoad[$name], $constraint], false)); + } + + $this->packagesToLoad[$name] = $constraint; + + return; + } + + // No need to load this package with this constraint because it is + // a subset of the constraint with which we have already loaded packages + if (Intervals::isSubsetOf($constraint, $this->loadedPackages[$name])) { + return; + } + + // We have already loaded that package but not in the constraint that's + // required. We extend the constraint and mark that package as not being loaded + // yet so we get the required package versions + $this->packagesToLoad[$name] = Intervals::compactConstraint(MultiConstraint::create([$this->loadedPackages[$name], $constraint], false)); + unset($this->loadedPackages[$name]); + } + + /** + * @param RepositoryInterface[] $repositories + */ + private function loadPackagesMarkedForLoading(Request $request, array $repositories): void + { + foreach ($this->packagesToLoad as $name => $constraint) { + if ($this->restrictedPackagesList !== null && !isset($this->restrictedPackagesList[$name])) { + unset($this->packagesToLoad[$name]); + continue; + } + $this->loadedPackages[$name] = $constraint; + } + + // Load packages in chunks of 50 to prevent memory usage build-up due to caches of all sorts + $packageBatches = array_chunk($this->packagesToLoad, self::LOAD_BATCH_SIZE, true); + $this->packagesToLoad = []; + + foreach ($repositories as $repoIndex => $repository) { + // these repos have their packages fixed or locked if they need to be loaded so we + // never need to load anything else from them + if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) { + continue; + } + + if (0 === \count($packageBatches)) { + break; + } + + foreach ($packageBatches as $batchIndex => $packageBatch) { + $result = $repository->loadPackages($packageBatch, $this->acceptableStabilities, $this->stabilityFlags, $this->loadedPerRepo[$repoIndex] ?? []); + + foreach ($result['namesFound'] as $name) { + // avoid loading the same package again from other repositories once it has been found + unset($packageBatches[$batchIndex][$name]); + } + foreach ($result['packages'] as $package) { + $this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()] = $package; + + if (in_array($package->getType(), $this->ignoredTypes, true) || ($this->allowedTypes !== null && !in_array($package->getType(), $this->allowedTypes, true))) { + continue; + } + $this->loadPackage($request, $repositories, $package, !isset($this->pathRepoUnlocked[$package->getName()])); + } + } + + $packageBatches = array_chunk(array_merge(...$packageBatches), self::LOAD_BATCH_SIZE, true); + } + } + + /** + * @param RepositoryInterface[] $repositories + */ + private function loadPackage(Request $request, array $repositories, BasePackage $package, bool $propagateUpdate): void + { + $index = $this->indexCounter++; + $this->packages[$index] = $package; + + if ($package instanceof AliasPackage) { + $this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package; + } + + $name = $package->getName(); + + // we're simply setting the root references on all versions for a name here and rely on the solver to pick the + // right version. It'd be more work to figure out which versions and which aliases of those versions this may + // apply to + if (isset($this->rootReferences[$name])) { + // do not modify the references on already locked or fixed packages + if (!$request->isLockedPackage($package) && !$request->isFixedPackage($package)) { + $package->setSourceDistReferences($this->rootReferences[$name]); + } + } + + // if propagateUpdate is false we are loading a fixed or locked package, root aliases do not apply as they are + // manually loaded as separate packages in this case + // + // packages in pathRepoUnlocked however need to also load root aliases, they have propagateUpdate set to + // false because their deps should not be unlocked, but that is irrelevant for root aliases + if (($propagateUpdate || isset($this->pathRepoUnlocked[$package->getName()])) && isset($this->rootAliases[$name][$package->getVersion()])) { + $alias = $this->rootAliases[$name][$package->getVersion()]; + if ($package instanceof AliasPackage) { + $basePackage = $package->getAliasOf(); + } else { + $basePackage = $package; + } + if ($basePackage instanceof CompletePackage) { + $aliasPackage = new CompleteAliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); + } else { + $aliasPackage = new AliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); + } + $aliasPackage->setRootPackageAlias(true); + + $newIndex = $this->indexCounter++; + $this->packages[$newIndex] = $aliasPackage; + $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][$newIndex] = $aliasPackage; + } + + foreach ($package->getRequires() as $link) { + $require = $link->getTarget(); + $linkConstraint = $link->getConstraint(); + + // if the required package is loaded as a locked package only and hasn't had its deps analyzed + if (isset($this->skippedLoad[$require])) { + // if we're doing a full update or this is a partial update with transitive deps and we're currently + // looking at a package which needs to be updated we need to unlock the package we now know is a + // dependency of another package which we are trying to update, and then attempt to load it again + if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { + $skippedRootRequires = $this->getSkippedRootRequires($request, $require); + + if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) { + $this->unlockPackage($request, $repositories, $require); + $this->markPackageNameForLoading($request, $require, $linkConstraint); + } else { + foreach ($skippedRootRequires as $rootRequire) { + if (!isset($this->updateAllowWarned[$rootRequire])) { + $this->updateAllowWarned[$rootRequire] = true; + $this->io->writeError('Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.'); + } + } + } + } elseif (isset($this->pathRepoUnlocked[$require]) && !isset($this->loadedPackages[$require])) { + // if doing a partial update and a package depends on a path-repo-unlocked package which is not referenced by the root, we need to ensure it gets loaded as it was not loaded by the request's root requirements + // and would not be loaded above if update propagation is not allowed (which happens if the requirer is itself a path-repo-unlocked package) or if transitive deps are not allowed to be unlocked + $this->markPackageNameForLoading($request, $require, $linkConstraint); + } + } else { + $this->markPackageNameForLoading($request, $require, $linkConstraint); + } + } + + // if we're doing a partial update with deps we also need to unlock packages which are being replaced in case + // they are currently locked and thus prevent this updateable package from being installable/updateable + if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { + foreach ($package->getReplaces() as $link) { + $replace = $link->getTarget(); + if (isset($this->loadedPackages[$replace], $this->skippedLoad[$replace])) { + $skippedRootRequires = $this->getSkippedRootRequires($request, $replace); + + if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) { + $this->unlockPackage($request, $repositories, $replace); + // the replaced package only needs to be loaded if something else requires it + $this->markPackageNameForLoadingIfRequired($request, $replace); + } else { + foreach ($skippedRootRequires as $rootRequire) { + if (!isset($this->updateAllowWarned[$rootRequire])) { + $this->updateAllowWarned[$rootRequire] = true; + $this->io->writeError('Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.'); + } + } + } + } + } + } + } + + /** + * Checks if a particular name is required directly in the request + * + * @param string $name packageName + */ + private function isRootRequire(Request $request, string $name): bool + { + $rootRequires = $request->getRequires(); + + return isset($rootRequires[$name]); + } + + /** + * @return string[] + */ + private function getSkippedRootRequires(Request $request, string $name): array + { + if (!isset($this->skippedLoad[$name])) { + return []; + } + + $rootRequires = $request->getRequires(); + $matches = []; + + if (isset($rootRequires[$name])) { + return array_map(static function (PackageInterface $package) use ($name): string { + if ($name !== $package->getName()) { + return $package->getName() .' (via replace of '.$name.')'; + } + + return $package->getName(); + }, $this->skippedLoad[$name]); + } + + foreach ($this->skippedLoad[$name] as $packageOrReplacer) { + if (isset($rootRequires[$packageOrReplacer->getName()])) { + $matches[] = $packageOrReplacer->getName(); + } + foreach ($packageOrReplacer->getReplaces() as $link) { + if (isset($rootRequires[$link->getTarget()])) { + if ($name !== $packageOrReplacer->getName()) { + $matches[] = $packageOrReplacer->getName() .' (via replace of '.$name.')'; + } else { + $matches[] = $packageOrReplacer->getName(); + } + break; + } + } + } + + return $matches; + } + + /** + * Checks whether the update allow list allows this package in the lock file to be updated + */ + private function isUpdateAllowed(BasePackage $package): bool + { + foreach ($this->updateAllowList as $pattern) { + $patternRegexp = BasePackage::packageNameToRegexp($pattern); + if (Preg::isMatch($patternRegexp, $package->getName())) { + return true; + } + } + + return false; + } + + private function warnAboutNonMatchingUpdateAllowList(Request $request): void + { + if (null === $request->getLockedRepository()) { + throw new \LogicException('No lock repo present and yet a partial update was requested.'); + } + + foreach ($this->updateAllowList as $pattern) { + $matchedPlatformPackage = false; + + $patternRegexp = BasePackage::packageNameToRegexp($pattern); + // update pattern matches a locked package? => all good + foreach ($request->getLockedRepository()->getPackages() as $package) { + if (Preg::isMatch($patternRegexp, $package->getName())) { + continue 2; + } + } + // update pattern matches a root require? => all good, probably a new package + foreach ($request->getRequires() as $packageName => $constraint) { + if (Preg::isMatch($patternRegexp, $packageName)) { + if (PlatformRepository::isPlatformPackage($packageName)) { + $matchedPlatformPackage = true; + continue; + } + continue 2; + } + } + if ($matchedPlatformPackage) { + $this->io->writeError('Pattern "' . $pattern . '" listed for update matches platform packages, but these cannot be updated by Composer.'); + } elseif (strpos($pattern, '*') !== false) { + $this->io->writeError('Pattern "' . $pattern . '" listed for update does not match any locked packages.'); + } else { + $this->io->writeError('Package "' . $pattern . '" listed for update is not locked.'); + } + } + } + + /** + * Reverts the decision to use a locked package if a partial update with transitive dependencies + * found that this package actually needs to be updated + * + * @param RepositoryInterface[] $repositories + */ + private function unlockPackage(Request $request, array $repositories, string $name): void + { + foreach ($this->skippedLoad[$name] as $packageOrReplacer) { + // if we unfixed a replaced package name, we also need to unfix the replacer itself + // as long as it was not unfixed yet + if ($packageOrReplacer->getName() !== $name && isset($this->skippedLoad[$packageOrReplacer->getName()])) { + $replacerName = $packageOrReplacer->getName(); + if ($request->getUpdateAllowTransitiveRootDependencies() || (!$this->isRootRequire($request, $name) && !$this->isRootRequire($request, $replacerName))) { + $this->unlockPackage($request, $repositories, $replacerName); + + if ($this->isRootRequire($request, $replacerName)) { + $this->markPackageNameForLoading($request, $replacerName, new MatchAllConstraint); + } else { + foreach ($this->packages as $loadedPackage) { + $requires = $loadedPackage->getRequires(); + if (isset($requires[$replacerName])) { + $this->markPackageNameForLoading($request, $replacerName, $requires[$replacerName]->getConstraint()); + } + } + } + } + } + } + + if (isset($this->pathRepoUnlocked[$name])) { + foreach ($this->packages as $index => $package) { + if ($package->getName() === $name) { + $this->removeLoadedPackage($request, $repositories, $package, $index); + } + } + } + + unset($this->skippedLoad[$name], $this->loadedPackages[$name], $this->maxExtendedReqs[$name], $this->pathRepoUnlocked[$name]); + + // remove locked package by this name which was already initialized + foreach ($request->getLockedPackages() as $lockedPackage) { + if (!($lockedPackage instanceof AliasPackage) && $lockedPackage->getName() === $name) { + if (false !== $index = array_search($lockedPackage, $this->packages, true)) { + $request->unlockPackage($lockedPackage); + $this->removeLoadedPackage($request, $repositories, $lockedPackage, $index); + + // make sure that any requirements for this package by other locked or fixed packages are now + // also loaded, as they were previously ignored because the locked (now unlocked) package already + // satisfied their requirements + // and if this package is replacing another that is required by a locked or fixed package, ensure + // that we load that replaced package in case an update to this package removes the replacement + foreach ($request->getFixedOrLockedPackages() as $fixedOrLockedPackage) { + if ($fixedOrLockedPackage === $lockedPackage) { + continue; + } + + if (isset($this->skippedLoad[$fixedOrLockedPackage->getName()])) { + $requires = $fixedOrLockedPackage->getRequires(); + if (isset($requires[$lockedPackage->getName()])) { + $this->markPackageNameForLoading($request, $lockedPackage->getName(), $requires[$lockedPackage->getName()]->getConstraint()); + } + + foreach ($lockedPackage->getReplaces() as $replace) { + if (isset($requires[$replace->getTarget()], $this->skippedLoad[$replace->getTarget()])) { + $this->unlockPackage($request, $repositories, $replace->getTarget()); + // this package is in $requires so no need to call markPackageNameForLoadingIfRequired + $this->markPackageNameForLoading($request, $replace->getTarget(), $replace->getConstraint()); + } + } + } + } + } + } + } + } + + private function markPackageNameForLoadingIfRequired(Request $request, string $name): void + { + if ($this->isRootRequire($request, $name)) { + $this->markPackageNameForLoading($request, $name, $request->getRequires()[$name]); + } + + foreach ($this->packages as $package) { + foreach ($package->getRequires() as $link) { + if ($name === $link->getTarget()) { + $this->markPackageNameForLoading($request, $link->getTarget(), $link->getConstraint()); + } + } + } + } + + /** + * @param RepositoryInterface[] $repositories + */ + private function removeLoadedPackage(Request $request, array $repositories, BasePackage $package, int $index): void + { + $repoIndex = array_search($package->getRepository(), $repositories, true); + + unset($this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()]); + unset($this->packages[$index]); + if (isset($this->aliasMap[spl_object_hash($package)])) { + foreach ($this->aliasMap[spl_object_hash($package)] as $aliasIndex => $aliasPackage) { + unset($this->loadedPerRepo[$repoIndex][$aliasPackage->getName()][$aliasPackage->getVersion()]); + unset($this->packages[$aliasIndex]); + } + unset($this->aliasMap[spl_object_hash($package)]); + } + } + + private function runOptimizer(Request $request, Pool $pool): Pool + { + if (null === $this->poolOptimizer) { + return $pool; + } + + $this->io->debug('Running pool optimizer.'); + + $before = microtime(true); + $total = \count($pool->getPackages()); + + $pool = $this->poolOptimizer->optimize($request, $pool); + + $filtered = $total - \count($pool->getPackages()); + + if (0 === $filtered) { + return $pool; + } + + $this->io->write(sprintf('Pool optimizer completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERY_VERBOSE); + $this->io->write(sprintf( + 'Found %s package versions referenced in your dependency graph. %s (%d%%) were optimized away.', + number_format($total), + number_format($filtered), + round(100 / $total * $filtered) + ), true, IOInterface::VERY_VERBOSE); + + return $pool; + } + + /** + * @param RepositoryInterface[] $repositories + */ + private function runSecurityAdvisoryFilter(Pool $pool, array $repositories, Request $request): Pool + { + if (null === $this->securityAdvisoryPoolFilter) { + return $pool; + } + + $this->io->debug('Running security advisory pool filter.'); + + $before = microtime(true); + $total = \count($pool->getPackages()); + + $pool = $this->securityAdvisoryPoolFilter->filter($pool, $repositories, $request); + + $filtered = $total - \count($pool->getPackages()); + + if (0 === $filtered) { + return $pool; + } + + $this->io->write(sprintf('Security advisory pool filter completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERY_VERBOSE); + $this->io->write(sprintf( + 'Found %s package versions referenced in your dependency graph. %s (%d%%) were filtered away.', + number_format($total), + number_format($filtered), + round(100 / $total * $filtered) + ), true, IOInterface::VERY_VERBOSE); + + return $pool; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php b/vendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php new file mode 100644 index 000000000..12005de0c --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php @@ -0,0 +1,475 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Version\VersionParser; +use Composer\Semver\CompilingMatcher; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Intervals; + +/** + * Optimizes a given pool + * + * @author Yanick Witschi + */ +class PoolOptimizer +{ + /** + * @var PolicyInterface + */ + private $policy; + + /** + * @var array + */ + private $irremovablePackages = []; + + /** + * @var array> + */ + private $requireConstraintsPerPackage = []; + + /** + * @var array> + */ + private $conflictConstraintsPerPackage = []; + + /** + * @var array + */ + private $packagesToRemove = []; + + /** + * @var array + */ + private $aliasesPerPackage = []; + + /** + * @var array> + */ + private $removedVersionsByPackage = []; + + public function __construct(PolicyInterface $policy) + { + $this->policy = $policy; + } + + public function optimize(Request $request, Pool $pool): Pool + { + $this->prepare($request, $pool); + + $this->optimizeByIdenticalDependencies($request, $pool); + + $this->optimizeImpossiblePackagesAway($request, $pool); + + $optimizedPool = $this->applyRemovalsToPool($pool); + + // No need to run this recursively at the moment + // because the current optimizations cannot provide + // even more gains when ran again. Might change + // in the future with additional optimizations. + + $this->irremovablePackages = []; + $this->requireConstraintsPerPackage = []; + $this->conflictConstraintsPerPackage = []; + $this->packagesToRemove = []; + $this->aliasesPerPackage = []; + $this->removedVersionsByPackage = []; + + return $optimizedPool; + } + + private function prepare(Request $request, Pool $pool): void + { + $irremovablePackageConstraintGroups = []; + + // Mark fixed or locked packages as irremovable + foreach ($request->getFixedOrLockedPackages() as $package) { + $irremovablePackageConstraintGroups[$package->getName()][] = new Constraint('==', $package->getVersion()); + } + + // Extract requested package requirements + foreach ($request->getRequires() as $require => $constraint) { + $this->extractRequireConstraintsPerPackage($require, $constraint); + } + + // First pass over all packages to extract information and mark package constraints irremovable + foreach ($pool->getPackages() as $package) { + // Extract package requirements + foreach ($package->getRequires() as $link) { + $this->extractRequireConstraintsPerPackage($link->getTarget(), $link->getConstraint()); + } + // Extract package conflicts + foreach ($package->getConflicts() as $link) { + $this->extractConflictConstraintsPerPackage($link->getTarget(), $link->getConstraint()); + } + + // Keep track of alias packages for every package so if either the alias or aliased is kept + // we keep the others as they are a unit of packages really + if ($package instanceof AliasPackage) { + $this->aliasesPerPackage[$package->getAliasOf()->id][] = $package; + } + } + + $irremovablePackageConstraints = []; + foreach ($irremovablePackageConstraintGroups as $packageName => $constraints) { + $irremovablePackageConstraints[$packageName] = 1 === \count($constraints) ? $constraints[0] : new MultiConstraint($constraints, false); + } + unset($irremovablePackageConstraintGroups); + + // Mark the packages as irremovable based on the constraints + foreach ($pool->getPackages() as $package) { + if (!isset($irremovablePackageConstraints[$package->getName()])) { + continue; + } + + if (CompilingMatcher::match($irremovablePackageConstraints[$package->getName()], Constraint::OP_EQ, $package->getVersion())) { + $this->markPackageIrremovable($package); + } + } + } + + private function markPackageIrremovable(BasePackage $package): void + { + $this->irremovablePackages[$package->id] = true; + if ($package instanceof AliasPackage) { + // recursing here so aliasesPerPackage for the aliasOf can be checked + // and all its aliases marked as irremovable as well + $this->markPackageIrremovable($package->getAliasOf()); + } + if (isset($this->aliasesPerPackage[$package->id])) { + foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) { + $this->irremovablePackages[$aliasPackage->id] = true; + } + } + } + + /** + * @return Pool Optimized pool + */ + private function applyRemovalsToPool(Pool $pool): Pool + { + $packages = []; + $removedVersions = []; + foreach ($pool->getPackages() as $package) { + if (!isset($this->packagesToRemove[$package->id])) { + $packages[] = $package; + } else { + $removedVersions[$package->getName()][$package->getVersion()] = $package->getPrettyVersion(); + } + } + + $optimizedPool = new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $removedVersions, $this->removedVersionsByPackage, $pool->getAllSecurityRemovedPackageVersions(), $pool->getAllAbandonedRemovedPackageVersions()); + + return $optimizedPool; + } + + private function optimizeByIdenticalDependencies(Request $request, Pool $pool): void + { + $identicalDefinitionsPerPackage = []; + $packageIdenticalDefinitionLookup = []; + + foreach ($pool->getPackages() as $package) { + + // If that package was already marked irremovable, we can skip + // the entire process for it + if (isset($this->irremovablePackages[$package->id])) { + continue; + } + + $this->markPackageForRemoval($package->id); + + $dependencyHash = $this->calculateDependencyHash($package); + + foreach ($package->getNames(false) as $packageName) { + if (!isset($this->requireConstraintsPerPackage[$packageName])) { + continue; + } + + foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) { + $groupHashParts = []; + + if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) { + $groupHashParts[] = 'require:' . (string) $requireConstraint; + } + + if (\count($package->getReplaces()) > 0) { + foreach ($package->getReplaces() as $link) { + if (CompilingMatcher::match($link->getConstraint(), Constraint::OP_EQ, $package->getVersion())) { + // Use the same hash part as the regular require hash because that's what the replacement does + $groupHashParts[] = 'require:' . (string) $link->getConstraint(); + } + } + } + + if (isset($this->conflictConstraintsPerPackage[$packageName])) { + foreach ($this->conflictConstraintsPerPackage[$packageName] as $conflictConstraint) { + if (CompilingMatcher::match($conflictConstraint, Constraint::OP_EQ, $package->getVersion())) { + $groupHashParts[] = 'conflict:' . (string) $conflictConstraint; + } + } + } + + if (0 === \count($groupHashParts)) { + continue; + } + + $groupHash = implode('', $groupHashParts); + $identicalDefinitionsPerPackage[$packageName][$groupHash][$dependencyHash][] = $package; + $packageIdenticalDefinitionLookup[$package->id][$packageName] = ['groupHash' => $groupHash, 'dependencyHash' => $dependencyHash]; + } + } + } + + foreach ($identicalDefinitionsPerPackage as $constraintGroups) { + foreach ($constraintGroups as $constraintGroup) { + foreach ($constraintGroup as $packages) { + // Only one package in this constraint group has the same requirements, we're not allowed to remove that package + if (1 === \count($packages)) { + $this->keepPackage($packages[0], $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); + continue; + } + + // Otherwise we find out which one is the preferred package in this constraint group which is + // then not allowed to be removed either + $literals = []; + + foreach ($packages as $package) { + $literals[] = $package->id; + } + + foreach ($this->policy->selectPreferredPackages($pool, $literals) as $preferredLiteral) { + $this->keepPackage($pool->literalToPackage($preferredLiteral), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); + } + } + } + } + } + + private function calculateDependencyHash(BasePackage $package): string + { + $hash = ''; + + $hashRelevantLinks = [ + 'requires' => $package->getRequires(), + 'conflicts' => $package->getConflicts(), + 'replaces' => $package->getReplaces(), + 'provides' => $package->getProvides(), + ]; + + foreach ($hashRelevantLinks as $key => $links) { + if (0 === \count($links)) { + continue; + } + + // start new hash section + $hash .= $key . ':'; + + $subhash = []; + + foreach ($links as $link) { + // To get the best dependency hash matches we should use Intervals::compactConstraint() here. + // However, the majority of projects are going to specify their constraints already pretty + // much in the best variant possible. In other words, we'd be wasting time here and it would actually hurt + // performance more than the additional few packages that could be filtered out would benefit the process. + $subhash[$link->getTarget()] = (string) $link->getConstraint(); + } + + // Sort for best result + ksort($subhash); + + foreach ($subhash as $target => $constraint) { + $hash .= $target . '@' . $constraint; + } + } + + return $hash; + } + + private function markPackageForRemoval(int $id): void + { + // We are not allowed to remove packages if they have been marked as irremovable + if (isset($this->irremovablePackages[$id])) { + throw new \LogicException('Attempted removing a package which was previously marked irremovable'); + } + + $this->packagesToRemove[$id] = true; + } + + /** + * @param array>>> $identicalDefinitionsPerPackage + * @param array> $packageIdenticalDefinitionLookup + */ + private function keepPackage(BasePackage $package, array $identicalDefinitionsPerPackage, array $packageIdenticalDefinitionLookup): void + { + // Already marked to keep + if (!isset($this->packagesToRemove[$package->id])) { + return; + } + + unset($this->packagesToRemove[$package->id]); + + if ($package instanceof AliasPackage) { + // recursing here so aliasesPerPackage for the aliasOf can be checked + // and all its aliases marked to be kept as well + $this->keepPackage($package->getAliasOf(), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); + } + + // record all the versions of the package group so we can list them later in Problem output + foreach ($package->getNames(false) as $name) { + if (isset($packageIdenticalDefinitionLookup[$package->id][$name])) { + $packageGroupPointers = $packageIdenticalDefinitionLookup[$package->id][$name]; + $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']]; + foreach ($packageGroup as $pkg) { + if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $pkg = $pkg->getAliasOf(); + } + $this->removedVersionsByPackage[spl_object_hash($package)][$pkg->getVersion()] = $pkg->getPrettyVersion(); + } + } + } + + if (isset($this->aliasesPerPackage[$package->id])) { + foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) { + unset($this->packagesToRemove[$aliasPackage->id]); + + // record all the versions of the package group so we can list them later in Problem output + foreach ($aliasPackage->getNames(false) as $name) { + if (isset($packageIdenticalDefinitionLookup[$aliasPackage->id][$name])) { + $packageGroupPointers = $packageIdenticalDefinitionLookup[$aliasPackage->id][$name]; + $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']]; + foreach ($packageGroup as $pkg) { + if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $pkg = $pkg->getAliasOf(); + } + $this->removedVersionsByPackage[spl_object_hash($aliasPackage)][$pkg->getVersion()] = $pkg->getPrettyVersion(); + } + } + } + } + } + } + + /** + * Use the list of locked packages to constrain the loaded packages + * This will reduce packages with significant numbers of historical versions to a smaller number + * and reduce the resulting rule set that is generated + */ + private function optimizeImpossiblePackagesAway(Request $request, Pool $pool): void + { + if (\count($request->getLockedPackages()) === 0) { + return; + } + + $packageIndex = []; + + foreach ($pool->getPackages() as $package) { + $id = $package->id; + + // Do not remove irremovable packages + if (isset($this->irremovablePackages[$id])) { + continue; + } + // Do not remove a package aliased by another package, nor aliases + if (isset($this->aliasesPerPackage[$id]) || $package instanceof AliasPackage) { + continue; + } + // Do not remove locked packages + if ($request->isFixedPackage($package) || $request->isLockedPackage($package)) { + continue; + } + + $packageIndex[$package->getName()][$package->id] = $package; + } + + foreach ($request->getLockedPackages() as $package) { + // If this locked package is no longer required by root or anything in the pool, it may get uninstalled so do not apply its requirements + // In a case where a requirement WERE to appear in the pool by a package that would not be used, it would've been unlocked and so not filtered still + $isUnusedPackage = true; + foreach ($package->getNames(false) as $packageName) { + if (isset($this->requireConstraintsPerPackage[$packageName])) { + $isUnusedPackage = false; + break; + } + } + + if ($isUnusedPackage) { + continue; + } + + foreach ($package->getRequires() as $link) { + $require = $link->getTarget(); + if (!isset($packageIndex[$require])) { + continue; + } + + $linkConstraint = $link->getConstraint(); + foreach ($packageIndex[$require] as $id => $requiredPkg) { + if (false === CompilingMatcher::match($linkConstraint, Constraint::OP_EQ, $requiredPkg->getVersion())) { + $this->markPackageForRemoval($id); + unset($packageIndex[$require][$id]); + } + } + } + } + } + + /** + * Disjunctive require constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate + * two require constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd + * only keep either one which can cause trouble (e.g. when using --prefer-lowest). + * + * @return void + */ + private function extractRequireConstraintsPerPackage(string $package, ConstraintInterface $constraint) + { + foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) { + $this->requireConstraintsPerPackage[$package][(string) $expanded] = $expanded; + } + } + + /** + * Disjunctive conflict constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate + * two conflict constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd + * only keep either one which can cause trouble (e.g. when using --prefer-lowest). + * + * @return void + */ + private function extractConflictConstraintsPerPackage(string $package, ConstraintInterface $constraint) + { + foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) { + $this->conflictConstraintsPerPackage[$package][(string) $expanded] = $expanded; + } + } + + /** + * @return ConstraintInterface[] + */ + private function expandDisjunctiveMultiConstraints(ConstraintInterface $constraint) + { + $constraint = Intervals::compactConstraint($constraint); + + if ($constraint instanceof MultiConstraint && $constraint->isDisjunctive()) { + // No need to call ourselves recursively here because Intervals::compactConstraint() ensures that there + // are no nested disjunctive MultiConstraint instances possible + return $constraint->getConstraints(); + } + + // Regular constraints and conjunctive MultiConstraints + return [$constraint]; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Problem.php b/vendor/composer/composer/src/Composer/DependencyResolver/Problem.php new file mode 100644 index 000000000..7651b0260 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Problem.php @@ -0,0 +1,705 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\Advisory\SecurityAdvisory; +use Composer\Package\CompletePackageInterface; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Link; +use Composer\Package\PackageInterface; +use Composer\Package\RootPackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\RepositorySet; +use Composer\Repository\LockArrayRepository; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Package\Version\VersionParser; +use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\MultiConstraint; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Represents a problem detected while solving dependencies + * + * @author Nils Adermann + */ +class Problem +{ + /** + * A map containing the id of each rule part of this problem as a key + * @var array + */ + protected $reasonSeen; + + /** + * A set of reasons for the problem, each is a rule or a root require and a rule + * @var array> + */ + protected $reasons = []; + + /** @var int */ + protected $section = 0; + + /** + * Add a rule as a reason + * + * @param Rule $rule A rule which is a reason for this problem + */ + public function addRule(Rule $rule): void + { + $this->addReason(spl_object_hash($rule), $rule); + } + + /** + * Retrieve all reasons for this problem + * + * @return array> The problem's reasons + */ + public function getReasons(): array + { + return $this->reasons; + } + + /** + * A human readable textual representation of the problem's reasons + * + * @param array $installedMap A map of all present packages + * @param array $learnedPool + */ + public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string + { + // TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections? + $reasons = array_merge(...array_reverse($this->reasons)); + + if (\count($reasons) === 1) { + reset($reasons); + $rule = current($reasons); + + if ($rule->getReason() !== Rule::RULE_ROOT_REQUIRE) { + throw new \LogicException("Single reason problems must contain a root require rule."); + } + + $reasonData = $rule->getReasonData(); + $packageName = $reasonData['packageName']; + $constraint = $reasonData['constraint']; + + $packages = $pool->whatProvides($packageName, $constraint); + if (\count($packages) === 0) { + return "\n ".implode(self::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $packageName, $constraint)); + } + } + + usort($reasons, function (Rule $rule1, Rule $rule2) use ($pool) { + $rule1Prio = $this->getRulePriority($rule1); + $rule2Prio = $this->getRulePriority($rule2); + if ($rule1Prio !== $rule2Prio) { + return $rule2Prio - $rule1Prio; + } + + return $this->getSortableString($pool, $rule1) <=> $this->getSortableString($pool, $rule2); + }); + + return self::formatDeduplicatedRules($reasons, ' ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); + } + + private function getSortableString(Pool $pool, Rule $rule): string + { + switch ($rule->getReason()) { + case Rule::RULE_ROOT_REQUIRE: + return $rule->getReasonData()['packageName']; + case Rule::RULE_FIXED: + return (string) $rule->getReasonData()['package']; + case Rule::RULE_PACKAGE_CONFLICT: + case Rule::RULE_PACKAGE_REQUIRES: + return $rule->getSourcePackage($pool) . '//' . $rule->getReasonData()->getPrettyString($rule->getSourcePackage($pool)); + case Rule::RULE_PACKAGE_SAME_NAME: + case Rule::RULE_PACKAGE_ALIAS: + case Rule::RULE_PACKAGE_INVERSE_ALIAS: + return (string) $rule->getReasonData(); + case Rule::RULE_LEARNED: + return implode('-', $rule->getLiterals()); + } + + // @phpstan-ignore deadCode.unreachable + throw new \LogicException('Unknown rule type: '.$rule->getReason()); + } + + private function getRulePriority(Rule $rule): int + { + switch ($rule->getReason()) { + case Rule::RULE_FIXED: + return 3; + case Rule::RULE_ROOT_REQUIRE: + return 2; + case Rule::RULE_PACKAGE_CONFLICT: + case Rule::RULE_PACKAGE_REQUIRES: + return 1; + case Rule::RULE_PACKAGE_SAME_NAME: + case Rule::RULE_LEARNED: + case Rule::RULE_PACKAGE_ALIAS: + case Rule::RULE_PACKAGE_INVERSE_ALIAS: + return 0; + } + + // @phpstan-ignore deadCode.unreachable + throw new \LogicException('Unknown rule type: '.$rule->getReason()); + } + + /** + * @param Rule[] $rules + * @param array $installedMap A map of all present packages + * @param array $learnedPool + * @internal + */ + public static function formatDeduplicatedRules(array $rules, string $indent, RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string + { + $messages = []; + $templates = []; + $parser = new VersionParser; + $deduplicatableRuleTypes = [Rule::RULE_PACKAGE_REQUIRES, Rule::RULE_PACKAGE_CONFLICT]; + foreach ($rules as $rule) { + $message = $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); + if (in_array($rule->getReason(), $deduplicatableRuleTypes, true) && Preg::isMatchStrictGroups('{^(?P\S+) (?P\S+) (?Prequires|conflicts)}', $message, $m)) { + $message = str_replace('%', '%%', $message); + $template = Preg::replace('{^\S+ \S+ }', '%s%s ', $message); + $messages[] = $template; + $templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2]; + $sourcePackage = $rule->getSourcePackage($pool); + foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($sourcePackage)) as $version => $prettyVersion) { + $templates[$template][$m[1]][$version] = $prettyVersion; + } + } elseif ($message !== '') { + $messages[] = $message; + } + } + + $result = []; + foreach (array_unique($messages) as $message) { + if (isset($templates[$message])) { + foreach ($templates[$message] as $package => $versions) { + uksort($versions, 'version_compare'); + if (!$isVerbose) { + $versions = self::condenseVersionList($versions, 1); + } + if (\count($versions) > 1) { + // remove the s from requires/conflicts to correct grammar + $message = Preg::replace('{^(%s%s (?:require|conflict))s}', '$1', $message); + $result[] = sprintf($message, $package, '['.implode(', ', $versions).']'); + } else { + $result[] = sprintf($message, $package, ' '.reset($versions)); + } + } + } else { + $result[] = $message; + } + } + + return "\n$indent- ".implode("\n$indent- ", $result); + } + + public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool + { + foreach ($this->reasons as $sectionRules) { + foreach ($sectionRules as $rule) { + if ($rule->isCausedByLock($repositorySet, $request, $pool)) { + return true; + } + } + } + + return false; + } + + /** + * Store a reason descriptor but ignore duplicates + * + * @param string $id A canonical identifier for the reason + * @param Rule $reason The reason descriptor + */ + protected function addReason(string $id, Rule $reason): void + { + // TODO: if a rule is part of a problem description in two sections, isn't this going to remove a message + // that is important to understand the issue? + + if (!isset($this->reasonSeen[$id])) { + $this->reasonSeen[$id] = true; + $this->reasons[$this->section][] = $reason; + } + } + + public function nextSection(): void + { + $this->section++; + } + + /** + * @internal + * @return array{0: string, 1: string} + */ + public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, string $packageName, ?ConstraintInterface $constraint = null): array + { + if (PlatformRepository::isPlatformPackage($packageName)) { + // handle php/php-*/hhvm + if (0 === stripos($packageName, 'php') || $packageName === 'hhvm') { + $version = self::getPlatformPackageVersion($pool, $packageName, phpversion()); + + $msg = "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but '; + + if (defined('HHVM_VERSION') || ($packageName === 'hhvm' && count($pool->whatProvides($packageName)) > 0)) { + return [$msg, 'your HHVM version does not satisfy that requirement.']; + } + + if ($packageName === 'hhvm') { + return [$msg, 'HHVM was not detected on this machine, make sure it is in your PATH.']; + } + + if (null === $version) { + return [$msg, 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".']; + } + + return [$msg, 'your '.$packageName.' version ('. $version .') does not satisfy that requirement.']; + } + + // handle php extensions + if (0 === stripos($packageName, 'ext-')) { + if (false !== strpos($packageName, ' ')) { + return ['- ', "PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.']; + } + + $ext = substr($packageName, 4); + $msg = "- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but '; + + $version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) === false ? '0' : phpversion($ext)); + if (null === $version) { + $providersStr = self::getProvidersList($repositorySet, $packageName, 5); + if ($providersStr !== null) { + $providersStr = "\n\n Alternatively you can require one of these packages that provide the extension (or parts of it):\n". + " Keep in mind that the suggestions are automated and may not be valid or safe to use\n$providersStr"; + } + + if (extension_loaded($ext)) { + return [ + $msg, + 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".' . $providersStr, + ]; + } + + return [$msg, 'it is missing from your system. Install or enable PHP\'s '.$ext.' extension.' . $providersStr]; + } + + return [$msg, 'it has the wrong version installed ('.$version.').']; + } + + // handle linked libs + if (0 === stripos($packageName, 'lib-')) { + if (strtolower($packageName) === 'lib-icu') { + $error = extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.'; + + return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error]; + } + + $providersStr = self::getProvidersList($repositorySet, $packageName, 5); + if ($providersStr !== null) { + $providersStr = "\n\n Alternatively you can require one of these packages that provide the library (or parts of it):\n". + " Keep in mind that the suggestions are automated and may not be valid or safe to use\n$providersStr"; + } + + return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.'.$providersStr]; + } + } + + $lockedPackage = null; + foreach ($request->getLockedPackages() as $package) { + if ($package->getName() === $packageName) { + $lockedPackage = $package; + if ($pool->isUnacceptableFixedOrLockedPackage($package)) { + return ["- ", $package->getPrettyName().' is fixed to '.$package->getPrettyVersion().' (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you list it as an argument for the update command.']; + } + break; + } + } + + if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && Preg::isMatch('{^dev-.*#.*}', $constraint->getPrettyString())) { + $newConstraint = Preg::replace('{ +as +([^,\s|]+)$}', '', $constraint->getPrettyString()); + $packages = $repositorySet->findPackages($packageName, new MultiConstraint([ + new Constraint(Constraint::STR_OP_EQ, $newConstraint), + new Constraint(Constraint::STR_OP_EQ, str_replace('#', '+', $newConstraint)), + ], false)); + if (\count($packages) > 0) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).'. The # character in branch names is replaced by a + character. Make sure to require it as "'.str_replace('#', '+', $constraint->getPrettyString()).'".']; + } + } + + // first check if the actual requested package is found in normal conditions + // if so it must mean it is rejected by another constraint than the one given here + $packages = $repositorySet->findPackages($packageName, $constraint); + if (\count($packages) > 0) { + $rootReqs = $repositorySet->getRootRequires(); + if (isset($rootReqs[$packageName])) { + $filtered = array_filter($packages, static function ($p) use ($rootReqs, $packageName): bool { + return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion())); + }); + if (0 === count($filtered)) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').']; + } + } + + $tempReqs = $repositorySet->getTemporaryConstraints(); + foreach (reset($packages)->getNames() as $name) { + if (isset($tempReqs[$name])) { + $filtered = array_filter($packages, static function ($p) use ($tempReqs, $name): bool { + return $tempReqs[$name]->matches(new Constraint('==', $p->getVersion())); + }); + if (0 === count($filtered)) { + return ["- Root composer.json requires $name".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your temporary update constraint ('.$name.':'.$tempReqs[$name]->getPrettyString().').']; + } + } + } + + if ($lockedPackage !== null) { + $fixedConstraint = new Constraint('==', $lockedPackage->getVersion()); + $filtered = array_filter($packages, static function ($p) use ($fixedConstraint): bool { + return $fixedConstraint->matches(new Constraint('==', $p->getVersion())); + }); + if (0 === count($filtered)) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but the package is fixed to '.$lockedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.']; + } + } + + $nonLockedPackages = array_filter($packages, static function ($p): bool { + return !$p->getRepository() instanceof LockArrayRepository; + }); + + if (0 === \count($nonLockedPackages)) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.']; + } + + if ($pool->isAbandonedRemovedPackageVersion($packageName, $constraint)) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, because they are abandoned and you configured "block-abandoned" to true in your "audit" config.']; + } + + if ($pool->isSecurityRemovedPackageVersion($packageName, $constraint)) { + $advisories = $repositorySet->getMatchingSecurityAdvisories($packages, false, true); + if (isset($advisories['advisories'][$packageName]) && \count($advisories['advisories'][$packageName]) > 0) { + $advisoriesList = array_map(static function (SecurityAdvisory $advisory): string { + if ($advisory->link !== null && $advisory->link !== '') { + return 'link).'>'.$advisory->advisoryId.''; + } + + if (str_starts_with($advisory->advisoryId, 'PKSA-')) { + return 'advisoryId).'>'.$advisory->advisoryId.''; + } + + return $advisory->advisoryId; + }, $advisories['advisories'][$packageName]); + } else { + $advisoriesList = array_map(static function (string $advisoryId): string { + if (str_starts_with($advisoryId, 'PKSA-')) { + return ''.$advisoryId.''; + } + + return $advisoryId; + }, $pool->getSecurityAdvisoryIdentifiersForPackageVersion($packageName, $constraint)); + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, because they are affected by security advisories ("' . implode('", "', $advisoriesList). '"). Go to https://packagist.org/security-advisories/ to find advisory details. To ignore the advisories, add them to the audit "ignore" config. To turn the feature off entirely, you can set "block-insecure" to false in your "audit" config.']; + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.']; + } + + // check if the package is found when bypassing stability checks + $packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); + if (\count($packages) > 0) { + // we must first verify if a valid package would be found in a lower priority repository + $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES); + if (\count($allReposPackages) > 0) { + return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint); + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.']; + } + + // check if the package is found when bypassing the constraint and stability checks + $packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); + if (\count($packages) > 0) { + // we must first verify if a valid package would be found in a lower priority repository + $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES); + if (\count($allReposPackages) > 0) { + return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint); + } + + $suffix = ''; + if ($constraint instanceof Constraint && $constraint->getVersion() === 'dev-master') { + foreach ($packages as $candidate) { + if (in_array($candidate->getVersion(), ['dev-default', 'dev-main'], true)) { + $suffix = ' Perhaps dev-master was renamed to '.$candidate->getPrettyVersion().'?'; + break; + } + } + } + + // check if the root package is a name match and hint the dependencies on root troubleshooting article + $allReposPackages = $packages; + $topPackage = reset($allReposPackages); + if ($topPackage instanceof RootPackageInterface) { + $suffix = ' See https://getcomposer.org/dep-on-root for details and assistance.'; + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.' . $suffix]; + } + + if (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $packageName)) { + $illegalChars = Preg::replace('{[A-Za-z0-9_./-]+}', '', $packageName); + + return ["- Root composer.json requires $packageName, it ", 'could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.']; + } + + $providersStr = self::getProvidersList($repositorySet, $packageName, 15); + if ($providersStr !== null) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it:\n".$providersStr." Consider requiring one of these to satisfy the $packageName requirement."]; + } + + return ["- Root composer.json requires $packageName, it ", "could not be found in any version, there may be a typo in the package name."]; + } + + /** + * @internal + * @param PackageInterface[] $packages + */ + public static function getPackageList(array $packages, bool $isVerbose, ?Pool $pool = null, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string + { + $prepared = []; + $hasDefaultBranch = []; + foreach ($packages as $package) { + $prepared[$package->getName()]['name'] = $package->getPrettyName(); + $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : ''); + if ($pool !== null && $constraint !== null) { + foreach ($pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) { + $prepared[$package->getName()]['versions'][$version] = $prettyVersion; + } + } + if ($pool !== null && $useRemovedVersionGroup) { + foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($package)) as $version => $prettyVersion) { + $prepared[$package->getName()]['versions'][$version] = $prettyVersion; + } + } + if ($package->isDefaultBranch()) { + $hasDefaultBranch[$package->getName()] = true; + } + } + + $preparedStrings = []; + foreach ($prepared as $name => $package) { + // remove the implicit default branch alias to avoid cruft in the display + if (isset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS], $hasDefaultBranch[$name])) { + unset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS]); + } + + uksort($package['versions'], 'version_compare'); + + if (!$isVerbose) { + $package['versions'] = self::condenseVersionList($package['versions'], 4); + } + $preparedStrings[] = $package['name'].'['.implode(', ', $package['versions']).']'; + } + + return implode(', ', $preparedStrings); + } + + /** + * @param string $version the effective runtime version of the platform package + * @return ?string a version string or null if it appears the package was artificially disabled + */ + private static function getPlatformPackageVersion(Pool $pool, string $packageName, string $version): ?string + { + $available = $pool->whatProvides($packageName); + + if (\count($available) > 0) { + $selected = null; + foreach ($available as $pkg) { + if ($pkg->getRepository() instanceof PlatformRepository) { + $selected = $pkg; + break; + } + } + if ($selected === null) { + $selected = reset($available); + } + + // must be a package providing/replacing and not a real platform package + if ($selected->getName() !== $packageName) { + /** @var Link $link */ + foreach (array_merge(array_values($selected->getProvides()), array_values($selected->getReplaces())) as $link) { + if ($link->getTarget() === $packageName) { + return $link->getPrettyConstraint().' '.substr($link->getDescription(), 0, -1).'d by '.$selected->getPrettyString(); + } + } + } + + $version = $selected->getPrettyVersion(); + $extra = $selected->getExtra(); + if ($selected instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) { + $version .= '; ' . str_replace('Package ', '', (string) $selected->getDescription()); + } + } else { + return null; + } + + return $version; + } + + /** + * @param array $versions an array of pretty versions, with normalized versions as keys + * @return list a list of pretty versions and '...' where versions were removed + */ + private static function condenseVersionList(array $versions, int $max, int $maxDev = 16): array + { + if (count($versions) <= $max) { + return array_values($versions); + } + + $filtered = []; + $byMajor = []; + foreach ($versions as $version => $pretty) { + if (0 === stripos((string) $version, 'dev-')) { + $byMajor['dev'][] = $pretty; + } else { + $byMajor[Preg::replace('{^(\d+)\..*}', '$1', (string) $version)][] = $pretty; + } + } + foreach ($byMajor as $majorVersion => $versionsForMajor) { + $maxVersions = $majorVersion === 'dev' ? $maxDev : $max; + if (count($versionsForMajor) > $maxVersions) { + // output only 1st and last versions + $filtered[] = $versionsForMajor[0]; + $filtered[] = '...'; + $filtered[] = $versionsForMajor[count($versionsForMajor) - 1]; + } else { + $filtered = array_merge($filtered, $versionsForMajor); + } + } + + return $filtered; + } + + /** + * @param PackageInterface[] $packages + */ + private static function hasMultipleNames(array $packages): bool + { + $name = null; + foreach ($packages as $package) { + if ($name === null || $name === $package->getName()) { + $name = $package->getName(); + } else { + return true; + } + } + + return false; + } + + /** + * @param non-empty-array $higherRepoPackages + * @param non-empty-array $allReposPackages + * @return array{0: string, 1: string} + */ + private static function computeCheckForLowerPrioRepo(Pool $pool, bool $isVerbose, string $packageName, array $higherRepoPackages, array $allReposPackages, string $reason, ?ConstraintInterface $constraint = null): array + { + $nextRepoPackages = []; + $nextRepo = null; + + foreach ($allReposPackages as $package) { + if ($nextRepo === null || $nextRepo === $package->getRepository()) { + $nextRepoPackages[] = $package; + $nextRepo = $package->getRepository(); + } else { + break; + } + } + + assert(null !== $nextRepo); + + if (\count($higherRepoPackages) > 0) { + $topPackage = reset($higherRepoPackages); + if ($topPackage instanceof RootPackageInterface) { + return [ + "- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ', + 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' '.$topPackage->getPrettyVersion().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.', + ]; + } + } + + if ($nextRepo instanceof LockArrayRepository) { + $singular = count($higherRepoPackages) === 1; + + $suggestion = 'Make sure you either fix the '.$reason.' or avoid updating this package to keep the one present in the lock file ('.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).').'; + // symlinked path repos cannot be locked so do not suggest keeping it locked + if ($nextRepoPackages[0]->getDistType() === 'path') { + $transportOptions = $nextRepoPackages[0]->getTransportOptions(); + if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) { + $suggestion = 'Make sure you fix the '.$reason.' as packages installed from symlinked path repos are updated even in partial updates and the one from the lock file can thus not be used.'; + } + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', + 'found ' . self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. '.$suggestion, + ]; + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages from the higher priority repository do not match your '.$reason.' and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.']; + } + + /** + * Turns a constraint into text usable in a sentence describing a request + */ + protected static function constraintToText(?ConstraintInterface $constraint = null): string + { + if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && !str_starts_with($constraint->getVersion(), 'dev-')) { + if (!Preg::isMatch('{^\d+(?:\.\d+)*$}', $constraint->getPrettyString())) { + return ' '.$constraint->getPrettyString() .' (exact version match)'; + } + + $versions = [$constraint->getPrettyString()]; + for ($i = 3 - substr_count($versions[0], '.'); $i > 0; $i--) { + $versions[] = end($versions) . '.0'; + } + + return ' ' . $constraint->getPrettyString() . ' (exact version match: ' . (count($versions) > 1 ? implode(', ', array_slice($versions, 0, -1)) . ' or ' . end($versions) : $versions[0]) . ')'; + } + + return $constraint !== null ? ' '.$constraint->getPrettyString() : ''; + } + + private static function getProvidersList(RepositorySet $repositorySet, string $packageName, int $maxProviders): ?string + { + $providers = $repositorySet->getProviders($packageName); + if (\count($providers) > 0) { + $providersStr = implode(array_map(static function ($p): string { + $description = $p['description'] !== '' && $p['description'] !== null ? ' '.substr($p['description'], 0, 100) : ''; + + return ' - '.$p['name'].$description."\n"; + }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers)); + if (count($providers) > $maxProviders + 1) { + $providersStr .= ' ... and '.(count($providers) - $maxProviders).' more.'."\n"; + } + + return $providersStr; + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Request.php b/vendor/composer/composer/src/Composer/DependencyResolver/Request.php new file mode 100644 index 000000000..64a07777e --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Request.php @@ -0,0 +1,253 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Repository\LockArrayRepository; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; + +/** + * @author Nils Adermann + */ +class Request +{ + /** + * Identifies a partial update for listed packages only, all dependencies will remain at locked versions + */ + public const UPDATE_ONLY_LISTED = 0; + + /** + * Identifies a partial update for listed packages and recursively all their dependencies, however dependencies + * also directly required by the root composer.json and their dependencies will remain at the locked version. + */ + public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE = 1; + + /** + * Identifies a partial update for listed packages and recursively all their dependencies, even dependencies + * also directly required by the root composer.json will be updated. + */ + public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS = 2; + + /** @var ?LockArrayRepository */ + protected $lockedRepository; + /** @var array */ + protected $requires = []; + /** @var array */ + protected $fixedPackages = []; + /** @var array */ + protected $lockedPackages = []; + /** @var array */ + protected $fixedLockedPackages = []; + /** @var array */ + protected $updateAllowList = []; + /** @var false|self::UPDATE_* */ + protected $updateAllowTransitiveDependencies = false; + /** @var non-empty-list|null */ + private $restrictedPackages = null; + + public function __construct(?LockArrayRepository $lockedRepository = null) + { + $this->lockedRepository = $lockedRepository; + } + + public function requireName(string $packageName, ?ConstraintInterface $constraint = null): void + { + $packageName = strtolower($packageName); + + if ($constraint === null) { + $constraint = new MatchAllConstraint(); + } + if (isset($this->requires[$packageName])) { + throw new \LogicException('Overwriting requires seems like a bug ('.$packageName.' '.$this->requires[$packageName]->getPrettyString().' => '.$constraint->getPrettyString().', check why it is happening, might be a root alias'); + } + $this->requires[$packageName] = $constraint; + } + + /** + * Mark a package as currently present and having to remain installed + * + * This is used for platform packages which cannot be modified by Composer. A rule enforcing their installation is + * generated for dependency resolution. Partial updates with dependencies cannot in any way modify these packages. + */ + public function fixPackage(BasePackage $package): void + { + $this->fixedPackages[spl_object_hash($package)] = $package; + } + + /** + * Mark a package as locked to a specific version but removable + * + * This is used for lock file packages which need to be treated similar to fixed packages by the pool builder in + * that by default they should really only have the currently present version loaded and no remote alternatives. + * + * However unlike fixed packages there will not be a special rule enforcing their installation for the solver, so + * if nothing requires these packages they will be removed. Additionally in a partial update these packages can be + * unlocked, meaning other versions can be installed if explicitly requested as part of the update. + */ + public function lockPackage(BasePackage $package): void + { + $this->lockedPackages[spl_object_hash($package)] = $package; + } + + /** + * Marks a locked package fixed. So it's treated irremovable like a platform package. + * + * This is necessary for the composer install step which verifies the lock file integrity and should not allow + * removal of any packages. At the same time lock packages there cannot simply be marked fixed, as error reporting + * would then report them as platform packages, so this still marks them as locked packages at the same time. + */ + public function fixLockedPackage(BasePackage $package): void + { + $this->fixedPackages[spl_object_hash($package)] = $package; + $this->fixedLockedPackages[spl_object_hash($package)] = $package; + } + + public function unlockPackage(BasePackage $package): void + { + unset($this->lockedPackages[spl_object_hash($package)]); + } + + /** + * @param array $updateAllowList + * @param false|self::UPDATE_* $updateAllowTransitiveDependencies + */ + public function setUpdateAllowList(array $updateAllowList, $updateAllowTransitiveDependencies): void + { + $this->updateAllowList = $updateAllowList; + $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies; + } + + /** + * @return array + */ + public function getUpdateAllowList(): array + { + return $this->updateAllowList; + } + + public function getUpdateAllowTransitiveDependencies(): bool + { + return $this->updateAllowTransitiveDependencies !== self::UPDATE_ONLY_LISTED; + } + + public function getUpdateAllowTransitiveRootDependencies(): bool + { + return $this->updateAllowTransitiveDependencies === self::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; + } + + /** + * @return array + */ + public function getRequires(): array + { + return $this->requires; + } + + /** + * @return array + */ + public function getFixedPackages(): array + { + return $this->fixedPackages; + } + + public function isFixedPackage(BasePackage $package): bool + { + return isset($this->fixedPackages[spl_object_hash($package)]); + } + + /** + * @return array + */ + public function getLockedPackages(): array + { + return $this->lockedPackages; + } + + public function isLockedPackage(PackageInterface $package): bool + { + return isset($this->lockedPackages[spl_object_hash($package)]) || isset($this->fixedLockedPackages[spl_object_hash($package)]); + } + + /** + * @return array + */ + public function getFixedOrLockedPackages(): array + { + return array_merge($this->fixedPackages, $this->lockedPackages); + } + + /** + * @return ($packageIds is true ? array : array) + * + * @TODO look into removing the packageIds option, the only place true is used + * is for the installed map in the solver problems. + * Some locked packages may not be in the pool, + * so they have a package->id of -1 + */ + public function getPresentMap(bool $packageIds = false): array + { + $presentMap = []; + + if ($this->lockedRepository !== null) { + foreach ($this->lockedRepository->getPackages() as $package) { + $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package; + } + } + + foreach ($this->fixedPackages as $package) { + $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package; + } + + return $presentMap; + } + + /** + * @return array + */ + public function getFixedPackagesMap(): array + { + $fixedPackagesMap = []; + + foreach ($this->fixedPackages as $package) { + $fixedPackagesMap[$package->getId()] = $package; + } + + return $fixedPackagesMap; + } + + public function getLockedRepository(): ?LockArrayRepository + { + return $this->lockedRepository; + } + + /** + * Restricts the pool builder from loading other packages than those listed here + * + * @param non-empty-list $names + */ + public function restrictPackages(array $names): void + { + $this->restrictedPackages = $names; + } + + /** + * @return list + */ + public function getRestrictedPackages(): ?array + { + return $this->restrictedPackages; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Rule.php b/vendor/composer/composer/src/Composer/DependencyResolver/Rule.php new file mode 100644 index 000000000..8dde02b37 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Rule.php @@ -0,0 +1,462 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Link; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositorySet; +use Composer\Package\Version\VersionParser; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * @author Nils Adermann + * @author Ruben Gonzalez + * @phpstan-type ReasonData Link|BasePackage|string|int|array{packageName: string, constraint: ConstraintInterface}|array{package: BasePackage} + */ +abstract class Rule +{ + // reason constants and // their reason data contents + public const RULE_ROOT_REQUIRE = 2; // array{packageName: string, constraint: ConstraintInterface} + public const RULE_FIXED = 3; // array{package: BasePackage} + public const RULE_PACKAGE_CONFLICT = 6; // Link + public const RULE_PACKAGE_REQUIRES = 7; // Link + public const RULE_PACKAGE_SAME_NAME = 10; // string (package name) + public const RULE_LEARNED = 12; // int (rule id) + public const RULE_PACKAGE_ALIAS = 13; // BasePackage + public const RULE_PACKAGE_INVERSE_ALIAS = 14; // BasePackage + + // bitfield defs + private const BITFIELD_TYPE = 0; + private const BITFIELD_REASON = 8; + private const BITFIELD_DISABLED = 16; + + /** @var int */ + protected $bitfield; + /** @var Request */ + protected $request; + /** + * @var Link|BasePackage|ConstraintInterface|string + * @phpstan-var ReasonData + */ + protected $reasonData; + + /** + * @param self::RULE_* $reason A RULE_* constant describing the reason for generating this rule + * @param mixed $reasonData + * + * @phpstan-param ReasonData $reasonData + */ + public function __construct($reason, $reasonData) + { + $this->reasonData = $reasonData; + + $this->bitfield = (0 << self::BITFIELD_DISABLED) | + ($reason << self::BITFIELD_REASON) | + (255 << self::BITFIELD_TYPE); + } + + /** + * @return list + */ + abstract public function getLiterals(): array; + + /** + * @return int|string + */ + abstract public function getHash(); + + abstract public function __toString(): string; + + abstract public function equals(Rule $rule): bool; + + /** + * @return self::RULE_* + */ + public function getReason(): int + { + return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON; + } + + /** + * @phpstan-return ReasonData + */ + public function getReasonData() + { + return $this->reasonData; + } + + public function getRequiredPackage(): ?string + { + switch ($this->getReason()) { + case self::RULE_ROOT_REQUIRE: + return $this->getReasonData()['packageName']; + case self::RULE_FIXED: + return $this->getReasonData()['package']->getName(); + case self::RULE_PACKAGE_REQUIRES: + return $this->getReasonData()->getTarget(); + } + + return null; + } + + /** + * @param RuleSet::TYPE_* $type + */ + public function setType($type): void + { + $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE); + } + + public function getType(): int + { + return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE; + } + + public function disable(): void + { + $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED); + } + + public function enable(): void + { + $this->bitfield &= ~(255 << self::BITFIELD_DISABLED); + } + + public function isDisabled(): bool + { + return 0 !== (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); + } + + public function isEnabled(): bool + { + return 0 === (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); + } + + abstract public function isAssertion(): bool; + + public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool + { + if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) { + if (PlatformRepository::isPlatformPackage($this->getReasonData()->getTarget())) { + return false; + } + if ($request->getLockedRepository() !== null) { + foreach ($request->getLockedRepository()->getPackages() as $package) { + if ($package->getName() === $this->getReasonData()->getTarget()) { + if ($pool->isUnacceptableFixedOrLockedPackage($package)) { + return true; + } + if (!$this->getReasonData()->getConstraint()->matches(new Constraint('=', $package->getVersion()))) { + return true; + } + // required package was locked but has been unlocked and still matches + if (!$request->isLockedPackage($package)) { + return true; + } + break; + } + } + } + } + + if ($this->getReason() === self::RULE_ROOT_REQUIRE) { + if (PlatformRepository::isPlatformPackage($this->getReasonData()['packageName'])) { + return false; + } + if ($request->getLockedRepository() !== null) { + foreach ($request->getLockedRepository()->getPackages() as $package) { + if ($package->getName() === $this->getReasonData()['packageName']) { + if ($pool->isUnacceptableFixedOrLockedPackage($package)) { + return true; + } + if (!$this->getReasonData()['constraint']->matches(new Constraint('=', $package->getVersion()))) { + return true; + } + break; + } + } + } + } + + return false; + } + + /** + * @internal + */ + public function getSourcePackage(Pool $pool): BasePackage + { + $literals = $this->getLiterals(); + + switch ($this->getReason()) { + case self::RULE_PACKAGE_CONFLICT: + $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); + $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); + + $reasonData = $this->getReasonData(); + // swap literals if they are not in the right order with package2 being the conflicter + if ($reasonData->getSource() === $package1->getName()) { + [$package2, $package1] = [$package1, $package2]; + } + + return $package2; + + case self::RULE_PACKAGE_REQUIRES: + $sourceLiteral = $literals[0]; + $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); + + return $sourcePackage; + + default: + throw new \LogicException('Not implemented'); + } + } + + /** + * @param BasePackage[] $installedMap + * @param array $learnedPool + */ + public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string + { + $literals = $this->getLiterals(); + + switch ($this->getReason()) { + case self::RULE_ROOT_REQUIRE: + $reasonData = $this->getReasonData(); + $packageName = $reasonData['packageName']; + $constraint = $reasonData['constraint']; + + $packages = $pool->whatProvides($packageName, $constraint); + if (0 === \count($packages)) { + return 'No package found to satisfy root composer.json require '.$packageName.' '.$constraint->getPrettyString(); + } + + $packagesNonAlias = array_values(array_filter($packages, static function ($p): bool { + return !($p instanceof AliasPackage); + })); + if (\count($packagesNonAlias) === 1) { + $package = $packagesNonAlias[0]; + if ($request->isLockedPackage($package)) { + return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion()." and an update of this package was not requested."; + } + } + + return 'Root composer.json requires '.$packageName.' '.$constraint->getPrettyString().' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages, $isVerbose, $constraint).'.'; + + case self::RULE_FIXED: + $package = $this->deduplicateDefaultBranchAlias($this->getReasonData()['package']); + + if ($request->isLockedPackage($package)) { + return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion().' and an update of this package was not requested.'; + } + + return $package->getPrettyName().' is present at version '.$package->getPrettyVersion() . ' and cannot be modified by Composer'; + + case self::RULE_PACKAGE_CONFLICT: + $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); + $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); + + $conflictTarget = $package1->getPrettyString(); + $reasonData = $this->getReasonData(); + + // swap literals if they are not in the right order with package2 being the conflicter + if ($reasonData->getSource() === $package1->getName()) { + [$package2, $package1] = [$package1, $package2]; + $conflictTarget = $package1->getPrettyName().' '.$reasonData->getPrettyConstraint(); + } + + // if the conflict is not directly against the package but something it provides/replaces, + // we try to find that link to display a better message + if ($reasonData->getTarget() !== $package1->getName()) { + $provideType = null; + $provided = null; + foreach ($package1->getProvides() as $provide) { + if ($provide->getTarget() === $reasonData->getTarget()) { + $provideType = 'provides'; + $provided = $provide->getPrettyConstraint(); + break; + } + } + foreach ($package1->getReplaces() as $replace) { + if ($replace->getTarget() === $reasonData->getTarget()) { + $provideType = 'replaces'; + $provided = $replace->getPrettyConstraint(); + break; + } + } + if (null !== $provideType) { + $conflictTarget = $reasonData->getTarget().' '.$reasonData->getPrettyConstraint().' ('.$package1->getPrettyString().' '.$provideType.' '.$reasonData->getTarget().' '.$provided.')'; + } + } + + return $package2->getPrettyString().' conflicts with '.$conflictTarget.'.'; + + case self::RULE_PACKAGE_REQUIRES: + assert(\count($literals) > 0); + $sourceLiteral = array_shift($literals); + $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); + $reasonData = $this->getReasonData(); + + $requires = []; + foreach ($literals as $literal) { + $requires[] = $pool->literalToPackage($literal); + } + + $text = $reasonData->getPrettyString($sourcePackage); + if (\count($requires) > 0) { + $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $reasonData->getConstraint()) . '.'; + } else { + $targetName = $reasonData->getTarget(); + + $reason = Problem::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $targetName, $reasonData->getConstraint()); + + return $text . ' -> ' . $reason[1]; + } + + return $text; + + case self::RULE_PACKAGE_SAME_NAME: + $packageNames = []; + foreach ($literals as $literal) { + $package = $pool->literalToPackage($literal); + $packageNames[$package->getName()] = true; + } + unset($literal); + $replacedName = $this->getReasonData(); + + if (\count($packageNames) > 1) { + if (!isset($packageNames[$replacedName])) { + $reason = 'They '.(\count($literals) === 2 ? 'both' : 'all').' replace '.$replacedName.' and thus cannot coexist.'; + } else { + $replacerNames = $packageNames; + unset($replacerNames[$replacedName]); + $replacerNames = array_keys($replacerNames); + + if (\count($replacerNames) === 1) { + $reason = $replacerNames[0] . ' replaces '; + } else { + $reason = '['.implode(', ', $replacerNames).'] replace '; + } + $reason .= $replacedName.' and thus cannot coexist with it.'; + } + + $installedPackages = []; + $removablePackages = []; + foreach ($literals as $literal) { + if (isset($installedMap[abs($literal)])) { + $installedPackages[] = $pool->literalToPackage($literal); + } else { + $removablePackages[] = $pool->literalToPackage($literal); + } + } + + if (\count($installedPackages) > 0 && \count($removablePackages) > 0) { + return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose, null, true).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose, null, true).'. '.$reason; + } + + return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals, $isVerbose, null, true).'. '.$reason; + } + + return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose, null, true) . '.'; + case self::RULE_LEARNED: + /** @TODO currently still generates way too much output to be helpful, and in some cases can even lead to endless recursion */ + // if (isset($learnedPool[$this->getReasonData()])) { + // echo $this->getReasonData()."\n"; + // $learnedString = ', learned rules:' . Problem::formatDeduplicatedRules($learnedPool[$this->getReasonData()], ' ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); + // } else { + // $learnedString = ' (reasoning unavailable)'; + // } + $learnedString = ' (conflict analysis result)'; + + if (\count($literals) === 1) { + $ruleText = $pool->literalToPrettyString($literals[0], $installedMap); + } else { + $groups = []; + foreach ($literals as $literal) { + $package = $pool->literalToPackage($literal); + if (isset($installedMap[$package->id])) { + $group = $literal > 0 ? 'keep' : 'remove'; + } else { + $group = $literal > 0 ? 'install' : 'don\'t install'; + } + + $groups[$group][] = $this->deduplicateDefaultBranchAlias($package); + } + $ruleTexts = []; + foreach ($groups as $group => $packages) { + $ruleTexts[] = $group . (\count($packages) > 1 ? ' one of' : '').' ' . $this->formatPackagesUnique($pool, $packages, $isVerbose); + } + + $ruleText = implode(' | ', $ruleTexts); + } + + return 'Conclusion: '.$ruleText.$learnedString; + case self::RULE_PACKAGE_ALIAS: + $aliasPackage = $pool->literalToPackage($literals[0]); + + // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless + if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + return ''; + } + $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); + + return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and thus requires it to be installed too.'; + case self::RULE_PACKAGE_INVERSE_ALIAS: + // inverse alias rules work the other way around than above + $aliasPackage = $pool->literalToPackage($literals[1]); + + // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless + if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + return ''; + } + $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); + + return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and must be installed with it.'; + default: + $ruleText = ''; + foreach ($literals as $i => $literal) { + if ($i !== 0) { + $ruleText .= '|'; + } + $ruleText .= $pool->literalToPrettyString($literal, $installedMap); + } + + return '('.$ruleText.')'; + } + } + + /** + * @param array $literalsOrPackages An array containing packages or literals + */ + protected function formatPackagesUnique(Pool $pool, array $literalsOrPackages, bool $isVerbose, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string + { + $packages = []; + foreach ($literalsOrPackages as $package) { + $packages[] = \is_object($package) ? $package : $pool->literalToPackage($package); + } + + return Problem::getPackageList($packages, $isVerbose, $pool, $constraint, $useRemovedVersionGroup); + } + + private function deduplicateDefaultBranchAlias(BasePackage $package): BasePackage + { + if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $package = $package->getAliasOf(); + } + + return $package; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php b/vendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php new file mode 100644 index 000000000..33d0ed0be --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php @@ -0,0 +1,117 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + * @phpstan-import-type ReasonData from Rule + */ +class Rule2Literals extends Rule +{ + /** @var int */ + protected $literal1; + /** @var int */ + protected $literal2; + + /** + * @param Rule::RULE_* $reason A RULE_* constant + * @param mixed $reasonData + * + * @phpstan-param ReasonData $reasonData + */ + public function __construct(int $literal1, int $literal2, $reason, $reasonData) + { + parent::__construct($reason, $reasonData); + + if ($literal1 < $literal2) { + $this->literal1 = $literal1; + $this->literal2 = $literal2; + } else { + $this->literal1 = $literal2; + $this->literal2 = $literal1; + } + } + + /** + * @return non-empty-list + */ + public function getLiterals(): array + { + return [$this->literal1, $this->literal2]; + } + + /** + * @inheritDoc + */ + public function getHash() + { + return $this->literal1.','.$this->literal2; + } + + /** + * Checks if this rule is equal to another one + * + * Ignores whether either of the rules is disabled. + * + * @param Rule $rule The rule to check against + * @return bool Whether the rules are equal + */ + public function equals(Rule $rule): bool + { + // specialized fast-case + if ($rule instanceof self) { + if ($this->literal1 !== $rule->literal1) { + return false; + } + + if ($this->literal2 !== $rule->literal2) { + return false; + } + + return true; + } + + $literals = $rule->getLiterals(); + if (2 !== \count($literals)) { + return false; + } + + if ($this->literal1 !== $literals[0]) { + return false; + } + + if ($this->literal2 !== $literals[1]) { + return false; + } + + return true; + } + + /** @return false */ + public function isAssertion(): bool + { + return false; + } + + /** + * Formats a rule as a string of the format (Literal1|Literal2|...) + */ + public function __toString(): string + { + $result = $this->isDisabled() ? 'disabled(' : '('; + + $result .= $this->literal1 . '|' . $this->literal2 . ')'; + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php new file mode 100644 index 000000000..3d14e27c4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php @@ -0,0 +1,194 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Repository\RepositorySet; + +/** + * @author Nils Adermann + * @implements \IteratorAggregate + * @internal + * @final + */ +class RuleSet implements \IteratorAggregate, \Countable +{ + // highest priority => lowest number + public const TYPE_PACKAGE = 0; + public const TYPE_REQUEST = 1; + public const TYPE_LEARNED = 4; + + /** + * READ-ONLY: Lookup table for rule id to rule object + * + * @var array + */ + public $ruleById = []; + + public const TYPES = [ + self::TYPE_PACKAGE => 'PACKAGE', + self::TYPE_REQUEST => 'REQUEST', + self::TYPE_LEARNED => 'LEARNED', + ]; + + /** @var array */ + protected $rules; + + /** @var 0|positive-int */ + protected $nextRuleId = 0; + + /** @var array */ + protected $rulesByHash = []; + + public function __construct() + { + foreach ($this->getTypes() as $type) { + $this->rules[$type] = []; + } + } + + /** + * @param self::TYPE_* $type + */ + public function add(Rule $rule, $type): void + { + if (!isset(self::TYPES[$type])) { + throw new \OutOfBoundsException('Unknown rule type: ' . $type); + } + + $hash = $rule->getHash(); + + // Do not add if rule already exists + if (isset($this->rulesByHash[$hash])) { + $potentialDuplicates = $this->rulesByHash[$hash]; + if (\is_array($potentialDuplicates)) { + foreach ($potentialDuplicates as $potentialDuplicate) { + if ($rule->equals($potentialDuplicate)) { + return; + } + } + } else { + if ($rule->equals($potentialDuplicates)) { + return; + } + } + } + + if (!isset($this->rules[$type])) { + $this->rules[$type] = []; + } + + $this->rules[$type][] = $rule; + $this->ruleById[$this->nextRuleId] = $rule; + $rule->setType($type); + + $this->nextRuleId++; + + if (!isset($this->rulesByHash[$hash])) { + $this->rulesByHash[$hash] = $rule; + } elseif (\is_array($this->rulesByHash[$hash])) { + $this->rulesByHash[$hash][] = $rule; + } else { + $originalRule = $this->rulesByHash[$hash]; + $this->rulesByHash[$hash] = [$originalRule, $rule]; + } + } + + public function count(): int + { + return $this->nextRuleId; + } + + public function ruleById(int $id): Rule + { + return $this->ruleById[$id]; + } + + /** @return array */ + public function getRules(): array + { + return $this->rules; + } + + public function getIterator(): RuleSetIterator + { + return new RuleSetIterator($this->getRules()); + } + + /** + * @param self::TYPE_*|array $types + */ + public function getIteratorFor($types): RuleSetIterator + { + if (!\is_array($types)) { + $types = [$types]; + } + + $allRules = $this->getRules(); + + /** @var array $rules */ + $rules = []; + + foreach ($types as $type) { + $rules[$type] = $allRules[$type]; + } + + return new RuleSetIterator($rules); + } + + /** + * @param array|self::TYPE_* $types + */ + public function getIteratorWithout($types): RuleSetIterator + { + if (!\is_array($types)) { + $types = [$types]; + } + + $rules = $this->getRules(); + + foreach ($types as $type) { + unset($rules[$type]); + } + + return new RuleSetIterator($rules); + } + + /** + * @return array{self::TYPE_PACKAGE, self::TYPE_REQUEST, self::TYPE_LEARNED} + */ + public function getTypes(): array + { + $types = self::TYPES; + + return array_keys($types); + } + + public function getPrettyString(?RepositorySet $repositorySet = null, ?Request $request = null, ?Pool $pool = null, bool $isVerbose = false): string + { + $string = "\n"; + foreach ($this->rules as $type => $rules) { + $string .= str_pad(self::TYPES[$type], 8, ' ') . ": "; + foreach ($rules as $rule) { + $string .= ($repositorySet !== null && $request !== null && $pool !== null ? $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose) : $rule)."\n"; + } + $string .= "\n\n"; + } + + return $string; + } + + public function __toString(): string + { + return $this->getPrettyString(); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php new file mode 100644 index 000000000..08c874ff7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -0,0 +1,327 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\Package\BasePackage; +use Composer\Package\AliasPackage; + +/** + * @author Nils Adermann + * @phpstan-import-type ReasonData from Rule + */ +class RuleSetGenerator +{ + /** @var PolicyInterface */ + protected $policy; + /** @var Pool */ + protected $pool; + /** @var RuleSet */ + protected $rules; + /** @var array */ + protected $addedMap = []; + /** @var array */ + protected $addedPackagesByNames = []; + + public function __construct(PolicyInterface $policy, Pool $pool) + { + $this->policy = $policy; + $this->pool = $pool; + $this->rules = new RuleSet; + } + + /** + * Creates a new rule for the requirements of a package + * + * This rule is of the form (-A|B|C), where B and C are the providers of + * one requirement of the package A. + * + * @param BasePackage $package The package with a requirement + * @param BasePackage[] $providers The providers of the requirement + * @param Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule + * @param mixed $reasonData Any data, e.g. the requirement name, that goes with the reason + * @return Rule|null The generated rule or null if tautological + * + * @phpstan-param ReasonData $reasonData + */ + protected function createRequireRule(BasePackage $package, array $providers, $reason, $reasonData): ?Rule + { + $literals = [-$package->id]; + + foreach ($providers as $provider) { + // self fulfilling rule? + if ($provider === $package) { + return null; + } + $literals[] = $provider->id; + } + + return new GenericRule($literals, $reason, $reasonData); + } + + /** + * Creates a rule to install at least one of a set of packages + * + * The rule is (A|B|C) with A, B and C different packages. If the given + * set of packages is empty an impossible rule is generated. + * + * @param non-empty-array $packages The set of packages to choose from + * @param Rule::RULE_* $reason A RULE_* constant describing the reason for + * generating this rule + * @param mixed $reasonData Additional data like the root require or fix request info + * @return Rule The generated rule + * + * @phpstan-param ReasonData $reasonData + */ + protected function createInstallOneOfRule(array $packages, $reason, $reasonData): Rule + { + $literals = []; + foreach ($packages as $package) { + $literals[] = $package->id; + } + + return new GenericRule($literals, $reason, $reasonData); + } + + /** + * Creates a rule for two conflicting packages + * + * The rule for conflicting packages A and B is (-A|-B). A is called the issuer + * and B the provider. + * + * @param BasePackage $issuer The package declaring the conflict + * @param BasePackage $provider The package causing the conflict + * @param Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule + * @param mixed $reasonData Any data, e.g. the package name, that goes with the reason + * @return ?Rule The generated rule + * + * @phpstan-param ReasonData $reasonData + */ + protected function createRule2Literals(BasePackage $issuer, BasePackage $provider, $reason, $reasonData): ?Rule + { + // ignore self conflict + if ($issuer === $provider) { + return null; + } + + return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData); + } + + /** + * @param non-empty-array $packages + * @param Rule::RULE_* $reason A RULE_* constant + * @param mixed $reasonData + * + * @phpstan-param ReasonData $reasonData + */ + protected function createMultiConflictRule(array $packages, $reason, $reasonData): Rule + { + $literals = []; + foreach ($packages as $package) { + $literals[] = -$package->id; + } + + if (\count($literals) === 2) { + return new Rule2Literals($literals[0], $literals[1], $reason, $reasonData); + } + + return new MultiConflictRule($literals, $reason, $reasonData); + } + + /** + * Adds a rule unless it duplicates an existing one of any type + * + * To be able to directly pass in the result of one of the rule creation + * methods null is allowed which will not insert a rule. + * + * @param RuleSet::TYPE_* $type A TYPE_* constant defining the rule type + * @param Rule $newRule The rule about to be added + */ + private function addRule($type, ?Rule $newRule = null): void + { + if (null === $newRule) { + return; + } + + $this->rules->add($newRule, $type); + } + + protected function addRulesForPackage(BasePackage $package, PlatformRequirementFilterInterface $platformRequirementFilter): void + { + /** @var \SplQueue */ + $workQueue = new \SplQueue; + $workQueue->enqueue($package); + + while (!$workQueue->isEmpty()) { + $package = $workQueue->dequeue(); + if (isset($this->addedMap[$package->id])) { + continue; + } + + $this->addedMap[$package->id] = $package; + + if (!$package instanceof AliasPackage) { + foreach ($package->getNames(false) as $name) { + $this->addedPackagesByNames[$name][] = $package; + } + } else { + $workQueue->enqueue($package->getAliasOf()); + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, [$package->getAliasOf()], Rule::RULE_PACKAGE_ALIAS, $package)); + + // aliases must be installed with their main package, so create a rule the other way around as well + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package->getAliasOf(), [$package], Rule::RULE_PACKAGE_INVERSE_ALIAS, $package->getAliasOf())); + + // if alias package has no self.version requires, its requirements do not + // need to be added as the aliased package processing will take care of it + if (!$package->hasSelfVersionRequires()) { + continue; + } + } + + foreach ($package->getRequires() as $link) { + $constraint = $link->getConstraint(); + if ($platformRequirementFilter->isIgnored($link->getTarget())) { + continue; + } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint); + } + + $possibleRequires = $this->pool->whatProvides($link->getTarget(), $constraint); + + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link)); + + foreach ($possibleRequires as $require) { + $workQueue->enqueue($require); + } + } + } + } + + protected function addConflictRules(PlatformRequirementFilterInterface $platformRequirementFilter): void + { + /** @var BasePackage $package */ + foreach ($this->addedMap as $package) { + foreach ($package->getConflicts() as $link) { + // even if conflict ends up being with an alias, there would be at least one actual package by this name + if (!isset($this->addedPackagesByNames[$link->getTarget()])) { + continue; + } + + $constraint = $link->getConstraint(); + if ($platformRequirementFilter->isIgnored($link->getTarget())) { + continue; + } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint, false); + } + + $conflicts = $this->pool->whatProvides($link->getTarget(), $constraint); + + foreach ($conflicts as $conflict) { + // define the conflict rule for regular packages, for alias packages it's only needed if the name + // matches the conflict exactly, otherwise the name match is by provide/replace which means the + // package which this is an alias of will conflict anyway, so no need to create additional rules + if (!$conflict instanceof AliasPackage || $conflict->getName() === $link->getTarget()) { + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link)); + } + } + } + } + + foreach ($this->addedPackagesByNames as $name => $packages) { + if (\count($packages) > 1) { + $reason = Rule::RULE_PACKAGE_SAME_NAME; + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createMultiConflictRule($packages, $reason, $name)); + } + } + } + + protected function addRulesForRequest(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void + { + foreach ($request->getFixedPackages() as $package) { + if ($package->id === -1) { + // fixed package was not added to the pool as it did not pass the stability requirements, this is fine + if ($this->pool->isUnacceptableFixedOrLockedPackage($package)) { + continue; + } + + // otherwise, looks like a bug + throw new \LogicException("Fixed package ".$package->getPrettyString()." was not added to solver pool."); + } + + $this->addRulesForPackage($package, $platformRequirementFilter); + + $rule = $this->createInstallOneOfRule([$package], Rule::RULE_FIXED, [ + 'package' => $package, + ]); + $this->addRule(RuleSet::TYPE_REQUEST, $rule); + } + + foreach ($request->getRequires() as $packageName => $constraint) { + if ($platformRequirementFilter->isIgnored($packageName)) { + continue; + } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint); + } + + $packages = $this->pool->whatProvides($packageName, $constraint); + if (\count($packages) > 0) { + foreach ($packages as $package) { + $this->addRulesForPackage($package, $platformRequirementFilter); + } + + $rule = $this->createInstallOneOfRule($packages, Rule::RULE_ROOT_REQUIRE, [ + 'packageName' => $packageName, + 'constraint' => $constraint, + ]); + $this->addRule(RuleSet::TYPE_REQUEST, $rule); + } + } + } + + protected function addRulesForRootAliases(PlatformRequirementFilterInterface $platformRequirementFilter): void + { + foreach ($this->pool->getPackages() as $package) { + // ensure that rules for root alias packages and aliases of packages which were loaded are also loaded + // even if the alias itself isn't required, otherwise a package could be installed without its alias which + // leads to unexpected behavior + if (!isset($this->addedMap[$package->id]) && + $package instanceof AliasPackage && + ($package->isRootPackageAlias() || isset($this->addedMap[$package->getAliasOf()->id])) + ) { + $this->addRulesForPackage($package, $platformRequirementFilter); + } + } + } + + public function getRulesFor(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): RuleSet + { + $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); + + $this->addRulesForRequest($request, $platformRequirementFilter); + + $this->addRulesForRootAliases($platformRequirementFilter); + + $this->addConflictRules($platformRequirementFilter); + + // Remove references to packages + $this->addedMap = $this->addedPackagesByNames = []; + + $rules = $this->rules; + + $this->rules = new RuleSet; + + return $rules; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php new file mode 100644 index 000000000..3b8383d47 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php @@ -0,0 +1,105 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + * @implements \Iterator + */ +class RuleSetIterator implements \Iterator +{ + /** @var array */ + protected $rules; + /** @var array */ + protected $types; + + /** @var int */ + protected $currentOffset; + /** @var RuleSet::TYPE_*|-1 */ + protected $currentType; + /** @var int */ + protected $currentTypeOffset; + + /** + * @param array $rules + */ + public function __construct(array $rules) + { + $this->rules = $rules; + $this->types = array_keys($rules); + sort($this->types); + + $this->rewind(); + } + + public function current(): Rule + { + return $this->rules[$this->currentType][$this->currentOffset]; + } + + /** + * @return RuleSet::TYPE_*|-1 + */ + public function key(): int + { + return $this->currentType; + } + + public function next(): void + { + $this->currentOffset++; + + if (!isset($this->rules[$this->currentType])) { + return; + } + + if ($this->currentOffset >= \count($this->rules[$this->currentType])) { + $this->currentOffset = 0; + + do { + $this->currentTypeOffset++; + + if (!isset($this->types[$this->currentTypeOffset])) { + $this->currentType = -1; + break; + } + + $this->currentType = $this->types[$this->currentTypeOffset]; + } while (0 === \count($this->rules[$this->currentType])); + } + } + + public function rewind(): void + { + $this->currentOffset = 0; + + $this->currentTypeOffset = -1; + $this->currentType = -1; + + do { + $this->currentTypeOffset++; + + if (!isset($this->types[$this->currentTypeOffset])) { + $this->currentType = -1; + break; + } + + $this->currentType = $this->types[$this->currentTypeOffset]; + } while (0 === \count($this->rules[$this->currentType])); + } + + public function valid(): bool + { + return isset($this->rules[$this->currentType], $this->rules[$this->currentType][$this->currentOffset]); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php new file mode 100644 index 000000000..ddd596033 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * An extension of SplDoublyLinkedList with seek and removal of current element + * + * SplDoublyLinkedList only allows deleting a particular offset and has no + * method to set the internal iterator to a particular offset. + * + * @author Nils Adermann + * @extends \SplDoublyLinkedList + */ +class RuleWatchChain extends \SplDoublyLinkedList +{ + /** + * Moves the internal iterator to the specified offset + * + * @param int $offset The offset to seek to. + */ + public function seek(int $offset): void + { + $this->rewind(); + for ($i = 0; $i < $offset; $i++, $this->next()); + } + + /** + * Removes the current element from the list + * + * As SplDoublyLinkedList only allows deleting a particular offset and + * incorrectly sets the internal iterator if you delete the current value + * this method sets the internal iterator back to the following element + * using the seek method. + */ + public function remove(): void + { + $offset = $this->key(); + $this->offsetUnset($offset); + $this->seek($offset); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php new file mode 100644 index 000000000..6a13b40ce --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php @@ -0,0 +1,167 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * The RuleWatchGraph efficiently propagates decisions to other rules + * + * All rules generated for solving a SAT problem should be inserted into the + * graph. When a decision on a literal is made, the graph can be used to + * propagate the decision to all other rules involving the literal, leading to + * other trivial decisions resulting from unit clauses. + * + * @author Nils Adermann + */ +class RuleWatchGraph +{ + /** @var array */ + protected $watchChains = []; + + /** + * Inserts a rule node into the appropriate chains within the graph + * + * The node is prepended to the watch chains for each of the two literals it + * watches. + * + * Assertions are skipped because they only depend on a single package and + * have no alternative literal that could be true, so there is no need to + * watch changes in any literals. + * + * @param RuleWatchNode $node The rule node to be inserted into the graph + */ + public function insert(RuleWatchNode $node): void + { + if ($node->getRule()->isAssertion()) { + return; + } + + if (!$node->getRule() instanceof MultiConflictRule) { + foreach ([$node->watch1, $node->watch2] as $literal) { + if (!isset($this->watchChains[$literal])) { + $this->watchChains[$literal] = new RuleWatchChain; + } + + $this->watchChains[$literal]->unshift($node); + } + } else { + foreach ($node->getRule()->getLiterals() as $literal) { + if (!isset($this->watchChains[$literal])) { + $this->watchChains[$literal] = new RuleWatchChain; + } + + $this->watchChains[$literal]->unshift($node); + } + } + } + + /** + * Propagates a decision on a literal to all rules watching the literal + * + * If a decision, e.g. +A has been made, then all rules containing -A, e.g. + * (-A|+B|+C) now need to satisfy at least one of the other literals, so + * that the rule as a whole becomes true, since with +A applied the rule + * is now (false|+B|+C) so essentially (+B|+C). + * + * This means that all rules watching the literal -A need to be updated to + * watch 2 other literals which can still be satisfied instead. So literals + * that conflict with previously made decisions are not an option. + * + * Alternatively it can occur that a unit clause results: e.g. if in the + * above example the rule was (-A|+B), then A turning true means that + * B must now be decided true as well. + * + * @param int $decidedLiteral The literal which was decided (A in our example) + * @param int $level The level at which the decision took place and at which + * all resulting decisions should be made. + * @param Decisions $decisions Used to check previous decisions and to + * register decisions resulting from propagation + * @return Rule|null If a conflict is found the conflicting rule is returned + */ + public function propagateLiteral(int $decidedLiteral, int $level, Decisions $decisions): ?Rule + { + // we invert the decided literal here, example: + // A was decided => (-A|B) now requires B to be true, so we look for + // rules which are fulfilled by -A, rather than A. + $literal = -$decidedLiteral; + + if (!isset($this->watchChains[$literal])) { + return null; + } + + $chain = $this->watchChains[$literal]; + + $chain->rewind(); + while ($chain->valid()) { + $node = $chain->current(); + if (!$node->getRule() instanceof MultiConflictRule) { + $otherWatch = $node->getOtherWatch($literal); + + if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) { + $ruleLiterals = $node->getRule()->getLiterals(); + + $alternativeLiterals = array_filter($ruleLiterals, static function ($ruleLiteral) use ($literal, $otherWatch, $decisions): bool { + return $literal !== $ruleLiteral && + $otherWatch !== $ruleLiteral && + !$decisions->conflict($ruleLiteral); + }); + + if (\count($alternativeLiterals) > 0) { + reset($alternativeLiterals); + $this->moveWatch($literal, current($alternativeLiterals), $node); + continue; + } + + if ($decisions->conflict($otherWatch)) { + return $node->getRule(); + } + + $decisions->decide($otherWatch, $level, $node->getRule()); + } + } else { + foreach ($node->getRule()->getLiterals() as $otherLiteral) { + if ($literal !== $otherLiteral && !$decisions->satisfy($otherLiteral)) { + if ($decisions->conflict($otherLiteral)) { + return $node->getRule(); + } + + $decisions->decide($otherLiteral, $level, $node->getRule()); + } + } + } + + $chain->next(); + } + + return null; + } + + /** + * Moves a rule node from one watch chain to another + * + * The rule node's watched literals are updated accordingly. + * + * @param int $fromLiteral A literal the node used to watch + * @param int $toLiteral A literal the node should watch now + * @param RuleWatchNode $node The rule node to be moved + */ + protected function moveWatch(int $fromLiteral, int $toLiteral, RuleWatchNode $node): void + { + if (!isset($this->watchChains[$toLiteral])) { + $this->watchChains[$toLiteral] = new RuleWatchChain; + } + + $node->moveWatch($fromLiteral, $toLiteral); + $this->watchChains[$fromLiteral]->remove(); + $this->watchChains[$toLiteral]->unshift($node); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php new file mode 100644 index 000000000..79c1fcba7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php @@ -0,0 +1,114 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * Wrapper around a Rule which keeps track of the two literals it watches + * + * Used by RuleWatchGraph to store rules in two RuleWatchChains. + * + * @author Nils Adermann + */ +class RuleWatchNode +{ + /** @var int */ + public $watch1; + /** @var int */ + public $watch2; + + /** @var Rule */ + protected $rule; + + /** + * Creates a new node watching the first and second literals of the rule. + * + * @param Rule $rule The rule to wrap + */ + public function __construct(Rule $rule) + { + $this->rule = $rule; + + $literals = $rule->getLiterals(); + + $literalCount = \count($literals); + $this->watch1 = $literalCount > 0 ? $literals[0] : 0; + $this->watch2 = $literalCount > 1 ? $literals[1] : 0; + } + + /** + * Places the second watch on the rule's literal, decided at the highest level + * + * Useful for learned rules where the literal for the highest rule is most + * likely to quickly lead to further decisions. + * + * @param Decisions $decisions The decisions made so far by the solver + */ + public function watch2OnHighest(Decisions $decisions): void + { + $literals = $this->rule->getLiterals(); + + // if there are only 2 elements, both are being watched anyway + if (\count($literals) < 3 || $this->rule instanceof MultiConflictRule) { + return; + } + + $watchLevel = 0; + + foreach ($literals as $literal) { + $level = $decisions->decisionLevel($literal); + + if ($level > $watchLevel) { + $this->watch2 = $literal; + $watchLevel = $level; + } + } + } + + /** + * Returns the rule this node wraps + */ + public function getRule(): Rule + { + return $this->rule; + } + + /** + * Given one watched literal, this method returns the other watched literal + * + * @param int $literal The watched literal that should not be returned + * @return int A literal + */ + public function getOtherWatch(int $literal): int + { + if ($this->watch1 === $literal) { + return $this->watch2; + } + + return $this->watch1; + } + + /** + * Moves a watch from one literal to another + * + * @param int $from The previously watched literal + * @param int $to The literal to be watched now + */ + public function moveWatch(int $from, int $to): void + { + if ($this->watch1 === $from) { + $this->watch1 = $to; + } else { + $this->watch2 = $to; + } + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/SecurityAdvisoryPoolFilter.php b/vendor/composer/composer/src/Composer/DependencyResolver/SecurityAdvisoryPoolFilter.php new file mode 100644 index 000000000..e06c1bcb1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/SecurityAdvisoryPoolFilter.php @@ -0,0 +1,124 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Advisory\AuditConfig; +use Composer\Advisory\Auditor; +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\Advisory\SecurityAdvisory; +use Composer\Package\PackageInterface; +use Composer\Package\RootPackageInterface; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositorySet; +use Composer\Semver\Constraint\Constraint; + +/** + * @internal + */ +class SecurityAdvisoryPoolFilter +{ + /** @var Auditor */ + private $auditor; + /** @var AuditConfig $auditConfig */ + private $auditConfig; + + public function __construct( + Auditor $auditor, + AuditConfig $auditConfig + ) { + $this->auditor = $auditor; + $this->auditConfig = $auditConfig; + } + + /** + * @param array $repositories + */ + public function filter(Pool $pool, array $repositories, Request $request): Pool + { + if (!$this->auditConfig->blockInsecure) { + return $pool; + } + + $repoSet = new RepositorySet(); + foreach ($repositories as $repo) { + $repoSet->addRepository($repo); + } + + $packagesForAdvisories = []; + foreach ($pool->getPackages() as $package) { + if (!$package instanceof RootPackageInterface && !PlatformRepository::isPlatformPackage($package->getName()) && !$request->isLockedPackage($package)) { + $packagesForAdvisories[] = $package; + } + } + + $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packagesForAdvisories, true, true); + if ($this->auditor->needsCompleteAdvisoryLoad($allAdvisories['advisories'], $this->auditConfig->ignoreListForBlocking)) { + $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packagesForAdvisories, false, true); + } + + $advisoryMap = $this->auditor->processAdvisories($allAdvisories['advisories'], $this->auditConfig->ignoreListForBlocking, $this->auditConfig->ignoreSeverityForBlocking)['advisories']; + + $packages = []; + $securityRemovedVersions = []; + $abandonedRemovedVersions = []; + foreach ($pool->getPackages() as $package) { + if ($this->auditConfig->blockAbandoned && count($this->auditor->filterAbandonedPackages([$package], $this->auditConfig->ignoreAbandonedForBlocking)) !== 0) { + foreach ($package->getNames(false) as $packageName) { + $abandonedRemovedVersions[$packageName][$package->getVersion()] = $package->getPrettyVersion(); + } + continue; + } + + $matchingAdvisories = $this->getMatchingAdvisories($package, $advisoryMap); + if (count($matchingAdvisories) > 0) { + foreach ($package->getNames(false) as $packageName) { + $securityRemovedVersions[$packageName][$package->getVersion()] = $matchingAdvisories; + } + + continue; + } + + $packages[] = $package; + } + + return new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $pool->getAllRemovedVersions(), $pool->getAllRemovedVersionsByPackage(), $securityRemovedVersions, $abandonedRemovedVersions); + } + + /** + * @param array> $advisoryMap + * @return list + */ + private function getMatchingAdvisories(PackageInterface $package, array $advisoryMap): array + { + if ($package->isDev()) { + return []; + } + + $matchingAdvisories = []; + foreach ($package->getNames(false) as $packageName) { + if (!isset($advisoryMap[$packageName])) { + continue; + } + + $packageConstraint = new Constraint(Constraint::STR_OP_EQ, $package->getVersion()); + foreach ($advisoryMap[$packageName] as $advisory) { + if ($advisory->affectedVersions->matches($packageConstraint)) { + $matchingAdvisories[] = $advisory; + } + } + } + + return $matchingAdvisories; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Solver.php b/vendor/composer/composer/src/Composer/DependencyResolver/Solver.php new file mode 100644 index 000000000..b8aa847d1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Solver.php @@ -0,0 +1,758 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; + +/** + * @author Nils Adermann + */ +class Solver +{ + private const BRANCH_LITERALS = 0; + private const BRANCH_LEVEL = 1; + + /** @var PolicyInterface */ + protected $policy; + /** @var Pool */ + protected $pool; + + /** @var RuleSet */ + protected $rules; + + /** @var RuleWatchGraph */ + protected $watchGraph; + /** @var Decisions */ + protected $decisions; + /** @var BasePackage[] */ + protected $fixedMap; + + /** @var int */ + protected $propagateIndex; + /** @var array, int}> */ + protected $branches = []; + /** @var Problem[] */ + protected $problems = []; + /** @var array */ + protected $learnedPool = []; + /** @var array */ + protected $learnedWhy = []; + + /** @var bool */ + public $testFlagLearnedPositiveLiteral = false; + + /** @var IOInterface */ + protected $io; + + public function __construct(PolicyInterface $policy, Pool $pool, IOInterface $io) + { + $this->io = $io; + $this->policy = $policy; + $this->pool = $pool; + } + + public function getRuleSetSize(): int + { + return \count($this->rules); + } + + public function getPool(): Pool + { + return $this->pool; + } + + // aka solver_makeruledecisions + + private function makeAssertionRuleDecisions(): void + { + $decisionStart = \count($this->decisions) - 1; + + $rulesCount = \count($this->rules); + for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) { + $rule = $this->rules->ruleById[$ruleIndex]; + + if (!$rule->isAssertion() || $rule->isDisabled()) { + continue; + } + + $literals = $rule->getLiterals(); + $literal = $literals[0]; + + if (!$this->decisions->decided($literal)) { + $this->decisions->decide($literal, 1, $rule); + continue; + } + + if ($this->decisions->satisfy($literal)) { + continue; + } + + // found a conflict + if (RuleSet::TYPE_LEARNED === $rule->getType()) { + $rule->disable(); + continue; + } + + $conflict = $this->decisions->decisionRule($literal); + + if (RuleSet::TYPE_PACKAGE === $conflict->getType()) { + $problem = new Problem(); + + $problem->addRule($rule); + $problem->addRule($conflict); + $rule->disable(); + $this->problems[] = $problem; + continue; + } + + // conflict with another root require/fixed package + $problem = new Problem(); + $problem->addRule($rule); + $problem->addRule($conflict); + + // push all of our rules (can only be root require/fixed package rules) + // asserting this literal on the problem stack + foreach ($this->rules->getIteratorFor(RuleSet::TYPE_REQUEST) as $assertRule) { + if ($assertRule->isDisabled() || !$assertRule->isAssertion()) { + continue; + } + + $assertRuleLiterals = $assertRule->getLiterals(); + $assertRuleLiteral = $assertRuleLiterals[0]; + + if (abs($literal) !== abs($assertRuleLiteral)) { + continue; + } + $problem->addRule($assertRule); + $assertRule->disable(); + } + $this->problems[] = $problem; + + $this->decisions->resetToOffset($decisionStart); + $ruleIndex = -1; + } + } + + protected function setupFixedMap(Request $request): void + { + $this->fixedMap = []; + foreach ($request->getFixedPackages() as $package) { + $this->fixedMap[$package->id] = $package; + } + } + + protected function checkForRootRequireProblems(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void + { + foreach ($request->getRequires() as $packageName => $constraint) { + if ($platformRequirementFilter->isIgnored($packageName)) { + continue; + } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint); + } + + if (0 === \count($this->pool->whatProvides($packageName, $constraint))) { + $problem = new Problem(); + $problem->addRule(new GenericRule([], Rule::RULE_ROOT_REQUIRE, ['packageName' => $packageName, 'constraint' => $constraint])); + $this->problems[] = $problem; + } + } + } + + public function solve(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): LockTransaction + { + $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); + + $this->setupFixedMap($request); + + $this->io->writeError('Generating rules', true, IOInterface::DEBUG); + $ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool); + $this->rules = $ruleSetGenerator->getRulesFor($request, $platformRequirementFilter); + unset($ruleSetGenerator); + $this->checkForRootRequireProblems($request, $platformRequirementFilter); + $this->decisions = new Decisions($this->pool); + $this->watchGraph = new RuleWatchGraph; + + foreach ($this->rules as $rule) { + $this->watchGraph->insert(new RuleWatchNode($rule)); + } + + /* make decisions based on root require/fix assertions */ + $this->makeAssertionRuleDecisions(); + + $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG); + $before = microtime(true); + $this->runSat(); + $this->io->writeError('', true, IOInterface::DEBUG); + $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE); + + if (\count($this->problems) > 0) { + throw new SolverProblemsException($this->problems, $this->learnedPool); + } + + return new LockTransaction($this->pool, $request->getPresentMap(), $request->getFixedPackagesMap(), $this->decisions); + } + + /** + * Makes a decision and propagates it to all rules. + * + * Evaluates each term affected by the decision (linked through watches) + * If we find unit rules we make new decisions based on them + * + * @return Rule|null A rule on conflict, otherwise null. + */ + protected function propagate(int $level): ?Rule + { + while ($this->decisions->validOffset($this->propagateIndex)) { + $decision = $this->decisions->atOffset($this->propagateIndex); + + $conflict = $this->watchGraph->propagateLiteral( + $decision[Decisions::DECISION_LITERAL], + $level, + $this->decisions + ); + + $this->propagateIndex++; + + if ($conflict !== null) { + return $conflict; + } + } + + return null; + } + + /** + * Reverts a decision at the given level. + */ + private function revert(int $level): void + { + while (!$this->decisions->isEmpty()) { + $literal = $this->decisions->lastLiteral(); + + if ($this->decisions->undecided($literal)) { + break; + } + + $decisionLevel = $this->decisions->decisionLevel($literal); + + if ($decisionLevel <= $level) { + break; + } + + $this->decisions->revertLast(); + $this->propagateIndex = \count($this->decisions); + } + + while (\count($this->branches) > 0 && $this->branches[\count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) { + array_pop($this->branches); + } + } + + /** + * setpropagatelearn + * + * add free decision (a positive literal) to decision queue + * increase level and propagate decision + * return if no conflict. + * + * in conflict case, analyze conflict rule, add resulting + * rule to learnt rule set, make decision from learnt + * rule (always unit) and re-propagate. + * + * returns the new solver level or 0 if unsolvable + */ + private function setPropagateLearn(int $level, int $literal, Rule $rule): int + { + $level++; + + $this->decisions->decide($literal, $level, $rule); + + while (true) { + $rule = $this->propagate($level); + + if (null === $rule) { + break; + } + + if ($level === 1) { + $this->analyzeUnsolvable($rule); + + return 0; + } + + // conflict + [$learnLiteral, $newLevel, $newRule, $why] = $this->analyze($level, $rule); + + if ($newLevel <= 0 || $newLevel >= $level) { + throw new SolverBugException( + "Trying to revert to invalid level ".$newLevel." from level ".$level."." + ); + } + + $level = $newLevel; + + $this->revert($level); + + $this->rules->add($newRule, RuleSet::TYPE_LEARNED); + + $this->learnedWhy[spl_object_hash($newRule)] = $why; + + $ruleNode = new RuleWatchNode($newRule); + $ruleNode->watch2OnHighest($this->decisions); + $this->watchGraph->insert($ruleNode); + + $this->decisions->decide($learnLiteral, $level, $newRule); + } + + return $level; + } + + /** + * @param non-empty-list $decisionQueue + */ + private function selectAndInstall(int $level, array $decisionQueue, Rule $rule): int + { + // choose best package to install from decisionQueue + $literals = $this->policy->selectPreferredPackages($this->pool, $decisionQueue, $rule->getRequiredPackage()); + + $selectedLiteral = array_shift($literals); + + // if there are multiple candidates, then branch + if (\count($literals) > 0) { + $this->branches[] = [$literals, $level]; + } + + return $this->setPropagateLearn($level, $selectedLiteral, $rule); + } + + /** + * @return array{int, int, GenericRule, int} + */ + protected function analyze(int $level, Rule $rule): array + { + $analyzedRule = $rule; + $ruleLevel = 1; + $num = 0; + $l1num = 0; + $seen = []; + $learnedLiteral = null; + $otherLearnedLiterals = []; + + $decisionId = \count($this->decisions); + + $this->learnedPool[] = []; + + while (true) { + $this->learnedPool[\count($this->learnedPool) - 1][] = $rule; + + foreach ($rule->getLiterals() as $literal) { + // multiconflictrule is really a bunch of rules in one, so some may not have finished propagating yet + if ($rule instanceof MultiConflictRule && !$this->decisions->decided($literal)) { + continue; + } + + // skip the one true literal + if ($this->decisions->satisfy($literal)) { + continue; + } + + if (isset($seen[abs($literal)])) { + continue; + } + $seen[abs($literal)] = true; + + $l = $this->decisions->decisionLevel($literal); + + if (1 === $l) { + $l1num++; + } elseif ($level === $l) { + $num++; + } else { + // not level1 or conflict level, add to new rule + $otherLearnedLiterals[] = $literal; + + if ($l > $ruleLevel) { + $ruleLevel = $l; + } + } + } + unset($literal); + + $l1retry = true; + while ($l1retry) { + $l1retry = false; + + if (0 === $num && 0 === --$l1num) { + // all level 1 literals done + break 2; + } + + while (true) { + if ($decisionId <= 0) { + throw new SolverBugException( + "Reached invalid decision id $decisionId while looking through $rule for a literal present in the analyzed rule $analyzedRule." + ); + } + + $decisionId--; + + $decision = $this->decisions->atOffset($decisionId); + $literal = $decision[Decisions::DECISION_LITERAL]; + + if (isset($seen[abs($literal)])) { + break; + } + } + + unset($seen[abs($literal)]); + + if (0 !== $num && 0 === --$num) { + if ($literal < 0) { + $this->testFlagLearnedPositiveLiteral = true; + } + $learnedLiteral = -$literal; + + if (0 === $l1num) { + break 2; + } + + foreach ($otherLearnedLiterals as $otherLiteral) { + unset($seen[abs($otherLiteral)]); + } + // only level 1 marks left + $l1num++; + $l1retry = true; + } else { + $decision = $this->decisions->atOffset($decisionId); + $rule = $decision[Decisions::DECISION_REASON]; + + if ($rule instanceof MultiConflictRule) { + // there is only ever exactly one positive decision in a MultiConflictRule + foreach ($rule->getLiterals() as $ruleLiteral) { + if (!isset($seen[abs($ruleLiteral)]) && $this->decisions->satisfy(-$ruleLiteral)) { + $this->learnedPool[\count($this->learnedPool) - 1][] = $rule; + $l = $this->decisions->decisionLevel($ruleLiteral); + if (1 === $l) { + $l1num++; + } elseif ($level === $l) { + $num++; + } else { + // not level1 or conflict level, add to new rule + $otherLearnedLiterals[] = $ruleLiteral; + + if ($l > $ruleLevel) { + $ruleLevel = $l; + } + } + $seen[abs($ruleLiteral)] = true; + break; + } + } + + $l1retry = true; + } + } + } + + $decision = $this->decisions->atOffset($decisionId); + $rule = $decision[Decisions::DECISION_REASON]; + } + + $why = \count($this->learnedPool) - 1; + + if (null === $learnedLiteral) { + throw new SolverBugException( + "Did not find a learnable literal in analyzed rule $analyzedRule." + ); + } + + array_unshift($otherLearnedLiterals, $learnedLiteral); + $newRule = new GenericRule($otherLearnedLiterals, Rule::RULE_LEARNED, $why); + + return [$learnedLiteral, $ruleLevel, $newRule, $why]; + } + + /** + * @param array $ruleSeen + */ + private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule, array &$ruleSeen): void + { + $why = spl_object_hash($conflictRule); + $ruleSeen[$why] = true; + + if ($conflictRule->getType() === RuleSet::TYPE_LEARNED) { + $learnedWhy = $this->learnedWhy[$why]; + $problemRules = $this->learnedPool[$learnedWhy]; + + foreach ($problemRules as $problemRule) { + if (!isset($ruleSeen[spl_object_hash($problemRule)])) { + $this->analyzeUnsolvableRule($problem, $problemRule, $ruleSeen); + } + } + + return; + } + + if ($conflictRule->getType() === RuleSet::TYPE_PACKAGE) { + // package rules cannot be part of a problem + return; + } + + $problem->nextSection(); + $problem->addRule($conflictRule); + } + + private function analyzeUnsolvable(Rule $conflictRule): void + { + $problem = new Problem(); + $problem->addRule($conflictRule); + + $ruleSeen = []; + + $this->analyzeUnsolvableRule($problem, $conflictRule, $ruleSeen); + + $this->problems[] = $problem; + + $seen = []; + $literals = $conflictRule->getLiterals(); + + foreach ($literals as $literal) { + // skip the one true literal + if ($this->decisions->satisfy($literal)) { + continue; + } + $seen[abs($literal)] = true; + } + + foreach ($this->decisions as $decision) { + $decisionLiteral = $decision[Decisions::DECISION_LITERAL]; + + // skip literals that are not in this rule + if (!isset($seen[abs($decisionLiteral)])) { + continue; + } + + $why = $decision[Decisions::DECISION_REASON]; + + $problem->addRule($why); + $this->analyzeUnsolvableRule($problem, $why, $ruleSeen); + + $literals = $why->getLiterals(); + foreach ($literals as $literal) { + // skip the one true literal + if ($this->decisions->satisfy($literal)) { + continue; + } + $seen[abs($literal)] = true; + } + } + } + + private function runSat(): void + { + $this->propagateIndex = 0; + + /* + * here's the main loop: + * 1) propagate new decisions (only needed once) + * 2) fulfill root requires/fixed packages + * 3) fulfill all unresolved rules + * 4) minimalize solution if we had choices + * if we encounter a problem, we rewind to a safe level and restart + * with step 1 + */ + + $level = 1; + $systemLevel = $level + 1; + + while (true) { + if (1 === $level) { + $conflictRule = $this->propagate($level); + if (null !== $conflictRule) { + $this->analyzeUnsolvable($conflictRule); + + return; + } + } + + // handle root require/fixed package rules + if ($level < $systemLevel) { + $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_REQUEST); + foreach ($iterator as $rule) { + if ($rule->isEnabled()) { + $decisionQueue = []; + $noneSatisfied = true; + + foreach ($rule->getLiterals() as $literal) { + if ($this->decisions->satisfy($literal)) { + $noneSatisfied = false; + break; + } + if ($literal > 0 && $this->decisions->undecided($literal)) { + $decisionQueue[] = $literal; + } + } + + if ($noneSatisfied && \count($decisionQueue) > 0) { + // if any of the options in the decision queue are fixed, only use those + $prunedQueue = []; + foreach ($decisionQueue as $literal) { + if (isset($this->fixedMap[abs($literal)])) { + $prunedQueue[] = $literal; + } + } + if (\count($prunedQueue) > 0) { + $decisionQueue = $prunedQueue; + } + } + + if ($noneSatisfied && \count($decisionQueue) > 0) { + $oLevel = $level; + $level = $this->selectAndInstall($level, $decisionQueue, $rule); + + if (0 === $level) { + return; + } + if ($level <= $oLevel) { + break; + } + } + } + } + + $systemLevel = $level + 1; + + // root requires/fixed packages left + $iterator->next(); + if ($iterator->valid()) { + continue; + } + } + + if ($level < $systemLevel) { + $systemLevel = $level; + } + + $rulesCount = \count($this->rules); + $pass = 1; + + $this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG); + for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) { + if ($i === $rulesCount) { + if (1 === $pass) { + $this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG); + } else { + $this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG); + } + + $i = 0; + $pass++; + } + + $rule = $this->rules->ruleById[$i]; + $literals = $rule->getLiterals(); + + if ($rule->isDisabled()) { + continue; + } + + $decisionQueue = []; + + // make sure that + // * all negative literals are installed + // * no positive literal is installed + // i.e. the rule is not fulfilled and we + // just need to decide on the positive literals + // + foreach ($literals as $literal) { + if ($literal <= 0) { + if (!$this->decisions->decidedInstall($literal)) { + continue 2; // next rule + } + } else { + if ($this->decisions->decidedInstall($literal)) { + continue 2; // next rule + } + if ($this->decisions->undecided($literal)) { + $decisionQueue[] = $literal; + } + } + } + + // need to have at least 2 item to pick from + if (\count($decisionQueue) < 2) { + continue; + } + + $level = $this->selectAndInstall($level, $decisionQueue, $rule); + + if (0 === $level) { + return; + } + + // something changed, so look at all rules again + $rulesCount = \count($this->rules); + $n = -1; + } + + if ($level < $systemLevel) { + continue; + } + + // minimization step + if (\count($this->branches) > 0) { + $lastLiteral = null; + $lastLevel = null; + $lastBranchIndex = 0; + $lastBranchOffset = 0; + + for ($i = \count($this->branches) - 1; $i >= 0; $i--) { + [$literals, $l] = $this->branches[$i]; + + foreach ($literals as $offset => $literal) { + if ($literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) { + $lastLiteral = $literal; + $lastBranchIndex = $i; + $lastBranchOffset = $offset; + $lastLevel = $l; + } + } + } + + if ($lastLiteral !== null) { + assert($lastLevel !== null); + unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]); + + $level = $lastLevel; + $this->revert($level); + + $why = $this->decisions->lastReason(); + + $level = $this->setPropagateLearn($level, $lastLiteral, $why); + + if ($level === 0) { + return; + } + + continue; + } + } + + break; + } + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php b/vendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php new file mode 100644 index 000000000..7ac72671d --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php @@ -0,0 +1,27 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + */ +class SolverBugException extends \RuntimeException +{ + public function __construct(string $message) + { + parent::__construct( + $message."\nThis exception was most likely caused by a bug in Composer.\n". + "Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n" + ); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php b/vendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php new file mode 100644 index 000000000..bd76e4fa3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php @@ -0,0 +1,148 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Util\IniHelper; +use Composer\Repository\RepositorySet; + +/** + * @author Nils Adermann + * + * @method self::ERROR_DEPENDENCY_RESOLUTION_FAILED getCode() + */ +class SolverProblemsException extends \RuntimeException +{ + public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2; + + /** @var Problem[] */ + protected $problems; + /** @var array */ + protected $learnedPool; + + /** + * @param Problem[] $problems + * @param array $learnedPool + */ + public function __construct(array $problems, array $learnedPool) + { + $this->problems = $problems; + $this->learnedPool = $learnedPool; + + parent::__construct('Failed resolving dependencies with '.\count($problems).' problems, call getPrettyString to get formatted details', self::ERROR_DEPENDENCY_RESOLUTION_FAILED); + } + + public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, bool $isDevExtraction = false): string + { + $installedMap = $request->getPresentMap(true); + $missingExtensions = []; + $isCausedByLock = false; + + $problems = []; + foreach ($this->problems as $problem) { + $problems[] = $problem->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $this->learnedPool)."\n"; + + $missingExtensions = array_merge($missingExtensions, $this->getExtensionProblems($problem->getReasons())); + + $isCausedByLock = $isCausedByLock || $problem->isCausedByLock($repositorySet, $request, $pool); + } + + $i = 1; + $text = "\n"; + foreach (array_unique($problems) as $problem) { + $text .= " Problem ".($i++).$problem; + } + + $hints = []; + if (!$isDevExtraction && (str_contains($text, 'could not be found') || str_contains($text, 'no matching package found'))) { + $hints[] = "Potential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead for further common problems."; + } + + if (\count($missingExtensions) > 0) { + $hints[] = $this->createExtensionHint($missingExtensions); + } + + if ($isCausedByLock && !$isDevExtraction && !$request->getUpdateAllowTransitiveRootDependencies()) { + $hints[] = "Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions."; + } + + if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match') && str_contains($text, '- ocramius/package-versions')) { + $hints[] = "ocramius/package-versions only provides support for Composer 2 in 1.8+, which requires PHP 7.4.\nIf you can not upgrade PHP you can require composer/package-versions-deprecated to resolve this with PHP 7.0+."; + } + + if (!class_exists('PHPUnit\Framework\TestCase', false)) { + if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match')) { + $hints[] = "You are using Composer 2, which some of your plugins seem to be incompatible with. Make sure you update your plugins or report a plugin-issue to ask them to support Composer 2."; + } + } + + if (\count($hints) > 0) { + $text .= "\n" . implode("\n\n", $hints); + } + + return $text; + } + + /** + * @return Problem[] + */ + public function getProblems(): array + { + return $this->problems; + } + + /** + * @param string[] $missingExtensions + */ + private function createExtensionHint(array $missingExtensions): string + { + $paths = IniHelper::getAll(); + + if ('' === $paths[0]) { + if (count($paths) === 1) { + return ''; + } + + array_shift($paths); + } + + $ignoreExtensionsArguments = implode(" ", array_map(static function ($extension) { + return "--ignore-platform-req=$extension"; + }, array_unique($missingExtensions))); + + $text = "To enable extensions, verify that they are enabled in your .ini files:\n - "; + $text .= implode("\n - ", $paths); + $text .= "\nYou can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode."; + $text .= "\nAlternatively, you can run Composer with `$ignoreExtensionsArguments` to temporarily ignore these required extensions."; + + return $text; + } + + /** + * @param Rule[][] $reasonSets + * @return string[] + */ + private function getExtensionProblems(array $reasonSets): array + { + $missingExtensions = []; + foreach ($reasonSets as $reasonSet) { + foreach ($reasonSet as $rule) { + $required = $rule->getRequiredPackage(); + if (null !== $required && 0 === strpos($required, 'ext-')) { + $missingExtensions[$required] = 1; + } + } + } + + return array_keys($missingExtensions); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Transaction.php b/vendor/composer/composer/src/Composer/DependencyResolver/Transaction.php new file mode 100644 index 000000000..7883bc222 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Transaction.php @@ -0,0 +1,368 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Link; +use Composer\Package\PackageInterface; +use Composer\Repository\PlatformRepository; +use Composer\DependencyResolver\Operation\OperationInterface; + +/** + * @author Nils Adermann + * @internal + */ +class Transaction +{ + /** + * @var OperationInterface[] + */ + protected $operations; + + /** + * Packages present at the beginning of the transaction + * @var PackageInterface[] + */ + protected $presentPackages; + + /** + * Package set resulting from this transaction + * @var array + */ + protected $resultPackageMap; + + /** + * @var array + */ + protected $resultPackagesByName = []; + + /** + * @param PackageInterface[] $presentPackages + * @param PackageInterface[] $resultPackages + */ + public function __construct(array $presentPackages, array $resultPackages) + { + $this->presentPackages = $presentPackages; + $this->setResultPackageMaps($resultPackages); + $this->operations = $this->calculateOperations(); + } + + /** + * @return OperationInterface[] + */ + public function getOperations(): array + { + return $this->operations; + } + + /** + * @param PackageInterface[] $resultPackages + */ + private function setResultPackageMaps(array $resultPackages): void + { + $packageSort = static function (PackageInterface $a, PackageInterface $b): int { + // sort alias packages by the same name behind their non alias version + if ($a->getName() === $b->getName()) { + if ($a instanceof AliasPackage !== $b instanceof AliasPackage) { + return $a instanceof AliasPackage ? -1 : 1; + } + + // if names are the same, compare version, e.g. to sort aliases reliably, actual order does not matter + return strcmp($b->getVersion(), $a->getVersion()); + } + + return strcmp($b->getName(), $a->getName()); + }; + + $this->resultPackageMap = []; + foreach ($resultPackages as $package) { + $this->resultPackageMap[spl_object_hash($package)] = $package; + foreach ($package->getNames() as $name) { + $this->resultPackagesByName[$name][] = $package; + } + } + + uasort($this->resultPackageMap, $packageSort); + foreach ($this->resultPackagesByName as $name => $packages) { + uasort($this->resultPackagesByName[$name], $packageSort); + } + } + + /** + * @return OperationInterface[] + */ + protected function calculateOperations(): array + { + $operations = []; + + $presentPackageMap = []; + $removeMap = []; + $presentAliasMap = []; + $removeAliasMap = []; + foreach ($this->presentPackages as $package) { + if ($package instanceof AliasPackage) { + $presentAliasMap[$package->getName().'::'.$package->getVersion()] = $package; + $removeAliasMap[$package->getName().'::'.$package->getVersion()] = $package; + } else { + $presentPackageMap[$package->getName()] = $package; + $removeMap[$package->getName()] = $package; + } + } + + $stack = $this->getRootPackages(); + + $visited = []; + $processed = []; + + while (\count($stack) > 0) { + $package = array_pop($stack); + + if (isset($processed[spl_object_hash($package)])) { + continue; + } + + if (!isset($visited[spl_object_hash($package)])) { + $visited[spl_object_hash($package)] = true; + + $stack[] = $package; + if ($package instanceof AliasPackage) { + $stack[] = $package->getAliasOf(); + } else { + foreach ($package->getRequires() as $link) { + $possibleRequires = $this->getProvidersInResult($link); + + foreach ($possibleRequires as $require) { + $stack[] = $require; + } + } + } + } elseif (!isset($processed[spl_object_hash($package)])) { + $processed[spl_object_hash($package)] = true; + + if ($package instanceof AliasPackage) { + $aliasKey = $package->getName().'::'.$package->getVersion(); + if (isset($presentAliasMap[$aliasKey])) { + unset($removeAliasMap[$aliasKey]); + } else { + $operations[] = new Operation\MarkAliasInstalledOperation($package); + } + } else { + if (isset($presentPackageMap[$package->getName()])) { + $source = $presentPackageMap[$package->getName()]; + + // do we need to update? + // TODO different for lock? + if ($package->getVersion() !== $presentPackageMap[$package->getName()]->getVersion() || + $package->getDistReference() !== $presentPackageMap[$package->getName()]->getDistReference() || + $package->getSourceReference() !== $presentPackageMap[$package->getName()]->getSourceReference() || + ( + $package instanceof CompletePackageInterface + && $presentPackageMap[$package->getName()] instanceof CompletePackageInterface + && ( + $package->isAbandoned() !== $presentPackageMap[$package->getName()]->isAbandoned() + || $package->getReplacementPackage() !== $presentPackageMap[$package->getName()]->getReplacementPackage() + ) + ) + ) { + $operations[] = new Operation\UpdateOperation($source, $package); + } + unset($removeMap[$package->getName()]); + } else { + $operations[] = new Operation\InstallOperation($package); + unset($removeMap[$package->getName()]); + } + } + } + } + + foreach ($removeMap as $name => $package) { + array_unshift($operations, new Operation\UninstallOperation($package)); + } + foreach ($removeAliasMap as $nameVersion => $package) { + $operations[] = new Operation\MarkAliasUninstalledOperation($package); + } + + $operations = $this->movePluginsToFront($operations); + // TODO fix this: + // we have to do this again here even though the above stack code did it because moving plugins moves them before uninstalls + $operations = $this->moveUninstallsToFront($operations); + + // TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place? + /* + if ('update' === $opType) { + $targetPackage = $operation->getTargetPackage(); + if ($targetPackage->isDev()) { + $initialPackage = $operation->getInitialPackage(); + if ($targetPackage->getVersion() === $initialPackage->getVersion() + && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference()) + && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference()) + ) { + $this->io->writeError(' - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG); + $this->io->writeError('', true, IOInterface::DEBUG); + + continue; + } + } + }*/ + + return $this->operations = $operations; + } + + /** + * Determine which packages in the result are not required by any other packages in it. + * + * These serve as a starting point to enumerate packages in a topological order despite potential cycles. + * If there are packages with a cycle on the top level the package with the lowest name gets picked + * + * @return array + */ + protected function getRootPackages(): array + { + $roots = $this->resultPackageMap; + + foreach ($this->resultPackageMap as $packageHash => $package) { + if (!isset($roots[$packageHash])) { + continue; + } + + foreach ($package->getRequires() as $link) { + $possibleRequires = $this->getProvidersInResult($link); + + foreach ($possibleRequires as $require) { + if ($require !== $package) { + unset($roots[spl_object_hash($require)]); + } + } + } + } + + return $roots; + } + + /** + * @return PackageInterface[] + */ + protected function getProvidersInResult(Link $link): array + { + if (!isset($this->resultPackagesByName[$link->getTarget()])) { + return []; + } + + return $this->resultPackagesByName[$link->getTarget()]; + } + + /** + * Workaround: if your packages depend on plugins, we must be sure + * that those are installed / updated first; else it would lead to packages + * being installed multiple times in different folders, when running Composer + * twice. + * + * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147, + * it at least fixes the symptoms and makes usage of composer possible (again) + * in such scenarios. + * + * @param OperationInterface[] $operations + * @return OperationInterface[] reordered operation list + */ + private function movePluginsToFront(array $operations): array + { + $dlModifyingPluginsNoDeps = []; + $dlModifyingPluginsWithDeps = []; + $dlModifyingPluginRequires = []; + $pluginsNoDeps = []; + $pluginsWithDeps = []; + $pluginRequires = []; + + foreach (array_reverse($operations, true) as $idx => $op) { + if ($op instanceof Operation\InstallOperation) { + $package = $op->getPackage(); + } elseif ($op instanceof Operation\UpdateOperation) { + $package = $op->getTargetPackage(); + } else { + continue; + } + + $extra = $package->getExtra(); + $isDownloadsModifyingPlugin = $package->getType() === 'composer-plugin' && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true; + + // is this a downloads modifying plugin or a dependency of one? + if ($isDownloadsModifyingPlugin || \count(array_intersect($package->getNames(), $dlModifyingPluginRequires)) > 0) { + // get the package's requires, but filter out any platform requirements + $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool { + return !PlatformRepository::isPlatformPackage($req); + }); + + // is this a plugin with no meaningful dependencies? + if ($isDownloadsModifyingPlugin && 0 === \count($requires)) { + // plugins with no dependencies go to the very front + array_unshift($dlModifyingPluginsNoDeps, $op); + } else { + // capture the requirements for this package so those packages will be moved up as well + $dlModifyingPluginRequires = array_merge($dlModifyingPluginRequires, $requires); + // move the operation to the front + array_unshift($dlModifyingPluginsWithDeps, $op); + } + + unset($operations[$idx]); + continue; + } + + // is this package a plugin? + $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer'; + + // is this a plugin or a dependency of a plugin? + if ($isPlugin || \count(array_intersect($package->getNames(), $pluginRequires)) > 0) { + // get the package's requires, but filter out any platform requirements + $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool { + return !PlatformRepository::isPlatformPackage($req); + }); + + // is this a plugin with no meaningful dependencies? + if ($isPlugin && 0 === \count($requires)) { + // plugins with no dependencies go to the very front + array_unshift($pluginsNoDeps, $op); + } else { + // capture the requirements for this package so those packages will be moved up as well + $pluginRequires = array_merge($pluginRequires, $requires); + // move the operation to the front + array_unshift($pluginsWithDeps, $op); + } + + unset($operations[$idx]); + } + } + + return array_merge($dlModifyingPluginsNoDeps, $dlModifyingPluginsWithDeps, $pluginsNoDeps, $pluginsWithDeps, $operations); + } + + /** + * Removals of packages should be executed before installations in + * case two packages resolve to the same path (due to custom installers) + * + * @param OperationInterface[] $operations + * @return OperationInterface[] reordered operation list + */ + private function moveUninstallsToFront(array $operations): array + { + $uninstOps = []; + foreach ($operations as $idx => $op) { + if ($op instanceof Operation\UninstallOperation || $op instanceof Operation\MarkAliasUninstalledOperation) { + $uninstOps[] = $op; + unset($operations[$idx]); + } + } + + return array_merge($uninstOps, $operations); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php b/vendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php new file mode 100644 index 000000000..1f37d7dfc --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php @@ -0,0 +1,224 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use Composer\Util\Platform; +use Symfony\Component\Finder\Finder; +use React\Promise\PromiseInterface; +use Composer\DependencyResolver\Operation\InstallOperation; + +/** + * Base downloader for archives + * + * @author Kirill chEbba Chebunin + * @author Jordi Boggiano + * @author François Pluchino + */ +abstract class ArchiveDownloader extends FileDownloader +{ + /** + * @var array + */ + protected $cleanupExecuted = []; + + public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + unset($this->cleanupExecuted[$package->getName()]); + + return parent::prepare($type, $package, $path, $prevPackage); + } + + public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + $this->cleanupExecuted[$package->getName()] = true; + + return parent::cleanup($type, $package, $path, $prevPackage); + } + + /** + * @inheritDoc + * + * @throws \RuntimeException + * @throws \UnexpectedValueException + */ + public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + if ($output) { + $this->io->writeError(" - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path)); + } + + $vendorDir = $this->config->get('vendor-dir'); + + // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains + // the archive to be extracted. This is the case when installing with create-project in the current directory + // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here. + if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) { + $this->filesystem->emptyDirectory($path); + } + + do { + $temporaryDir = $vendorDir.'/composer/'.bin2hex(random_bytes(4)); + } while (is_dir($temporaryDir)); + + $this->addCleanupPath($package, $temporaryDir); + // avoid cleaning up $path if installing in "." for eg create-project as we can not + // delete the directory we are currently in on windows + if (!is_dir($path) || realpath($path) !== Platform::getCwd()) { + $this->addCleanupPath($package, $path); + } + + $this->filesystem->ensureDirectoryExists($temporaryDir); + $fileName = $this->getFileName($package, $path); + + $filesystem = $this->filesystem; + + $cleanup = function () use ($path, $filesystem, $temporaryDir, $package) { + // remove cache if the file was corrupted + $this->clearLastCacheWrite($package); + + // clean up + $filesystem->removeDirectory($temporaryDir); + if (is_dir($path) && realpath($path) !== Platform::getCwd()) { + $filesystem->removeDirectory($path); + } + $this->removeCleanupPath($package, $temporaryDir); + $realpath = realpath($path); + if ($realpath !== false) { + $this->removeCleanupPath($package, $realpath); + } + }; + + try { + $promise = $this->extract($package, $fileName, $temporaryDir); + } catch (\Exception $e) { + $cleanup(); + throw $e; + } + + return $promise->then(function () use ($package, $filesystem, $fileName, $temporaryDir, $path): PromiseInterface { + if (file_exists($fileName)) { + $filesystem->unlink($fileName); + } + + /** + * Returns the folder content, excluding .DS_Store + * + * @param string $dir Directory + * @return \SplFileInfo[] + */ + $getFolderContent = static function ($dir): array { + $finder = Finder::create() + ->ignoreVCS(false) + ->ignoreDotFiles(false) + ->notName('.DS_Store') + ->depth(0) + ->in($dir); + + return iterator_to_array($finder); + }; + $renameRecursively = null; + /** + * Renames (and recursively merges if needed) a folder into another one + * + * For custom installers, where packages may share paths, and given Composer 2's parallelism, we need to make sure + * that the source directory gets merged into the target one if the target exists. Otherwise rename() by default would + * put the source into the target e.g. src/ => target/src/ (assuming target exists) instead of src/ => target/ + * + * @param string $from Directory + * @param string $to Directory + * @return void + */ + $renameRecursively = static function ($from, $to) use ($filesystem, $getFolderContent, $package, &$renameRecursively) { + $contentDir = $getFolderContent($from); + + // move files back out of the temp dir + foreach ($contentDir as $file) { + $file = (string) $file; + if (is_dir($to . '/' . basename($file))) { + if (!is_dir($file)) { + throw new \RuntimeException('Installing '.$package.' would lead to overwriting the '.$to.'/'.basename($file).' directory with a file from the package, invalid operation.'); + } + $renameRecursively($file, $to . '/' . basename($file)); + } else { + $filesystem->rename($file, $to . '/' . basename($file)); + } + } + }; + + $renameAsOne = false; + if (!file_exists($path)) { + $renameAsOne = true; + } elseif ($filesystem->isDirEmpty($path)) { + try { + if ($filesystem->removeDirectoryPhp($path)) { + $renameAsOne = true; + } + } catch (\RuntimeException $e) { + // ignore error, and simply do not renameAsOne + } + } + + $contentDir = $getFolderContent($temporaryDir); + $singleDirAtTopLevel = 1 === count($contentDir) && is_dir((string) reset($contentDir)); + + if ($renameAsOne) { + // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents + if ($singleDirAtTopLevel) { + $extractedDir = (string) reset($contentDir); + } else { + $extractedDir = $temporaryDir; + } + $filesystem->rename($extractedDir, $path); + } else { + // only one dir in the archive, extract its contents out of it + $from = $temporaryDir; + if ($singleDirAtTopLevel) { + $from = (string) reset($contentDir); + } + + $renameRecursively($from, $path); + } + + $promise = $filesystem->removeDirectoryAsync($temporaryDir); + + return $promise->then(function () use ($package, $path, $temporaryDir) { + $this->removeCleanupPath($package, $temporaryDir); + $this->removeCleanupPath($package, $path); + }); + }, static function ($e) use ($cleanup) { + $cleanup(); + + throw $e; + }); + } + + /** + * @inheritDoc + */ + protected function getInstallOperationAppendix(PackageInterface $package, string $path): string + { + return ': Extracting archive'; + } + + /** + * Extract file to directory + * + * @param string $file Extracted file + * @param string $path Directory + * @phpstan-return PromiseInterface + * + * @throws \UnexpectedValueException If can not extract downloaded file to path + */ + abstract protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php b/vendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php new file mode 100644 index 000000000..857a7f09e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; + +/** + * ChangeReport interface. + * + * @author Sascha Egerer + */ +interface ChangeReportInterface +{ + /** + * Checks for changes to the local copy + * + * @param PackageInterface $package package instance + * @param string $path package directory + * @return string|null changes or null + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/DownloadManager.php b/vendor/composer/composer/src/Composer/Downloader/DownloadManager.php new file mode 100644 index 000000000..67fa99ff5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/DownloadManager.php @@ -0,0 +1,442 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Exception\IrrecoverableDownloadException; +use React\Promise\PromiseInterface; + +/** + * Downloaders manager. + * + * @author Konstantin Kudryashov + */ +class DownloadManager +{ + /** @var IOInterface */ + private $io; + /** @var bool */ + private $preferDist = false; + /** @var bool */ + private $preferSource; + /** @var array */ + private $packagePreferences = []; + /** @var Filesystem */ + private $filesystem; + /** @var array */ + private $downloaders = []; + + /** + * Initializes download manager. + * + * @param IOInterface $io The Input Output Interface + * @param bool $preferSource prefer downloading from source + * @param Filesystem|null $filesystem custom Filesystem object + */ + public function __construct(IOInterface $io, bool $preferSource = false, ?Filesystem $filesystem = null) + { + $this->io = $io; + $this->preferSource = $preferSource; + $this->filesystem = $filesystem ?: new Filesystem(); + } + + /** + * Makes downloader prefer source installation over the dist. + * + * @param bool $preferSource prefer downloading from source + */ + public function setPreferSource(bool $preferSource): self + { + $this->preferSource = $preferSource; + + return $this; + } + + /** + * Makes downloader prefer dist installation over the source. + * + * @param bool $preferDist prefer downloading from dist + */ + public function setPreferDist(bool $preferDist): self + { + $this->preferDist = $preferDist; + + return $this; + } + + /** + * Sets fine tuned preference settings for package level source/dist selection. + * + * @param array $preferences array of preferences by package patterns + */ + public function setPreferences(array $preferences): self + { + $this->packagePreferences = $preferences; + + return $this; + } + + /** + * Sets installer downloader for a specific installation type. + * + * @param string $type installation type + * @param DownloaderInterface $downloader downloader instance + */ + public function setDownloader(string $type, DownloaderInterface $downloader): self + { + $type = strtolower($type); + $this->downloaders[$type] = $downloader; + + return $this; + } + + /** + * Returns downloader for a specific installation type. + * + * @param string $type installation type + * @throws \InvalidArgumentException if downloader for provided type is not registered + */ + public function getDownloader(string $type): DownloaderInterface + { + $type = strtolower($type); + if (!isset($this->downloaders[$type])) { + throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders)))); + } + + return $this->downloaders[$type]; + } + + /** + * Returns downloader for already installed package. + * + * @param PackageInterface $package package instance + * @throws \InvalidArgumentException if package has no installation source specified + * @throws \LogicException if specific downloader used to load package with + * wrong type + */ + public function getDownloaderForPackage(PackageInterface $package): ?DownloaderInterface + { + $installationSource = $package->getInstallationSource(); + + if ('metapackage' === $package->getType()) { + return null; + } + + if ('dist' === $installationSource) { + $downloader = $this->getDownloader($package->getDistType()); + } elseif ('source' === $installationSource) { + $downloader = $this->getDownloader($package->getSourceType()); + } else { + throw new \InvalidArgumentException( + 'Package '.$package.' does not have an installation source set' + ); + } + + if ($installationSource !== $downloader->getInstallationSource()) { + throw new \LogicException(sprintf( + 'Downloader "%s" is a %s type downloader and can not be used to download %s for package %s', + get_class($downloader), + $downloader->getInstallationSource(), + $installationSource, + $package + )); + } + + return $downloader; + } + + public function getDownloaderType(DownloaderInterface $downloader): string + { + return array_search($downloader, $this->downloaders); + } + + /** + * Downloads package into target dir. + * + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @param PackageInterface|null $prevPackage previous package instance in case of updates + * @phpstan-return PromiseInterface + * + * @throws \InvalidArgumentException if package have no urls to download from + * @throws \RuntimeException + */ + public function download(PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $this->filesystem->ensureDirectoryExists(dirname($targetDir)); + + $sources = $this->getAvailableSources($package, $prevPackage); + + $io = $this->io; + + $download = function ($retry = false) use (&$sources, $io, $package, $targetDir, &$download, $prevPackage) { + $source = array_shift($sources); + if ($retry) { + $io->writeError(' Now trying to download from ' . $source . ''); + } + $package->setInstallationSource($source); + + $downloader = $this->getDownloaderForPackage($package); + if (!$downloader) { + return \React\Promise\resolve(null); + } + + $handleError = static function ($e) use ($sources, $source, $package, $io, $download) { + if ($e instanceof \RuntimeException && !$e instanceof IrrecoverableDownloadException) { + if (!$sources) { + throw $e; + } + + $io->writeError( + ' Failed to download '. + $package->getPrettyName(). + ' from ' . $source . ': '. + $e->getMessage().'' + ); + + return $download(true); + } + + throw $e; + }; + + try { + $result = $downloader->download($package, $targetDir, $prevPackage); + } catch (\Exception $e) { + return $handleError($e); + } + + $res = $result->then(static function ($res) { + return $res; + }, $handleError); + + return $res; + }; + + return $download(); + } + + /** + * Prepares an operation execution + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @param PackageInterface|null $prevPackage previous package instance in case of updates + * @phpstan-return PromiseInterface + */ + public function prepare(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->prepare($type, $package, $targetDir, $prevPackage); + } + + return \React\Promise\resolve(null); + } + + /** + * Installs package into target dir. + * + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @phpstan-return PromiseInterface + * + * @throws \InvalidArgumentException if package have no urls to download from + * @throws \RuntimeException + */ + public function install(PackageInterface $package, string $targetDir): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->install($package, $targetDir); + } + + return \React\Promise\resolve(null); + } + + /** + * Updates package from initial to target version. + * + * @param PackageInterface $initial initial package version + * @param PackageInterface $target target package version + * @param string $targetDir target dir + * @phpstan-return PromiseInterface + * + * @throws \InvalidArgumentException if initial package is not installed + */ + public function update(PackageInterface $initial, PackageInterface $target, string $targetDir): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($target); + $initialDownloader = $this->getDownloaderForPackage($initial); + + // no downloaders present means update from metapackage to metapackage, nothing to do + if (!$initialDownloader && !$downloader) { + return \React\Promise\resolve(null); + } + + // if we have a downloader present before, but not after, the package became a metapackage and its files should be removed + if (!$downloader) { + return $initialDownloader->remove($initial, $targetDir); + } + + $initialType = $this->getDownloaderType($initialDownloader); + $targetType = $this->getDownloaderType($downloader); + if ($initialType === $targetType) { + try { + return $downloader->update($initial, $target, $targetDir); + } catch (\RuntimeException $e) { + if (!$this->io->isInteractive()) { + throw $e; + } + $this->io->writeError(' Update failed ('.$e->getMessage().')'); + if (!$this->io->askConfirmation(' Would you like to try reinstalling the package instead [yes]? ')) { + throw $e; + } + } + } + + // if downloader type changed, or update failed and user asks for reinstall, + // we wipe the dir and do a new install instead of updating it + $promise = $initialDownloader->remove($initial, $targetDir); + + return $promise->then(function ($res) use ($target, $targetDir): PromiseInterface { + return $this->install($target, $targetDir); + }); + } + + /** + * Removes package from target dir. + * + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @phpstan-return PromiseInterface + */ + public function remove(PackageInterface $package, string $targetDir): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->remove($package, $targetDir); + } + + return \React\Promise\resolve(null); + } + + /** + * Cleans up a failed operation + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @param PackageInterface|null $prevPackage previous package instance in case of updates + * @phpstan-return PromiseInterface + */ + public function cleanup(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->cleanup($type, $package, $targetDir, $prevPackage); + } + + return \React\Promise\resolve(null); + } + + /** + * Determines the install preference of a package + * + * @param PackageInterface $package package instance + */ + protected function resolvePackageInstallPreference(PackageInterface $package): string + { + foreach ($this->packagePreferences as $pattern => $preference) { + $pattern = '{^'.str_replace('\\*', '.*', preg_quote($pattern)).'$}i'; + if (Preg::isMatch($pattern, $package->getName())) { + if ('dist' === $preference || (!$package->isDev() && 'auto' === $preference)) { + return 'dist'; + } + + return 'source'; + } + } + + return $package->isDev() ? 'source' : 'dist'; + } + + /** + * @return string[] + * @phpstan-return array<'dist'|'source'>&non-empty-array + */ + private function getAvailableSources(PackageInterface $package, ?PackageInterface $prevPackage = null): array + { + $sourceType = $package->getSourceType(); + $distType = $package->getDistType(); + + // add source before dist by default + $sources = []; + if ($sourceType) { + $sources[] = 'source'; + } + if ($distType) { + $sources[] = 'dist'; + } + + if (empty($sources)) { + throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified'); + } + + if ( + $prevPackage + // if we are updating, we want to keep the same source as the previously installed package (if available in the new one) + && in_array($prevPackage->getInstallationSource(), $sources, true) + // unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over + && !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev()) + ) { + $prevSource = $prevPackage->getInstallationSource(); + usort($sources, static function ($a, $b) use ($prevSource): int { + return $a === $prevSource ? -1 : 1; + }); + + return $sources; + } + + // reverse sources in case dist is the preferred source for this package + if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) { + $sources = array_reverse($sources); + } + + return $sources; + } + + /** + * Downloaders expect a /path/to/dir without trailing slash + * + * If any Installer provides a path with a trailing slash, this can cause bugs so make sure we remove them + */ + private function normalizeTargetDir(string $dir): string + { + if ($dir === '\\' || $dir === '/') { + return $dir; + } + + return rtrim($dir, '\\/'); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php b/vendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php new file mode 100644 index 000000000..8cb86cdbb --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php @@ -0,0 +1,99 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use React\Promise\PromiseInterface; + +/** + * Downloader interface. + * + * @author Konstantin Kudryashov + * @author Jordi Boggiano + */ +interface DownloaderInterface +{ + /** + * Returns installation source (either source or dist). + * + * @return string "source" or "dist" + */ + public function getInstallationSource(): string; + + /** + * This should do any network-related tasks to prepare for an upcoming install/update + * + * @param string $path download path + * @phpstan-return PromiseInterface + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface; + + /** + * Do anything that needs to be done between all downloads have been completed and the actual operation is executed + * + * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore + * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or + * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can + * be undone as much as possible. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $path download path + * @param PackageInterface $prevPackage previous package instance in case of an update + * @phpstan-return PromiseInterface + */ + public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface; + + /** + * Installs specific package into specific folder. + * + * @param PackageInterface $package package instance + * @param string $path download path + * @phpstan-return PromiseInterface + */ + public function install(PackageInterface $package, string $path): PromiseInterface; + + /** + * Updates specific package in specific folder from initial to target version. + * + * @param PackageInterface $initial initial package + * @param PackageInterface $target updated package + * @param string $path download path + * @phpstan-return PromiseInterface + */ + public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface; + + /** + * Removes specific package from specific folder. + * + * @param PackageInterface $package package instance + * @param string $path download path + * @phpstan-return PromiseInterface + */ + public function remove(PackageInterface $package, string $path): PromiseInterface; + + /** + * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps + * + * Note that cleanup will be called for all packages, either after install/update/uninstall is complete, + * or if any package failed any operation. This is to give all installers a change to cleanup things + * they did previously, so you need to keep track of changes applied in the installer/downloader themselves. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $path download path + * @param PackageInterface $prevPackage previous package instance in case of an update + * @phpstan-return PromiseInterface + */ + public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php b/vendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php new file mode 100644 index 000000000..6e5b67c0a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; + +/** + * DVCS Downloader interface. + * + * @author James Titcumb + */ +interface DvcsDownloaderInterface +{ + /** + * Checks for unpushed changes to a current branch + * + * @param PackageInterface $package package instance + * @param string $path package directory + * @return string|null changes or null + */ + public function getUnpushedChanges(PackageInterface $package, string $path): ?string; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/FileDownloader.php b/vendor/composer/composer/src/Composer/Downloader/FileDownloader.php new file mode 100644 index 000000000..006757a7b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/FileDownloader.php @@ -0,0 +1,545 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Config; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\IO\NullIO; +use Composer\Exception\IrrecoverableDownloadException; +use Composer\Package\Comparer\Comparer; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\Package\PackageInterface; +use Composer\Plugin\PluginEvents; +use Composer\Plugin\PostFileDownloadEvent; +use Composer\Plugin\PreFileDownloadEvent; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\Filesystem; +use Composer\Util\Http\Response; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use Composer\Util\HttpDownloader; +use Composer\Util\Url as UrlUtil; +use Composer\Util\ProcessExecutor; +use React\Promise\PromiseInterface; + +/** + * Base downloader for files + * + * @author Kirill chEbba Chebunin + * @author Jordi Boggiano + * @author François Pluchino + * @author Nils Adermann + */ +class FileDownloader implements DownloaderInterface, ChangeReportInterface +{ + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var HttpDownloader */ + protected $httpDownloader; + /** @var Filesystem */ + protected $filesystem; + /** @var ?Cache */ + protected $cache; + /** @var ?EventDispatcher */ + protected $eventDispatcher; + /** @var ProcessExecutor */ + protected $process; + /** + * @var array + * @private + * @internal + */ + public static $downloadMetadata = []; + /** + * Collects response headers when running on GH Actions + * + * @see https://github.com/composer/composer/issues/11148 + * @var array> + * @private + * @internal + */ + public static $responseHeaders = []; + + /** + * @var array Map of package name to cache key + */ + private $lastCacheWrites = []; + /** @var array Map of package name to list of paths */ + private $additionalCleanupPaths = []; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The config + * @param HttpDownloader $httpDownloader The remote filesystem + * @param EventDispatcher $eventDispatcher The event dispatcher + * @param Cache $cache Cache instance + * @param Filesystem $filesystem The filesystem + */ + public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?Cache $cache = null, ?Filesystem $filesystem = null, ?ProcessExecutor $process = null) + { + $this->io = $io; + $this->config = $config; + $this->eventDispatcher = $eventDispatcher; + $this->httpDownloader = $httpDownloader; + $this->cache = $cache; + $this->process = $process ?? new ProcessExecutor($io); + $this->filesystem = $filesystem ?? new Filesystem($this->process); + + if ($this->cache !== null && $this->cache->gcIsNecessary()) { + $this->io->writeError('Running cache garbage collection', true, IOInterface::VERY_VERBOSE); + $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); + } + } + + /** + * @inheritDoc + */ + public function getInstallationSource(): string + { + return 'dist'; + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface + { + if (null === $package->getDistUrl()) { + throw new \InvalidArgumentException('The given package is missing url information'); + } + + $cacheKeyGenerator = static function (PackageInterface $package, $key): string { + $cacheKey = hash('sha1', $key); + + return $package->getName().'/'.$cacheKey.'.'.$package->getDistType(); + }; + + $retries = 3; + $distUrls = $package->getDistUrls(); + /** @var array $urls */ + $urls = []; + foreach ($distUrls as $index => $url) { + $processedUrl = $this->processUrl($package, $url); + $urls[$index] = [ + 'base' => $url, + 'processed' => $processedUrl, + // we use the complete download url here to avoid conflicting entries + // from different packages, which would potentially allow a given package + // in a third party repo to pre-populate the cache for the same package in + // packagist for example. + 'cacheKey' => $cacheKeyGenerator($package, $processedUrl), + ]; + } + assert(count($urls) > 0); + + $fileName = $this->getFileName($package, $path); + $this->filesystem->ensureDirectoryExists($path); + $this->filesystem->ensureDirectoryExists(dirname($fileName)); + + $accept = null; + $reject = null; + $download = function () use ($output, $cacheKeyGenerator, $package, $fileName, &$urls, &$accept, &$reject) { + $url = reset($urls); + $index = key($urls); + + if ($this->eventDispatcher !== null) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $url['processed'], 'package', $package); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + if ($preFileDownloadEvent->getCustomCacheKey() !== null) { + $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getCustomCacheKey()); + } elseif ($preFileDownloadEvent->getProcessedUrl() !== $url['processed']) { + $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getProcessedUrl()); + } + $url['processed'] = $preFileDownloadEvent->getProcessedUrl(); + } + + $urls[$index] = $url; + + $checksum = $package->getDistSha1Checksum(); + $cacheKey = $url['cacheKey']; + + // use from cache if it is present and has a valid checksum or we have no checksum to check against + if ($this->cache !== null && ($checksum === null || $checksum === '' || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) { + if ($output) { + $this->io->writeError(" - Loading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") from cache", true, IOInterface::VERY_VERBOSE); + } + // mark the file as having been written in cache even though it is only read from cache, so that if + // the cache is corrupt the archive will be deleted and the next attempt will re-download it + // see https://github.com/composer/composer/issues/10028 + if (!$this->cache->isReadOnly()) { + $this->lastCacheWrites[$package->getName()] = $cacheKey; + } + $result = \React\Promise\resolve($fileName); + } else { + if ($output) { + $this->io->writeError(" - Downloading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + } + + $result = $this->httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions()) + ->then($accept, $reject); + } + + return $result->then(function ($result) use ($fileName, $checksum, $url, $package): string { + // in case of retry, the first call's Promise chain finally calls this twice at the end, + // once with $result being the returned $fileName from $accept, and then once for every + // failed request with a null result, which can be skipped. + if (null === $result) { + return $fileName; + } + + if (!file_exists($fileName)) { + throw new \UnexpectedValueException($url['base'].' could not be saved to '.$fileName.', make sure the' + .' directory is writable and you have internet connectivity'); + } + + if ($checksum !== null && $checksum !== '' && hash_file('sha1', $fileName) !== $checksum) { + throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')'); + } + + if ($this->eventDispatcher !== null) { + $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, $fileName, $checksum, $url['processed'], 'package', $package); + $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); + } + + return $fileName; + }); + }; + + $accept = function (Response $response) use ($package, $fileName, &$urls): string { + $url = reset($urls); + $cacheKey = $url['cacheKey']; + $fileSize = @filesize($fileName); + if (false === $fileSize) { + $fileSize = $response->getHeader('Content-Length') ?? '?'; + } + FileDownloader::$downloadMetadata[$package->getName()] = $fileSize; + + if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) { + FileDownloader::$responseHeaders[$package->getName()] = $response->getHeaders(); + } + + if ($this->cache !== null && !$this->cache->isReadOnly()) { + $this->lastCacheWrites[$package->getName()] = $cacheKey; + $this->cache->copyFrom($cacheKey, $fileName); + } + + $response->collect(); + + return $fileName; + }; + + $reject = function ($e) use (&$urls, $download, $fileName, $package, &$retries) { + // clean up + if (file_exists($fileName)) { + $this->filesystem->unlink($fileName); + } + $this->clearLastCacheWrite($package); + + if ($e instanceof IrrecoverableDownloadException) { + throw $e; + } + + if ($e instanceof MaxFileSizeExceededException) { + throw $e; + } + + if ($e instanceof TransportException) { + // if we got an http response with a proper code, then requesting again will probably not help, abort + if (0 !== $e->getCode() && !in_array($e->getCode(), [500, 502, 503, 504], true)) { + $retries = 0; + } + } + + // special error code returned when network is being artificially disabled + if ($e instanceof TransportException && $e->getStatusCode() === 499) { + $retries = 0; + $urls = []; + } + + if ($retries > 0) { + usleep(500000); + $retries--; + + return $download(); + } + + array_shift($urls); + if (\count($urls) > 0) { + if ($this->io->isDebug()) { + $this->io->writeError(' Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); + $this->io->writeError(' Trying the next URL for '.$package->getName()); + } else { + $this->io->writeError(' Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')'); + } + + $retries = 3; + usleep(100000); + + return $download(); + } + + throw $e; + }; + + return $download(); + } + + /** + * @inheritDoc + */ + public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + $fileName = $this->getFileName($package, $path); + if (file_exists($fileName)) { + $this->filesystem->unlink($fileName); + } + + $dirsToCleanUp = [ + $path, + $this->config->get('vendor-dir').'/'.explode('/', $package->getPrettyName())[0], + $this->config->get('vendor-dir').'/composer/', + $this->config->get('vendor-dir'), + ]; + + if (isset($this->additionalCleanupPaths[$package->getName()])) { + foreach ($this->additionalCleanupPaths[$package->getName()] as $pathToClean) { + $this->filesystem->remove($pathToClean); + } + } + + foreach ($dirsToCleanUp as $dir) { + if (is_dir($dir) && $this->filesystem->isDirEmpty($dir) && realpath($dir) !== Platform::getCwd()) { + $this->filesystem->removeDirectoryPhp($dir); + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + if ($output) { + $this->io->writeError(" - " . InstallOperation::format($package)); + } + + $vendorDir = $this->config->get('vendor-dir'); + + // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains + // the file to be installed. This is the case when installing with create-project in the current directory + // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here. + if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) { + $this->filesystem->emptyDirectory($path); + } + $this->filesystem->ensureDirectoryExists($path); + $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . $this->getDistPath($package, PATHINFO_BASENAME)); + + // Single files can not have a mode set like files in archives + // so we make sure if the file is a binary that it is executable + foreach ($package->getBinaries() as $bin) { + if (file_exists($path . '/' . $bin) && !is_executable($path . '/' . $bin)) { + Silencer::call('chmod', $path . '/' . $bin, 0777 & ~umask()); + } + } + + return \React\Promise\resolve(null); + } + + /** + * @param PATHINFO_EXTENSION|PATHINFO_BASENAME $component + */ + protected function getDistPath(PackageInterface $package, int $component): string + { + return pathinfo((string) parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), $component); + } + + protected function clearLastCacheWrite(PackageInterface $package): void + { + if ($this->cache !== null && isset($this->lastCacheWrites[$package->getName()])) { + $this->cache->remove($this->lastCacheWrites[$package->getName()]); + unset($this->lastCacheWrites[$package->getName()]); + } + } + + protected function addCleanupPath(PackageInterface $package, string $path): void + { + $this->additionalCleanupPaths[$package->getName()][] = $path; + } + + protected function removeCleanupPath(PackageInterface $package, string $path): void + { + if (isset($this->additionalCleanupPaths[$package->getName()])) { + $idx = array_search($path, $this->additionalCleanupPaths[$package->getName()], true); + if (false !== $idx) { + unset($this->additionalCleanupPaths[$package->getName()][$idx]); + } + } + } + + /** + * @inheritDoc + */ + public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface + { + $this->io->writeError(" - " . UpdateOperation::format($initial, $target) . $this->getInstallOperationAppendix($target, $path)); + + $promise = $this->remove($initial, $path, false); + + return $promise->then(function () use ($target, $path): PromiseInterface { + return $this->install($target, $path, false); + }); + } + + /** + * @inheritDoc + */ + public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + if ($output) { + $this->io->writeError(" - " . UninstallOperation::format($package)); + } + $promise = $this->filesystem->removeDirectoryAsync($path); + + return $promise->then(static function ($result) use ($path): void { + if (!$result) { + throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); + } + }); + } + + /** + * Gets file name for specific package + * + * @param PackageInterface $package package instance + * @param string $path download path + * @return string file name + */ + protected function getFileName(PackageInterface $package, string $path): string + { + $extension = $this->getDistPath($package, PATHINFO_EXTENSION); + if ($extension === '') { + $extension = $package->getDistType(); + } + + return rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . hash('md5', $package . spl_object_hash($package)) . '.' . $extension, '.'); + } + + /** + * Gets appendix message to add to the "- Upgrading x" string being output on update + * + * @param PackageInterface $package package instance + * @param string $path download path + */ + protected function getInstallOperationAppendix(PackageInterface $package, string $path): string + { + return ''; + } + + /** + * Process the download url + * + * @param PackageInterface $package package instance + * @param non-empty-string $url download url + * @throws \RuntimeException If any problem with the url + * @return non-empty-string url + */ + protected function processUrl(PackageInterface $package, string $url): string + { + if (!extension_loaded('openssl') && 0 === strpos($url, 'https:')) { + throw new \RuntimeException('You must enable the openssl extension to download files via https'); + } + + if ($package->getDistReference() !== null) { + $url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference()); + } + + return $url; + } + + /** + * @inheritDoc + * @throws \RuntimeException + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + $prevIO = $this->io; + + $this->io = new NullIO; + $this->io->loadConfiguration($this->config); + $e = null; + $output = ''; + + $targetDir = Filesystem::trimTrailingSlash($path); + try { + if (is_dir($targetDir.'_compare')) { + $this->filesystem->removeDirectory($targetDir.'_compare'); + } + + $promise = $this->download($package, $targetDir.'_compare', null, false); + $promise->then(null, static function ($ex) use (&$e) { + $e = $ex; + }); + $this->httpDownloader->wait(); + if ($e !== null) { + throw $e; + } + $promise = $this->install($package, $targetDir.'_compare', false); + $promise->then(null, static function ($ex) use (&$e) { + $e = $ex; + }); + $this->process->wait(); + if ($e !== null) { + throw $e; + } + + $comparer = new Comparer(); + $comparer->setSource($targetDir.'_compare'); + $comparer->setUpdate($targetDir); + $comparer->doCompare(); + $output = $comparer->getChangedAsString(true); + $this->filesystem->removeDirectory($targetDir.'_compare'); + } catch (\Exception $e) { + } + + $this->io = $prevIO; + + if ($e !== null) { + if ($this->io->isDebug()) { + throw $e; + } + + return 'Failed to detect changes: ['.get_class($e).'] '.$e->getMessage(); + } + + $output = trim($output); + + return strlen($output) > 0 ? $output : null; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/FilesystemException.php b/vendor/composer/composer/src/Composer/Downloader/FilesystemException.php new file mode 100644 index 000000000..8dbc8313e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/FilesystemException.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +/** + * Exception thrown when issues exist on local filesystem + * + * @author Javier Spagnoletti + */ +class FilesystemException extends \Exception +{ + public function __construct(string $message = '', int $code = 0, ?\Exception $previous = null) + { + parent::__construct("Filesystem exception: \n".$message, $code, $previous); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/FossilDownloader.php b/vendor/composer/composer/src/Composer/Downloader/FossilDownloader.php new file mode 100644 index 000000000..89a35a609 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/FossilDownloader.php @@ -0,0 +1,128 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Util\Platform; +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use RuntimeException; + +/** + * @author BohwaZ + */ +class FossilDownloader extends VcsDownloader +{ + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($url, $this->io); + + $repoFile = $path . '.fossil'; + $realPath = Platform::realpath($path); + + $this->io->writeError("Cloning ".$package->getSourceReference()); + $this->execute(['fossil', 'clone', '--', $url, $repoFile]); + $this->execute(['fossil', 'open', '--nested', '--', $repoFile], $realPath); + $this->execute(['fossil', 'update', '--', (string) $package->getSourceReference()], $realPath); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($url, $this->io); + + $this->io->writeError(" Updating to ".$target->getSourceReference()); + + if (!$this->hasMetadataRepository($path)) { + throw new RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); + } + + $realPath = Platform::realpath($path); + $this->execute(['fossil', 'pull'], $realPath); + $this->execute(['fossil', 'up', (string) $target->getSourceReference()], $realPath); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + if (!$this->hasMetadataRepository($path)) { + return null; + } + + $this->process->execute(['fossil', 'changes'], $output, Platform::realpath($path)); + + $output = trim($output); + + return strlen($output) > 0 ? $output : null; + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + $this->execute(['fossil', 'timeline', '-t', 'ci', '-W', '0', '-n', '0', 'before', $toReference], Platform::realpath($path), $output); + + $log = ''; + $match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/'; + + foreach ($this->process->splitLines($output) as $line) { + if (Preg::isMatch($match, $line)) { + break; + } + $log .= $line; + } + + return $log; + } + + /** + * @param non-empty-list $command + * @throws RuntimeException + */ + private function execute(array $command, ?string $cwd = null, ?string &$output = null): void + { + if (0 !== $this->process->execute($command, $output, $cwd)) { + throw new RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + return is_file($path . '/.fslckout') || is_file($path . '/_FOSSIL_'); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/GitDownloader.php b/vendor/composer/composer/src/Composer/Downloader/GitDownloader.php new file mode 100644 index 000000000..cd15044c7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/GitDownloader.php @@ -0,0 +1,634 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Git as GitUtil; +use Composer\Util\Url; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Cache; +use React\Promise\PromiseInterface; + +/** + * @author Jordi Boggiano + */ +class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface +{ + /** + * @var bool[] + * @phpstan-var array + */ + private $hasStashedChanges = []; + /** + * @var bool[] + * @phpstan-var array + */ + private $hasDiscardedChanges = []; + /** + * @var GitUtil + */ + private $gitUtil; + /** + * @var array + * @phpstan-var array> + */ + private $cachedPackages = []; + + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null) + { + parent::__construct($io, $config, $process, $fs); + $this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem); + } + + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + // Do not create an extra local cache when repository is already local + if (Filesystem::isLocalPath($url)) { + return \React\Promise\resolve(null); + } + + GitUtil::cleanEnv($this->process); + + $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; + $gitVersion = GitUtil::getVersion($this->process); + + // --dissociate option is only available since git 2.3.0-rc0 + if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) { + $this->io->writeError(" - Syncing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") into cache"); + $this->io->writeError(sprintf(' Cloning to cache at %s', $cachePath), true, IOInterface::DEBUG); + $ref = $package->getSourceReference(); + if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref, $package->getPrettyVersion()) && is_dir($cachePath)) { + $this->cachedPackages[$package->getId()][$ref] = true; + } + } elseif (null === $gitVersion) { + throw new \RuntimeException('git was not found in your PATH, skipping source download'); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + GitUtil::cleanEnv($this->process); + $path = $this->normalizePath($path); + $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; + $ref = $package->getSourceReference(); + + if (!empty($this->cachedPackages[$package->getId()][$ref])) { + $msg = "Cloning ".$this->getShortHash($ref).' from cache'; + + $cloneFlags = ['--dissociate', '--reference', $cachePath]; + $transportOptions = $package->getTransportOptions(); + if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) { + $cloneFlags = []; + } + + $commands = [ + array_merge(['git', 'clone', '--no-checkout', $cachePath, $path], $cloneFlags), + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ['git', 'remote', 'add', 'composer', '--', '%sanitizedUrl%'], + ]; + } else { + $msg = "Cloning ".$this->getShortHash($ref); + $commands = [ + array_merge(['git', 'clone', '--no-checkout', '--', '%url%', $path]), + ['git', 'remote', 'add', 'composer', '--', '%url%'], + ['git', 'fetch', 'composer'], + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'], + ]; + if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { + throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting'); + } + } + + $this->io->writeError($msg); + + $this->gitUtil->runCommands($commands, $url, $path, true); + + $sourceUrl = $package->getSourceUrl(); + if ($url !== $sourceUrl && $sourceUrl !== null) { + $this->updateOriginUrl($path, $sourceUrl); + } else { + $this->setPushUrl($path, $url); + } + + if ($newRef = $this->updateToCommit($package, $path, (string) $ref, $package->getPrettyVersion())) { + if ($package->getDistReference() === $package->getSourceReference()) { + $package->setDistReference($newRef); + } + $package->setSourceReference($newRef); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + GitUtil::cleanEnv($this->process); + $path = $this->normalizePath($path); + if (!$this->hasMetadataRepository($path)) { + throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); + } + + $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; + $ref = $target->getSourceReference(); + + if (!empty($this->cachedPackages[$target->getId()][$ref])) { + $msg = "Checking out ".$this->getShortHash($ref).' from cache'; + $remoteUrl = $cachePath; + } else { + $msg = "Checking out ".$this->getShortHash($ref); + $remoteUrl = '%url%'; + if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { + throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting'); + } + } + + $this->io->writeError($msg); + + if (0 !== $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $output, $path)) { + $commands = [ + ['git', 'remote', 'set-url', 'composer', '--', $remoteUrl], + ['git', 'fetch', 'composer'], + ['git', 'fetch', '--tags', 'composer'], + ]; + + $this->gitUtil->runCommands($commands, $url, $path); + } + + $command = ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%']; + $this->gitUtil->runCommands([$command], $url, $path); + + if ($newRef = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) { + if ($target->getDistReference() === $target->getSourceReference()) { + $target->setDistReference($newRef); + } + $target->setSourceReference($newRef); + } + + $updateOriginUrl = false; + if ( + 0 === $this->process->execute(['git', 'remote', '-v'], $output, $path) + && Preg::isMatch('{^origin\s+(?P\S+)}m', $output, $originMatch) + && Preg::isMatch('{^composer\s+(?P\S+)}m', $output, $composerMatch) + ) { + if ($originMatch['url'] === $composerMatch['url'] && $composerMatch['url'] !== $target->getSourceUrl()) { + $updateOriginUrl = true; + } + } + if ($updateOriginUrl && $target->getSourceUrl() !== null) { + $this->updateOriginUrl($path, $target->getSourceUrl()); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + GitUtil::cleanEnv($this->process); + if (!$this->hasMetadataRepository($path)) { + return null; + } + + $command = ['git', 'status', '--porcelain', '--untracked-files=no']; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + $output = trim($output); + + return strlen($output) > 0 ? $output : null; + } + + public function getUnpushedChanges(PackageInterface $package, string $path): ?string + { + GitUtil::cleanEnv($this->process); + $path = $this->normalizePath($path); + if (!$this->hasMetadataRepository($path)) { + return null; + } + + $command = ['git', 'show-ref', '--head', '-d']; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + $refs = trim($output); + if (!Preg::isMatchStrictGroups('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) { + // could not match the HEAD for some reason + return null; + } + + $headRef = $match[1]; + if (!Preg::isMatchAllStrictGroups('{^'.preg_quote($headRef).' refs/heads/(.+)$}mi', $refs, $matches)) { + // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this + return null; + } + + $candidateBranches = $matches[1]; + // use the first match as branch name for now + $branch = $candidateBranches[0]; + $unpushedChanges = null; + $branchNotFoundError = false; + + // do two passes, as if we find anything we want to fetch and then re-try + for ($i = 0; $i <= 1; $i++) { + $remoteBranches = []; + + // try to find matching branch names in remote repos + foreach ($candidateBranches as $candidate) { + if (Preg::isMatchAllStrictGroups('{^[a-f0-9]+ refs/remotes/((?:[^/]+)/'.preg_quote($candidate).')$}mi', $refs, $matches)) { + foreach ($matches[1] as $match) { + $branch = $candidate; + $remoteBranches[] = $match; + } + break; + } + } + + // if it doesn't exist, then we assume it is an unpushed branch + // this is bad as we have no reference point to do a diff so we just bail listing + // the branch as being unpushed + if (count($remoteBranches) === 0) { + $unpushedChanges = 'Branch ' . $branch . ' could not be found on any remote and appears to be unpushed'; + $branchNotFoundError = true; + } else { + // if first iteration found no remote branch but it has now found some, reset $unpushedChanges + // so we get the real diff output no matter its length + if ($branchNotFoundError) { + $unpushedChanges = null; + } + foreach ($remoteBranches as $remoteBranch) { + $command = ['git', 'diff', '--name-status', $remoteBranch.'...'.$branch, '--']; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + $output = trim($output); + // keep the shortest diff from all remote branches we compare against + if ($unpushedChanges === null || strlen($output) < strlen($unpushedChanges)) { + $unpushedChanges = $output; + } + } + } + + // first pass and we found unpushed changes, fetch from all remotes to make sure we have up to date + // remotes and then try again as outdated remotes can sometimes cause false-positives + if ($unpushedChanges && $i === 0) { + $this->process->execute(['git', 'fetch', '--all'], $output, $path); + + // update list of refs after fetching + $command = ['git', 'show-ref', '--head', '-d']; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + $refs = trim($output); + } + + // abort after first pass if we didn't find anything + if (!$unpushedChanges) { + break; + } + } + + return $unpushedChanges; + } + + /** + * @inheritDoc + */ + protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface + { + GitUtil::cleanEnv($this->process); + $path = $this->normalizePath($path); + + $unpushed = $this->getUnpushedChanges($package, $path); + if ($unpushed && ($this->io->isInteractive() || $this->config->get('discard-changes') !== true)) { + throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch: '."\n".$unpushed); + } + + if (null === ($changes = $this->getLocalChanges($package, $path))) { + return \React\Promise\resolve(null); + } + + if (!$this->io->isInteractive()) { + $discardChanges = $this->config->get('discard-changes'); + if (true === $discardChanges) { + return $this->discardChanges($path); + } + if ('stash' === $discardChanges) { + if (!$update) { + return parent::cleanChanges($package, $path, $update); + } + + return $this->stashChanges($path); + } + + return parent::cleanChanges($package, $path, $update); + } + + $changes = array_map(static function ($elem): string { + return ' '.$elem; + }, Preg::split('{\s*\r?\n\s*}', $changes)); + $this->io->writeError(' '.$package->getPrettyName().' has modified files:'); + $this->io->writeError(array_slice($changes, 0, 10)); + if (count($changes) > 10) { + $this->io->writeError(' ' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list'); + } + + while (true) { + switch ($this->io->ask(' Discard changes [y,n,v,d,'.($update ? 's,' : '').'?]? ', '?')) { + case 'y': + $this->discardChanges($path); + break 2; + + case 's': + if (!$update) { + goto help; + } + + $this->stashChanges($path); + break 2; + + case 'n': + throw new \RuntimeException('Update aborted'); + + case 'v': + $this->io->writeError($changes); + break; + + case 'd': + $this->viewDiff($path); + break; + + case '?': + default: + help : + $this->io->writeError([ + ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), + ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', + ' v - view modified files', + ' d - view local modifications (diff)', + ]); + if ($update) { + $this->io->writeError(' s - stash changes and try to reapply them after the update'); + } + $this->io->writeError(' ? - print help'); + break; + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function reapplyChanges(string $path): void + { + $path = $this->normalizePath($path); + if (!empty($this->hasStashedChanges[$path])) { + unset($this->hasStashedChanges[$path]); + $this->io->writeError(' Re-applying stashed changes'); + if (0 !== $this->process->execute(['git', 'stash', 'pop'], $output, $path)) { + throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput()); + } + } + + unset($this->hasDiscardedChanges[$path]); + } + + /** + * Updates the given path to the given commit ref + * + * @throws \RuntimeException + * @return null|string if a string is returned, it is the commit reference that was checked out if the original could not be found + */ + protected function updateToCommit(PackageInterface $package, string $path, string $reference, string $prettyVersion): ?string + { + $force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? ['-f'] : []; + + // This uses the "--" sequence to separate branch from file parameters. + // + // Otherwise git tries the branch name as well as file name. + // If the non-existent branch is actually the name of a file, the file + // is checked out. + + $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion); + + /** + * @var \Closure(non-empty-list): bool $execute + * @phpstan-ignore varTag.nativeType + */ + $execute = function (array $command) use (&$output, $path) { + /** @var non-empty-list $command */ + $output = ''; + + return 0 === $this->process->execute($command, $output, $path); + }; + + $branches = null; + if ($execute(['git', 'branch', '-r'])) { + $branches = $output; + } + + // check whether non-commitish are branches or tags, and fetch branches with the remote name + $gitRef = $reference; + if (!Preg::isMatch('{^[a-f0-9]{40}$}', $reference) + && null !== $branches + && Preg::isMatch('{^\s+composer/'.preg_quote($reference).'$}m', $branches) + ) { + $command1 = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$reference, '--']); + $command2 = ['git', 'reset', '--hard', 'composer/'.$reference, '--']; + + if ($execute($command1) && $execute($command2)) { + return null; + } + } + + // try to checkout branch by name and then reset it so it's on the proper branch name + if (Preg::isMatch('{^[a-f0-9]{40}$}', $reference)) { + // add 'v' in front of the branch if it was stripped when generating the pretty name + if (null !== $branches && !Preg::isMatch('{^\s+composer/'.preg_quote($branch).'$}m', $branches) && Preg::isMatch('{^\s+composer/v'.preg_quote($branch).'$}m', $branches)) { + $branch = 'v' . $branch; + } + + $command = ['git', 'checkout', $branch, '--']; + $fallbackCommand = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$branch, '--']); + $resetCommand = ['git', 'reset', '--hard', $reference, '--']; + + if (($execute($command) || $execute($fallbackCommand)) && $execute($resetCommand)) { + return null; + } + } + + $command1 = array_merge(['git', 'checkout'], $force, [$gitRef, '--']); + $command2 = ['git', 'reset', '--hard', $gitRef, '--']; + if ($execute($command1) && $execute($command2)) { + return null; + } + + $exceptionExtra = ''; + + // reference was not found (prints "fatal: reference is not a tree: $ref") + if (false !== strpos($this->process->getErrorOutput(), $reference)) { + $this->io->writeError(' '.$reference.' is gone (history was rewritten?)'); + $exceptionExtra = "\nIt looks like the commit hash is not available in the repository, maybe ".($package->isDev() ? 'the commit was removed from the branch' : 'the tag was recreated').'? Run "composer update '.$package->getPrettyName().'" to resolve this.'; + } + + $command = implode(' ', $command1). ' && '.implode(' ', $command2); + + throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() . $exceptionExtra)); + } + + protected function updateOriginUrl(string $path, string $url): void + { + $this->process->execute(['git', 'remote', 'set-url', 'origin', '--', $url], $output, $path); + $this->setPushUrl($path, $url); + } + + protected function setPushUrl(string $path, string $url): void + { + // set push url for github projects + if (Preg::isMatch('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) { + $protocols = $this->config->get('github-protocols'); + $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git'; + if (!in_array('ssh', $protocols, true)) { + $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git'; + } + $cmd = ['git', 'remote', 'set-url', '--push', 'origin', '--', $pushUrl]; + $this->process->execute($cmd, $ignoredOutput, $path); + } + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + $path = $this->normalizePath($path); + $command = GitUtil::buildRevListCommand($this->process, array_merge(['--format=%h - %an: %s', $fromReference.'..'.$toReference], GitUtil::getNoShowSignatureFlags($this->process))); + + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + return GitUtil::parseRevListOutput($output, $this->process); + } + + /** + * @phpstan-return PromiseInterface + * @throws \RuntimeException + */ + protected function discardChanges(string $path): PromiseInterface + { + $path = $this->normalizePath($path); + if (0 !== $this->process->execute(['git', 'clean', '-df'], $output, $path)) { + throw new \RuntimeException("Could not reset changes\n\n:".$output); + } + if (0 !== $this->process->execute(['git', 'reset', '--hard'], $output, $path)) { + throw new \RuntimeException("Could not reset changes\n\n:".$output); + } + + $this->hasDiscardedChanges[$path] = true; + + return \React\Promise\resolve(null); + } + + /** + * @phpstan-return PromiseInterface + * @throws \RuntimeException + */ + protected function stashChanges(string $path): PromiseInterface + { + $path = $this->normalizePath($path); + if (0 !== $this->process->execute(['git', 'stash', '--include-untracked'], $output, $path)) { + throw new \RuntimeException("Could not stash changes\n\n:".$output); + } + + $this->hasStashedChanges[$path] = true; + + return \React\Promise\resolve(null); + } + + /** + * @throws \RuntimeException + */ + protected function viewDiff(string $path): void + { + $path = $this->normalizePath($path); + if (0 !== $this->process->execute(['git', 'diff', 'HEAD'], $output, $path)) { + throw new \RuntimeException("Could not view diff\n\n:".$output); + } + + $this->io->writeError($output); + } + + protected function normalizePath(string $path): string + { + if (Platform::isWindows() && strlen($path) > 0) { + $basePath = $path; + $removed = []; + + while (!is_dir($basePath) && $basePath !== '\\') { + array_unshift($removed, basename($basePath)); + $basePath = dirname($basePath); + } + + if ($basePath === '\\') { + return $path; + } + + $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/'); + } + + return $path; + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + $path = $this->normalizePath($path); + + return is_dir($path.'/.git'); + } + + protected function getShortHash(string $reference): string + { + if (!$this->io->isVerbose() && Preg::isMatch('{^[0-9a-f]{40}$}', $reference)) { + return substr($reference, 0, 10); + } + + return $reference; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/GzipDownloader.php b/vendor/composer/composer/src/Composer/Downloader/GzipDownloader.php new file mode 100644 index 000000000..03e0a205d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/GzipDownloader.php @@ -0,0 +1,66 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Util\Platform; + +/** + * GZip archive downloader. + * + * @author Pavel Puchkin + */ +class GzipDownloader extends ArchiveDownloader +{ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + $filename = pathinfo(parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), PATHINFO_FILENAME); + $targetFilepath = $path . DIRECTORY_SEPARATOR . $filename; + + // Try to use gunzip on *nix + if (!Platform::isWindows()) { + $command = ['sh', '-c', 'gzip -cd -- "$0" > "$1"', $file, $targetFilepath]; + + if (0 === $this->process->execute($command, $ignoredOutput)) { + return \React\Promise\resolve(null); + } + + if (extension_loaded('zlib')) { + // Fallback to using the PHP extension. + $this->extractUsingExt($file, $targetFilepath); + + return \React\Promise\resolve(null); + } + + $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); + throw new \RuntimeException($processError); + } + + // Windows version of PHP has built-in support of gzip functions + $this->extractUsingExt($file, $targetFilepath); + + return \React\Promise\resolve(null); + } + + private function extractUsingExt(string $file, string $targetFilepath): void + { + $archiveFile = gzopen($file, 'rb'); + $targetFile = fopen($targetFilepath, 'wb'); + while ($string = gzread($archiveFile, 4096)) { + fwrite($targetFile, $string, Platform::strlen($string)); + } + gzclose($archiveFile); + fclose($targetFile); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/HgDownloader.php b/vendor/composer/composer/src/Composer/Downloader/HgDownloader.php new file mode 100644 index 000000000..6b01feb2a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/HgDownloader.php @@ -0,0 +1,121 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Util\Hg as HgUtils; + +/** + * @author Per Bernhardt + */ +class HgDownloader extends VcsDownloader +{ + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + if (null === HgUtils::getVersion($this->process)) { + throw new \RuntimeException('hg was not found in your PATH, skipping source download'); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + $hgUtils = new HgUtils($this->io, $this->config, $this->process); + + $cloneCommand = static function (string $url) use ($path): array { + return ['hg', 'clone', '--', $url, $path]; + }; + + $hgUtils->runCommand($cloneCommand, $url, $path); + + $command = ['hg', 'up', '--', (string) $package->getSourceReference()]; + if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + $hgUtils = new HgUtils($this->io, $this->config, $this->process); + + $ref = $target->getSourceReference(); + $this->io->writeError(" Updating to ".$target->getSourceReference()); + + if (!$this->hasMetadataRepository($path)) { + throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); + } + + $command = static function ($url): array { + return ['hg', 'pull', '--', $url]; + }; + $hgUtils->runCommand($command, $url, $path); + + $command = static function () use ($ref): array { + return ['hg', 'up', '--', $ref]; + }; + $hgUtils->runCommand($command, $url, $path); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + if (!is_dir($path.'/.hg')) { + return null; + } + + $this->process->execute(['hg', 'st'], $output, realpath($path)); + + $output = trim($output); + + return strlen($output) > 0 ? $output : null; + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + $command = ['hg', 'log', '-r', $fromReference.':'.$toReference, '--style', 'compact']; + + if (0 !== $this->process->execute($command, $output, realpath($path))) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + return $output; + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + return is_dir($path . '/.hg'); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php b/vendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php new file mode 100644 index 000000000..e57e7affd --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php @@ -0,0 +1,17 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +class MaxFileSizeExceededException extends TransportException +{ +} diff --git a/vendor/composer/composer/src/Composer/Downloader/PathDownloader.php b/vendor/composer/composer/src/Composer/Downloader/PathDownloader.php new file mode 100644 index 000000000..f71ea2568 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/PathDownloader.php @@ -0,0 +1,335 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\Archiver\ArchivableFilesFinder; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Util\Platform; +use Composer\Util\Filesystem; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; + +/** + * Download a package from a local path. + * + * @author Samuel Roze + * @author Johann Reinke + */ +class PathDownloader extends FileDownloader implements VcsCapableDownloaderInterface +{ + private const STRATEGY_SYMLINK = 10; + private const STRATEGY_MIRROR = 20; + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface + { + $path = Filesystem::trimTrailingSlash($path); + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot download.'); + } + $realUrl = realpath($url); + if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) { + throw new \RuntimeException(sprintf( + 'Source path "%s" is not found for package %s', + $url, + $package->getName() + )); + } + + if (realpath($path) === $realUrl) { + return \React\Promise\resolve(null); + } + + if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) { + // IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours. + // + // Please see https://github.com/composer/composer/pull/5974 and https://github.com/composer/composer/pull/6174 + // for previous attempts that were shut down because they did not work well enough or introduced too many risks. + throw new \RuntimeException(sprintf( + 'Package %s cannot install to "%s" inside its source at "%s"', + $package->getName(), + realpath($path), + $realUrl + )); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + $path = Filesystem::trimTrailingSlash($path); + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.'); + } + $realUrl = realpath($url); + if (false === $realUrl) { + throw new \RuntimeException('Failed to realpath '.$url); + } + + if (realpath($path) === $realUrl) { + if ($output) { + $this->io->writeError(" - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path)); + } + + return \React\Promise\resolve(null); + } + + // Get the transport options with default values + $transportOptions = $package->getTransportOptions() + ['relative' => true]; + + [$currentStrategy, $allowedStrategies] = $this->computeAllowedStrategies($transportOptions); + + $symfonyFilesystem = new SymfonyFilesystem(); + $this->filesystem->removeDirectory($path); + + if ($output) { + $this->io->writeError(" - " . InstallOperation::format($package).': ', false); + } + + $isFallback = false; + if (self::STRATEGY_SYMLINK === $currentStrategy) { + try { + if (Platform::isWindows()) { + // Implement symlinks as NTFS junctions on Windows + if ($output) { + $this->io->writeError(sprintf('Junctioning from %s', $url), false); + } + $this->filesystem->junction($realUrl, $path); + } else { + $path = rtrim($path, "/"); + if ($output) { + $this->io->writeError(sprintf('Symlinking from %s', $url), false); + } + if ($transportOptions['relative'] === true) { + $absolutePath = $path; + if (!$this->filesystem->isAbsolutePath($absolutePath)) { + $absolutePath = Platform::getCwd() . DIRECTORY_SEPARATOR . $path; + } + $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl, false, true); + $symfonyFilesystem->symlink($shortestPath.'/', $path); + } else { + $symfonyFilesystem->symlink($realUrl.'/', $path); + } + } + } catch (IOException $e) { + if (in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { + if ($output) { + $this->io->writeError(''); + $this->io->writeError(' Symlink failed, fallback to use mirroring!'); + } + $currentStrategy = self::STRATEGY_MIRROR; + $isFallback = true; + } else { + throw new \RuntimeException(sprintf('Symlink from "%s" to "%s" failed!', $realUrl, $path)); + } + } + } + + // Fallback if symlink failed or if symlink is not allowed for the package + if (self::STRATEGY_MIRROR === $currentStrategy) { + $realUrl = $this->filesystem->normalizePath($realUrl); + + if ($output) { + $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? ' ' : '', $url), false); + } + $iterator = new ArchivableFilesFinder($realUrl, []); + $symfonyFilesystem->mirror($realUrl, $path, $iterator); + } + + if ($output) { + $this->io->writeError(''); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + $path = Filesystem::trimTrailingSlash($path); + /** + * realpath() may resolve Windows junctions to the source path, so we'll check for a junction first + * to prevent a false positive when checking if the dist and install paths are the same. + * See https://bugs.php.net/bug.php?id=77639 + * + * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process + * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which + * is disastrous within a junction. So in that case we have no other real choice but to fail hard. + */ + if (Platform::isWindows() && $this->filesystem->isJunction($path)) { + if ($output) { + $this->io->writeError(" - " . UninstallOperation::format($package).", source is still present in $path"); + } + if (!$this->filesystem->removeJunction($path)) { + $this->io->writeError(" Could not remove junction at " . $path . " - is another process locking it?"); + throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName()); + } + + return \React\Promise\resolve(null); + } + + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot remove.'); + } + + // ensure that the source path (dist url) is not the same as the install path, which + // can happen when using custom installers, see https://github.com/composer/composer/pull/9116 + // not using realpath here as we do not want to resolve the symlink to the original dist url + // it points to + $fs = new Filesystem; + $absPath = $fs->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path; + $absDistUrl = $fs->isAbsolutePath($url) ? $url : Platform::getCwd() . '/' . $url; + if ($fs->normalizePath($absPath) === $fs->normalizePath($absDistUrl)) { + if ($output) { + $this->io->writeError(" - " . UninstallOperation::format($package).", source is still present in $path"); + } + + return \React\Promise\resolve(null); + } + + return parent::remove($package, $path, $output); + } + + /** + * @inheritDoc + */ + public function getVcsReference(PackageInterface $package, string $path): ?string + { + $path = Filesystem::trimTrailingSlash($path); + $parser = new VersionParser; + $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io); + $dumper = new ArrayDumper; + + $packageConfig = $dumper->dump($package); + $packageVersion = $guesser->guessVersion($packageConfig, $path); + if ($packageVersion !== null) { + return $packageVersion['commit']; + } + + return null; + } + + /** + * @inheritDoc + */ + protected function getInstallOperationAppendix(PackageInterface $package, string $path): string + { + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.'); + } + $realUrl = realpath($url); + if (false === $realUrl) { + throw new \RuntimeException('Failed to realpath '.$url); + } + + if (realpath($path) === $realUrl) { + return ': Source already present'; + } + + [$currentStrategy] = $this->computeAllowedStrategies($package->getTransportOptions()); + + if ($currentStrategy === self::STRATEGY_SYMLINK) { + if (Platform::isWindows()) { + return ': Junctioning from '.$package->getDistUrl(); + } + + return ': Symlinking from '.$package->getDistUrl(); + } + + return ': Mirroring from '.$package->getDistUrl(); + } + + /** + * @param mixed[] $transportOptions + * + * @phpstan-return array{self::STRATEGY_*, non-empty-list} + */ + private function computeAllowedStrategies(array $transportOptions): array + { + // When symlink transport option is null, both symlink and mirror are allowed + $currentStrategy = self::STRATEGY_SYMLINK; + $allowedStrategies = [self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR]; + + $mirrorPathRepos = Platform::getEnv('COMPOSER_MIRROR_PATH_REPOS'); + if ((bool) $mirrorPathRepos) { + $currentStrategy = self::STRATEGY_MIRROR; + } + + $symlinkOption = $transportOptions['symlink'] ?? null; + + if (true === $symlinkOption) { + $currentStrategy = self::STRATEGY_SYMLINK; + $allowedStrategies = [self::STRATEGY_SYMLINK]; + } elseif (false === $symlinkOption) { + $currentStrategy = self::STRATEGY_MIRROR; + $allowedStrategies = [self::STRATEGY_MIRROR]; + } + + // Check we can use junctions safely if we are on Windows + if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) { + if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { + throw new \RuntimeException('You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed'); + } + $currentStrategy = self::STRATEGY_MIRROR; + $allowedStrategies = [self::STRATEGY_MIRROR]; + } + + // Check we can use symlink() otherwise + if (!Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !function_exists('symlink')) { + if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { + throw new \RuntimeException('Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed'); + } + $currentStrategy = self::STRATEGY_MIRROR; + $allowedStrategies = [self::STRATEGY_MIRROR]; + } + + return [$currentStrategy, $allowedStrategies]; + } + + /** + * Returns true if junctions can be created and safely used on Windows + * + * A PHP bug makes junction detection fragile, leading to possible data loss + * when removing a package. See https://bugs.php.net/bug.php?id=77552 + * + * For safety we require a minimum version of Windows 7, so we can call the + * system rmdir which will preserve target content if given a junction. + * + * The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7). + */ + private function safeJunctions(): bool + { + // We need to call mklink, and rmdir on Windows 7 (version 6.1) + return function_exists('proc_open') && + (PHP_WINDOWS_VERSION_MAJOR > 6 || + (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1)); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php b/vendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php new file mode 100644 index 000000000..faf159e3f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php @@ -0,0 +1,128 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Repository\VcsRepository; +use Composer\Util\Perforce; + +/** + * @author Matt Whittom + */ +class PerforceDownloader extends VcsDownloader +{ + /** @var Perforce|null */ + protected $perforce; + + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + $ref = $package->getSourceReference(); + $label = $this->getLabelFromSourceReference((string) $ref); + + $this->io->writeError('Cloning ' . $ref); + $this->initPerforce($package, $path, $url); + $this->perforce->setStream($ref); + $this->perforce->p4Login(); + $this->perforce->writeP4ClientSpec(); + $this->perforce->connectClient(); + $this->perforce->syncCodeBase($label); + $this->perforce->cleanupClientSpec(); + + return \React\Promise\resolve(null); + } + + private function getLabelFromSourceReference(string $ref): ?string + { + $pos = strpos($ref, '@'); + if (false !== $pos) { + return substr($ref, $pos + 1); + } + + return null; + } + + public function initPerforce(PackageInterface $package, string $path, string $url): void + { + if (!empty($this->perforce)) { + $this->perforce->initializePath($path); + + return; + } + + $repository = $package->getRepository(); + $repoConfig = null; + if ($repository instanceof VcsRepository) { + $repoConfig = $this->getRepoConfig($repository); + } + $this->perforce = Perforce::create($repoConfig, $url, $path, $this->process, $this->io); + } + + /** + * @return array + */ + private function getRepoConfig(VcsRepository $repository): array + { + return $repository->getRepoConfig(); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + return $this->doInstall($target, $path, $url); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + $this->io->writeError('Perforce driver does not check for local changes before overriding'); + + return null; + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + return $this->perforce->getCommitLogs($fromReference, $toReference); + } + + public function setPerforce(Perforce $perforce): void + { + $this->perforce = $perforce; + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/PharDownloader.php b/vendor/composer/composer/src/Composer/Downloader/PharDownloader.php new file mode 100644 index 000000000..e0ae4fae1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/PharDownloader.php @@ -0,0 +1,41 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; + +/** + * Downloader for phar files + * + * @author Kirill chEbba Chebunin + */ +class PharDownloader extends ArchiveDownloader +{ + /** + * @inheritDoc + */ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + // Can throw an UnexpectedValueException + $archive = new \Phar($file); + $archive->extractTo($path, null, true); + /* TODO: handle openssl signed phars + * https://github.com/composer/composer/pull/33#issuecomment-2250768 + * https://github.com/koto/phar-util + * http://blog.kotowicz.net/2010/08/hardening-php-how-to-securely-include.html + */ + + return \React\Promise\resolve(null); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/RarDownloader.php b/vendor/composer/composer/src/Composer/Downloader/RarDownloader.php new file mode 100644 index 000000000..1ee5948c3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/RarDownloader.php @@ -0,0 +1,81 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Util\IniHelper; +use Composer\Util\Platform; +use Composer\Package\PackageInterface; +use RarArchive; + +/** + * RAR archive downloader. + * + * Based on previous work by Jordi Boggiano ({@see ZipDownloader}). + * + * @author Derrick Nelson + */ +class RarDownloader extends ArchiveDownloader +{ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + $processError = null; + + // Try to use unrar on *nix + if (!Platform::isWindows()) { + $command = ['sh', '-c', 'unrar x -- "$0" "$1" >/dev/null && chmod -R u+w "$1"', $file, $path]; + + if (0 === $this->process->execute($command, $ignoredOutput)) { + return \React\Promise\resolve(null); + } + + $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); + } + + if (!class_exists('RarArchive')) { + // php.ini path is added to the error message to help users find the correct file + $iniMessage = IniHelper::getMessage(); + + $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n" + . $iniMessage . "\n" . $processError; + + if (!Platform::isWindows()) { + $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage; + } + + throw new \RuntimeException($error); + } + + $rarArchive = RarArchive::open($file); + + if (false === $rarArchive) { + throw new \UnexpectedValueException('Could not open RAR archive: ' . $file); + } + + $entries = $rarArchive->getEntries(); + + if (false === $entries) { + throw new \RuntimeException('Could not retrieve RAR archive entries'); + } + + foreach ($entries as $entry) { + if (false === $entry->extract($path)) { + throw new \RuntimeException('Could not extract entry'); + } + } + + $rarArchive->close(); + + return \React\Promise\resolve(null); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/SvnDownloader.php b/vendor/composer/composer/src/Composer/Downloader/SvnDownloader.php new file mode 100644 index 000000000..a0a9acfad --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/SvnDownloader.php @@ -0,0 +1,251 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\Svn as SvnUtil; +use Composer\Repository\VcsRepository; +use React\Promise\PromiseInterface; + +/** + * @author Ben Bieker + * @author Till Klampaeckel + */ +class SvnDownloader extends VcsDownloader +{ + /** @var bool */ + protected $cacheCredentials = true; + + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + SvnUtil::cleanEnv(); + $util = new SvnUtil($url, $this->io, $this->config, $this->process); + if (null === $util->binaryVersion()) { + throw new \RuntimeException('svn was not found in your PATH, skipping source download'); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + SvnUtil::cleanEnv(); + $ref = $package->getSourceReference(); + + $repo = $package->getRepository(); + if ($repo instanceof VcsRepository) { + $repoConfig = $repo->getRepoConfig(); + if (array_key_exists('svn-cache-credentials', $repoConfig)) { + $this->cacheCredentials = (bool) $repoConfig['svn-cache-credentials']; + } + } + + $this->io->writeError(" Checking out ".$package->getSourceReference()); + $this->execute($package, $url, ['svn', 'co'], sprintf("%s/%s", $url, $ref), null, $path); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + SvnUtil::cleanEnv(); + $ref = $target->getSourceReference(); + + if (!$this->hasMetadataRepository($path)) { + throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); + } + + $util = new SvnUtil($url, $this->io, $this->config, $this->process); + $flags = []; + if (version_compare($util->binaryVersion(), '1.7.0', '>=')) { + $flags[] = '--ignore-ancestry'; + } + + $this->io->writeError(" Checking out " . $ref); + $this->execute($target, $url, array_merge(['svn', 'switch'], $flags), sprintf("%s/%s", $url, $ref), $path); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + if (!$this->hasMetadataRepository($path)) { + return null; + } + + $this->process->execute(['svn', 'status', '--ignore-externals'], $output, $path); + + return Preg::isMatch('{^ *[^X ] +}m', $output) ? $output : null; + } + + /** + * Execute an SVN command and try to fix up the process with credentials + * if necessary. + * + * @param string $baseUrl Base URL of the repository + * @param non-empty-list $command SVN command to run + * @param string $url SVN url + * @param string $cwd Working directory + * @param string $path Target for a checkout + * @throws \RuntimeException + */ + protected function execute(PackageInterface $package, string $baseUrl, array $command, string $url, ?string $cwd = null, ?string $path = null): string + { + $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); + $util->setCacheCredentials($this->cacheCredentials); + try { + return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose()); + } catch (\RuntimeException $e) { + throw new \RuntimeException( + $package->getPrettyName().' could not be downloaded, '.$e->getMessage() + ); + } + } + + /** + * @inheritDoc + */ + protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface + { + if (null === ($changes = $this->getLocalChanges($package, $path))) { + return \React\Promise\resolve(null); + } + + if (!$this->io->isInteractive()) { + if (true === $this->config->get('discard-changes')) { + return $this->discardChanges($path); + } + + return parent::cleanChanges($package, $path, $update); + } + + $changes = array_map(static function ($elem): string { + return ' '.$elem; + }, Preg::split('{\s*\r?\n\s*}', $changes)); + $countChanges = count($changes); + $this->io->writeError(sprintf(' '.$package->getPrettyName().' has modified file%s:', $countChanges === 1 ? '' : 's')); + $this->io->writeError(array_slice($changes, 0, 10)); + if ($countChanges > 10) { + $remainingChanges = $countChanges - 10; + $this->io->writeError( + sprintf( + ' '.$remainingChanges.' more file%s modified, choose "v" to view the full list', + $remainingChanges === 1 ? '' : 's' + ) + ); + } + + while (true) { + switch ($this->io->ask(' Discard changes [y,n,v,?]? ', '?')) { + case 'y': + $this->discardChanges($path); + break 2; + + case 'n': + throw new \RuntimeException('Update aborted'); + + case 'v': + $this->io->writeError($changes); + break; + + case '?': + default: + $this->io->writeError([ + ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), + ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', + ' v - view modified files', + ' ? - print help', + ]); + break; + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + if (Preg::isMatch('{@(\d+)$}', $fromReference) && Preg::isMatch('{@(\d+)$}', $toReference)) { + // retrieve the svn base url from the checkout folder + $command = ['svn', 'info', '--non-interactive', '--xml', '--', $path]; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException( + 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput() + ); + } + + $urlPattern = '#(.*)#'; + if (Preg::isMatchStrictGroups($urlPattern, $output, $matches)) { + $baseUrl = $matches[1]; + } else { + throw new \RuntimeException( + 'Unable to determine svn url for path '. $path + ); + } + + // strip paths from references and only keep the actual revision + $fromRevision = Preg::replace('{.*@(\d+)$}', '$1', $fromReference); + $toRevision = Preg::replace('{.*@(\d+)$}', '$1', $toReference); + + $command = ['svn', 'log', '-r', $fromRevision.':'.$toRevision, '--incremental']; + + $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); + $util->setCacheCredentials($this->cacheCredentials); + try { + return $util->executeLocal($command, $path, null, $this->io->isVerbose()); + } catch (\RuntimeException $e) { + throw new \RuntimeException( + 'Failed to execute ' . implode(' ', $command) . "\n\n".$e->getMessage() + ); + } + } + + return "Could not retrieve changes between $fromReference and $toReference due to missing revision information"; + } + + /** + * @phpstan-return PromiseInterface + */ + protected function discardChanges(string $path): PromiseInterface + { + if (0 !== $this->process->execute(['svn', 'revert', '-R', '.'], $output, $path)) { + throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + return is_dir($path.'/.svn'); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/TarDownloader.php b/vendor/composer/composer/src/Composer/Downloader/TarDownloader.php new file mode 100644 index 000000000..0a16f3ade --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/TarDownloader.php @@ -0,0 +1,36 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use React\Promise\PromiseInterface; + +/** + * Downloader for tar files: tar, tar.gz or tar.bz2 + * + * @author Kirill chEbba Chebunin + */ +class TarDownloader extends ArchiveDownloader +{ + /** + * @inheritDoc + */ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + // Can throw an UnexpectedValueException + $archive = new \PharData($file); + $archive->extractTo($path, null, true); + + return \React\Promise\resolve(null); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/TransportException.php b/vendor/composer/composer/src/Composer/Downloader/TransportException.php new file mode 100644 index 000000000..289450ce5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/TransportException.php @@ -0,0 +1,88 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +/** + * @author Jordi Boggiano + */ +class TransportException extends \RuntimeException +{ + /** @var ?array */ + protected $headers; + /** @var ?string */ + protected $response; + /** @var ?int */ + protected $statusCode; + /** @var array */ + protected $responseInfo = []; + + public function __construct(string $message = "", int $code = 400, ?\Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } + + /** + * @param array $headers + */ + public function setHeaders(array $headers): void + { + $this->headers = $headers; + } + + /** + * @return ?array + */ + public function getHeaders(): ?array + { + return $this->headers; + } + + public function setResponse(?string $response): void + { + $this->response = $response; + } + + public function getResponse(): ?string + { + return $this->response; + } + + /** + * @param ?int $statusCode + */ + public function setStatusCode($statusCode): void + { + $this->statusCode = $statusCode; + } + + public function getStatusCode(): ?int + { + return $this->statusCode; + } + + /** + * @return array + */ + public function getResponseInfo(): array + { + return $this->responseInfo; + } + + /** + * @param array $responseInfo + */ + public function setResponseInfo(array $responseInfo): void + { + $this->responseInfo = $responseInfo; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php b/vendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php new file mode 100644 index 000000000..c99005aa4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; + +/** + * VCS Capable Downloader interface. + * + * @author Steve Buzonas + */ +interface VcsCapableDownloaderInterface +{ + /** + * Gets the VCS Reference for the package at path + * + * @param PackageInterface $package package instance + * @param string $path package directory + * @return string|null reference or null + */ + public function getVcsReference(PackageInterface $package, string $path): ?string; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/VcsDownloader.php b/vendor/composer/composer/src/Composer/Downloader/VcsDownloader.php new file mode 100644 index 000000000..626bcb5c5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/VcsDownloader.php @@ -0,0 +1,363 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Config; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Util\ProcessExecutor; +use Composer\IO\IOInterface; +use Composer\Util\Filesystem; +use React\Promise\PromiseInterface; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; + +/** + * @author Jordi Boggiano + */ +abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface, VcsCapableDownloaderInterface +{ + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var Filesystem */ + protected $filesystem; + /** @var array */ + protected $hasCleanedChanges = []; + + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null) + { + $this->io = $io; + $this->config = $config; + $this->process = $process ?? new ProcessExecutor($io); + $this->filesystem = $fs ?? new Filesystem($this->process); + } + + /** + * @inheritDoc + */ + public function getInstallationSource(): string + { + return 'source'; + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + if (!$package->getSourceReference()) { + throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); + } + + $urls = $this->prepareUrls($package->getSourceUrls()); + + while ($url = array_shift($urls)) { + try { + return $this->doDownload($package, $path, $url, $prevPackage); + } catch (\Exception $e) { + // rethrow phpunit exceptions to avoid hard to debug bug failures + if ($e instanceof \PHPUnit\Framework\Exception) { + throw $e; + } + if ($this->io->isDebug()) { + $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); + } elseif (count($urls)) { + $this->io->writeError(' Failed, trying the next URL'); + } + if (!count($urls)) { + throw $e; + } + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + if ($type === 'update') { + $this->cleanChanges($prevPackage, $path, true); + $this->hasCleanedChanges[$prevPackage->getUniqueName()] = true; + } elseif ($type === 'install') { + $this->filesystem->emptyDirectory($path); + } elseif ($type === 'uninstall') { + $this->cleanChanges($package, $path, false); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + if ($type === 'update' && isset($this->hasCleanedChanges[$prevPackage->getUniqueName()])) { + $this->reapplyChanges($path); + unset($this->hasCleanedChanges[$prevPackage->getUniqueName()]); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(PackageInterface $package, string $path): PromiseInterface + { + if (!$package->getSourceReference()) { + throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); + } + + $this->io->writeError(" - " . InstallOperation::format($package).': ', false); + + $urls = $this->prepareUrls($package->getSourceUrls()); + while ($url = array_shift($urls)) { + try { + $this->doInstall($package, $path, $url); + break; + } catch (\Exception $e) { + // rethrow phpunit exceptions to avoid hard to debug bug failures + if ($e instanceof \PHPUnit\Framework\Exception) { + throw $e; + } + if ($this->io->isDebug()) { + $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); + } elseif (count($urls)) { + $this->io->writeError(' Failed, trying the next URL'); + } + if (!count($urls)) { + throw $e; + } + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface + { + if (!$target->getSourceReference()) { + throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information'); + } + + $this->io->writeError(" - " . UpdateOperation::format($initial, $target).': ', false); + + $urls = $this->prepareUrls($target->getSourceUrls()); + + $exception = null; + while ($url = array_shift($urls)) { + try { + $this->doUpdate($initial, $target, $path, $url); + + $exception = null; + break; + } catch (\Exception $exception) { + // rethrow phpunit exceptions to avoid hard to debug bug failures + if ($exception instanceof \PHPUnit\Framework\Exception) { + throw $exception; + } + if ($this->io->isDebug()) { + $this->io->writeError('Failed: ['.get_class($exception).'] '.$exception->getMessage()); + } elseif (count($urls)) { + $this->io->writeError(' Failed, trying the next URL'); + } + } + } + + // print the commit logs if in verbose mode and VCS metadata is present + // because in case of missing metadata code would trigger another exception + if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) { + $message = 'Pulling in changes:'; + $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path); + + if ('' === trim($logs)) { + $message = 'Rolling back changes:'; + $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path); + } + + if ('' !== trim($logs)) { + $logs = implode("\n", array_map(static function ($line): string { + return ' ' . $line; + }, explode("\n", $logs))); + + // escape angle brackets for proper output in the console + $logs = str_replace('<', '\<', $logs); + + $this->io->writeError(' '.$message); + $this->io->writeError($logs); + } + } + + if (!$urls && $exception) { + throw $exception; + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function remove(PackageInterface $package, string $path): PromiseInterface + { + $this->io->writeError(" - " . UninstallOperation::format($package)); + + $promise = $this->filesystem->removeDirectoryAsync($path); + + return $promise->then(static function (bool $result) use ($path) { + if (!$result) { + throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); + } + }); + } + + /** + * @inheritDoc + */ + public function getVcsReference(PackageInterface $package, string $path): ?string + { + $parser = new VersionParser; + $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io); + $dumper = new ArrayDumper; + + $packageConfig = $dumper->dump($package); + if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { + return $packageVersion['commit']; + } + + return null; + } + + /** + * Prompt the user to check if changes should be stashed/removed or the operation aborted + * + * @param bool $update if true (update) the changes can be stashed and reapplied after an update, + * if false (remove) the changes should be assumed to be lost if the operation is not aborted + * + * @throws \RuntimeException in case the operation must be aborted + * @phpstan-return PromiseInterface + */ + protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface + { + // the default implementation just fails if there are any changes, override in child classes to provide stash-ability + if (null !== $this->getLocalChanges($package, $path)) { + throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.'); + } + + return \React\Promise\resolve(null); + } + + /** + * Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not) + * + * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly + */ + protected function reapplyChanges(string $path): void + { + } + + /** + * Downloads data needed to run an install/update later + * + * @param PackageInterface $package package instance + * @param string $path download path + * @param string $url package url + * @param PackageInterface|null $prevPackage previous package (in case of an update) + * @phpstan-return PromiseInterface + */ + abstract protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface; + + /** + * Downloads specific package into specific folder. + * + * @param PackageInterface $package package instance + * @param string $path download path + * @param string $url package url + * @phpstan-return PromiseInterface + */ + abstract protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface; + + /** + * Updates specific package in specific folder from initial to target version. + * + * @param PackageInterface $initial initial package + * @param PackageInterface $target updated package + * @param string $path download path + * @param string $url package url + * @phpstan-return PromiseInterface + */ + abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface; + + /** + * Fetches the commit logs between two commits + * + * @param string $fromReference the source reference + * @param string $toReference the target reference + * @param string $path the package path + */ + abstract protected function getCommitLogs(string $fromReference, string $toReference, string $path): string; + + /** + * Checks if VCS metadata repository has been initialized + * repository example: .git|.svn|.hg + */ + abstract protected function hasMetadataRepository(string $path): bool; + + /** + * @param string[] $urls + * + * @return string[] + */ + private function prepareUrls(array $urls): array + { + foreach ($urls as $index => $url) { + if (Filesystem::isLocalPath($url)) { + // realpath() below will not understand + // url that starts with "file://" + $fileProtocol = 'file://'; + $isFileProtocol = false; + if (0 === strpos($url, $fileProtocol)) { + $url = substr($url, strlen($fileProtocol)); + $isFileProtocol = true; + } + + // realpath() below will not understand %20 spaces etc. + if (false !== strpos($url, '%')) { + $url = rawurldecode($url); + } + + $urls[$index] = realpath($url); + + if ($isFileProtocol) { + $urls[$index] = $fileProtocol . $urls[$index]; + } + } + } + + return $urls; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/XzDownloader.php b/vendor/composer/composer/src/Composer/Downloader/XzDownloader.php new file mode 100644 index 000000000..76560b37e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/XzDownloader.php @@ -0,0 +1,38 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; + +/** + * Xz archive downloader. + * + * @author Pavel Puchkin + * @author Pierre Rudloff + */ +class XzDownloader extends ArchiveDownloader +{ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + $command = ['tar', '-xJf', $file, '-C', $path]; + + if (0 === $this->process->execute($command, $ignoredOutput)) { + return \React\Promise\resolve(null); + } + + $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); + + throw new \RuntimeException($processError); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/ZipDownloader.php b/vendor/composer/composer/src/Composer/Downloader/ZipDownloader.php new file mode 100644 index 000000000..09b6538dd --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/ZipDownloader.php @@ -0,0 +1,307 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\IniHelper; +use Composer\Util\Platform; +use Symfony\Component\Process\ExecutableFinder; +use Symfony\Component\Process\Process; +use React\Promise\PromiseInterface; +use ZipArchive; + +/** + * @author Jordi Boggiano + */ +class ZipDownloader extends ArchiveDownloader +{ + /** @var array> */ + private static $unzipCommands; + /** @var bool */ + private static $hasZipArchive; + /** @var bool */ + private static $isWindows; + + /** @var ZipArchive|null */ + private $zipArchiveObject; // @phpstan-ignore property.onlyRead (helper property that is set via reflection for testing purposes) + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface + { + if (null === self::$unzipCommands) { + self::$unzipCommands = []; + $finder = new ExecutableFinder; + if (Platform::isWindows() && null !== ($cmd = $finder->find('7z', null, ['C:\Program Files\7-Zip']))) { + self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; + } + if (null !== ($cmd = $finder->find('unzip'))) { + self::$unzipCommands[] = ['unzip', $cmd, '-qq', '%file%', '-d', '%path%']; + } + if (!Platform::isWindows() && null !== ($cmd = $finder->find('7z'))) { // 7z linux/macOS support is only used if unzip is not present + self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; + } elseif (!Platform::isWindows() && null !== ($cmd = $finder->find('7zz'))) { // 7zz linux/macOS support is only used if unzip is not present + self::$unzipCommands[] = ['7zz', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; + } elseif (!Platform::isWindows() && null !== ($cmd = $finder->find('7za'))) { // 7za linux/macOS support is only used if unzip is not present + self::$unzipCommands[] = ['7za', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; + } + } + + $procOpenMissing = false; + if (!function_exists('proc_open')) { + self::$unzipCommands = []; + $procOpenMissing = true; + } + + if (null === self::$hasZipArchive) { + self::$hasZipArchive = class_exists('ZipArchive'); + } + + if (!self::$hasZipArchive && !self::$unzipCommands) { + // php.ini path is added to the error message to help users find the correct file + $iniMessage = IniHelper::getMessage(); + if ($procOpenMissing) { + $error = "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n" . $iniMessage; + } else { + $error = "The zip extension and unzip/7z commands are both missing, skipping.\n" . $iniMessage; + } + + throw new \RuntimeException($error); + } + + if (null === self::$isWindows) { + self::$isWindows = Platform::isWindows(); + + if (!self::$isWindows && !self::$unzipCommands) { + if ($procOpenMissing) { + $this->io->writeError("proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension."); + $this->io->writeError("This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost."); + $this->io->writeError("Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them."); + } else { + $this->io->writeError("As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension."); + $this->io->writeError("This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost."); + $this->io->writeError("Installing 'unzip' or '7z' (21.01+) may remediate them."); + } + } + } + + return parent::download($package, $path, $prevPackage, $output); + } + + /** + * extract $file to $path with "unzip" command + * + * @param string $file File to extract + * @param string $path Path where to extract file + * @phpstan-return PromiseInterface + */ + private function extractWithSystemUnzip(PackageInterface $package, string $file, string $path): PromiseInterface + { + static $warned7ZipLinux = false; + + // Force Exception throwing if the other alternative extraction method is not available + $isLastChance = !self::$hasZipArchive; + + if (0 === \count(self::$unzipCommands)) { + // This was call as the favorite extract way, but is not available + // We switch to the alternative + return $this->extractWithZipArchive($package, $file, $path); + } + + $commandSpec = reset(self::$unzipCommands); + $executable = $commandSpec[0]; + $command = array_slice($commandSpec, 1); + $map = [ + // normalize separators to backslashes to avoid problems with 7-zip on windows + // see https://github.com/composer/composer/issues/10058 + '%file%' => strtr($file, '/', DIRECTORY_SEPARATOR), + '%path%' => strtr($path, '/', DIRECTORY_SEPARATOR), + ]; + $command = array_map(static function ($value) use ($map) { + return strtr($value, $map); + }, $command); + + if (!$warned7ZipLinux && !Platform::isWindows() && in_array($executable, ['7z', '7zz', '7za'], true)) { + $warned7ZipLinux = true; + if (0 === $this->process->execute([$commandSpec[1]], $output)) { + if (Preg::isMatchStrictGroups('{^\s*7-Zip(?: \[64\])? ([0-9.]+)}', $output, $match) && version_compare($match[1], '21.01', '<')) { + $this->io->writeError(' Unzipping using '.$executable.' '.$match[1].' may result in incorrect file permissions. Install '.$executable.' 21.01+ or unzip to ensure you get correct permissions.'); + } + } + } + + $io = $this->io; + $tryFallback = function (\Throwable $processError) use ($isLastChance, $io, $file, $path, $package, $executable): PromiseInterface { + if ($isLastChance) { + throw $processError; + } + + if (str_contains($processError->getMessage(), 'zip bomb')) { + throw $processError; + } + + if (!is_file($file)) { + $io->writeError(' '.$processError->getMessage().''); + $io->writeError(' This most likely is due to a custom installer plugin not handling the returned Promise from the downloader'); + $io->writeError(' See https://github.com/composer/installers/commit/5006d0c28730ade233a8f42ec31ac68fb1c5c9bb for an example fix'); + } else { + $io->writeError(' '.$processError->getMessage().''); + $io->writeError(' The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)'); + $io->writeError(' Unzip with '.$executable.' command failed, falling back to ZipArchive class'); + + // additional debug data to try to figure out GH actions issues https://github.com/composer/composer/issues/11148 + if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) { + $io->writeError(' Additional debug info, please report to https://github.com/composer/composer/issues/11148 if you see this:'); + $io->writeError('File size: '.@filesize($file)); + $io->writeError('File SHA1: '.hash_file('sha1', $file)); + $io->writeError('First 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), 0, 100))); + $io->writeError('Last 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), -100))); + if (strlen((string) $package->getDistUrl()) > 0) { + $io->writeError('Origin URL: '.$this->processUrl($package, (string) $package->getDistUrl())); + $io->writeError('Response Headers: '.json_encode(FileDownloader::$responseHeaders[$package->getName()] ?? [])); + } + } + } + + return $this->extractWithZipArchive($package, $file, $path); + }; + + try { + $promise = $this->process->executeAsync($command); + + return $promise->then(function (Process $process) use ($tryFallback, $command, $package, $file) { + if (!$process->isSuccessful()) { + if (isset($this->cleanupExecuted[$package->getName()])) { + throw new \RuntimeException('Failed to extract '.$package->getName().' as the installation was aborted by another package operation.'); + } + + $output = $process->getErrorOutput(); + $output = str_replace(', '.$file.'.zip or '.$file.'.ZIP', '', $output); + + return $tryFallback(new \RuntimeException('Failed to extract '.$package->getName().': ('.$process->getExitCode().') '.implode(' ', $command)."\n\n".$output)); + } + }); + } catch (\Throwable $e) { + return $tryFallback($e); + } + } + + /** + * extract $file to $path with ZipArchive + * + * @param string $file File to extract + * @param string $path Path where to extract file + * @phpstan-return PromiseInterface + */ + private function extractWithZipArchive(PackageInterface $package, string $file, string $path): PromiseInterface + { + $processError = null; + $zipArchive = $this->zipArchiveObject ?: new ZipArchive(); + + try { + if (!file_exists($file) || ($filesize = filesize($file)) === false || $filesize === 0) { + $retval = -1; + } else { + $retval = $zipArchive->open($file); + } + + if (true === $retval) { + $totalSize = 0; + $archiveSize = filesize($file); + $totalFiles = $zipArchive->count(); + if ($totalFiles > 0) { + $inspectAll = false; + $filesToInspect = min($totalFiles, 5); + for ($i = 0; $i < $filesToInspect; $i++) { + $stat = $zipArchive->statIndex($inspectAll ? $i : random_int(0, $totalFiles - 1)); + if ($stat === false) { + continue; + } + $totalSize += $stat['size']; + if (!$inspectAll && $stat['size'] > $stat['comp_size'] * 200) { + $totalSize = 0; + $inspectAll = true; + $i = -1; + $filesToInspect = $totalFiles; + } + } + if ($archiveSize !== false && $totalSize > $archiveSize * 100 && $totalSize > 50 * 1024 * 1024) { + throw new \RuntimeException('Invalid zip file for "'.$package->getName().'" with compression ratio >99% (possible zip bomb)'); + } + } + + $extractResult = $zipArchive->extractTo($path); + + if (true === $extractResult) { + $zipArchive->close(); + + return \React\Promise\resolve(null); + } + + $processError = new \RuntimeException(rtrim("There was an error extracting the ZIP file for \"{$package->getName()}\", it is either corrupted or using an invalid format.\n")); + } else { + $processError = new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file)."\n"), $retval); + } + } catch (\ErrorException $e) { + $processError = new \RuntimeException('The archive for "'.$package->getName().'" may contain identical file names with different capitalization (which fails on case insensitive filesystems): '.$e->getMessage(), 0, $e); + } catch (\Throwable $e) { + $processError = $e; + } + + throw $processError; + } + + /** + * extract $file to $path + * + * @param string $file File to extract + * @param string $path Path where to extract file + */ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + return $this->extractWithSystemUnzip($package, $file, $path); + } + + /** + * Give a meaningful error message to the user. + */ + protected function getErrorMessage(int $retval, string $file): string + { + switch ($retval) { + case ZipArchive::ER_EXISTS: + return sprintf("File '%s' already exists.", $file); + case ZipArchive::ER_INCONS: + return sprintf("Zip archive '%s' is inconsistent.", $file); + case ZipArchive::ER_INVAL: + return sprintf("Invalid argument (%s)", $file); + case ZipArchive::ER_MEMORY: + return sprintf("Malloc failure (%s)", $file); + case ZipArchive::ER_NOENT: + return sprintf("No such zip file: '%s'", $file); + case ZipArchive::ER_NOZIP: + return sprintf("'%s' is not a zip archive.", $file); + case ZipArchive::ER_OPEN: + return sprintf("Can't open zip file: %s", $file); + case ZipArchive::ER_READ: + return sprintf("Zip read error (%s)", $file); + case ZipArchive::ER_SEEK: + return sprintf("Zip seek error (%s)", $file); + case -1: + return sprintf("'%s' is a corrupted zip archive (0 bytes), try again.", $file); + default: + return sprintf("'%s' is not a valid zip archive, got error code: %s", $file, $retval); + } + } +} diff --git a/vendor/composer/composer/src/Composer/EventDispatcher/Event.php b/vendor/composer/composer/src/Composer/EventDispatcher/Event.php new file mode 100644 index 000000000..4230df676 --- /dev/null +++ b/vendor/composer/composer/src/Composer/EventDispatcher/Event.php @@ -0,0 +1,103 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +/** + * The base event class + * + * @author Nils Adermann + */ +class Event +{ + /** + * @var string This event's name + */ + protected $name; + + /** + * @var string[] Arguments passed by the user, these will be forwarded to CLI script handlers + */ + protected $args; + + /** + * @var mixed[] Flags usable in PHP script handlers + */ + protected $flags; + + /** + * @var bool Whether the event should not be passed to more listeners + */ + private $propagationStopped = false; + + /** + * Constructor. + * + * @param string $name The event name + * @param string[] $args Arguments passed by the user + * @param mixed[] $flags Optional flags to pass data not as argument + */ + public function __construct(string $name, array $args = [], array $flags = []) + { + $this->name = $name; + $this->args = $args; + $this->flags = $flags; + } + + /** + * Returns the event's name. + * + * @return string The event name + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns the event's arguments. + * + * @return string[] The event arguments + */ + public function getArguments(): array + { + return $this->args; + } + + /** + * Returns the event's flags. + * + * @return mixed[] The event flags + */ + public function getFlags(): array + { + return $this->flags; + } + + /** + * Checks if stopPropagation has been called + * + * @return bool Whether propagation has been stopped + */ + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Prevents the event from being passed to further listeners + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } +} diff --git a/vendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php b/vendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php new file mode 100644 index 000000000..de0c136a2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php @@ -0,0 +1,775 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +use Composer\DependencyResolver\Transaction; +use Composer\Installer\InstallerEvent; +use Composer\IO\ConsoleIO; +use Composer\IO\IOInterface; +use Composer\Composer; +use Composer\Package\PackageInterface; +use Composer\PartialComposer; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PreCommandRunEvent; +use Composer\Util\Platform; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\Repository\RepositoryInterface; +use Composer\Script; +use Composer\Installer\PackageEvent; +use Composer\Installer\BinaryInstaller; +use Composer\Util\ProcessExecutor; +use Composer\Script\Event as ScriptEvent; +use Composer\Autoload\ClassLoader; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\ExecutableFinder; + +/** + * The Event Dispatcher. + * + * Example in command: + * $dispatcher = new EventDispatcher($this->requireComposer(), $this->getApplication()->getIO()); + * // ... + * $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD); + * // ... + * + * @author François Pluchino + * @author Jordi Boggiano + * @author Nils Adermann + */ +class EventDispatcher +{ + /** @var PartialComposer */ + protected $composer; + /** @var IOInterface */ + protected $io; + /** @var ?ClassLoader */ + protected $loader; + /** @var ProcessExecutor */ + protected $process; + /** @var array>> */ + protected $listeners = []; + /** @var bool */ + protected $runScripts = true; + /** @var list */ + private $eventStack; + /** @var list */ + private $skipScripts; + /** @var string|null */ + private $previousHash = null; + /** @var array */ + private $previousListeners = []; + + /** + * Constructor. + * + * @param PartialComposer $composer The composer instance + * @param IOInterface $io The IOInterface instance + */ + public function __construct(PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null) + { + $this->composer = $composer; + $this->io = $io; + $this->process = $process ?? new ProcessExecutor($io); + $this->eventStack = []; + $this->skipScripts = array_values(array_filter( + array_map('trim', explode(',', (string) Platform::getEnv('COMPOSER_SKIP_SCRIPTS'))), + static function ($val) { + return $val !== ''; + } + )); + } + + /** + * Set whether script handlers are active or not + * + * @return $this + */ + public function setRunScripts(bool $runScripts = true): self + { + $this->runScripts = $runScripts; + + return $this; + } + + /** + * Dispatch an event + * + * @param string|null $eventName The event name, required if no $event is provided + * @param Event $event An event instance, required if no $eventName is provided + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + public function dispatch(?string $eventName, ?Event $event = null): int + { + if (null === $event) { + if (null === $eventName) { + throw new \InvalidArgumentException('If no $event is passed in to '.__METHOD__.' you have to pass in an $eventName, got null.'); + } + $event = new Event($eventName); + } + + return $this->doDispatch($event); + } + + /** + * Dispatch a script event. + * + * @param string $eventName The constant in ScriptEvents + * @param array $additionalArgs Arguments passed by the user + * @param array $flags Optional flags to pass data not as argument + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + public function dispatchScript(string $eventName, bool $devMode = false, array $additionalArgs = [], array $flags = []): int + { + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + + return $this->doDispatch(new ScriptEvent($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags)); + } + + /** + * Dispatch a package event. + * + * @param string $eventName The constant in PackageEvents + * @param bool $devMode Whether or not we are in dev mode + * @param RepositoryInterface $localRepo The installed repository + * @param OperationInterface[] $operations The list of operations + * @param OperationInterface $operation The package being installed/updated/removed + * + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + public function dispatchPackageEvent(string $eventName, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation): int + { + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + + return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $localRepo, $operations, $operation)); + } + + /** + * Dispatch a installer event. + * + * @param string $eventName The constant in InstallerEvents + * @param bool $devMode Whether or not we are in dev mode + * @param bool $executeOperations True if operations will be executed, false in --dry-run + * @param Transaction $transaction The transaction contains the list of operations + * + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + public function dispatchInstallerEvent(string $eventName, bool $devMode, bool $executeOperations, Transaction $transaction): int + { + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + + return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $executeOperations, $transaction)); + } + + /** + * Triggers the listeners of an event. + * + * @param Event $event The event object to pass to the event handlers/listeners. + * @throws \RuntimeException|\Exception + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + protected function doDispatch(Event $event) + { + if (Platform::getEnv('COMPOSER_DEBUG_EVENTS')) { + $details = null; + if ($event instanceof PackageEvent) { + $details = (string) $event->getOperation(); + } elseif ($event instanceof CommandEvent) { + $details = $event->getCommandName(); + } elseif ($event instanceof PreCommandRunEvent) { + $details = $event->getCommand(); + } + $this->io->writeError('Dispatching '.$event->getName().''.($details ? ' ('.$details.')' : '').' event'); + } + + $listeners = $this->getListeners($event); + + $this->pushEvent($event); + + $autoloadersBefore = spl_autoload_functions(); + + try { + $returnMax = 0; + foreach ($listeners as $callable) { + $return = 0; + $this->ensureBinDirIsInPath(); + + $additionalArgs = $event->getArguments(); + if (is_string($callable) && str_contains($callable, '@no_additional_args')) { + $callable = Preg::replace('{ ?@no_additional_args}', '', $callable); + $additionalArgs = []; + } + $formattedEventNameWithArgs = $event->getName() . ($additionalArgs !== [] ? ' (' . implode(', ', $additionalArgs) . ')' : ''); + if (!is_string($callable)) { + $this->makeAutoloader($event, $callable); + if (!is_callable($callable)) { + $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; + + throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public'); + } + if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) { + $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), true, IOInterface::VERBOSE); + } + $return = false === $callable($event) ? 1 : 0; + } elseif ($this->isComposerScript($callable)) { + $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, $callable), true, IOInterface::VERBOSE); + + $script = explode(' ', substr($callable, 1)); + $scriptName = $script[0]; + unset($script[0]); + + $index = array_search('@additional_args', $script, true); + if ($index !== false) { + $args = array_splice($script, $index, 0, $additionalArgs); + } else { + $args = array_merge($script, $additionalArgs); + } + $flags = $event->getFlags(); + if (isset($flags['script-alias-input'])) { + $argsString = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script)); + $flags['script-alias-input'] = $argsString . ' ' . $flags['script-alias-input']; + unset($argsString); + } + if (strpos($callable, '@composer ') === 0) { + $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . ' ' . implode(' ', $args); + if (0 !== ($exitCode = $this->executeTty($exec))) { + $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); + + throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); + } + } else { + if (!$this->getListeners(new Event($scriptName))) { + $this->io->writeError(sprintf('You made a reference to a non-existent script %s', $callable), true, IOInterface::QUIET); + } + + try { + /** @var InstallerEvent $event */ + $scriptEvent = new ScriptEvent($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags); + $scriptEvent->setOriginatingEvent($event); + $return = $this->dispatch($scriptName, $scriptEvent); + } catch (ScriptExecutionException $e) { + $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); + throw $e; + } + } + } elseif ($this->isPhpScript($callable)) { + $className = substr($callable, 0, strpos($callable, '::')); + $methodName = substr($callable, strpos($callable, '::') + 2); + + $this->makeAutoloader($event, $callable); + if (!class_exists($className)) { + $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + if (!is_callable($callable)) { + $this->io->writeError('Method '.$callable.' is not callable, can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + + try { + $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0; + } catch (\Exception $e) { + $message = "Script %s handling the %s event terminated with an exception"; + $this->io->writeError(''.sprintf($message, $callable, $event->getName()).'', true, IOInterface::QUIET); + throw $e; + } + } elseif ($this->isCommandClass($callable)) { + $className = $callable; + + $this->makeAutoloader($event, [$callable, 'run']); + if (!class_exists($className)) { + $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + if (!is_a($className, Command::class, true)) { + $this->io->writeError('Class '.$className.' does not extend '.Command::class.', can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($event->getName())))) { + $this->io->writeError('You cannot bind '.$event->getName().' to a Command class, use a non-reserved name', true, IOInterface::QUIET); + continue; + } + + $app = new Application(); + $app->setCatchExceptions(false); + if (method_exists($app, 'setCatchErrors')) { + $app->setCatchErrors(false); + } + $app->setAutoExit(false); + $cmd = new $className($event->getName()); + // @phpstan-ignore method.notFound, function.alreadyNarrowedType + if (method_exists($app, 'addCommand')) { + $app->addCommand($cmd); + } else { + // Compatibility layer for symfony/console <7.4 + // @phpstan-ignore method.notFound + $app->add($cmd); + } + $app->setDefaultCommand((string) $cmd->getName(), true); + try { + $args = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $additionalArgs)); + // reusing the output from $this->io is mostly needed for tests, but generally speaking + // it does not hurt to keep the same stream as the current Application + if ($this->io instanceof ConsoleIO) { + $reflProp = new \ReflectionProperty($this->io, 'output'); + if (\PHP_VERSION_ID < 80100) { + $reflProp->setAccessible(true); + } + $output = $reflProp->getValue($this->io); + } else { + $output = new ConsoleOutput(); + } + $return = $app->run(new StringInput($event->getFlags()['script-alias-input'] ?? $args), $output); + } catch (\Exception $e) { + $message = "Script %s handling the %s event terminated with an exception"; + $this->io->writeError(''.sprintf($message, $callable, $event->getName()).'', true, IOInterface::QUIET); + throw $e; + } + } else { + $args = implode(' ', array_map(['Composer\Util\ProcessExecutor', 'escape'], $additionalArgs)); + + // @putenv does not receive arguments + if (strpos($callable, '@putenv ') === 0) { + $exec = $callable; + } else { + if (str_contains($callable, '@additional_args')) { + $exec = str_replace('@additional_args', $args, $callable); + } else { + $exec = $callable . ($args === '' ? '' : ' '.$args); + } + } + + if ($this->io->isVerbose()) { + $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec)); + } elseif ($this->eventNeedsToOutput($event)) { + $this->io->writeError(sprintf('> %s', $exec)); + } + + $possibleLocalBinaries = $this->composer->getPackage()->getBinaries(); + if (count($possibleLocalBinaries) > 0) { + foreach ($possibleLocalBinaries as $localExec) { + if (Preg::isMatch('{\b'.preg_quote($callable).'$}', $localExec)) { + $caller = BinaryInstaller::determineBinaryCaller($localExec); + $exec = Preg::replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec); + break; + } + } + } + + if (strpos($exec, '@putenv ') === 0) { + if (false === strpos($exec, '=')) { + Platform::clearEnv(substr($exec, 8)); + } else { + [$var, $value] = explode('=', substr($exec, 8), 2); + Platform::putEnv($var, $value); + } + + continue; + } + if (strpos($exec, '@php ') === 0) { + $pathAndArgs = substr($exec, 5); + if (Platform::isWindows()) { + $pathAndArgs = Preg::replaceCallback('{^\S+}', static function ($path) { + return str_replace('/', '\\', $path[0]); + }, $pathAndArgs); + } + // match somename (not in quote, and not a qualified path) and if it is not a valid path from CWD then try to find it + // in $PATH. This allows support for `@php foo` where foo is a binary name found in PATH but not an actual relative path + $matched = Preg::isMatchStrictGroups('{^[^\'"\s/\\\\]+}', $pathAndArgs, $match); + if ($matched && !file_exists($match[0])) { + $finder = new ExecutableFinder; + if ($pathToExec = $finder->find($match[0])) { + if (Platform::isWindows()) { + $execWithoutExt = Preg::replace('{\.(exe|bat|cmd|com)$}i', '', $pathToExec); + // prefer non-extension file if it exists when executing with PHP + if (file_exists($execWithoutExt)) { + $pathToExec = $execWithoutExt; + } + unset($execWithoutExt); + } + $pathAndArgs = $pathToExec . substr($pathAndArgs, strlen($match[0])); + } + } + $exec = $this->getPhpExecCommand() . ' ' . $pathAndArgs; + } else { + $finder = new PhpExecutableFinder(); + $phpPath = $finder->find(false); + if ($phpPath) { + Platform::putEnv('PHP_BINARY', $phpPath); + } + + if (Platform::isWindows()) { + $exec = Preg::replaceCallback('{^\S+}', static function ($path) { + return str_replace('/', '\\', $path[0]); + }, $exec); + } + } + + // if composer is being executed, make sure it runs the expected composer from current path + // resolution, even if bin-dir contains composer too because the project requires composer/composer + // see https://github.com/composer/composer/issues/8748 + if (strpos($exec, 'composer ') === 0) { + $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . substr($exec, 8); + } + + if (0 !== ($exitCode = $this->executeTty($exec))) { + $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); + + throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); + } + } + + $returnMax = max($returnMax, $return); + + if ($event->isPropagationStopped()) { + break; + } + } + } finally { + $this->popEvent(); + + $knownIdentifiers = []; + foreach ($autoloadersBefore as $key => $cb) { + $knownIdentifiers[$this->getCallbackIdentifier($cb)] = ['key' => $key, 'callback' => $cb]; + } + foreach (spl_autoload_functions() as $cb) { + // once we get to the first known autoloader, we can leave any appended autoloader without problems + if (isset($knownIdentifiers[$this->getCallbackIdentifier($cb)]) && $knownIdentifiers[$this->getCallbackIdentifier($cb)]['key'] === 0) { + break; + } + + // other newly appeared prepended autoloaders should be appended instead to ensure Composer loads its classes first + if ($cb instanceof ClassLoader) { + $cb->unregister(); + $cb->register(false); + } else { + spl_autoload_unregister($cb); + spl_autoload_register($cb); + } + } + } + + return $returnMax; + } + + protected function executeTty(string $exec): int + { + if ($this->io->isInteractive()) { + return $this->process->executeTty($exec); + } + + return $this->process->execute($exec); + } + + protected function getPhpExecCommand(): string + { + $finder = new PhpExecutableFinder(); + $phpPath = $finder->find(false); + if (!$phpPath) { + throw new \RuntimeException('Failed to locate PHP binary to execute '.$phpPath); + } + $phpArgs = $finder->findArguments(); + $phpArgs = \count($phpArgs) > 0 ? ' ' . implode(' ', $phpArgs) : ''; + $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen')); + $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions')); + $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit')); + + return ProcessExecutor::escape($phpPath) . $phpArgs . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag; + } + + /** + * @param Event $event Event invoking the PHP callable + * + * @return mixed + */ + protected function executeEventPhpScript(string $className, string $methodName, Event $event) + { + if ($this->io->isVerbose()) { + $this->io->writeError(sprintf('> %s: %s::%s', $event->getName(), $className, $methodName)); + } elseif ($this->eventNeedsToOutput($event)) { + $this->io->writeError(sprintf('> %s::%s', $className, $methodName)); + } + + return $className::$methodName($event); + } + + private function eventNeedsToOutput(Event $event): bool + { + // do not output the command being run when using `composer exec` as it is fairly obvious the user is running it + if ($event->getName() === '__exec_command') { + return false; + } + + // do not output the command being run when using `composer ` as it is also fairly obvious the user is running it + if (($event->getFlags()['script-alias-input'] ?? null) !== null) { + return false; + } + + return true; + } + + /** + * Add a listener for a particular event + * + * @param string $eventName The event name - typically a constant + * @param callable|string $listener A callable expecting an event argument, or a command string to be executed (same as a composer.json "scripts" entry) + * @param int $priority A higher value represents a higher priority + */ + public function addListener(string $eventName, $listener, int $priority = 0): void + { + $this->listeners[$eventName][$priority][] = $listener; + } + + /** + * @param callable|object $listener A callable or an object instance for which all listeners should be removed + */ + public function removeListener($listener): void + { + foreach ($this->listeners as $eventName => $priorities) { + foreach ($priorities as $priority => $listeners) { + foreach ($listeners as $index => $candidate) { + if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) { + unset($this->listeners[$eventName][$priority][$index]); + } + } + } + } + } + + /** + * Adds object methods as listeners for the events in getSubscribedEvents + * + * @see EventSubscriberInterface + */ + public function addSubscriber(EventSubscriberInterface $subscriber): void + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->addListener($eventName, [$subscriber, $params]); + } elseif (is_string($params[0])) { + $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); + } + } + } + } + + /** + * Retrieves all listeners for a given event + * + * @return array All listeners: callables and scripts + */ + protected function getListeners(Event $event): array + { + $scriptListeners = $this->runScripts ? $this->getScriptListeners($event) : []; + + if (!isset($this->listeners[$event->getName()][0])) { + $this->listeners[$event->getName()][0] = []; + } + krsort($this->listeners[$event->getName()]); + + $listeners = $this->listeners; + $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners); + + return array_merge(...$listeners[$event->getName()]); + } + + /** + * Checks if an event has listeners registered + */ + public function hasEventListeners(Event $event): bool + { + $listeners = $this->getListeners($event); + + return count($listeners) > 0; + } + + /** + * Finds all listeners defined as scripts in the package + * + * @param Event $event Event object + * @return string[] Listeners + */ + protected function getScriptListeners(Event $event): array + { + $package = $this->composer->getPackage(); + $scripts = $package->getScripts(); + + if (empty($scripts[$event->getName()])) { + return []; + } + + if (in_array($event->getName(), $this->skipScripts, true)) { + $this->io->writeError('Skipped script listeners for '.$event->getName().' because of COMPOSER_SKIP_SCRIPTS', true, IOInterface::VERBOSE); + + return []; + } + + return $scripts[$event->getName()]; + } + + /** + * Checks if string given references a class path and method + */ + protected function isPhpScript(string $callable): bool + { + return false === strpos($callable, ' ') && false !== strpos($callable, '::'); + } + + /** + * Checks if string given references a command class + */ + protected function isCommandClass(string $callable): bool + { + return str_contains($callable, '\\') && !str_contains($callable, ' ') && str_ends_with($callable, 'Command'); + } + + /** + * Checks if string given references a composer run-script + */ + protected function isComposerScript(string $callable): bool + { + return str_starts_with($callable, '@') && !str_starts_with($callable, '@php ') && !str_starts_with($callable, '@putenv '); + } + + /** + * Push an event to the stack of active event + * + * @throws \RuntimeException + */ + protected function pushEvent(Event $event): int + { + $eventName = $event->getName(); + if (in_array($eventName, $this->eventStack)) { + throw new \RuntimeException(sprintf("Circular call to script handler '%s' detected", $eventName)); + } + + return array_push($this->eventStack, $eventName); + } + + /** + * Pops the active event from the stack + */ + protected function popEvent(): ?string + { + return array_pop($this->eventStack); + } + + private function ensureBinDirIsInPath(): void + { + $pathEnv = 'PATH'; + + // checking if only Path and not PATH is set then we probably need to update the Path env + // on Windows getenv is case-insensitive so we cannot check it via Platform::getEnv and + // we need to check in $_SERVER directly + if (!isset($_SERVER[$pathEnv]) && isset($_SERVER['Path'])) { + $pathEnv = 'Path'; + } + + // add the bin dir to the PATH to make local binaries of deps usable in scripts + $binDir = $this->composer->getConfig()->get('bin-dir'); + if (is_dir($binDir)) { + $binDir = realpath($binDir); + $pathValue = (string) Platform::getEnv($pathEnv); + if (!Preg::isMatch('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}', $pathValue)) { + Platform::putEnv($pathEnv, $binDir.PATH_SEPARATOR.$pathValue); + } + } + } + + /** + * @param callable $cb DO NOT MOVE TO TYPE HINT as private autoload callbacks are not technically callable + */ + private function getCallbackIdentifier($cb): string + { + if (is_string($cb)) { + return 'fn:'.$cb; + } + if (is_object($cb)) { + return 'obj:'.spl_object_hash($cb); + } + if (is_array($cb)) { + return 'array:'.(is_string($cb[0]) ? $cb[0] : get_class($cb[0]) .'#'.spl_object_hash($cb[0])).'::'.$cb[1]; + } + + // not great but also do not want to break everything here + return 'unsupported'; + } + + /** + * @param mixed $callable Technically a callable shape but as the class may not be autoloadable yet PHP might not see it this way, so we cannot type hint as such + */ + private function makeAutoloader(Event $event, $callable): void + { + if (!$this->composer instanceof Composer) { + return; + } + + if (is_array($callable)) { + if (is_string($callable[0])) { + $callableKey = $callable[0] . '::' . $callable[1]; + } else { + $callableKey = get_class($callable[0]).'::'.$callable[1]; + } + } elseif (is_string($callable)) { + $callableKey = $callable; + } elseif ($callable instanceof \Closure) { + $callableKey = 'closure'; + } else { + $callableKey = 'unknown'; + } + if (isset($this->previousListeners[$callableKey])) { + return; + } + $this->previousListeners[$callableKey] = true; + + $package = $this->composer->getPackage(); + $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages(); + $generator = $this->composer->getAutoloadGenerator(); + $hash = implode(',', array_map(static function (PackageInterface $p) { + return $p->getName().'/'.$p->getVersion(); + }, $packages)); + if ($event instanceof ScriptEvent || $event instanceof PackageEvent || $event instanceof InstallerEvent) { + $generator->setDevMode($event->isDevMode()); + $hash .= $event->isDevMode() ? '/dev' : ''; + } + $hash = hash('sha256', $hash); + + if ($this->previousHash === $hash) { + return; + } + + $this->previousHash = $hash; + + $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages); + $map = $generator->parseAutoloads($packageMap, $package); + + if ($this->loader !== null) { + $this->loader->unregister(); + } + + $this->loader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir')); + $this->loader->register(false); + } +} diff --git a/vendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php b/vendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php new file mode 100644 index 000000000..fe0c5bd02 --- /dev/null +++ b/vendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php @@ -0,0 +1,48 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +/** + * An EventSubscriber knows which events it is interested in. + * + * If an EventSubscriber is added to an EventDispatcher, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array> The event names to listen to + */ + public static function getSubscribedEvents(); +} diff --git a/vendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php b/vendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php new file mode 100644 index 000000000..72a4aa273 --- /dev/null +++ b/vendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +/** + * Thrown when a script running an external process exits with a non-0 status code + * + * @author Jordi Boggiano + */ +class ScriptExecutionException extends \RuntimeException +{ +} diff --git a/vendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php b/vendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php new file mode 100644 index 000000000..a442786ae --- /dev/null +++ b/vendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php @@ -0,0 +1,20 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Exception; + +/** + * @author Jordi Boggiano + */ +class IrrecoverableDownloadException extends \RuntimeException +{ +} diff --git a/vendor/composer/composer/src/Composer/Exception/NoSslException.php b/vendor/composer/composer/src/Composer/Exception/NoSslException.php new file mode 100644 index 000000000..4696dd3a5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Exception/NoSslException.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Exception; + +/** + * Specific exception for Composer\Util\HttpDownloader creation. + * + * @author Jordi Boggiano + */ +class NoSslException extends \RuntimeException +{ +} diff --git a/vendor/composer/composer/src/Composer/Factory.php b/vendor/composer/composer/src/Composer/Factory.php new file mode 100644 index 000000000..e83aaeda6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Factory.php @@ -0,0 +1,757 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Config\JsonConfigSource; +use Composer\Json\JsonFile; +use Composer\IO\IOInterface; +use Composer\Package\Archiver; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\RootPackageInterface; +use Composer\Repository\FilesystemRepository; +use Composer\Repository\RepositoryManager; +use Composer\Repository\RepositoryFactory; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\HttpDownloader; +use Composer\Util\Loop; +use Composer\Util\Silencer; +use Composer\Plugin\PluginEvents; +use Composer\EventDispatcher\Event; +use Phar; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Output\ConsoleOutput; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Autoload\AutoloadGenerator; +use Composer\Package\Version\VersionParser; +use Composer\Downloader\TransportException; +use Composer\Json\JsonValidationException; +use Composer\Repository\InstalledRepositoryInterface; +use UnexpectedValueException; +use ZipArchive; + +/** + * Creates a configured instance of composer. + * + * @author Ryan Weaver + * @author Jordi Boggiano + * @author Igor Wiedler + * @author Nils Adermann + */ +class Factory +{ + /** + * @throws \RuntimeException + */ + protected static function getHomeDir(): string + { + $home = Platform::getEnv('COMPOSER_HOME'); + if ($home) { + return $home; + } + + if (Platform::isWindows()) { + if (!Platform::getEnv('APPDATA')) { + throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly'); + } + + return rtrim(strtr(Platform::getEnv('APPDATA'), '\\', '/'), '/') . '/Composer'; + } + + $userDir = self::getUserDir(); + $dirs = []; + + if (self::useXdg()) { + // XDG Base Directory Specifications + $xdgConfig = Platform::getEnv('XDG_CONFIG_HOME'); + if (!$xdgConfig) { + $xdgConfig = $userDir . '/.config'; + } + + $dirs[] = $xdgConfig . '/composer'; + } + + $dirs[] = $userDir . '/.composer'; + + // select first dir which exists of: $XDG_CONFIG_HOME/composer or ~/.composer + foreach ($dirs as $dir) { + if (Silencer::call('is_dir', $dir)) { + return $dir; + } + } + + // if none exists, we default to first defined one (XDG one if system uses it, or ~/.composer otherwise) + return $dirs[0]; + } + + protected static function getCacheDir(string $home): string + { + $cacheDir = Platform::getEnv('COMPOSER_CACHE_DIR'); + if ($cacheDir) { + return $cacheDir; + } + + $homeEnv = Platform::getEnv('COMPOSER_HOME'); + if ($homeEnv) { + return $homeEnv . '/cache'; + } + + if (Platform::isWindows()) { + if ($cacheDir = Platform::getEnv('LOCALAPPDATA')) { + $cacheDir .= '/Composer'; + } else { + $cacheDir = $home . '/cache'; + } + + return rtrim(strtr($cacheDir, '\\', '/'), '/'); + } + + $userDir = self::getUserDir(); + if (PHP_OS === 'Darwin') { + // Migrate existing cache dir in old location if present + if (is_dir($home . '/cache') && !is_dir($userDir . '/Library/Caches/composer')) { + Silencer::call('rename', $home . '/cache', $userDir . '/Library/Caches/composer'); + } + + return $userDir . '/Library/Caches/composer'; + } + + if ($home === $userDir . '/.composer' && is_dir($home . '/cache')) { + return $home . '/cache'; + } + + if (self::useXdg()) { + $xdgCache = Platform::getEnv('XDG_CACHE_HOME') ?: $userDir . '/.cache'; + + return $xdgCache . '/composer'; + } + + return $home . '/cache'; + } + + protected static function getDataDir(string $home): string + { + $homeEnv = Platform::getEnv('COMPOSER_HOME'); + if ($homeEnv) { + return $homeEnv; + } + + if (Platform::isWindows()) { + return strtr($home, '\\', '/'); + } + + $userDir = self::getUserDir(); + if ($home !== $userDir . '/.composer' && self::useXdg()) { + $xdgData = Platform::getEnv('XDG_DATA_HOME') ?: $userDir . '/.local/share'; + + return $xdgData . '/composer'; + } + + return $home; + } + + public static function createConfig(?IOInterface $io = null, ?string $cwd = null): Config + { + $cwd = $cwd ?? Platform::getCwd(true); + + $config = new Config(true, $cwd); + + // determine and add main dirs to the config + $home = self::getHomeDir(); + $config->merge([ + 'config' => [ + 'home' => $home, + 'cache-dir' => self::getCacheDir($home), + 'data-dir' => self::getDataDir($home), + ], + ], Config::SOURCE_DEFAULT); + + // load global config + $file = new JsonFile($config->get('home').'/config.json'); + if ($file->exists()) { + if ($io instanceof IOInterface) { + $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG); + } + self::validateJsonSchema($io, $file); + $config->merge($file->read(), $file->getPath()); + } + $config->setConfigSource(new JsonConfigSource($file)); + + $htaccessProtect = $config->get('htaccess-protect'); + if ($htaccessProtect) { + // Protect directory against web access. Since HOME could be + // the www-data's user home and be web-accessible it is a + // potential security risk + $dirs = [$config->get('home'), $config->get('cache-dir'), $config->get('data-dir')]; + foreach ($dirs as $dir) { + if (!file_exists($dir . '/.htaccess')) { + if (!is_dir($dir)) { + Silencer::call('mkdir', $dir, 0777, true); + } + Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all'); + } + } + } + + // load global auth file + $file = new JsonFile($config->get('home').'/auth.json'); + if ($file->exists()) { + if ($io instanceof IOInterface) { + $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG); + } + self::validateJsonSchema($io, $file, JsonFile::AUTH_SCHEMA); + $config->merge(['config' => $file->read()], $file->getPath()); + } + $config->setAuthConfigSource(new JsonConfigSource($file, true)); + + self::loadComposerAuthEnv($config, $io); + + return $config; + } + + public static function getComposerFile(): string + { + $env = Platform::getEnv('COMPOSER'); + if (is_string($env)) { + $env = trim($env); + if ('' !== $env) { + if (is_dir($env)) { + throw new \RuntimeException('The COMPOSER environment variable is set to '.$env.' which is a directory, this variable should point to a composer.json or be left unset.'); + } + + return $env; + } + } + + return './composer.json'; + } + + public static function getLockFile(string $composerFile): string + { + return "json" === pathinfo($composerFile, PATHINFO_EXTENSION) + ? substr($composerFile, 0, -4).'lock' + : $composerFile . '.lock'; + } + + /** + * @return array{highlight: OutputFormatterStyle, warning: OutputFormatterStyle} + */ + public static function createAdditionalStyles(): array + { + return [ + 'highlight' => new OutputFormatterStyle('red'), + 'warning' => new OutputFormatterStyle('black', 'yellow'), + ]; + } + + public static function createOutput(): ConsoleOutput + { + $styles = self::createAdditionalStyles(); + $formatter = new OutputFormatter(false, $styles); + + return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter); + } + + /** + * Creates a Composer instance + * + * @param IOInterface $io IO instance + * @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will + * read from the default filename + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + * @param bool $disableScripts Whether scripts should not be run + * @param bool $fullLoad Whether to initialize everything or only main project stuff (used when loading the global composer) + * @throws \InvalidArgumentException + * @throws UnexpectedValueException + * @return Composer|PartialComposer Composer if $fullLoad is true, otherwise PartialComposer + * @phpstan-return ($fullLoad is true ? Composer : PartialComposer) + */ + public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, ?string $cwd = null, bool $fullLoad = true, bool $disableScripts = false) + { + // if a custom composer.json path is given, we change the default cwd to be that file's directory + if (is_string($localConfig) && is_file($localConfig) && null === $cwd) { + $cwd = dirname($localConfig); + } + + $cwd = $cwd ?? Platform::getCwd(true); + + // load Composer configuration + if (null === $localConfig) { + $localConfig = static::getComposerFile(); + } + + $localConfigSource = Config::SOURCE_UNKNOWN; + if (is_string($localConfig)) { + $composerFile = $localConfig; + + $file = new JsonFile($localConfig, null, $io); + + if (!$file->exists()) { + if ($localConfig === './composer.json' || $localConfig === 'composer.json') { + $message = 'Composer could not find a composer.json file in '.$cwd; + } else { + $message = 'Composer could not find the config file: '.$localConfig; + } + $instructions = $fullLoad ? 'To initialize a project, please create a composer.json file. See https://getcomposer.org/basic-usage' : ''; + throw new \InvalidArgumentException($message.PHP_EOL.$instructions); + } + + if (!Platform::isInputCompletionProcess()) { + try { + $file->validateSchema(JsonFile::LAX_SCHEMA); + } catch (JsonValidationException $e) { + $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); + $message = $e->getMessage() . ':' . PHP_EOL . $errors; + throw new JsonValidationException($message); + } + } + + $localConfig = $file->read(); + $localConfigSource = $file->getPath(); + } + + // Load config and override with local config/auth config + $config = static::createConfig($io, $cwd); + $isGlobal = $localConfigSource !== Config::SOURCE_UNKNOWN && realpath($config->get('home')) === realpath(dirname($localConfigSource)); + $config->merge($localConfig, $localConfigSource); + + if (isset($composerFile)) { + $io->writeError('Loading config file ' . $composerFile .' ('.realpath($composerFile).')', true, IOInterface::DEBUG); + $config->setConfigSource(new JsonConfigSource(new JsonFile(realpath($composerFile), null, $io))); + + $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json', null, $io); + if ($localAuthFile->exists()) { + $io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG); + self::validateJsonSchema($io, $localAuthFile, JsonFile::AUTH_SCHEMA); + $config->merge(['config' => $localAuthFile->read()], $localAuthFile->getPath()); + $config->setLocalAuthConfigSource(new JsonConfigSource($localAuthFile, true)); + } + } + + // make sure we load the auth env again over the local auth.json + composer.json config + self::loadComposerAuthEnv($config, $io); + + $vendorDir = $config->get('vendor-dir'); + + // initialize composer + $composer = $fullLoad ? new Composer() : new PartialComposer(); + $composer->setConfig($config); + if ($isGlobal) { + $composer->setGlobal(); + } + + if ($fullLoad) { + // load auth configs into the IO instance + $io->loadConfiguration($config); + + // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it + // we only load if the InstalledVersions class wasn't defined yet so that this is only loaded once + if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/installed.php')) { + // force loading the class at this point so it is loaded from the composer phar and not from the vendor dir + // as we cannot guarantee integrity of that file + if (class_exists('Composer\InstalledVersions')) { + FilesystemRepository::safelyLoadInstalledVersions($installedVersionsPath); + } + } + } + + $httpDownloader = self::createHttpDownloader($io, $config); + $process = new ProcessExecutor($io); + $loop = new Loop($httpDownloader, $process); + $composer->setLoop($loop); + + // initialize event dispatcher + $dispatcher = new EventDispatcher($composer, $io, $process); + $dispatcher->setRunScripts(!$disableScripts); + $composer->setEventDispatcher($dispatcher); + + // initialize repository manager + $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher, $process); + $composer->setRepositoryManager($rm); + + // force-set the version of the global package if not defined as + // guessing it adds no value and only takes time + if (!$fullLoad && !isset($localConfig['version'])) { + $localConfig['version'] = '1.0.0'; + } + + // load package + $parser = new VersionParser; + $guesser = new VersionGuesser($config, $process, $parser, $io); + $loader = $this->loadRootPackage($rm, $config, $parser, $guesser, $io); + $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd); + $composer->setPackage($package); + + // load local repository + $this->addLocalRepository($io, $rm, $vendorDir, $package, $process); + + // initialize installation manager + $im = $this->createInstallationManager($loop, $io, $dispatcher); + $composer->setInstallationManager($im); + + if ($composer instanceof Composer) { + // initialize download manager + $dm = $this->createDownloadManager($io, $config, $httpDownloader, $process, $dispatcher); + $composer->setDownloadManager($dm); + + // initialize autoload generator + $generator = new AutoloadGenerator($dispatcher, $io); + $composer->setAutoloadGenerator($generator); + + // initialize archive manager + $am = $this->createArchiveManager($config, $dm, $loop); + $composer->setArchiveManager($am); + } + + // add installers to the manager (must happen after download manager is created since they read it out of $composer) + $this->createDefaultInstallers($im, $composer, $io, $process); + + // init locker if possible + if ($composer instanceof Composer && isset($composerFile)) { + $lockFile = self::getLockFile($composerFile); + if (!$config->get('lock') && file_exists($lockFile)) { + $io->writeError(''.$lockFile.' is present but ignored as the "lock" config option is disabled.'); + } + + $locker = new Package\Locker($io, new JsonFile($config->get('lock') ? $lockFile : Platform::getDevNull(), null, $io), $im, file_get_contents($composerFile), $process); + $composer->setLocker($locker); + } elseif ($composer instanceof Composer) { + $locker = new Package\Locker($io, new JsonFile(Platform::getDevNull(), null, $io), $im, JsonFile::encode($localConfig), $process); + $composer->setLocker($locker); + } + + if ($composer instanceof Composer) { + $globalComposer = null; + if (!$composer->isGlobal()) { + $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins, $disableScripts); + } + + $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins); + $composer->setPluginManager($pm); + + if ($composer->isGlobal()) { + $pm->setRunningInGlobalDir(true); + } + + $pm->loadInstalledPlugins(); + } + + if ($fullLoad) { + $initEvent = new Event(PluginEvents::INIT); + $composer->getEventDispatcher()->dispatch($initEvent->getName(), $initEvent); + + // once everything is initialized we can + // purge packages from local repos if they have been deleted on the filesystem + $this->purgePackages($rm->getLocalRepository(), $im); + } + + return $composer; + } + + /** + * @param bool $disablePlugins Whether plugins should not be loaded + * @param bool $disableScripts Whether scripts should not be executed + */ + public static function createGlobal(IOInterface $io, bool $disablePlugins = false, bool $disableScripts = false): ?Composer + { + $factory = new static(); + + return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, $disableScripts, true); + } + + protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, string $vendorDir, RootPackageInterface $rootPackage, ?ProcessExecutor $process = null): void + { + $fs = null; + if ($process) { + $fs = new Filesystem($process); + } + + $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io), true, $rootPackage, $fs)); + } + + /** + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + * @return PartialComposer|Composer|null By default PartialComposer, but Composer if $fullLoad is set to true + * @phpstan-return ($fullLoad is true ? Composer|null : PartialComposer|null) + */ + protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, bool $disableScripts, bool $fullLoad = false): ?PartialComposer + { + // make sure if disable plugins was 'local' it is now turned off + $disablePlugins = $disablePlugins === 'global' || $disablePlugins === true; + + $composer = null; + try { + $composer = $this->createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad, $disableScripts); + } catch (\Exception $e) { + $io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG); + } + + return $composer; + } + + public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process, ?EventDispatcher $eventDispatcher = null): Downloader\DownloadManager + { + $cache = null; + if ($config->get('cache-files-ttl') > 0) { + $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./'); + $cache->setReadOnly($config->get('cache-read-only')); + } + + $fs = new Filesystem($process); + + $dm = new Downloader\DownloadManager($io, false, $fs); + switch ($preferred = $config->get('preferred-install')) { + case 'dist': + $dm->setPreferDist(true); + break; + case 'source': + $dm->setPreferSource(true); + break; + case 'auto': + default: + // noop + break; + } + + if (is_array($preferred)) { + $dm->setPreferences($preferred); + } + + $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $process, $fs)); + $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $process, $fs)); + $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $process, $fs)); + $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $process, $fs)); + $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config, $process, $fs)); + $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + + return $dm; + } + + /** + * @param Config $config The configuration + * @param Downloader\DownloadManager $dm Manager use to download sources + * @return Archiver\ArchiveManager + */ + public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop) + { + $am = new Archiver\ArchiveManager($dm, $loop); + if (class_exists(ZipArchive::class)) { + $am->addArchiver(new Archiver\ZipArchiver); + } + if (class_exists(Phar::class)) { + $am->addArchiver(new Archiver\PharArchiver); + } + + return $am; + } + + /** + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + */ + protected function createPluginManager(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = false): Plugin\PluginManager + { + return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins); + } + + public function createInstallationManager(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null): Installer\InstallationManager + { + return new Installer\InstallationManager($loop, $io, $eventDispatcher); + } + + protected function createDefaultInstallers(Installer\InstallationManager $im, PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null): void + { + $fs = new Filesystem($process); + $binaryInstaller = new Installer\BinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fs, rtrim($composer->getConfig()->get('vendor-dir'), '/')); + + $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null, $fs, $binaryInstaller)); + $im->addInstaller(new Installer\PluginInstaller($io, $composer, $fs, $binaryInstaller)); + $im->addInstaller(new Installer\MetapackageInstaller($io)); + } + + /** + * @param InstalledRepositoryInterface $repo repository to purge packages from + * @param Installer\InstallationManager $im manager to check whether packages are still installed + */ + protected function purgePackages(InstalledRepositoryInterface $repo, Installer\InstallationManager $im): void + { + foreach ($repo->getPackages() as $package) { + if (!$im->isPackageInstalled($repo, $package)) { + $repo->removePackage($package); + } + } + } + + protected function loadRootPackage(RepositoryManager $rm, Config $config, VersionParser $parser, VersionGuesser $guesser, IOInterface $io): Package\Loader\RootPackageLoader + { + return new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io); + } + + /** + * @param IOInterface $io IO instance + * @param mixed $config either a configuration array or a filename to read from, if null it will read from + * the default filename + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + * @param bool $disableScripts Whether scripts should not be run + */ + public static function create(IOInterface $io, $config = null, $disablePlugins = false, bool $disableScripts = false): Composer + { + $factory = new static(); + + // for BC reasons, if a config is passed in either as array or a path that is not the default composer.json path + // we disable local plugins as they really should not be loaded from CWD + // If you want to avoid this behavior, you should be calling createComposer directly with a $cwd arg set correctly + // to the path where the composer.json being loaded resides + if ($config !== null && $config !== self::getComposerFile() && $disablePlugins === false) { + $disablePlugins = 'local'; + } + + return $factory->createComposer($io, $config, $disablePlugins, null, true, $disableScripts); + } + + /** + * If you are calling this in a plugin, you probably should instead use $composer->getLoop()->getHttpDownloader() + * + * @param IOInterface $io IO instance + * @param Config $config Config instance + * @param mixed[] $options Array of options passed directly to HttpDownloader constructor + */ + public static function createHttpDownloader(IOInterface $io, Config $config, array $options = []): HttpDownloader + { + static $warned = false; + $disableTls = false; + // allow running the config command if disable-tls is in the arg list, even if openssl is missing, to allow disabling it via the config command + if (isset($_SERVER['argv']) && in_array('disable-tls', $_SERVER['argv']) && (in_array('conf', $_SERVER['argv']) || in_array('config', $_SERVER['argv']))) { + $warned = true; + $disableTls = !extension_loaded('openssl'); + } elseif ($config->get('disable-tls') === true) { + if (!$warned) { + $io->writeError('You are running Composer with SSL/TLS protection disabled.'); + } + $warned = true; + $disableTls = true; + } elseif (!extension_loaded('openssl')) { + throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. ' + . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); + } + $httpDownloaderOptions = []; + if ($disableTls === false) { + if ('' !== $config->get('cafile')) { + $httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile'); + } + if ('' !== $config->get('capath')) { + $httpDownloaderOptions['ssl']['capath'] = $config->get('capath'); + } + $httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options); + } + try { + $httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls); + } catch (TransportException $e) { + if (false !== strpos($e->getMessage(), 'cafile')) { + $io->write('Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.'); + $io->write('A valid CA certificate file is required for SSL/TLS protection.'); + $io->write('You can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); + } + throw $e; + } + + return $httpDownloader; + } + + private static function loadComposerAuthEnv(Config $config, ?IOInterface $io): void + { + $composerAuthEnv = Platform::getEnv('COMPOSER_AUTH'); + if (false === $composerAuthEnv || '' === $composerAuthEnv) { + return; + } + + $authData = json_decode($composerAuthEnv); + if (null === $authData) { + throw new UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object'); + } + + if ($io instanceof IOInterface) { + $io->writeError('Loading auth config from COMPOSER_AUTH', true, IOInterface::DEBUG); + } + self::validateJsonSchema($io, $authData, JsonFile::AUTH_SCHEMA, 'COMPOSER_AUTH'); + $authData = json_decode($composerAuthEnv, true); + if (null !== $authData) { + $config->merge(['config' => $authData], 'COMPOSER_AUTH'); + } + } + + private static function useXdg(): bool + { + foreach (array_keys($_SERVER) as $key) { + if (strpos((string) $key, 'XDG_') === 0) { + return true; + } + } + + if (Silencer::call('is_dir', '/etc/xdg')) { + return true; + } + + return false; + } + + /** + * @throws \RuntimeException + */ + private static function getUserDir(): string + { + $home = Platform::getEnv('HOME'); + if (!$home) { + throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly'); + } + + return rtrim(strtr($home, '\\', '/'), '/'); + } + + /** + * @param mixed $fileOrData + * @param JsonFile::*_SCHEMA $schema + */ + private static function validateJsonSchema(?IOInterface $io, $fileOrData, int $schema = JsonFile::LAX_SCHEMA, ?string $source = null): void + { + if (Platform::isInputCompletionProcess()) { + return; + } + + try { + if ($fileOrData instanceof JsonFile) { + $fileOrData->validateSchema($schema); + } else { + if (null === $source) { + throw new \InvalidArgumentException('$source is required to be provided if $fileOrData is arbitrary data'); + } + JsonFile::validateJsonSchema($source, $fileOrData, $schema); + } + } catch (JsonValidationException $e) { + $msg = $e->getMessage().', this may result in errors and should be resolved:'.PHP_EOL.' - '.implode(PHP_EOL.' - ', $e->getErrors()); + if ($io instanceof IOInterface) { + $io->writeError(''.$msg.''); + } else { + throw new UnexpectedValueException($msg); + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php new file mode 100644 index 000000000..8167fdd3e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php @@ -0,0 +1,28 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +use Composer\Repository\PlatformRepository; + +final class IgnoreAllPlatformRequirementFilter implements PlatformRequirementFilterInterface +{ + public function isIgnored(string $req): bool + { + return PlatformRepository::isPlatformPackage($req); + } + + public function isUpperBoundIgnored(string $req): bool + { + return $this->isIgnored($req); + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php new file mode 100644 index 000000000..73d536376 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php @@ -0,0 +1,97 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +use Composer\Package\BasePackage; +use Composer\Pcre\Preg; +use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Interval; +use Composer\Semver\Intervals; + +final class IgnoreListPlatformRequirementFilter implements PlatformRequirementFilterInterface +{ + /** + * @var non-empty-string + */ + private $ignoreRegex; + + /** + * @var non-empty-string + */ + private $ignoreUpperBoundRegex; + + /** + * @param string[] $reqList + */ + public function __construct(array $reqList) + { + $ignoreAll = $ignoreUpperBound = []; + foreach ($reqList as $req) { + if (substr($req, -1) === '+') { + $ignoreUpperBound[] = substr($req, 0, -1); + } else { + $ignoreAll[] = $req; + } + } + $this->ignoreRegex = BasePackage::packageNamesToRegexp($ignoreAll); + $this->ignoreUpperBoundRegex = BasePackage::packageNamesToRegexp($ignoreUpperBound); + } + + public function isIgnored(string $req): bool + { + if (!PlatformRepository::isPlatformPackage($req)) { + return false; + } + + return Preg::isMatch($this->ignoreRegex, $req); + } + + public function isUpperBoundIgnored(string $req): bool + { + if (!PlatformRepository::isPlatformPackage($req)) { + return false; + } + + return $this->isIgnored($req) || Preg::isMatch($this->ignoreUpperBoundRegex, $req); + } + + /** + * @param bool $allowUpperBoundOverride For conflicts we do not want the upper bound to be skipped + */ + public function filterConstraint(string $req, ConstraintInterface $constraint, bool $allowUpperBoundOverride = true): ConstraintInterface + { + if (!PlatformRepository::isPlatformPackage($req)) { + return $constraint; + } + + if (!$allowUpperBoundOverride || !Preg::isMatch($this->ignoreUpperBoundRegex, $req)) { + return $constraint; + } + + if (Preg::isMatch($this->ignoreRegex, $req)) { + return new MatchAllConstraint; + } + + $intervals = Intervals::get($constraint); + $last = end($intervals['numeric']); + if ($last !== false && (string) $last->getEnd() !== (string) Interval::untilPositiveInfinity()) { + $constraint = new MultiConstraint([$constraint, new Constraint('>=', $last->getEnd()->getVersion())], false); + } + + return $constraint; + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php new file mode 100644 index 000000000..ab225d6c9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +final class IgnoreNothingPlatformRequirementFilter implements PlatformRequirementFilterInterface +{ + /** + * @return false + */ + public function isIgnored(string $req): bool + { + return false; + } + + /** + * @return false + */ + public function isUpperBoundIgnored(string $req): bool + { + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php new file mode 100644 index 000000000..f15eff92e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php @@ -0,0 +1,47 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +final class PlatformRequirementFilterFactory +{ + /** + * @param mixed $boolOrList + */ + public static function fromBoolOrList($boolOrList): PlatformRequirementFilterInterface + { + if (is_bool($boolOrList)) { + return $boolOrList ? self::ignoreAll() : self::ignoreNothing(); + } + + if (is_array($boolOrList)) { + return new IgnoreListPlatformRequirementFilter($boolOrList); + } + + throw new \InvalidArgumentException( + sprintf( + 'PlatformRequirementFilter: Unknown $boolOrList parameter %s. Please report at https://github.com/composer/composer/issues/new.', + get_debug_type($boolOrList) + ) + ); + } + + public static function ignoreAll(): PlatformRequirementFilterInterface + { + return new IgnoreAllPlatformRequirementFilter(); + } + + public static function ignoreNothing(): PlatformRequirementFilterInterface + { + return new IgnoreNothingPlatformRequirementFilter(); + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php new file mode 100644 index 000000000..59e824591 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php @@ -0,0 +1,20 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +interface PlatformRequirementFilterInterface +{ + public function isIgnored(string $req): bool; + + public function isUpperBoundIgnored(string $req): bool; +} diff --git a/vendor/composer/composer/src/Composer/IO/BaseIO.php b/vendor/composer/composer/src/Composer/IO/BaseIO.php new file mode 100644 index 000000000..fc3155c3a --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/BaseIO.php @@ -0,0 +1,311 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +use Composer\Config; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Silencer; +use Psr\Log\LogLevel; + +abstract class BaseIO implements IOInterface +{ + /** @var array */ + protected $authentications = []; + + /** + * @inheritDoc + */ + public function getAuthentications() + { + return $this->authentications; + } + + /** + * @return void + */ + public function resetAuthentications() + { + $this->authentications = []; + } + + /** + * @inheritDoc + */ + public function hasAuthentication($repositoryName) + { + return isset($this->authentications[$repositoryName]); + } + + /** + * @inheritDoc + */ + public function getAuthentication($repositoryName) + { + if (isset($this->authentications[$repositoryName])) { + return $this->authentications[$repositoryName]; + } + + return ['username' => null, 'password' => null]; + } + + /** + * @inheritDoc + */ + public function setAuthentication($repositoryName, $username, $password = null) + { + $this->authentications[$repositoryName] = ['username' => $username, 'password' => $password]; + } + + /** + * @inheritDoc + */ + public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->write($messages, $newline, $verbosity); + } + + /** + * @inheritDoc + */ + public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->writeError($messages, $newline, $verbosity); + } + + /** + * Check for overwrite and set the authentication information for the repository. + * + * @param string $repositoryName The unique name of repository + * @param string $username The username + * @param string $password The password + * + * @return void + */ + protected function checkAndSetAuthentication(string $repositoryName, string $username, ?string $password = null) + { + if ($this->hasAuthentication($repositoryName)) { + $auth = $this->getAuthentication($repositoryName); + if ($auth['username'] === $username && $auth['password'] === $password) { + return; + } + + $this->writeError( + sprintf( + "Warning: You should avoid overwriting already defined auth settings for %s.", + $repositoryName + ) + ); + } + $this->setAuthentication($repositoryName, $username, $password); + } + + /** + * @inheritDoc + */ + public function loadConfiguration(Config $config) + { + $bitbucketOauth = $config->get('bitbucket-oauth'); + $githubOauth = $config->get('github-oauth'); + $gitlabOauth = $config->get('gitlab-oauth'); + $gitlabToken = $config->get('gitlab-token'); + $forgejoToken = $config->get('forgejo-token'); + $httpBasic = $config->get('http-basic'); + $bearerToken = $config->get('bearer'); + $customHeaders = $config->get('custom-headers'); + $clientCertificate = $config->get('client-certificate'); + + // reload oauth tokens from config if available + + foreach ($bitbucketOauth as $domain => $cred) { + $this->checkAndSetAuthentication($domain, $cred['consumer-key'], $cred['consumer-secret']); + } + + foreach ($githubOauth as $domain => $token) { + if ($domain !== 'github.com' && !in_array($domain, $config->get('github-domains'), true)) { + $this->debug($domain.' is not in the configured github-domains, adding it implicitly as authentication is configured for this domain'); + $config->merge(['config' => ['github-domains' => array_merge($config->get('github-domains'), [$domain])]], 'implicit-due-to-auth'); + } + + // allowed chars for GH tokens are from https://github.blog/changelog/2021-03-04-authentication-token-format-updates/ + // plus dots which were at some point used for GH app integration tokens + if (!Preg::isMatch('{^[.A-Za-z0-9_]+$}', $token)) { + throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"'); + } + $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic'); + } + + foreach ($gitlabOauth as $domain => $token) { + if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) { + $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain'); + $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth'); + } + + $token = is_array($token) ? $token["token"] : $token; + $this->checkAndSetAuthentication($domain, $token, 'oauth2'); + } + + foreach ($gitlabToken as $domain => $token) { + if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) { + $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain'); + $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth'); + } + + $username = is_array($token) ? $token["username"] : $token; + $password = is_array($token) ? $token["token"] : 'private-token'; + $this->checkAndSetAuthentication($domain, $username, $password); + } + + foreach ($forgejoToken as $domain => $cred) { + if (!in_array($domain, $config->get('forgejo-domains'), true)) { + $this->debug($domain.' is not in the configured forgejo-domains, adding it implicitly as authentication is configured for this domain'); + $config->merge(['config' => ['forgejo-domains' => array_merge($config->get('forgejo-domains'), [$domain])]], 'implicit-due-to-auth'); + } + + $this->checkAndSetAuthentication($domain, $cred['username'], $cred['token']); + } + + // reload http basic credentials from config if available + foreach ($httpBasic as $domain => $cred) { + $this->checkAndSetAuthentication($domain, $cred['username'], $cred['password']); + } + + foreach ($bearerToken as $domain => $token) { + $this->checkAndSetAuthentication($domain, $token, 'bearer'); + } + + // load custom HTTP headers from config + foreach ($customHeaders as $domain => $headers) { + if ($headers !== null) { + $this->checkAndSetAuthentication($domain, (string) json_encode($headers), 'custom-headers'); + } + } + + // reload ssl client certificate credentials from config if available + foreach ($clientCertificate as $domain => $cred) { + $sslOptions = array_filter( + [ + 'local_cert' => $cred['local_cert'] ?? null, + 'local_pk' => $cred['local_pk'] ?? null, + 'passphrase' => $cred['passphrase'] ?? null, + ], + static function (?string $value): bool { return $value !== null; } + ); + if (!isset($sslOptions['local_cert'])) { + $this->writeError( + sprintf( + 'Warning: Client certificate configuration is missing key `local_cert` for %s.', + $domain + ) + ); + continue; + } + $this->checkAndSetAuthentication($domain, 'client-certificate', (string) json_encode($sslOptions)); + } + + // setup process timeout + ProcessExecutor::setTimeout($config->get('process-timeout')); + } + + /** + * @param string|\Stringable $message + */ + public function emergency($message, array $context = []): void + { + $this->log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function alert($message, array $context = []): void + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function critical($message, array $context = []): void + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function error($message, array $context = []): void + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function warning($message, array $context = []): void + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function notice($message, array $context = []): void + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function info($message, array $context = []): void + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function debug($message, array $context = []): void + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * @param mixed|LogLevel::* $level + * @param string|\Stringable $message + */ + public function log($level, $message, array $context = []): void + { + $message = (string) $message; + + if ($context !== []) { + $json = Silencer::call('json_encode', $context, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + if ($json !== false) { + $message .= ' ' . $json; + } + } + + if (in_array($level, [LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR])) { + $this->writeError(''.$message.''); + } elseif ($level === LogLevel::WARNING) { + $this->writeError(''.$message.''); + } elseif ($level === LogLevel::NOTICE) { + $this->writeError(''.$message.'', true, self::VERBOSE); + } elseif ($level === LogLevel::INFO) { + $this->writeError(''.$message.'', true, self::VERY_VERBOSE); + } else { + $this->writeError($message, true, self::DEBUG); + } + } +} diff --git a/vendor/composer/composer/src/Composer/IO/BufferIO.php b/vendor/composer/composer/src/Composer/IO/BufferIO.php new file mode 100644 index 000000000..aab89b02b --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/BufferIO.php @@ -0,0 +1,103 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +use Composer\Pcre\Preg; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Helper\HelperSet; + +/** + * @author Jordi Boggiano + */ +class BufferIO extends ConsoleIO +{ + public function __construct(string $input = '', int $verbosity = StreamOutput::VERBOSITY_NORMAL, ?OutputFormatterInterface $formatter = null) + { + $input = new StringInput($input); + $input->setInteractive(false); + + $stream = fopen('php://memory', 'rw'); + if ($stream === false) { + throw new \RuntimeException('Unable to open memory output stream'); + } + $output = new StreamOutput($stream, $verbosity, $formatter !== null ? $formatter->isDecorated() : false, $formatter); + + parent::__construct($input, $output, new HelperSet([ + new QuestionHelper(), + ])); + } + + /** + * @return string output + */ + public function getOutput(): string + { + assert($this->output instanceof StreamOutput); + fseek($this->output->getStream(), 0); + + $output = (string) stream_get_contents($this->output->getStream()); + + $output = Preg::replaceCallback("{(?<=^|\n|\x08)(.+?)(\x08+)}", static function ($matches): string { + $pre = strip_tags($matches[1]); + + if (strlen($pre) === strlen($matches[2])) { + return ''; + } + + // TODO reverse parse the string, skipping span tags and \033\[([0-9;]+)m(.*?)\033\[0m style blobs + return rtrim($matches[1])."\n"; + }, $output); + + return $output; + } + + /** + * @param string[] $inputs + * + * @see createStream + */ + public function setUserInputs(array $inputs): void + { + if (!$this->input instanceof StreamableInputInterface) { + throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.'); + } + + $this->input->setStream($this->createStream($inputs)); + $this->input->setInteractive(true); + } + + /** + * @param string[] $inputs + * + * @return resource stream + */ + private function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+'); + if ($stream === false) { + throw new \RuntimeException('Unable to open memory output stream'); + } + + foreach ($inputs as $input) { + fwrite($stream, $input.PHP_EOL); + } + + rewind($stream); + + return $stream; + } +} diff --git a/vendor/composer/composer/src/Composer/IO/ConsoleIO.php b/vendor/composer/composer/src/Composer/IO/ConsoleIO.php new file mode 100644 index 000000000..96afc11b5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/ConsoleIO.php @@ -0,0 +1,413 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +use Composer\Pcre\Preg; +use Composer\Question\StrictConfirmationQuestion; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\Question; + +/** + * The Input/Output helper. + * + * @author François Pluchino + * @author Jordi Boggiano + */ +class ConsoleIO extends BaseIO +{ + /** @var InputInterface */ + protected $input; + /** @var OutputInterface */ + protected $output; + /** @var HelperSet */ + protected $helperSet; + /** @var string */ + protected $lastMessage = ''; + /** @var string */ + protected $lastMessageErr = ''; + + /** @var float */ + private $startTime; + /** @var array */ + private $verbosityMap; + + /** + * Constructor. + * + * @param InputInterface $input The input instance + * @param OutputInterface $output The output instance + * @param HelperSet $helperSet The helperSet instance + */ + public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet) + { + $this->input = $input; + $this->output = $output; + $this->helperSet = $helperSet; + $this->verbosityMap = [ + self::QUIET => OutputInterface::VERBOSITY_QUIET, + self::NORMAL => OutputInterface::VERBOSITY_NORMAL, + self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE, + self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE, + self::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ]; + } + + /** + * @return void + */ + public function enableDebugging(float $startTime) + { + $this->startTime = $startTime; + } + + /** + * @inheritDoc + */ + public function isInteractive() + { + return $this->input->isInteractive(); + } + + /** + * @inheritDoc + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + + /** + * @inheritDoc + */ + public function isVerbose() + { + return $this->output->isVerbose(); + } + + /** + * @inheritDoc + */ + public function isVeryVerbose() + { + return $this->output->isVeryVerbose(); + } + + /** + * @inheritDoc + */ + public function isDebug() + { + return $this->output->isDebug(); + } + + /** + * @inheritDoc + */ + public function write($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $messages = self::sanitize($messages); + + $this->doWrite($messages, $newline, false, $verbosity); + } + + /** + * @inheritDoc + */ + public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $messages = self::sanitize($messages); + + $this->doWrite($messages, $newline, true, $verbosity); + } + + /** + * @inheritDoc + */ + public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->doWrite($messages, $newline, false, $verbosity, true); + } + + /** + * @inheritDoc + */ + public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->doWrite($messages, $newline, true, $verbosity, true); + } + + /** + * @param string[]|string $messages + */ + private function doWrite($messages, bool $newline, bool $stderr, int $verbosity, bool $raw = false): void + { + $sfVerbosity = $this->verbosityMap[$verbosity]; + if ($sfVerbosity > $this->output->getVerbosity()) { + return; + } + + if ($raw) { + $sfVerbosity |= OutputInterface::OUTPUT_RAW; + } + + if (null !== $this->startTime) { + $memoryUsage = memory_get_usage() / 1024 / 1024; + $timeSpent = microtime(true) - $this->startTime; + $messages = array_map(static function ($message) use ($memoryUsage, $timeSpent): string { + return sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message); + }, (array) $messages); + } + + if (true === $stderr && $this->output instanceof ConsoleOutputInterface) { + $this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity); + $this->lastMessageErr = implode($newline ? "\n" : '', (array) $messages); + + return; + } + + $this->output->write($messages, $newline, $sfVerbosity); + $this->lastMessage = implode($newline ? "\n" : '', (array) $messages); + } + + /** + * @inheritDoc + */ + public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL) + { + $this->doOverwrite($messages, $newline, $size, false, $verbosity); + } + + /** + * @inheritDoc + */ + public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL) + { + $this->doOverwrite($messages, $newline, $size, true, $verbosity); + } + + /** + * @param string[]|string $messages + */ + private function doOverwrite($messages, bool $newline, ?int $size, bool $stderr, int $verbosity): void + { + // messages can be an array, let's convert it to string anyway + $messages = implode($newline ? "\n" : '', (array) $messages); + + // since overwrite is supposed to overwrite last message... + if (!isset($size)) { + // removing possible formatting of lastMessage with strip_tags + $size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage)); + } + // ...let's fill its length with backspaces + $this->doWrite(str_repeat("\x08", $size), false, $stderr, $verbosity); + + // write the new message + $this->doWrite($messages, false, $stderr, $verbosity); + + // In cmd.exe on Win8.1 (possibly 10?), the line can not be cleared, so we need to + // track the length of previous output and fill it with spaces to make sure the line is cleared. + // See https://github.com/composer/composer/pull/5836 for more details + $fill = $size - strlen(strip_tags($messages)); + if ($fill > 0) { + // whitespace whatever has left + $this->doWrite(str_repeat(' ', $fill), false, $stderr, $verbosity); + // move the cursor back + $this->doWrite(str_repeat("\x08", $fill), false, $stderr, $verbosity); + } + + if ($newline) { + $this->doWrite('', true, $stderr, $verbosity); + } + + if ($stderr) { + $this->lastMessageErr = $messages; + } else { + $this->lastMessage = $messages; + } + } + + /** + * @return ProgressBar + */ + public function getProgressBar(int $max = 0) + { + return new ProgressBar($this->getErrorOutput(), $max); + } + + /** + * @inheritDoc + */ + public function ask($question, $default = null) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new Question(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } + + /** + * @inheritDoc + */ + public function askConfirmation($question, $default = true) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new StrictConfirmationQuestion(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } + + /** + * @inheritDoc + */ + public function askAndValidate($question, $validator, $attempts = null, $default = null) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new Question(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default); + $question->setValidator($validator); + $question->setMaxAttempts($attempts); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } + + /** + * @inheritDoc + */ + public function askAndHideAnswer($question) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new Question(self::sanitize($question)); + $question->setHidden(true); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } + + /** + * @inheritDoc + */ + public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new ChoiceQuestion(self::sanitize($question), self::sanitize($choices), is_string($default) ? self::sanitize($default) : $default); + $question->setMaxAttempts($attempts ?: null); // IOInterface requires false, and Question requires null or int + $question->setErrorMessage($errorMessage); + $question->setMultiselect($multiselect); + + $result = $helper->ask($this->input, $this->getErrorOutput(), $question); + + $isAssoc = (bool) \count(array_filter(array_keys($choices), 'is_string')); + if ($isAssoc) { + return $result; + } + + if (!is_array($result)) { + return (string) array_search($result, $choices, true); + } + + $results = []; + foreach ($choices as $index => $choice) { + if (in_array($choice, $result, true)) { + $results[] = (string) $index; + } + } + + return $results; + } + + public function getTable(): Table + { + return new Table($this->output); + } + + private function getErrorOutput(): OutputInterface + { + if ($this->output instanceof ConsoleOutputInterface) { + return $this->output->getErrorOutput(); + } + + return $this->output; + } + + /** + * Sanitize string to remove control characters + * + * If $allowNewlines is true, \x0A (\n) and \x0D\x0A (\r\n) are let through. Single \r are still sanitized away to prevent overwriting whole lines. + * + * All other control chars (except NULL bytes) as well as ANSI escape sequences are removed. + * + * Invalid unicode sequences are turned into question marks. + * + * @param string|iterable $messages + * @return string|array + * @phpstan-return ($messages is string ? string : array) + */ + public static function sanitize($messages, bool $allowNewlines = true) + { + // Match ANSI escape sequences: + // - CSI (Control Sequence Introducer): ESC [ params intermediate final + // - OSC (Operating System Command): ESC ] ... ESC \ or BEL + // - Other ESC sequences: ESC followed by any character + $escapePattern = '\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\x1B\].*?(?:\x1B\\\\|\x07)|\x1B.'; + $pattern = $allowNewlines ? "{{$escapePattern}|[\x01-\x09\x0B\x0C\x0E-\x1A]|\r(?!\n)}u" : "{{$escapePattern}|[\x01-\x1A]}u"; + if (is_string($messages)) { + $messages = self::ensureValidUtf8($messages); + return Preg::replace($pattern, '', $messages); + } + + $sanitized = []; + foreach ($messages as $key => $message) { + $message = self::ensureValidUtf8($message); + $sanitized[$key] = Preg::replace($pattern, '', $message); + } + + return $sanitized; + } + + /** + * Ensures a string is valid UTF-8, replacing invalid byte sequences with '?' + */ + private static function ensureValidUtf8(string $string): string + { + // Quick check: if string is already valid UTF-8, return as-is + if (function_exists('mb_check_encoding') && mb_check_encoding($string, 'UTF-8')) { + return $string; + } + + // Use mb_convert_encoding to replace invalid sequences with '?' + // This makes it visible when data quality issues occur + if (function_exists('mb_convert_encoding')) { + return (string) mb_convert_encoding($string, 'UTF-8', 'UTF-8'); + } + + // Fallback to iconv if mbstring unavailable + if (function_exists('iconv')) { + $cleaned = @iconv('UTF-8', 'UTF-8//TRANSLIT', $string); + if ($cleaned !== false) { + return $cleaned; + } + } + + // Last resort: return as-is (should never happen - Composer requires mbstring OR iconv) + return $string; + } +} diff --git a/vendor/composer/composer/src/Composer/IO/IOInterface.php b/vendor/composer/composer/src/Composer/IO/IOInterface.php new file mode 100644 index 000000000..88b1cd46c --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/IOInterface.php @@ -0,0 +1,242 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +use Composer\Config; +use Psr\Log\LoggerInterface; + +/** + * The Input/Output helper interface. + * + * @author François Pluchino + */ +interface IOInterface extends LoggerInterface +{ + public const QUIET = 1; + public const NORMAL = 2; + public const VERBOSE = 4; + public const VERY_VERBOSE = 8; + public const DEBUG = 16; + + /** + * Is this input means interactive? + * + * @return bool + */ + public function isInteractive(); + + /** + * Is this output verbose? + * + * @return bool + */ + public function isVerbose(); + + /** + * Is the output very verbose? + * + * @return bool + */ + public function isVeryVerbose(); + + /** + * Is the output in debug verbosity? + * + * @return bool + */ + public function isDebug(); + + /** + * Is this output decorated? + * + * @return bool + */ + public function isDecorated(); + + /** + * Writes a message to the output. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function write($messages, bool $newline = true, int $verbosity = self::NORMAL); + + /** + * Writes a message to the error output. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL); + + /** + * Writes a message to the output, without formatting it. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL); + + /** + * Writes a message to the error output, without formatting it. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL); + + /** + * Overwrites a previous message to the output. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $size The size of line + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL); + + /** + * Overwrites a previous message to the error output. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $size The size of line + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL); + + /** + * Asks a question to the user. + * + * @param string $question The question to ask + * @param string|bool|int|float|null $default The default answer if none is given by the user + * + * @throws \RuntimeException If there is no data to read in the input stream + * @return mixed The user answer + */ + public function ask(string $question, $default = null); + + /** + * Asks a confirmation to the user. + * + * The question will be asked until the user answers by nothing, yes, or no. + * + * @param string $question The question to ask + * @param bool $default The default answer if the user enters nothing + * + * @return bool true if the user has confirmed, false otherwise + */ + public function askConfirmation(string $question, bool $default = true); + + /** + * Asks for a value and validates the response. + * + * The validator receives the data to validate. It must return the + * validated data when the data is valid and throw an exception + * otherwise. + * + * @param string $question The question to ask + * @param callable $validator A PHP callback + * @param null|int $attempts Max number of times to ask before giving up (default of null means infinite) + * @param mixed $default The default answer if none is given by the user + * + * @throws \Exception When any of the validators return an error + * @return mixed + */ + public function askAndValidate(string $question, callable $validator, ?int $attempts = null, $default = null); + + /** + * Asks a question to the user and hide the answer. + * + * @param string $question The question to ask + * + * @return string|null The answer + */ + public function askAndHideAnswer(string $question); + + /** + * Asks the user to select a value. + * + * @param string $question The question to ask + * @param string[] $choices List of choices to pick from + * @param bool|string $default The default answer if the user enters nothing + * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked + * @param bool $multiselect Select more than one value separated by comma + * + * @throws \InvalidArgumentException + * + * @return int|string|list|bool The selected value or values (the key of the choices array) + * @phpstan-return ($multiselect is true ? list : string|int|bool) + */ + public function select(string $question, array $choices, $default, $attempts = false, string $errorMessage = 'Value "%s" is invalid', bool $multiselect = false); + + /** + * Get all authentication information entered. + * + * @return array The map of authentication data + */ + public function getAuthentications(); + + /** + * Verify if the repository has a authentication information. + * + * @param string $repositoryName The unique name of repository + * + * @return bool + */ + public function hasAuthentication(string $repositoryName); + + /** + * Get the username and password of repository. + * + * @param string $repositoryName The unique name of repository + * + * @return array{username: string|null, password: string|null} + */ + public function getAuthentication(string $repositoryName); + + /** + * Set the authentication information for the repository. + * + * @param string $repositoryName The unique name of repository + * @param string $username The username + * @param null|string $password The password + * + * @return void + */ + public function setAuthentication(string $repositoryName, string $username, ?string $password = null); + + /** + * Loads authentications from a config instance + * + * @return void + */ + public function loadConfiguration(Config $config); +} diff --git a/vendor/composer/composer/src/Composer/IO/NullIO.php b/vendor/composer/composer/src/Composer/IO/NullIO.php new file mode 100644 index 000000000..fd73feec9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/NullIO.php @@ -0,0 +1,129 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +/** + * IOInterface that is not interactive and never writes the output + * + * @author Christophe Coevoet + */ +class NullIO extends BaseIO +{ + /** + * @inheritDoc + */ + public function isInteractive(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function isVerbose(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function isVeryVerbose(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function isDebug(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function isDecorated(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function write($messages, bool $newline = true, int $verbosity = self::NORMAL): void + { + } + + /** + * @inheritDoc + */ + public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL): void + { + } + + /** + * @inheritDoc + */ + public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL): void + { + } + + /** + * @inheritDoc + */ + public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL): void + { + } + + /** + * @inheritDoc + */ + public function ask($question, $default = null) + { + return $default; + } + + /** + * @inheritDoc + */ + public function askConfirmation($question, $default = true): bool + { + return $default; + } + + /** + * @inheritDoc + */ + public function askAndValidate($question, $validator, $attempts = null, $default = null) + { + return $default; + } + + /** + * @inheritDoc + */ + public function askAndHideAnswer($question): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) + { + return $default; + } +} diff --git a/vendor/composer/composer/src/Composer/InstalledVersions.php b/vendor/composer/composer/src/Composer/InstalledVersions.php new file mode 100644 index 000000000..2052022fd --- /dev/null +++ b/vendor/composer/composer/src/Composer/InstalledVersions.php @@ -0,0 +1,396 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool + */ + private static $installedIsLocalDir; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer.php b/vendor/composer/composer/src/Composer/Installer.php new file mode 100644 index 000000000..2934c57e2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer.php @@ -0,0 +1,1565 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Advisory\AuditConfig; +use Composer\Autoload\AutoloadGenerator; +use Composer\Console\GithubActionError; +use Composer\DependencyResolver\DefaultPolicy; +use Composer\DependencyResolver\LocalRepoTransaction; +use Composer\DependencyResolver\LockTransaction; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\PoolOptimizer; +use Composer\DependencyResolver\Pool; +use Composer\DependencyResolver\Request; +use Composer\DependencyResolver\SecurityAdvisoryPoolFilter; +use Composer\DependencyResolver\Solver; +use Composer\DependencyResolver\SolverProblemsException; +use Composer\DependencyResolver\PolicyInterface; +use Composer\Downloader\DownloadManager; +use Composer\Downloader\TransportException; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\Installer\InstallationManager; +use Composer\Installer\InstallerEvents; +use Composer\Installer\SuggestedPackagesReporter; +use Composer\IO\IOInterface; +use Composer\Package\AliasPackage; +use Composer\Package\RootAliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Link; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\Version\VersionParser; +use Composer\Package\Package; +use Composer\Repository\ArrayRepository; +use Composer\Repository\RepositorySet; +use Composer\Repository\CompositeRepository; +use Composer\Semver\Constraint\Constraint; +use Composer\Package\Locker; +use Composer\Package\RootPackageInterface; +use Composer\Repository\InstalledArrayRepository; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Repository\InstalledRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositoryManager; +use Composer\Repository\LockArrayRepository; +use Composer\Script\ScriptEvents; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Advisory\Auditor; +use Composer\Util\Platform; + +/** + * @author Jordi Boggiano + * @author Beau Simensen + * @author Konstantin Kudryashov + * @author Nils Adermann + */ +class Installer +{ + public const ERROR_NONE = 0; // no error/success state + public const ERROR_GENERIC_FAILURE = 1; + public const ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE = 3; + public const ERROR_LOCK_FILE_INVALID = 4; + // used/declared in SolverProblemsException, carried over here for completeness + public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2; + public const ERROR_AUDIT_FAILED = 5; + // technically exceptions are thrown with various status codes >400, but the process exit code is normalized to 100 + public const ERROR_TRANSPORT_EXCEPTION = 100; + + /** + * @var IOInterface + */ + protected $io; + + /** + * @var Config + */ + protected $config; + + /** + * @var RootPackageInterface&BasePackage + */ + protected $package; + + // TODO can we get rid of the below and just use the package itself? + /** + * @var RootPackageInterface&BasePackage + */ + protected $fixedRootPackage; + + /** + * @var DownloadManager + */ + protected $downloadManager; + + /** + * @var RepositoryManager + */ + protected $repositoryManager; + + /** + * @var Locker + */ + protected $locker; + + /** + * @var InstallationManager + */ + protected $installationManager; + + /** + * @var EventDispatcher + */ + protected $eventDispatcher; + + /** + * @var AutoloadGenerator + */ + protected $autoloadGenerator; + + /** @var bool */ + protected $preferSource = false; + /** @var bool */ + protected $preferDist = false; + /** @var bool */ + protected $optimizeAutoloader = false; + /** @var bool */ + protected $classMapAuthoritative = false; + /** @var bool */ + protected $apcuAutoloader = false; + /** @var string|null */ + protected $apcuAutoloaderPrefix = null; + /** @var bool */ + protected $devMode = false; + /** @var bool */ + protected $dryRun = false; + /** @var bool */ + protected $downloadOnly = false; + /** @var bool */ + protected $verbose = false; + /** @var bool */ + protected $update = false; + /** @var bool */ + protected $install = true; + /** @var bool */ + protected $dumpAutoloader = true; + /** @var bool */ + protected $runScripts = true; + /** @var bool */ + protected $preferStable = false; + /** @var bool */ + protected $preferLowest = false; + /** @var bool */ + protected $minimalUpdate = false; + /** @var bool */ + protected $writeLock; + /** @var bool */ + protected $executeOperations = true; + /** @var bool */ + protected $audit = true; + /** @var bool */ + protected $errorOnAudit = false; + /** @var Auditor::FORMAT_* */ + protected $auditFormat = Auditor::FORMAT_SUMMARY; + /** @var AuditConfig|null */ + private $auditConfig = null; + /** @var list */ + private $ignoredTypes = ['php-ext', 'php-ext-zend']; + /** @var list|null */ + private $allowedTypes = null; + + /** @var bool */ + protected $updateMirrors = false; + /** + * Array of package names/globs flagged for update + * + * @var non-empty-list|null + */ + protected $updateAllowList = null; + /** @var Request::UPDATE_* */ + protected $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; + + /** + * @var SuggestedPackagesReporter + */ + protected $suggestedPackagesReporter; + + /** + * @var PlatformRequirementFilterInterface + */ + protected $platformRequirementFilter; + + /** + * @var ?RepositoryInterface + */ + protected $additionalFixedRepository; + + /** @var array */ + protected $temporaryConstraints = []; + + /** + * Constructor + * + * @param RootPackageInterface&BasePackage $package + */ + public function __construct(IOInterface $io, Config $config, RootPackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator) + { + $this->io = $io; + $this->config = $config; + $this->package = $package; + $this->downloadManager = $downloadManager; + $this->repositoryManager = $repositoryManager; + $this->locker = $locker; + $this->installationManager = $installationManager; + $this->eventDispatcher = $eventDispatcher; + $this->autoloadGenerator = $autoloadGenerator; + $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io); + $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); + + $this->writeLock = $config->get('lock'); + } + + /** + * Run installation (or update) + * + * @throws \Exception + * @return int 0 on success or a positive error code on failure + * @phpstan-return self::ERROR_* + */ + public function run(): int + { + // Disable GC to save CPU cycles, as the dependency solver can create hundreds of thousands + // of PHP objects, the GC can spend quite some time walking the tree of references looking + // for stuff to collect while there is nothing to collect. This slows things down dramatically + // and turning it off results in much better performance. Do not try this at home however. + gc_collect_cycles(); + gc_disable(); + + if ($this->updateAllowList !== null && $this->updateMirrors) { + throw new \RuntimeException("The installer options updateMirrors and updateAllowList are mutually exclusive."); + } + + $isFreshInstall = $this->repositoryManager->getLocalRepository()->isFresh(); + + // Force update if there is no lock file present + if (!$this->update && !$this->locker->isLocked()) { + $this->io->writeError('No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.'); + $this->update = true; + } + + if ($this->dryRun) { + $this->verbose = true; + $this->runScripts = false; + $this->executeOperations = false; + $this->writeLock = false; + $this->dumpAutoloader = false; + $this->mockLocalRepositories($this->repositoryManager); + } + + if ($this->downloadOnly) { + $this->dumpAutoloader = false; + } + + if ($this->update && !$this->install) { + $this->dumpAutoloader = false; + } + + if ($this->runScripts) { + Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0'); + + // dispatch pre event + // should we treat this more strictly as running an update and then running an install, triggering events multiple times? + $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD; + $this->eventDispatcher->dispatchScript($eventName, $this->devMode); + } + + $this->downloadManager->setPreferSource($this->preferSource); + $this->downloadManager->setPreferDist($this->preferDist); + + $localRepo = $this->repositoryManager->getLocalRepository(); + + try { + if ($this->update) { + $res = $this->doUpdate($localRepo, $this->install); + } else { + $res = $this->doInstall($localRepo); + } + if ($res !== 0) { + return $res; + } + } catch (\Exception $e) { + if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) { + $this->installationManager->notifyInstalls($this->io); + } + + throw $e; + } + if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) { + $this->installationManager->notifyInstalls($this->io); + } + + if ($this->update) { + $installedRepo = new InstalledRepository([ + $this->locker->getLockedRepository($this->devMode), + $this->createPlatformRepo(false), + new RootPackageRepository(clone $this->package), + ]); + if ($isFreshInstall) { + $this->suggestedPackagesReporter->addSuggestionsFromPackage($this->package); + } + $this->suggestedPackagesReporter->outputMinimalistic($installedRepo); + } + + // Find abandoned packages and warn user + $lockedRepository = $this->locker->getLockedRepository(true); + foreach ($lockedRepository->getPackages() as $package) { + if (!$package instanceof CompletePackage || !$package->isAbandoned()) { + continue; + } + + $replacement = is_string($package->getReplacementPackage()) + ? 'Use ' . $package->getReplacementPackage() . ' instead' + : 'No replacement was suggested'; + + $this->io->writeError( + sprintf( + "Package %s is abandoned, you should avoid using it. %s.", + $package->getPrettyName(), + $replacement + ) + ); + } + + if ($this->dumpAutoloader) { + // write autoloader + if ($this->optimizeAutoloader) { + $this->io->writeError('Generating optimized autoload files'); + } else { + $this->io->writeError('Generating autoload files'); + } + + $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative); + $this->autoloadGenerator->setApcu($this->apcuAutoloader, $this->apcuAutoloaderPrefix); + $this->autoloadGenerator->setRunScripts($this->runScripts); + $this->autoloadGenerator->setPlatformRequirementFilter($this->platformRequirementFilter); + $this + ->autoloadGenerator + ->dump( + $this->config, + $localRepo, + $this->package, + $this->installationManager, + 'composer', + $this->optimizeAutoloader, + null, + $this->locker + ); + } + + if ($this->install && $this->executeOperations) { + // force binaries re-generation in case they are missing + foreach ($localRepo->getPackages() as $package) { + $this->installationManager->ensureBinariesPresence($package); + } + } + + $fundEnv = Platform::getEnv('COMPOSER_FUND'); + $showFunding = true; + if (is_numeric($fundEnv)) { + $showFunding = intval($fundEnv) !== 0; + } + + if ($showFunding) { + $fundingCount = 0; + foreach ($localRepo->getPackages() as $package) { + if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) { + $fundingCount++; + } + } + if ($fundingCount > 0) { + $this->io->writeError([ + sprintf( + "%d package%s you are using %s looking for funding.", + $fundingCount, + 1 === $fundingCount ? '' : 's', + 1 === $fundingCount ? 'is' : 'are' + ), + 'Use the `composer fund` command to find out more!', + ]); + } + } + + if ($this->runScripts) { + // dispatch post event + $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; + $this->eventDispatcher->dispatchScript($eventName, $this->devMode); + } + + // re-enable GC except on HHVM which triggers a warning here + if (!defined('HHVM_VERSION')) { + gc_enable(); + } + + $auditConfig = $this->getAuditConfig(); + + if ($auditConfig->audit) { + if ($this->update && !$this->install) { + $packages = $lockedRepository->getCanonicalPackages(); + $target = 'locked'; + } else { + $packages = $localRepo->getCanonicalPackages(); + $target = 'installed'; + } + if (count($packages) > 0) { + try { + $auditor = new Auditor(); + $repoSet = new RepositorySet(); + foreach ($this->repositoryManager->getRepositories() as $repo) { + $repoSet->addRepository($repo); + } + + return $auditor->audit($this->io, $repoSet, $packages, $auditConfig->auditFormat, true, $auditConfig->ignoreListForAudit, $auditConfig->auditAbandoned, $auditConfig->ignoreSeverityForAudit, $auditConfig->ignoreUnreachable, $auditConfig->ignoreAbandonedForAudit) > 0 && $this->errorOnAudit ? self::ERROR_AUDIT_FAILED : 0; + } catch (TransportException $e) { + $this->io->error('Failed to audit '.$target.' packages.'); + if ($this->io->isVerbose()) { + $this->io->error('['.get_class($e).'] '.$e->getMessage()); + } + } + } else { + $this->io->writeError('No '.$target.' packages - skipping audit.'); + } + } + + return 0; + } + + /** + * @phpstan-return self::ERROR_* + */ + protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doInstall): int + { + $platformRepo = $this->createPlatformRepo(true); + $aliases = $this->getRootAliases(true); + + $lockedRepository = null; + + try { + if ($this->locker->isLocked()) { + $lockedRepository = $this->locker->getLockedRepository(true); + } + } catch (\Seld\JsonLint\ParsingException $e) { + if ($this->updateAllowList !== null || $this->updateMirrors) { + // in case we are doing a partial update or updating mirrors, the lock file is needed so we error + throw $e; + } + // otherwise, ignoring parse errors as the lock file will be regenerated from scratch when + // doing a full update + } + + if (($this->updateAllowList !== null || $this->updateMirrors) && !$lockedRepository) { + $this->io->writeError('Cannot update ' . ($this->updateMirrors ? 'lock file information' : 'only a partial set of packages') . ' without a lock file present. Run `composer update` to generate a lock file.', true, IOInterface::QUIET); + + return self::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE; + } + + $this->io->writeError('Loading composer repositories with package information'); + + // creating repository set + $policy = $this->createPolicy(true, $lockedRepository); + $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases); + $repositories = $this->repositoryManager->getRepositories(); + foreach ($repositories as $repository) { + $repositorySet->addRepository($repository); + } + if ($lockedRepository) { + $repositorySet->addRepository($lockedRepository); + } + + $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); + $this->requirePackagesForUpdate($request, $lockedRepository, true); + + // pass the allow list into the request, so the pool builder can apply it + if ($this->updateAllowList !== null) { + $request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies); + } + + $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy), $this->ignoredTypes, $this->allowedTypes, $this->createSecurityAuditPoolFilter()); + + $this->io->writeError('Updating dependencies'); + + // solve dependencies + $solver = new Solver($policy, $pool, $this->io); + try { + $lockTransaction = $solver->solve($request, $this->platformRequirementFilter); + $ruleSetSize = $solver->getRuleSetSize(); + $solver = null; + } catch (SolverProblemsException $e) { + $err = 'Your requirements could not be resolved to an installable set of packages.'; + $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); + + $this->io->writeError(''. $err .'', true, IOInterface::QUIET); + $this->io->writeError($prettyProblem); + if (!$this->devMode) { + $this->io->writeError('Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.', true, IOInterface::QUIET); + } + + $ghe = new GithubActionError($this->io); + $ghe->emit($err."\n".$prettyProblem); + + return max(self::ERROR_GENERIC_FAILURE, $e->getCode()); + } + + $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); + $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE); + + $pool = null; + + if (!$lockTransaction->getOperations()) { + $this->io->writeError('Nothing to modify in lock file'); + + if ($this->minimalUpdate && $this->updateAllowList === null && $this->locker->isFresh()) { + $this->io->writeError('The --minimal-changes option should be used with package arguments or after modifying composer.json requirements, otherwise it will likely not yield any dependency changes.'); + } + } + + $exitCode = $this->extractDevPackages($lockTransaction, $platformRepo, $aliases, $policy, $lockedRepository); + if ($exitCode !== 0) { + return $exitCode; + } + + Semver\CompilingMatcher::clear(); + + // write lock + $platformReqs = $this->extractPlatformRequirements($this->package->getRequires()); + $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires()); + + $installsUpdates = $uninstalls = []; + if ($lockTransaction->getOperations()) { + $installNames = $updateNames = $uninstallNames = []; + foreach ($lockTransaction->getOperations() as $operation) { + if ($operation instanceof InstallOperation) { + $installsUpdates[] = $operation; + $installNames[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UpdateOperation) { + // when mirrors/metadata from a package gets updated we do not want to list it as an + // update in the output as it is only an internal lock file metadata update + if ($this->updateMirrors + && $operation->getInitialPackage()->getName() === $operation->getTargetPackage()->getName() + && $operation->getInitialPackage()->getVersion() === $operation->getTargetPackage()->getVersion() + ) { + continue; + } + + $installsUpdates[] = $operation; + $updateNames[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UninstallOperation) { + $uninstalls[] = $operation; + $uninstallNames[] = $operation->getPackage()->getPrettyName(); + } + } + + if ($this->config->get('lock')) { + $this->io->writeError(sprintf( + "Lock file operations: %d install%s, %d update%s, %d removal%s", + count($installNames), + 1 === count($installNames) ? '' : 's', + count($updateNames), + 1 === count($updateNames) ? '' : 's', + count($uninstalls), + 1 === count($uninstalls) ? '' : 's' + )); + if ($installNames) { + $this->io->writeError("Installs: ".implode(', ', $installNames), true, IOInterface::VERBOSE); + } + if ($updateNames) { + $this->io->writeError("Updates: ".implode(', ', $updateNames), true, IOInterface::VERBOSE); + } + if ($uninstalls) { + $this->io->writeError("Removals: ".implode(', ', $uninstallNames), true, IOInterface::VERBOSE); + } + } + } + + $sortByName = static function ($a, $b): int { + if ($a instanceof UpdateOperation) { + $a = $a->getTargetPackage()->getName(); + } else { + $a = $a->getPackage()->getName(); + } + if ($b instanceof UpdateOperation) { + $b = $b->getTargetPackage()->getName(); + } else { + $b = $b->getPackage()->getName(); + } + + return strcmp($a, $b); + }; + usort($uninstalls, $sortByName); + usort($installsUpdates, $sortByName); + + foreach (array_merge($uninstalls, $installsUpdates) as $operation) { + // collect suggestions + if ($operation instanceof InstallOperation) { + $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage()); + } + + // output op if lock file is enabled, but alias op only in debug verbosity + if ($this->config->get('lock') && (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug())) { + $sourceRepo = ''; + if ($this->io->isVeryVerbose() && false === strpos($operation->getOperationType(), 'Alias')) { + $operationPkg = ($operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage()); + if ($operationPkg->getRepository() !== null) { + $sourceRepo = ' from ' . $operationPkg->getRepository()->getRepoName(); + } + } + $this->io->writeError(' - ' . $operation->show(true) . $sourceRepo); + } + } + + $updatedLock = $this->locker->setLockData( + $lockTransaction->getNewLockPackages(false, $this->updateMirrors), + $lockTransaction->getNewLockPackages(true, $this->updateMirrors), + $platformReqs, + $platformDevReqs, + $lockTransaction->getAliases($aliases), + $this->package->getMinimumStability(), + $this->package->getStabilityFlags(), + $this->preferStable || $this->package->getPreferStable(), + $this->preferLowest, + $this->config->get('platform') ?: [], + $this->writeLock && $this->executeOperations + ); + if ($updatedLock && $this->writeLock && $this->executeOperations) { + $this->io->writeError('Writing lock file'); + } + + if ($doInstall) { + // TODO ensure lock is used from locker as-is, since it may not have been written to disk in case of executeOperations == false + return $this->doInstall($localRepo, true); + } + + return 0; + } + + /** + * Run the solver a second time on top of the existing update result with only the current result set in the pool + * and see what packages would get removed if we only had the non-dev packages in the solver request + * + * @param array> $aliases + * + * @phpstan-param list $aliases + * @phpstan-return self::ERROR_* + */ + protected function extractDevPackages(LockTransaction $lockTransaction, PlatformRepository $platformRepo, array $aliases, PolicyInterface $policy, ?LockArrayRepository $lockedRepository = null): int + { + if (!$this->package->getDevRequires()) { + return 0; + } + + $resultRepo = new ArrayRepository([]); + $loader = new ArrayLoader(null, true); + $dumper = new ArrayDumper(); + foreach ($lockTransaction->getNewLockPackages(false) as $pkg) { + $resultRepo->addPackage($loader->load($dumper->dump($pkg))); + } + + $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases); + $repositorySet->addRepository($resultRepo); + + $request = $this->createRequest($this->fixedRootPackage, $platformRepo); + $this->requirePackagesForUpdate($request, $lockedRepository, false); + + $pool = $repositorySet->createPoolWithAllPackages(); + + $solver = new Solver($policy, $pool, $this->io); + try { + $nonDevLockTransaction = $solver->solve($request, $this->platformRequirementFilter); + $solver = null; + } catch (SolverProblemsException $e) { + $err = 'Unable to find a compatible set of packages based on your non-dev requirements alone.'; + $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), true); + + $this->io->writeError(''. $err .'', true, IOInterface::QUIET); + $this->io->writeError('Your requirements can be resolved successfully when require-dev packages are present.'); + $this->io->writeError('You may need to move packages from require-dev or some of their dependencies to require.'); + $this->io->writeError($prettyProblem); + + $ghe = new GithubActionError($this->io); + $ghe->emit($err."\n".$prettyProblem); + + return $e->getCode(); + } + + $lockTransaction->setNonDevPackages($nonDevLockTransaction); + + return 0; + } + + /** + * @param bool $alreadySolved Whether the function is called as part of an update command or independently + * @return int exit code + * @phpstan-return self::ERROR_* + */ + protected function doInstall(InstalledRepositoryInterface $localRepo, bool $alreadySolved = false): int + { + if ($this->config->get('lock')) { + $this->io->writeError('Installing dependencies from lock file'.($this->devMode ? ' (including require-dev)' : '').''); + } + + $lockedRepository = $this->locker->getLockedRepository($this->devMode); + + // verify that the lock file works with the current platform repository + // we can skip this part if we're doing this as the second step after an update + if (!$alreadySolved) { + $this->io->writeError('Verifying lock file contents can be installed on current platform.'); + + $platformRepo = $this->createPlatformRepo(false); + // creating repository set + $policy = $this->createPolicy(false); + // use aliases from lock file only, so empty root aliases here + $repositorySet = $this->createRepositorySet(false, $platformRepo, [], $lockedRepository); + $repositorySet->addRepository($lockedRepository); + + // creating requirements request + $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); + + if (!$this->locker->isFresh()) { + $this->io->writeError('Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update `.', true, IOInterface::QUIET); + } + + $missingRequirementInfo = $this->locker->getMissingRequirementInfo($this->package, $this->devMode); + if ($missingRequirementInfo !== []) { + $this->io->writeError($missingRequirementInfo); + + if (!$this->config->get('allow-missing-requirements')) { + return self::ERROR_LOCK_FILE_INVALID; + } + } + + foreach ($lockedRepository->getPackages() as $package) { + $request->fixLockedPackage($package); + } + + $rootRequires = $this->package->getRequires(); + if ($this->devMode) { + $rootRequires = array_merge($rootRequires, $this->package->getDevRequires()); + } + foreach ($rootRequires as $link) { + if (PlatformRepository::isPlatformPackage($link->getTarget())) { + $request->requireName($link->getTarget(), $link->getConstraint()); + } + } + + foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) { + if (!isset($rootRequires[$link->getTarget()])) { + $request->requireName($link->getTarget(), $link->getConstraint()); + } + } + unset($rootRequires, $link); + + $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, null, $this->ignoredTypes, $this->allowedTypes, null); + + // solve dependencies + $solver = new Solver($policy, $pool, $this->io); + try { + $lockTransaction = $solver->solve($request, $this->platformRequirementFilter); + $solver = null; + + // installing the locked packages on this platform resulted in lock modifying operations, there wasn't a conflict, but the lock file as-is seems to not work on this system + if (0 !== count($lockTransaction->getOperations())) { + $this->io->writeError('Your lock file cannot be installed on this system without changes. Please run composer update.', true, IOInterface::QUIET); + + return self::ERROR_LOCK_FILE_INVALID; + } + } catch (SolverProblemsException $e) { + $err = 'Your lock file does not contain a compatible set of packages. Please run composer update.'; + $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); + + $this->io->writeError(''. $err .'', true, IOInterface::QUIET); + $this->io->writeError($prettyProblem); + + $ghe = new GithubActionError($this->io); + $ghe->emit($err."\n".$prettyProblem); + + return max(self::ERROR_GENERIC_FAILURE, $e->getCode()); + } + } + + // TODO in how far do we need to do anything here to ensure dev packages being updated to latest in lock without version change are treated correctly? + $localRepoTransaction = new LocalRepoTransaction($lockedRepository, $localRepo); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_OPERATIONS_EXEC, $this->devMode, $this->executeOperations, $localRepoTransaction); + + $installs = $updates = $uninstalls = []; + foreach ($localRepoTransaction->getOperations() as $operation) { + if ($operation instanceof InstallOperation) { + $installs[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UpdateOperation) { + $updates[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UninstallOperation) { + $uninstalls[] = $operation->getPackage()->getPrettyName(); + } + } + + if ($installs === [] && $updates === [] && $uninstalls === []) { + $this->io->writeError('Nothing to install, update or remove'); + } else { + $this->io->writeError(sprintf( + "Package operations: %d install%s, %d update%s, %d removal%s", + count($installs), + 1 === count($installs) ? '' : 's', + count($updates), + 1 === count($updates) ? '' : 's', + count($uninstalls), + 1 === count($uninstalls) ? '' : 's' + )); + if ($installs) { + $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE); + } + if ($updates) { + $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE); + } + if ($uninstalls) { + $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE); + } + } + + if ($this->executeOperations) { + $localRepo->setDevPackageNames($this->locker->getDevPackageNames()); + $this->installationManager->execute($localRepo, $localRepoTransaction->getOperations(), $this->devMode, $this->runScripts, $this->downloadOnly); + + // see https://github.com/composer/composer/issues/2764 + if (count($localRepoTransaction->getOperations()) > 0) { + $vendorDir = $this->config->get('vendor-dir'); + if (is_dir($vendorDir)) { + // suppress errors as this fails sometimes on OSX for no apparent reason + // see https://github.com/composer/composer/issues/4070#issuecomment-129792748 + @touch($vendorDir); + } + } + } else { + foreach ($localRepoTransaction->getOperations() as $operation) { + // output op, but alias op only in debug verbosity + if (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug()) { + $this->io->writeError(' - ' . $operation->show(false)); + } + } + } + + return 0; + } + + protected function createPlatformRepo(bool $forUpdate): PlatformRepository + { + if ($forUpdate) { + $platformOverrides = $this->config->get('platform') ?: []; + } else { + $platformOverrides = $this->locker->getPlatformOverrides(); + } + + return new PlatformRepository([], $platformOverrides); + } + + /** + * @param array> $rootAliases + * + * @phpstan-param list $rootAliases + */ + private function createRepositorySet(bool $forUpdate, PlatformRepository $platformRepo, array $rootAliases = [], ?RepositoryInterface $lockedRepository = null): RepositorySet + { + if ($forUpdate) { + $minimumStability = $this->package->getMinimumStability(); + $stabilityFlags = $this->package->getStabilityFlags(); + + $requires = array_merge($this->package->getRequires(), $this->package->getDevRequires()); + } else { + $minimumStability = $this->locker->getMinimumStability(); + $stabilityFlags = $this->locker->getStabilityFlags(); + + $requires = []; + foreach ($lockedRepository->getPackages() as $package) { + $constraint = new Constraint('=', $package->getVersion()); + $constraint->setPrettyString($package->getPrettyVersion()); + $requires[$package->getName()] = $constraint; + } + } + + $rootRequires = []; + foreach ($requires as $req => $constraint) { + if ($constraint instanceof Link) { + $constraint = $constraint->getConstraint(); + } + // skip platform requirements from the root package to avoid filtering out existing platform packages + if ($this->platformRequirementFilter->isIgnored($req)) { + continue; + } elseif ($this->platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $this->platformRequirementFilter->filterConstraint($req, $constraint); + } + $rootRequires[$req] = $constraint; + } + + $this->fixedRootPackage = clone $this->package; + $this->fixedRootPackage->setRequires([]); + $this->fixedRootPackage->setDevRequires([]); + + $stabilityFlags[$this->package->getName()] = BasePackage::STABILITIES[VersionParser::parseStability($this->package->getVersion())]; + + $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $this->package->getReferences(), $rootRequires, $this->temporaryConstraints); + $repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage)); + $repositorySet->addRepository($platformRepo); + if ($this->additionalFixedRepository) { + // allow using installed repos if needed to avoid warnings about installed repositories being used in the RepositorySet + // see https://github.com/composer/composer/pull/9574 + $additionalFixedRepositories = $this->additionalFixedRepository; + if ($additionalFixedRepositories instanceof CompositeRepository) { + $additionalFixedRepositories = $additionalFixedRepositories->getRepositories(); + } else { + $additionalFixedRepositories = [$additionalFixedRepositories]; + } + foreach ($additionalFixedRepositories as $additionalFixedRepository) { + if ($additionalFixedRepository instanceof InstalledRepository || $additionalFixedRepository instanceof InstalledRepositoryInterface) { + $repositorySet->allowInstalledRepositories(); + break; + } + } + + $repositorySet->addRepository($this->additionalFixedRepository); + } + + return $repositorySet; + } + + private function createPolicy(bool $forUpdate, ?LockArrayRepository $lockedRepo = null): DefaultPolicy + { + $preferStable = null; + $preferLowest = null; + if (!$forUpdate) { + $preferStable = $this->locker->getPreferStable(); + $preferLowest = $this->locker->getPreferLowest(); + } + // old lock file without prefer stable/lowest will return null + // so in this case we use the composer.json info + if (null === $preferStable) { + $preferStable = $this->preferStable || $this->package->getPreferStable(); + } + if (null === $preferLowest) { + $preferLowest = $this->preferLowest; + } + + $preferredVersions = null; + if ($forUpdate && $this->minimalUpdate && $lockedRepo !== null) { + $preferredVersions = []; + foreach ($lockedRepo->getPackages() as $pkg) { + if ($pkg instanceof AliasPackage || ($this->updateAllowList !== null && in_array($pkg->getName(), $this->updateAllowList, true))) { + continue; + } + $preferredVersions[$pkg->getName()] = $pkg->getVersion(); + } + } + + return new DefaultPolicy($preferStable, $preferLowest, $preferredVersions); + } + + /** + * @param RootPackageInterface&BasePackage $rootPackage + */ + private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo, ?LockArrayRepository $lockedRepository = null): Request + { + $request = new Request($lockedRepository); + + $request->fixPackage($rootPackage); + if ($rootPackage instanceof RootAliasPackage) { + $request->fixPackage($rootPackage->getAliasOf()); + } + + $fixedPackages = $platformRepo->getPackages(); + if ($this->additionalFixedRepository) { + $fixedPackages = array_merge($fixedPackages, $this->additionalFixedRepository->getPackages()); + } + + // fix the version of all platform packages + additionally installed packages + // to prevent the solver trying to remove or update those + // TODO why not replaces? + $provided = $rootPackage->getProvides(); + foreach ($fixedPackages as $package) { + // skip platform packages that are provided by the root package + if ($package->getRepository() !== $platformRepo + || !isset($provided[$package->getName()]) + || !$provided[$package->getName()]->getConstraint()->matches(new Constraint('=', $package->getVersion())) + ) { + $request->fixPackage($package); + } + } + + return $request; + } + + private function requirePackagesForUpdate(Request $request, ?LockArrayRepository $lockedRepository = null, bool $includeDevRequires = true): void + { + // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata + if ($this->updateMirrors) { + $excludedPackages = []; + if (!$includeDevRequires) { + $excludedPackages = array_flip($this->locker->getDevPackageNames()); + } + + foreach ($lockedRepository->getPackages() as $lockedPackage) { + // exclude alias packages here as for root aliases, both alias and aliased are + // present in the lock repo and we only want to require the aliased version + if (!$lockedPackage instanceof AliasPackage && !isset($excludedPackages[$lockedPackage->getName()])) { + $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); + } + } + } else { + $links = $this->package->getRequires(); + if ($includeDevRequires) { + $links = array_merge($links, $this->package->getDevRequires()); + } + foreach ($links as $link) { + $request->requireName($link->getTarget(), $link->getConstraint()); + } + } + } + + /** + * @return array> + * + * @phpstan-return list + */ + private function getRootAliases(bool $forUpdate): array + { + if ($forUpdate) { + $aliases = $this->package->getAliases(); + } else { + $aliases = $this->locker->getAliases(); + } + + return $aliases; + } + + /** + * @param Link[] $links + * + * @return array + */ + private function extractPlatformRequirements(array $links): array + { + $platformReqs = []; + foreach ($links as $link) { + if (PlatformRepository::isPlatformPackage($link->getTarget())) { + $platformReqs[$link->getTarget()] = $link->getPrettyConstraint(); + } + } + + return $platformReqs; + } + + /** + * Replace local repositories with InstalledArrayRepository instances + * + * This is to prevent any accidental modification of the existing repos on disk + */ + private function mockLocalRepositories(RepositoryManager $rm): void + { + $packages = []; + foreach ($rm->getLocalRepository()->getPackages() as $package) { + $packages[(string) $package] = clone $package; + } + foreach ($packages as $key => $package) { + if ($package instanceof AliasPackage) { + $alias = (string) $package->getAliasOf(); + $className = get_class($package); + $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion()); + } + } + $rm->setLocalRepository( + new InstalledArrayRepository($packages) + ); + } + + private function createPoolOptimizer(PolicyInterface $policy): ?PoolOptimizer + { + // Not the best architectural decision here, would need to be able + // to configure from the outside of Installer but this is only + // a debugging tool and should never be required in any other use case + if ('0' === Platform::getEnv('COMPOSER_POOL_OPTIMIZER')) { + $this->io->write('Pool Optimizer was disabled for debugging purposes.', true, IOInterface::DEBUG); + + return null; + } + + return new PoolOptimizer($policy); + } + + private function getAuditConfig(): AuditConfig + { + if (null === $this->auditConfig) { + $this->auditConfig = AuditConfig::fromConfig($this->config, $this->audit, $this->auditFormat); + } + + return $this->auditConfig; + } + + private function createSecurityAuditPoolFilter(): ?SecurityAdvisoryPoolFilter + { + $auditConfig = $this->getAuditConfig(); + + if ($auditConfig->blockInsecure && !$this->updateMirrors) { + return new SecurityAdvisoryPoolFilter(new Auditor(), $auditConfig); + } + + return null; + } + + /** + * Create Installer + */ + public static function create(IOInterface $io, Composer $composer): self + { + return new static( + $io, + $composer->getConfig(), + $composer->getPackage(), + $composer->getDownloadManager(), + $composer->getRepositoryManager(), + $composer->getLocker(), + $composer->getInstallationManager(), + $composer->getEventDispatcher(), + $composer->getAutoloadGenerator() + ); + } + + /** + * Packages of those types are ignored, by default php-ext and php-ext-zend are ignored + * + * @param list $types + * @return $this + */ + public function setIgnoredTypes(array $types): self + { + $this->ignoredTypes = $types; + + return $this; + } + + /** + * Only packages of those types are allowed if set to non-null + * + * @param list|null $types + * @return $this + */ + public function setAllowedTypes(?array $types): self + { + $this->allowedTypes = $types; + + return $this; + } + + /** + * @return $this + */ + public function setAdditionalFixedRepository(RepositoryInterface $additionalFixedRepository): self + { + $this->additionalFixedRepository = $additionalFixedRepository; + + return $this; + } + + /** + * @param array $constraints + */ + public function setTemporaryConstraints(array $constraints): self + { + $this->temporaryConstraints = $constraints; + + return $this; + } + + /** + * Whether to run in drymode or not + */ + public function setDryRun(bool $dryRun = true): self + { + $this->dryRun = $dryRun; + + return $this; + } + + /** + * Checks, if this is a dry run (simulation mode). + */ + public function isDryRun(): bool + { + return $this->dryRun; + } + + /** + * Whether to download only or not. + */ + public function setDownloadOnly(bool $downloadOnly = true): self + { + $this->downloadOnly = $downloadOnly; + + return $this; + } + + /** + * prefer source installation + */ + public function setPreferSource(bool $preferSource = true): self + { + $this->preferSource = $preferSource; + + return $this; + } + + /** + * prefer dist installation + */ + public function setPreferDist(bool $preferDist = true): self + { + $this->preferDist = $preferDist; + + return $this; + } + + /** + * Whether or not generated autoloader are optimized + */ + public function setOptimizeAutoloader(bool $optimizeAutoloader): self + { + $this->optimizeAutoloader = $optimizeAutoloader; + if (!$this->optimizeAutoloader) { + // Force classMapAuthoritative off when not optimizing the + // autoloader + $this->setClassMapAuthoritative(false); + } + + return $this; + } + + /** + * Whether or not generated autoloader considers the class map + * authoritative. + */ + public function setClassMapAuthoritative(bool $classMapAuthoritative): self + { + $this->classMapAuthoritative = $classMapAuthoritative; + if ($this->classMapAuthoritative) { + // Force optimizeAutoloader when classmap is authoritative + $this->setOptimizeAutoloader(true); + } + + return $this; + } + + /** + * Whether or not generated autoloader considers APCu caching. + */ + public function setApcuAutoloader(bool $apcuAutoloader, ?string $apcuAutoloaderPrefix = null): self + { + $this->apcuAutoloader = $apcuAutoloader; + $this->apcuAutoloaderPrefix = $apcuAutoloaderPrefix; + + return $this; + } + + /** + * update packages + */ + public function setUpdate(bool $update): self + { + $this->update = $update; + + return $this; + } + + /** + * Allows disabling the install step after an update + */ + public function setInstall(bool $install): self + { + $this->install = $install; + + return $this; + } + + /** + * enables dev packages + */ + public function setDevMode(bool $devMode = true): self + { + $this->devMode = $devMode; + + return $this; + } + + /** + * set whether to run autoloader or not + * + * This is disabled implicitly when enabling dryRun + */ + public function setDumpAutoloader(bool $dumpAutoloader = true): self + { + $this->dumpAutoloader = $dumpAutoloader; + + return $this; + } + + /** + * set whether to run scripts or not + * + * This is disabled implicitly when enabling dryRun + * + * @deprecated Use setRunScripts(false) on the EventDispatcher instance being injected instead + */ + public function setRunScripts(bool $runScripts = true): self + { + $this->runScripts = $runScripts; + + return $this; + } + + /** + * set the config instance + */ + public function setConfig(Config $config): self + { + $this->config = $config; + + return $this; + } + + /** + * run in verbose mode + */ + public function setVerbose(bool $verbose = true): self + { + $this->verbose = $verbose; + + return $this; + } + + /** + * Checks, if running in verbose mode. + */ + public function isVerbose(): bool + { + return $this->verbose; + } + + /** + * set ignore Platform Package requirements + * + * If this is set to true, all platform requirements are ignored + * If this is set to false, no platform requirements are ignored + * If this is set to string[], those packages will be ignored + * + * @param bool|string[] $ignorePlatformReqs + * + * + * @deprecated use setPlatformRequirementFilter instead + */ + public function setIgnorePlatformRequirements($ignorePlatformReqs): self + { + trigger_error('Installer::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED); + + return $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); + } + + public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter): self + { + $this->platformRequirementFilter = $platformRequirementFilter; + + return $this; + } + + /** + * Update the lock file to the exact same versions and references but use current remote metadata like URLs and mirror info + */ + public function setUpdateMirrors(bool $updateMirrors): self + { + $this->updateMirrors = $updateMirrors; + + return $this; + } + + /** + * restrict the update operation to a few packages, all other packages + * that are already installed will be kept at their current version + * + * @param string[] $packages + */ + public function setUpdateAllowList(array $packages): self + { + if (count($packages) === 0) { + $this->updateAllowList = null; + } else { + $this->updateAllowList = array_values(array_unique(array_map('strtolower', $packages))); + } + + return $this; + } + + /** + * Should dependencies of packages marked for update be updated? + * + * Depending on the chosen constant this will either only update the directly named packages, all transitive + * dependencies which are not root requirement or all transitive dependencies including root requirements + * + * @param int $updateAllowTransitiveDependencies One of the UPDATE_ constants on the Request class + */ + public function setUpdateAllowTransitiveDependencies(int $updateAllowTransitiveDependencies): self + { + if (!in_array($updateAllowTransitiveDependencies, [Request::UPDATE_ONLY_LISTED, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS], true)) { + throw new \RuntimeException("Invalid value for updateAllowTransitiveDependencies supplied"); + } + + $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies; + + return $this; + } + + /** + * Should packages be preferred in a stable version when updating? + */ + public function setPreferStable(bool $preferStable = true): self + { + $this->preferStable = $preferStable; + + return $this; + } + + /** + * Should packages be preferred in a lowest version when updating? + */ + public function setPreferLowest(bool $preferLowest = true): self + { + $this->preferLowest = $preferLowest; + + return $this; + } + + /** + * Only relevant for partial updates (with setUpdateAllowList), if this is enabled currently locked versions will be preferred for packages which are not in the allowlist + * + * This reduces the update to + */ + public function setMinimalUpdate(bool $minimalUpdate = true): self + { + $this->minimalUpdate = $minimalUpdate; + + return $this; + } + + /** + * Should the lock file be updated when updating? + * + * This is disabled implicitly when enabling dryRun + */ + public function setWriteLock(bool $writeLock = true): self + { + $this->writeLock = $writeLock; + + return $this; + } + + /** + * Should the operations (package install, update and removal) be executed on disk? + * + * This is disabled implicitly when enabling dryRun + */ + public function setExecuteOperations(bool $executeOperations = true): self + { + $this->executeOperations = $executeOperations; + + return $this; + } + + /** + * Should an audit be run after installation is complete? + * + * @deprecated Use setAuditConfig instead of calling this + */ + public function setAudit(bool $audit): self + { + $this->audit = $audit; + $this->auditConfig = null; // Invalidate cached config + + return $this; + } + + /** + * Should exit with status code 5 on audit error + */ + public function setErrorOnAudit(bool $errorOnAudit): self + { + $this->errorOnAudit = $errorOnAudit; + + return $this; + } + + /** + * What format should be used for audit output? + * + * @param Auditor::FORMAT_* $auditFormat + * + * @deprecated Use setAuditConfig instead of calling this + */ + public function setAuditFormat(string $auditFormat): self + { + $this->auditFormat = $auditFormat; + $this->auditConfig = null; // Invalidate cached config + + return $this; + } + + /** + * Sets a custom AuditConfig to override the default configuration from Config + */ + public function setAuditConfig(AuditConfig $auditConfig): self + { + $this->auditConfig = $auditConfig; + + return $this; + } + + /** + * Disables plugins. + * + * Call this if you want to ensure that third-party code never gets + * executed. The default is to automatically install, and execute + * custom third-party installers. + */ + public function disablePlugins(): self + { + $this->installationManager->disablePlugins(); + + return $this; + } + + public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter): self + { + $this->suggestedPackagesReporter = $suggestedPackagesReporter; + + return $this; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/BinaryInstaller.php b/vendor/composer/composer/src/Composer/Installer/BinaryInstaller.php new file mode 100644 index 000000000..1f15ac3ca --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/BinaryInstaller.php @@ -0,0 +1,410 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\Silencer; + +/** + * Utility to handle installation of package "bin"/binaries + * + * @author Jordi Boggiano + * @author Konstantin Kudryashov + * @author Helmut Hummel + */ +class BinaryInstaller +{ + /** @var string */ + protected $binDir; + /** @var string */ + protected $binCompat; + /** @var IOInterface */ + protected $io; + /** @var Filesystem */ + protected $filesystem; + /** @var string|null */ + private $vendorDir; + + public function __construct(IOInterface $io, string $binDir, string $binCompat, ?Filesystem $filesystem = null, ?string $vendorDir = null) + { + $this->binDir = $binDir; + $this->binCompat = $binCompat; + $this->io = $io; + $this->filesystem = $filesystem ?: new Filesystem(); + $this->vendorDir = $vendorDir; + } + + public function installBinaries(PackageInterface $package, string $installPath, bool $warnOnOverwrite = true): void + { + $binaries = $this->getBinaries($package); + if (!$binaries) { + return; + } + + Platform::workaroundFilesystemIssues(); + + foreach ($binaries as $bin) { + $binPath = $installPath.'/'.$bin; + if (!file_exists($binPath)) { + $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package'); + continue; + } + if (is_dir($binPath)) { + $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': found a directory at that path'); + continue; + } + if (!$this->filesystem->isAbsolutePath($binPath)) { + // in case a custom installer returned a relative path for the + // $package, we can now safely turn it into a absolute path (as we + // already checked the binary's existence). The following helpers + // will require absolute paths to work properly. + $binPath = realpath($binPath); + } + $this->initializeBinDir(); + $link = $this->binDir.'/'.basename($bin); + if (file_exists($link)) { + if (!is_link($link)) { + if ($warnOnOverwrite) { + $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file'); + } + continue; + } + if (realpath($link) === realpath($binPath)) { + // It is a linked binary from a previous installation, which can be replaced with a proxy file + $this->filesystem->unlink($link); + } + } + + $binCompat = $this->binCompat; + if ($binCompat === "auto" && (Platform::isWindows() || Platform::isWindowsSubsystemForLinux())) { + $binCompat = 'full'; + } + + if ($binCompat === "full") { + $this->installFullBinaries($binPath, $link, $bin, $package); + } else { + $this->installUnixyProxyBinaries($binPath, $link); + } + Silencer::call('chmod', $binPath, 0777 & ~umask()); + } + } + + public function removeBinaries(PackageInterface $package): void + { + $this->initializeBinDir(); + + $binaries = $this->getBinaries($package); + if (!$binaries) { + return; + } + foreach ($binaries as $bin) { + $link = $this->binDir.'/'.basename($bin); + if (is_link($link) || file_exists($link)) { // still checking for symlinks here for legacy support + $this->filesystem->unlink($link); + } + if (is_file($link.'.bat')) { + $this->filesystem->unlink($link.'.bat'); + } + } + + // attempt removing the bin dir in case it is left empty + if (is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) { + Silencer::call('rmdir', $this->binDir); + } + } + + public static function determineBinaryCaller(string $bin): string + { + if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) { + return 'call'; + } + + $handle = fopen($bin, 'r'); + $line = fgets($handle); + fclose($handle); + if (Preg::isMatchStrictGroups('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', (string) $line, $match)) { + return trim($match[1]); + } + + return 'php'; + } + + /** + * @return string[] + */ + protected function getBinaries(PackageInterface $package): array + { + return $package->getBinaries(); + } + + protected function installFullBinaries(string $binPath, string $link, string $bin, PackageInterface $package): void + { + // add unixy support for cygwin and similar environments + if ('.bat' !== substr($binPath, -4)) { + $this->installUnixyProxyBinaries($binPath, $link); + $link .= '.bat'; + if (file_exists($link)) { + $this->io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed'); + } + } + if (!file_exists($link)) { + file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link)); + Silencer::call('chmod', $link, 0777 & ~umask()); + } + } + + protected function installUnixyProxyBinaries(string $binPath, string $link): void + { + file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link)); + Silencer::call('chmod', $link, 0777 & ~umask()); + } + + protected function initializeBinDir(): void + { + $this->filesystem->ensureDirectoryExists($this->binDir); + $this->binDir = realpath($this->binDir); + } + + protected function generateWindowsProxyCode(string $bin, string $link): string + { + $binPath = $this->filesystem->findShortestPath($link, $bin); + $caller = self::determineBinaryCaller($bin); + + // if the target is a php file, we run the unixy proxy file + // to ensure that _composer_autoload_path gets defined, instead + // of running the binary directly + if ($caller === 'php') { + return "@ECHO OFF\r\n". + "setlocal DISABLEDELAYEDEXPANSION\r\n". + "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape(basename($link, '.bat')), '"\'')."\r\n". + "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n". + "{$caller} \"%BIN_TARGET%\" %*\r\n"; + } + + return "@ECHO OFF\r\n". + "setlocal DISABLEDELAYEDEXPANSION\r\n". + "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"\'')."\r\n". + "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n". + "{$caller} \"%BIN_TARGET%\" %*\r\n"; + } + + protected function generateUnixyProxyCode(string $bin, string $link): string + { + $binPath = $this->filesystem->findShortestPath($link, $bin); + + $binDir = ProcessExecutor::escape(dirname($binPath)); + $binFile = basename($binPath); + + $binContents = (string) file_get_contents($bin, false, null, 0, 500); + // For php files, we generate a PHP proxy instead of a shell one, + // which allows calling the proxy with a custom php process + if (Preg::isMatch('{^(#!.*\r?\n)?[\r\n\t ]*<\?php}', $binContents, $match)) { + // carry over the existing shebang if present, otherwise add our own + $proxyCode = $match[1] === null ? '#!/usr/bin/env php' : trim($match[1]); + $binPathExported = $this->filesystem->findShortestPathCode($link, $bin, false, true); + $streamProxyCode = $streamHint = ''; + $globalsCode = '$GLOBALS[\'_composer_bin_dir\'] = __DIR__;'."\n"; + $phpunitHack1 = $phpunitHack2 = ''; + // Don't expose autoload path when vendor dir was not set in custom installers + if ($this->vendorDir !== null) { + // ensure comparisons work accurately if the CWD is a symlink, as $link is realpath'd already + $vendorDirReal = realpath($this->vendorDir); + if ($vendorDirReal === false) { + $vendorDirReal = $this->vendorDir; + } + $globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' . $this->filesystem->findShortestPathCode($link, $vendorDirReal . '/autoload.php', false, true).";\n"; + } + // Add workaround for PHPUnit process isolation + if ($this->filesystem->normalizePath($bin) === $this->filesystem->normalizePath($this->vendorDir.'/phpunit/phpunit/phpunit')) { + // workaround issue on PHPUnit 6.5+ running on PHP 8+ + $globalsCode .= '$GLOBALS[\'__PHPUNIT_ISOLATION_EXCLUDE_LIST\'] = $GLOBALS[\'__PHPUNIT_ISOLATION_BLACKLIST\'] = array(realpath('.$binPathExported.'));'."\n"; + // workaround issue on all PHPUnit versions running on PHP <8 + $phpunitHack1 = "'phpvfscomposer://'."; + $phpunitHack2 = ' + $data = str_replace(\'__DIR__\', var_export(dirname($this->realpath), true), $data); + $data = str_replace(\'__FILE__\', var_export($this->realpath, true), $data);'; + } + if (trim($match[0]) !== 'realpath = realpath(\$opened_path) ?: \$opened_path; + \$opened_path = $phpunitHack1\$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); + }$phpunitHack2 + + \$this->position += strlen(\$data); + + return \$data; + } + + public function stream_cast(\$castAs) + { + return \$this->handle; + } + + public function stream_close() + { + fclose(\$this->handle); + } + + public function stream_lock(\$operation) + { + return \$operation ? flock(\$this->handle, \$operation) : true; + } + + public function stream_seek(\$offset, \$whence) + { + if (0 === fseek(\$this->handle, \$offset, \$whence)) { + \$this->position = ftell(\$this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return \$this->position; + } + + public function stream_eof() + { + return feof(\$this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option(\$option, \$arg1, \$arg2) + { + return true; + } + + public function url_stat(\$path, \$flags) + { + \$path = substr(\$path, 17); + if (file_exists(\$path)) { + return stat(\$path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . $binPathExported); + } +} + +STREAMPROXY; + } + + return $proxyCode . "\n" . << /dev/null) +if [ -z "\$self" ]; then + self="\$selfArg" +fi + +dir=\$(cd "\${self%[/\\\\]*}" > /dev/null; cd $binDir && pwd) + +if [ -d /proc/cygdrive ]; then + case \$(which php) in + \$(readlink -n /proc/cygdrive)/*) + # We are in Cygwin using Windows php, so the path must be translated + dir=\$(cygpath -m "\$dir"); + ;; + esac +fi + +export COMPOSER_RUNTIME_BIN_DIR="\$(cd "\${self%[/\\\\]*}" > /dev/null; pwd)" + +# If bash is sourcing this file, we have to source the target as well +bashSource="\$BASH_SOURCE" +if [ -n "\$bashSource" ]; then + if [ "\$bashSource" != "\$0" ]; then + source "\${dir}/$binFile" "\$@" + return + fi +fi + +exec "\${dir}/$binFile" "\$@" + +PROXY; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php b/vendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php new file mode 100644 index 000000000..d920b0ecc --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Package\PackageInterface; + +/** + * Interface for the package installation manager that handle binary installation. + * + * @author Jordi Boggiano + */ +interface BinaryPresenceInterface +{ + /** + * Make sure binaries are installed for a given package. + * + * @param PackageInterface $package package instance + * + * @return void + */ + public function ensureBinariesPresence(PackageInterface $package); +} diff --git a/vendor/composer/composer/src/Composer/Installer/InstallationManager.php b/vendor/composer/composer/src/Composer/Installer/InstallationManager.php new file mode 100644 index 000000000..3b0b0edfe --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/InstallationManager.php @@ -0,0 +1,673 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\IO\IOInterface; +use Composer\IO\ConsoleIO; +use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; +use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; +use Composer\Downloader\FileDownloader; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\Loop; +use Composer\Util\Platform; +use React\Promise\PromiseInterface; +use Seld\Signal\SignalHandler; + +/** + * Package operation manager. + * + * @author Konstantin Kudryashov + * @author Jordi Boggiano + * @author Nils Adermann + */ +class InstallationManager +{ + /** @var list */ + private $installers = []; + /** @var array */ + private $cache = []; + /** @var array> */ + private $notifiablePackages = []; + /** @var Loop */ + private $loop; + /** @var IOInterface */ + private $io; + /** @var ?EventDispatcher */ + private $eventDispatcher; + /** @var bool */ + private $outputProgress; + + public function __construct(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null) + { + $this->loop = $loop; + $this->io = $io; + $this->eventDispatcher = $eventDispatcher; + } + + public function reset(): void + { + $this->notifiablePackages = []; + FileDownloader::$downloadMetadata = []; + } + + /** + * Adds installer + * + * @param InstallerInterface $installer installer instance + */ + public function addInstaller(InstallerInterface $installer): void + { + array_unshift($this->installers, $installer); + $this->cache = []; + } + + /** + * Removes installer + * + * @param InstallerInterface $installer installer instance + */ + public function removeInstaller(InstallerInterface $installer): void + { + if (false !== ($key = array_search($installer, $this->installers, true))) { + array_splice($this->installers, $key, 1); + $this->cache = []; + } + } + + /** + * Disables plugins. + * + * We prevent any plugins from being instantiated by + * disabling the PluginManager. This ensures that no third-party + * code is ever executed. + */ + public function disablePlugins(): void + { + foreach ($this->installers as $i => $installer) { + if (!$installer instanceof PluginInstaller) { + continue; + } + + $installer->disablePlugins(); + } + } + + /** + * Returns installer for a specific package type. + * + * @param string $type package type + * + * @throws \InvalidArgumentException if installer for provided type is not registered + */ + public function getInstaller(string $type): InstallerInterface + { + $type = strtolower($type); + + if (isset($this->cache[$type])) { + return $this->cache[$type]; + } + + foreach ($this->installers as $installer) { + if ($installer->supports($type)) { + return $this->cache[$type] = $installer; + } + } + + throw new \InvalidArgumentException('Unknown installer type: '.$type); + } + + /** + * Checks whether provided package is installed in one of the registered installers. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + */ + public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool + { + if ($package instanceof AliasPackage) { + return $repo->hasPackage($package) && $this->isPackageInstalled($repo, $package->getAliasOf()); + } + + return $this->getInstaller($package->getType())->isInstalled($repo, $package); + } + + /** + * Install binary for the given package. + * If the installer associated to this package doesn't handle that function, it'll do nothing. + * + * @param PackageInterface $package Package instance + */ + public function ensureBinariesPresence(PackageInterface $package): void + { + try { + $installer = $this->getInstaller($package->getType()); + } catch (\InvalidArgumentException $e) { + // no installer found for the current package type (@see `getInstaller()`) + return; + } + + // if the given installer support installing binaries + if ($installer instanceof BinaryPresenceInterface) { + $installer->ensureBinariesPresence($package); + } + } + + /** + * Executes solver operation. + * + * @param InstalledRepositoryInterface $repo repository in which to add/remove/update packages + * @param OperationInterface[] $operations operations to execute + * @param bool $devMode whether the install is being run in dev mode + * @param bool $runScripts whether to dispatch script events + * @param bool $downloadOnly whether to only download packages + */ + public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = true, bool $runScripts = true, bool $downloadOnly = false): void + { + /** @var array> $cleanupPromises */ + $cleanupPromises = []; + + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use (&$cleanupPromises) { + $this->io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + $this->runCleanup($cleanupPromises); + $handler->exitWithLastSignal(); + }); + + try { + // execute operations in batches to make sure download-modifying-plugins are installed + // before the other packages get downloaded + $batches = []; + $batch = []; + foreach ($operations as $index => $operation) { + if ($operation instanceof UpdateOperation || $operation instanceof InstallOperation) { + $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); + if ($package->getType() === 'composer-plugin') { + $extra = $package->getExtra(); + if (isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) { + if (count($batch) > 0) { + $batches[] = $batch; + } + $batches[] = [$index => $operation]; + $batch = []; + + continue; + } + } + } + $batch[$index] = $operation; + } + + if (count($batch) > 0) { + $batches[] = $batch; + } + + foreach ($batches as $batchToExecute) { + $this->downloadAndExecuteBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations); + } + } catch (\Exception $e) { + $this->runCleanup($cleanupPromises); + + throw $e; + } finally { + $signalHandler->unregister(); + } + + if ($downloadOnly) { + return; + } + + // do a last write so that we write the repository even if nothing changed + // as that can trigger an update of some files like InstalledVersions.php if + // running a new composer version + $repo->write($devMode, $this); + } + + /** + * @param OperationInterface[] $operations List of operations to execute in this batch + * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners + * @phpstan-param array> $cleanupPromises + */ + private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises, bool $devMode, bool $runScripts, bool $downloadOnly, array $allOperations): void + { + $promises = []; + + foreach ($operations as $index => $operation) { + $opType = $operation->getOperationType(); + + // ignoring alias ops as they don't need to execute anything at this stage + if (!in_array($opType, ['update', 'install', 'uninstall'], true)) { + continue; + } + + if ($opType === 'update') { + /** @var UpdateOperation $operation */ + $package = $operation->getTargetPackage(); + $initialPackage = $operation->getInitialPackage(); + } else { + /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */ + $package = $operation->getPackage(); + $initialPackage = null; + } + $installer = $this->getInstaller($package->getType()); + + $cleanupPromises[$index] = static function () use ($opType, $installer, $package, $initialPackage): ?PromiseInterface { + // avoid calling cleanup if the download was not even initialized for a package + // as without installation source configured nothing will work + if (null === $package->getInstallationSource()) { + return \React\Promise\resolve(null); + } + + return $installer->cleanup($opType, $package, $initialPackage); + }; + + if ($opType !== 'uninstall') { + $promise = $installer->download($package, $initialPackage); + if (null !== $promise) { + $promises[] = $promise; + } + } + } + + // execute all downloads first + if (count($promises) > 0) { + $this->waitOnPromises($promises); + } + + if ($downloadOnly) { + $this->runCleanup($cleanupPromises); + + return; + } + + // execute operations in batches to make sure every plugin is installed in the + // right order and activated before the packages depending on it are installed + $batches = []; + $batch = []; + foreach ($operations as $index => $operation) { + if ($operation instanceof InstallOperation || $operation instanceof UpdateOperation) { + $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); + if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') { + if (count($batch) > 0) { + $batches[] = $batch; + } + $batches[] = [$index => $operation]; + $batch = []; + + continue; + } + } + $batch[$index] = $operation; + } + + if (count($batch) > 0) { + $batches[] = $batch; + } + + foreach ($batches as $batchToExecute) { + $this->executeBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $allOperations); + } + } + + /** + * @param OperationInterface[] $operations List of operations to execute in this batch + * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners + * @phpstan-param array> $cleanupPromises + */ + private function executeBatch(InstalledRepositoryInterface $repo, array $operations, array $cleanupPromises, bool $devMode, bool $runScripts, array $allOperations): void + { + $promises = []; + $postExecCallbacks = []; + + foreach ($operations as $index => $operation) { + $opType = $operation->getOperationType(); + + // ignoring alias ops as they don't need to execute anything + if (!in_array($opType, ['update', 'install', 'uninstall'], true)) { + // output alias ops in debug verbosity as they have no output otherwise + if ($this->io->isDebug()) { + $this->io->writeError(' - ' . $operation->show(false)); + } + $this->{$opType}($repo, $operation); + + continue; + } + + if ($opType === 'update') { + /** @var UpdateOperation $operation */ + $package = $operation->getTargetPackage(); + $initialPackage = $operation->getInitialPackage(); + } else { + /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */ + $package = $operation->getPackage(); + $initialPackage = null; + } + + $installer = $this->getInstaller($package->getType()); + + $eventName = [ + 'install' => PackageEvents::PRE_PACKAGE_INSTALL, + 'update' => PackageEvents::PRE_PACKAGE_UPDATE, + 'uninstall' => PackageEvents::PRE_PACKAGE_UNINSTALL, + ][$opType]; + + if ($runScripts && $this->eventDispatcher !== null) { + $this->eventDispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); + } + + $dispatcher = $this->eventDispatcher; + $io = $this->io; + + $promise = $installer->prepare($opType, $package, $initialPackage); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $promise = $promise->then(function () use ($opType, $repo, $operation) { + return $this->{$opType}($repo, $operation); + })->then($cleanupPromises[$index]) + ->then(function () use ($devMode, $repo): void { + $repo->write($devMode, $this); + }, static function ($e) use ($opType, $package, $io): void { + $io->writeError(' ' . ucfirst($opType) .' of '.$package->getPrettyName().' failed'); + + throw $e; + }); + + $eventName = [ + 'install' => PackageEvents::POST_PACKAGE_INSTALL, + 'update' => PackageEvents::POST_PACKAGE_UPDATE, + 'uninstall' => PackageEvents::POST_PACKAGE_UNINSTALL, + ][$opType]; + + if ($runScripts && $dispatcher !== null) { + $postExecCallbacks[] = static function () use ($dispatcher, $eventName, $devMode, $repo, $allOperations, $operation): void { + $dispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); + }; + } + + $promises[] = $promise; + } + + // execute all prepare => installs/updates/removes => cleanup steps + if (count($promises) > 0) { + $this->waitOnPromises($promises); + } + + Platform::workaroundFilesystemIssues(); + + foreach ($postExecCallbacks as $cb) { + $cb(); + } + } + + /** + * @param array> $promises + */ + private function waitOnPromises(array $promises): void + { + $progress = null; + if ( + $this->outputProgress + && $this->io instanceof ConsoleIO + && !((bool) Platform::getEnv('CI')) + && !$this->io->isDebug() + && count($promises) > 1 + ) { + $progress = $this->io->getProgressBar(); + } + $this->loop->wait($promises, $progress); + if ($progress !== null) { + $progress->clear(); + // ProgressBar in non-decorated output does not output a final line-break and clear() does nothing + if (!$this->io->isDecorated()) { + $this->io->writeError(''); + } + } + } + + /** + * Executes download operation. + * + * @phpstan-return PromiseInterface|null + */ + public function download(PackageInterface $package): ?PromiseInterface + { + $installer = $this->getInstaller($package->getType()); + $promise = $installer->cleanup("install", $package); + + return $promise; + } + + /** + * Executes install operation. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param InstallOperation $operation operation instance + * @phpstan-return PromiseInterface|null + */ + public function install(InstalledRepositoryInterface $repo, InstallOperation $operation): ?PromiseInterface + { + $package = $operation->getPackage(); + $installer = $this->getInstaller($package->getType()); + $promise = $installer->install($repo, $package); + $this->markForNotification($package); + + return $promise; + } + + /** + * Executes update operation. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param UpdateOperation $operation operation instance + * @phpstan-return PromiseInterface|null + */ + public function update(InstalledRepositoryInterface $repo, UpdateOperation $operation): ?PromiseInterface + { + $initial = $operation->getInitialPackage(); + $target = $operation->getTargetPackage(); + + $initialType = $initial->getType(); + $targetType = $target->getType(); + + if ($initialType === $targetType) { + $installer = $this->getInstaller($initialType); + $promise = $installer->update($repo, $initial, $target); + $this->markForNotification($target); + } else { + $promise = $this->getInstaller($initialType)->uninstall($repo, $initial); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $installer = $this->getInstaller($targetType); + $promise = $promise->then(static function () use ($installer, $repo, $target): PromiseInterface { + $promise = $installer->install($repo, $target); + if ($promise instanceof PromiseInterface) { + return $promise; + } + + return \React\Promise\resolve(null); + }); + } + + return $promise; + } + + /** + * Uninstalls package. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param UninstallOperation $operation operation instance + * @phpstan-return PromiseInterface|null + */ + public function uninstall(InstalledRepositoryInterface $repo, UninstallOperation $operation): ?PromiseInterface + { + $package = $operation->getPackage(); + $installer = $this->getInstaller($package->getType()); + + return $installer->uninstall($repo, $package); + } + + /** + * Executes markAliasInstalled operation. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param MarkAliasInstalledOperation $operation operation instance + */ + public function markAliasInstalled(InstalledRepositoryInterface $repo, MarkAliasInstalledOperation $operation): void + { + $package = $operation->getPackage(); + + if (!$repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + } + + /** + * Executes markAlias operation. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param MarkAliasUninstalledOperation $operation operation instance + */ + public function markAliasUninstalled(InstalledRepositoryInterface $repo, MarkAliasUninstalledOperation $operation): void + { + $package = $operation->getPackage(); + + $repo->removePackage($package); + } + + /** + * Returns the installation path of a package + * + * @return string|null absolute path to install to, which does not end with a slash, or null if the package does not have anything installed on disk + */ + public function getInstallPath(PackageInterface $package): ?string + { + $installer = $this->getInstaller($package->getType()); + + return $installer->getInstallPath($package); + } + + public function setOutputProgress(bool $outputProgress): void + { + $this->outputProgress = $outputProgress; + } + + public function notifyInstalls(IOInterface $io): void + { + $promises = []; + + try { + foreach ($this->notifiablePackages as $repoUrl => $packages) { + // non-batch API, deprecated + if (str_contains($repoUrl, '%package%')) { + foreach ($packages as $package) { + $url = str_replace('%package%', $package->getPrettyName(), $repoUrl); + + $params = [ + 'version' => $package->getPrettyVersion(), + 'version_normalized' => $package->getVersion(), + ]; + $opts = [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'header' => ['Content-type: application/x-www-form-urlencoded'], + 'content' => http_build_query($params, '', '&'), + 'timeout' => 3, + ], + ]; + + $promises[] = $this->loop->getHttpDownloader()->add($url, $opts); + } + + continue; + } + + $postData = ['downloads' => []]; + foreach ($packages as $package) { + $packageNotification = [ + 'name' => $package->getPrettyName(), + 'version' => $package->getVersion(), + ]; + if (strpos($repoUrl, 'packagist.org/') !== false) { + if (isset(FileDownloader::$downloadMetadata[$package->getName()])) { + $packageNotification['downloaded'] = FileDownloader::$downloadMetadata[$package->getName()]; + } else { + $packageNotification['downloaded'] = false; + } + } + $postData['downloads'][] = $packageNotification; + } + + $opts = [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'header' => ['Content-Type: application/json'], + 'content' => json_encode($postData), + 'timeout' => 6, + ], + ]; + + $promises[] = $this->loop->getHttpDownloader()->add($repoUrl, $opts); + } + + $this->loop->wait($promises); + } catch (\Exception $e) { + } + + $this->reset(); + } + + private function markForNotification(PackageInterface $package): void + { + if ($package->getNotificationUrl() !== null) { + $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package; + } + } + + /** + * @phpstan-param array> $cleanupPromises + */ + private function runCleanup(array $cleanupPromises): void + { + $promises = []; + + $this->loop->abortJobs(); + + foreach ($cleanupPromises as $cleanup) { + $promises[] = new \React\Promise\Promise(static function ($resolve) use ($cleanup): void { + $promise = $cleanup(); + if (!$promise instanceof PromiseInterface) { + $resolve(null); + } else { + $promise->then(static function () use ($resolve): void { + $resolve(null); + }); + } + }); + } + + if (count($promises) > 0) { + $this->loop->wait($promises); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/InstallerEvent.php b/vendor/composer/composer/src/Composer/Installer/InstallerEvent.php new file mode 100644 index 000000000..8cb699e33 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/InstallerEvent.php @@ -0,0 +1,85 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Composer; +use Composer\DependencyResolver\Transaction; +use Composer\EventDispatcher\Event; +use Composer\IO\IOInterface; + +class InstallerEvent extends Event +{ + /** + * @var Composer + */ + private $composer; + + /** + * @var IOInterface + */ + private $io; + + /** + * @var bool + */ + private $devMode; + + /** + * @var bool + */ + private $executeOperations; + + /** + * @var Transaction + */ + private $transaction; + + /** + * Constructor. + */ + public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, bool $executeOperations, Transaction $transaction) + { + parent::__construct($eventName); + + $this->composer = $composer; + $this->io = $io; + $this->devMode = $devMode; + $this->executeOperations = $executeOperations; + $this->transaction = $transaction; + } + + public function getComposer(): Composer + { + return $this->composer; + } + + public function getIO(): IOInterface + { + return $this->io; + } + + public function isDevMode(): bool + { + return $this->devMode; + } + + public function isExecutingOperations(): bool + { + return $this->executeOperations; + } + + public function getTransaction(): ?Transaction + { + return $this->transaction; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/InstallerEvents.php b/vendor/composer/composer/src/Composer/Installer/InstallerEvents.php new file mode 100644 index 000000000..fc3da51ee --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/InstallerEvents.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +class InstallerEvents +{ + /** + * The PRE_OPERATIONS_EXEC event occurs before the lock file gets + * installed and operations are executed. + * + * The event listener method receives an Composer\Installer\InstallerEvent instance. + * + * @var string + */ + public const PRE_OPERATIONS_EXEC = 'pre-operations-exec'; +} diff --git a/vendor/composer/composer/src/Composer/Installer/InstallerInterface.php b/vendor/composer/composer/src/Composer/Installer/InstallerInterface.php new file mode 100644 index 000000000..7c92e91d4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/InstallerInterface.php @@ -0,0 +1,124 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Package\PackageInterface; +use Composer\Repository\InstalledRepositoryInterface; +use InvalidArgumentException; +use React\Promise\PromiseInterface; + +/** + * Interface for the package installation manager. + * + * @author Konstantin Kudryashov + * @author Jordi Boggiano + */ +interface InstallerInterface +{ + /** + * Decides if the installer supports the given type + * + * @return bool + */ + public function supports(string $packageType); + + /** + * Checks that provided package is installed. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + * + * @return bool + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package); + + /** + * Downloads the files needed to later install the given package. + * + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null); + + /** + * Do anything that needs to be done between all downloads have been completed and the actual operation is executed + * + * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore + * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or + * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can + * be undone as much as possible. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function prepare(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null); + + /** + * Installs specific package. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package); + + /** + * Updates specific package. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $initial already installed package version + * @param PackageInterface $target updated version + * @throws InvalidArgumentException if $initial package is not installed + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target); + + /** + * Uninstalls specific package. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package); + + /** + * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps + * + * Note that cleanup will be called for all packages regardless if they failed an operation or not, to give + * all installers a change to cleanup things they did previously, so you need to keep track of changes + * applied in the installer/downloader themselves. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function cleanup(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null); + + /** + * Returns the absolute installation path of a package. + * + * @return string|null absolute path to install to, which MUST not end with a slash, or null if the package does not have anything installed on disk + */ + public function getInstallPath(PackageInterface $package); +} diff --git a/vendor/composer/composer/src/Composer/Installer/LibraryInstaller.php b/vendor/composer/composer/src/Composer/Installer/LibraryInstaller.php new file mode 100644 index 000000000..57282ecd1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/LibraryInstaller.php @@ -0,0 +1,342 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\PartialComposer; +use Composer\Pcre\Preg; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Package\PackageInterface; +use Composer\Util\Filesystem; +use Composer\Util\Silencer; +use Composer\Util\Platform; +use React\Promise\PromiseInterface; +use Composer\Downloader\DownloadManager; + +/** + * Package installation manager. + * + * @author Jordi Boggiano + * @author Konstantin Kudryashov + */ +class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface +{ + /** @var PartialComposer */ + protected $composer; + /** @var string */ + protected $vendorDir; + /** @var DownloadManager|null */ + protected $downloadManager; + /** @var IOInterface */ + protected $io; + /** @var string */ + protected $type; + /** @var Filesystem */ + protected $filesystem; + /** @var BinaryInstaller */ + protected $binaryInstaller; + + /** + * Initializes library installer. + */ + public function __construct(IOInterface $io, PartialComposer $composer, ?string $type = 'library', ?Filesystem $filesystem = null, ?BinaryInstaller $binaryInstaller = null) + { + $this->composer = $composer; + $this->downloadManager = $composer instanceof Composer ? $composer->getDownloadManager() : null; + $this->io = $io; + $this->type = $type; + + $this->filesystem = $filesystem ?: new Filesystem(); + $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/'); + $this->binaryInstaller = $binaryInstaller ?: new BinaryInstaller($this->io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem, $this->vendorDir); + } + + /** + * @inheritDoc + */ + public function supports(string $packageType) + { + return $packageType === $this->type || null === $this->type; + } + + /** + * @inheritDoc + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + return false; + } + + $installPath = $this->getInstallPath($package); + + if (Filesystem::isReadable($installPath)) { + return true; + } + + if (Platform::isWindows() && $this->filesystem->isJunction($installPath)) { + return true; + } + + if (is_link($installPath)) { + if (realpath($installPath) === false) { + return false; + } + + return true; + } + + return false; + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + return $this->getDownloadManager()->download($package, $downloadPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + return $this->getDownloadManager()->prepare($type, $package, $downloadPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + return $this->getDownloadManager()->cleanup($type, $package, $downloadPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + // remove the binaries if it appears the package files are missing + if (!Filesystem::isReadable($downloadPath) && $repo->hasPackage($package)) { + $this->binaryInstaller->removeBinaries($package); + } + + $promise = $this->installCode($package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $binaryInstaller = $this->binaryInstaller; + $installPath = $this->getInstallPath($package); + + return $promise->then(static function () use ($binaryInstaller, $installPath, $package, $repo): void { + $binaryInstaller->installBinaries($package, $installPath); + if (!$repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + }); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + if (!$repo->hasPackage($initial)) { + throw new \InvalidArgumentException('Package is not installed: '.$initial); + } + + $this->initializeVendorDir(); + + $this->binaryInstaller->removeBinaries($initial); + $promise = $this->updateCode($initial, $target); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $binaryInstaller = $this->binaryInstaller; + $installPath = $this->getInstallPath($target); + + return $promise->then(static function () use ($binaryInstaller, $installPath, $target, $initial, $repo): void { + $binaryInstaller->installBinaries($target, $installPath); + $repo->removePackage($initial); + if (!$repo->hasPackage($target)) { + $repo->addPackage(clone $target); + } + }); + } + + /** + * @inheritDoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + throw new \InvalidArgumentException('Package is not installed: '.$package); + } + + $promise = $this->removeCode($package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $binaryInstaller = $this->binaryInstaller; + $downloadPath = $this->getPackageBasePath($package); + $filesystem = $this->filesystem; + + return $promise->then(static function () use ($binaryInstaller, $filesystem, $downloadPath, $package, $repo): void { + $binaryInstaller->removeBinaries($package); + $repo->removePackage($package); + + if (strpos($package->getName(), '/')) { + $packageVendorDir = dirname($downloadPath); + if (is_dir($packageVendorDir) && $filesystem->isDirEmpty($packageVendorDir)) { + Silencer::call('rmdir', $packageVendorDir); + } + } + }); + } + + /** + * @inheritDoc + * + * @return string + */ + public function getInstallPath(PackageInterface $package) + { + $this->initializeVendorDir(); + + $basePath = ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName(); + $targetDir = $package->getTargetDir(); + + return $basePath . ($targetDir ? '/'.$targetDir : ''); + } + + /** + * Make sure binaries are installed for a given package. + * + * @param PackageInterface $package Package instance + */ + public function ensureBinariesPresence(PackageInterface $package) + { + $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package), false); + } + + /** + * Returns the base path of the package without target-dir path + * + * It is used for BC as getInstallPath tends to be overridden by + * installer plugins but not getPackageBasePath + * + * @return string + */ + protected function getPackageBasePath(PackageInterface $package) + { + $installPath = $this->getInstallPath($package); + $targetDir = $package->getTargetDir(); + + if ($targetDir) { + return Preg::replace('{/*'.str_replace('/', '/+', preg_quote($targetDir)).'/?$}', '', $installPath); + } + + return $installPath; + } + + /** + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + protected function installCode(PackageInterface $package) + { + $downloadPath = $this->getInstallPath($package); + + return $this->getDownloadManager()->install($package, $downloadPath); + } + + /** + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + protected function updateCode(PackageInterface $initial, PackageInterface $target) + { + $initialDownloadPath = $this->getInstallPath($initial); + $targetDownloadPath = $this->getInstallPath($target); + if ($targetDownloadPath !== $initialDownloadPath) { + // if the target and initial dirs intersect, we force a remove + install + // to avoid the rename wiping the target dir as part of the initial dir cleanup + if (strpos($initialDownloadPath, $targetDownloadPath) === 0 + || strpos($targetDownloadPath, $initialDownloadPath) === 0 + ) { + $promise = $this->removeCode($initial); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + return $promise->then(function () use ($target): PromiseInterface { + $promise = $this->installCode($target); + if ($promise instanceof PromiseInterface) { + return $promise; + } + + return \React\Promise\resolve(null); + }); + } + + $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); + } + + return $this->getDownloadManager()->update($initial, $target, $targetDownloadPath); + } + + /** + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + protected function removeCode(PackageInterface $package) + { + $downloadPath = $this->getPackageBasePath($package); + + return $this->getDownloadManager()->remove($package, $downloadPath); + } + + /** + * @return void + */ + protected function initializeVendorDir() + { + $this->filesystem->ensureDirectoryExists($this->vendorDir); + $this->vendorDir = realpath($this->vendorDir); + } + + protected function getDownloadManager(): DownloadManager + { + assert($this->downloadManager instanceof DownloadManager, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance to be able to install/... packages')); + + return $this->downloadManager; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php b/vendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php new file mode 100644 index 000000000..f61a537fb --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php @@ -0,0 +1,134 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Package\PackageInterface; +use Composer\IO\IOInterface; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; + +/** + * Metapackage installation manager. + * + * @author Martin Hasoň + */ +class MetapackageInstaller implements InstallerInterface +{ + /** @var IOInterface */ + private $io; + + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + /** + * @inheritDoc + */ + public function supports(string $packageType) + { + return $packageType === 'metapackage'; + } + + /** + * @inheritDoc + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) + { + return $repo->hasPackage($package); + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) + { + // noop + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + // noop + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + // noop + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->io->writeError(" - " . InstallOperation::format($package)); + + $repo->addPackage(clone $package); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + if (!$repo->hasPackage($initial)) { + throw new \InvalidArgumentException('Package is not installed: '.$initial); + } + + $this->io->writeError(" - " . UpdateOperation::format($initial, $target)); + + $repo->removePackage($initial); + $repo->addPackage(clone $target); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + throw new \InvalidArgumentException('Package is not installed: '.$package); + } + + $this->io->writeError(" - " . UninstallOperation::format($package)); + + $repo->removePackage($package); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + * + * @return null + */ + public function getInstallPath(PackageInterface $package) + { + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/NoopInstaller.php b/vendor/composer/composer/src/Composer/Installer/NoopInstaller.php new file mode 100644 index 000000000..22cf9f80e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/NoopInstaller.php @@ -0,0 +1,118 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Package\PackageInterface; + +/** + * Does not install anything but marks packages installed in the repo + * + * Useful for dry runs + * + * @author Jordi Boggiano + */ +class NoopInstaller implements InstallerInterface +{ + /** + * @inheritDoc + */ + public function supports(string $packageType) + { + return true; + } + + /** + * @inheritDoc + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) + { + return $repo->hasPackage($package); + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + if (!$repo->hasPackage($initial)) { + throw new \InvalidArgumentException('Package is not installed: '.$initial); + } + + $repo->removePackage($initial); + if (!$repo->hasPackage($target)) { + $repo->addPackage(clone $target); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + throw new \InvalidArgumentException('Package is not installed: '.$package); + } + $repo->removePackage($package); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getInstallPath(PackageInterface $package) + { + $targetDir = $package->getTargetDir(); + + return $package->getPrettyName() . ($targetDir ? '/'.$targetDir : ''); + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/PackageEvent.php b/vendor/composer/composer/src/Composer/Installer/PackageEvent.php new file mode 100644 index 000000000..1630f437e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/PackageEvent.php @@ -0,0 +1,110 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\Repository\RepositoryInterface; +use Composer\EventDispatcher\Event; + +/** + * The Package Event. + * + * @author Jordi Boggiano + */ +class PackageEvent extends Event +{ + /** + * @var Composer + */ + private $composer; + + /** + * @var IOInterface + */ + private $io; + + /** + * @var bool + */ + private $devMode; + + /** + * @var RepositoryInterface + */ + private $localRepo; + + /** + * @var OperationInterface[] + */ + private $operations; + + /** + * @var OperationInterface The operation instance which is being executed + */ + private $operation; + + /** + * Constructor. + * + * @param OperationInterface[] $operations + */ + public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation) + { + parent::__construct($eventName); + + $this->composer = $composer; + $this->io = $io; + $this->devMode = $devMode; + $this->localRepo = $localRepo; + $this->operations = $operations; + $this->operation = $operation; + } + + public function getComposer(): Composer + { + return $this->composer; + } + + public function getIO(): IOInterface + { + return $this->io; + } + + public function isDevMode(): bool + { + return $this->devMode; + } + + public function getLocalRepo(): RepositoryInterface + { + return $this->localRepo; + } + + /** + * @return OperationInterface[] + */ + public function getOperations(): array + { + return $this->operations; + } + + /** + * Returns the package instance. + */ + public function getOperation(): OperationInterface + { + return $this->operation; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/PackageEvents.php b/vendor/composer/composer/src/Composer/Installer/PackageEvents.php new file mode 100644 index 000000000..04f51a91f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/PackageEvents.php @@ -0,0 +1,75 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +/** + * Package Events. + * + * @author Jordi Boggiano + */ +class PackageEvents +{ + /** + * The PRE_PACKAGE_INSTALL event occurs before a package is installed. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const PRE_PACKAGE_INSTALL = 'pre-package-install'; + + /** + * The POST_PACKAGE_INSTALL event occurs after a package is installed. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const POST_PACKAGE_INSTALL = 'post-package-install'; + + /** + * The PRE_PACKAGE_UPDATE event occurs before a package is updated. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const PRE_PACKAGE_UPDATE = 'pre-package-update'; + + /** + * The POST_PACKAGE_UPDATE event occurs after a package is updated. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const POST_PACKAGE_UPDATE = 'post-package-update'; + + /** + * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall'; + + /** + * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const POST_PACKAGE_UNINSTALL = 'post-package-uninstall'; +} diff --git a/vendor/composer/composer/src/Composer/Installer/PluginInstaller.php b/vendor/composer/composer/src/Composer/Installer/PluginInstaller.php new file mode 100644 index 000000000..58ba0d7d7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/PluginInstaller.php @@ -0,0 +1,139 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\PartialComposer; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Package\PackageInterface; +use Composer\Plugin\PluginManager; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use React\Promise\PromiseInterface; + +/** + * Installer for plugin packages + * + * @author Jordi Boggiano + * @author Nils Adermann + */ +class PluginInstaller extends LibraryInstaller +{ + public function __construct(IOInterface $io, PartialComposer $composer, ?Filesystem $fs = null, ?BinaryInstaller $binaryInstaller = null) + { + parent::__construct($io, $composer, 'composer-plugin', $fs, $binaryInstaller); + } + + /** + * @inheritDoc + */ + public function supports(string $packageType) + { + return $packageType === 'composer-plugin' || $packageType === 'composer-installer'; + } + + public function disablePlugins(): void + { + $this->getPluginManager()->disablePlugins(); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + // fail install process early if it is going to fail due to a plugin not being allowed + if (($type === 'install' || $type === 'update') && !$this->getPluginManager()->arePluginsDisabled('local')) { + $this->getPluginManager()->isPluginAllowed($package->getName(), false, true === ($package->getExtra()['plugin-optional'] ?? false)); + } + + return parent::prepare($type, $package, $prevPackage); + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) + { + $extra = $package->getExtra(); + if (empty($extra['class'])) { + throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); + } + + return parent::download($package, $prevPackage); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $promise = parent::install($repo, $package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + return $promise->then(function () use ($package, $repo): void { + try { + Platform::workaroundFilesystemIssues(); + $this->getPluginManager()->registerPackage($package, true); + } catch (\Exception $e) { + $this->rollbackInstall($e, $repo, $package); + } + }); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + $promise = parent::update($repo, $initial, $target); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + return $promise->then(function () use ($initial, $target, $repo): void { + try { + Platform::workaroundFilesystemIssues(); + $this->getPluginManager()->deactivatePackage($initial); + $this->getPluginManager()->registerPackage($target, true); + } catch (\Exception $e) { + $this->rollbackInstall($e, $repo, $target); + } + }); + } + + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->getPluginManager()->uninstallPackage($package); + + return parent::uninstall($repo, $package); + } + + private function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package): void + { + $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin'); + parent::uninstall($repo, $package); + throw $e; + } + + protected function getPluginManager(): PluginManager + { + assert($this->composer instanceof Composer, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance.')); + $pluginManager = $this->composer->getPluginManager(); + + return $pluginManager; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/ProjectInstaller.php b/vendor/composer/composer/src/Composer/Installer/ProjectInstaller.php new file mode 100644 index 000000000..ccf439bfa --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/ProjectInstaller.php @@ -0,0 +1,124 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Downloader\DownloadManager; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Util\Filesystem; + +/** + * Project Installer is used to install a single package into a directory as + * root project. + * + * @author Benjamin Eberlei + */ +class ProjectInstaller implements InstallerInterface +{ + /** @var string */ + private $installPath; + /** @var DownloadManager */ + private $downloadManager; + /** @var Filesystem */ + private $filesystem; + + public function __construct(string $installPath, DownloadManager $dm, Filesystem $fs) + { + $this->installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/'; + $this->downloadManager = $dm; + $this->filesystem = $fs; + } + + /** + * Decides if the installer supports the given type + */ + public function supports(string $packageType): bool + { + return true; + } + + /** + * @inheritDoc + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface + { + $installPath = $this->installPath; + if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) { + throw new \InvalidArgumentException("Project directory $installPath is not empty."); + } + if (!is_dir($installPath)) { + mkdir($installPath, 0777, true); + } + + return $this->downloadManager->download($package, $installPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface + { + return $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface + { + return $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface + { + return $this->downloadManager->install($package, $this->installPath); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target): ?PromiseInterface + { + throw new \InvalidArgumentException("not supported"); + } + + /** + * @inheritDoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface + { + throw new \InvalidArgumentException("not supported"); + } + + /** + * Returns the installation path of a package + * + * @return string configured install path + */ + public function getInstallPath(PackageInterface $package): string + { + return $this->installPath; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php b/vendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php new file mode 100644 index 000000000..f33349d8d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php @@ -0,0 +1,228 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\InstalledRepository; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Add suggested packages from different places to output them in the end. + * + * @author Haralan Dobrev + */ +class SuggestedPackagesReporter +{ + public const MODE_LIST = 1; + public const MODE_BY_PACKAGE = 2; + public const MODE_BY_SUGGESTION = 4; + + /** + * @var array + */ + protected $suggestedPackages = []; + + /** + * @var IOInterface + */ + private $io; + + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + /** + * @return array Suggested packages with source, target and reason keys. + */ + public function getPackages(): array + { + return $this->suggestedPackages; + } + + /** + * Add suggested packages to be listed after install + * + * Could be used to add suggested packages both from the installer + * or from CreateProjectCommand. + * + * @param string $source Source package which made the suggestion + * @param string $target Target package to be suggested + * @param string $reason Reason the target package to be suggested + */ + public function addPackage(string $source, string $target, string $reason): SuggestedPackagesReporter + { + $this->suggestedPackages[] = [ + 'source' => $source, + 'target' => $target, + 'reason' => $reason, + ]; + + return $this; + } + + /** + * Add all suggestions from a package. + */ + public function addSuggestionsFromPackage(PackageInterface $package): SuggestedPackagesReporter + { + $source = $package->getPrettyName(); + foreach ($package->getSuggests() as $target => $reason) { + $this->addPackage( + $source, + $target, + $reason + ); + } + + return $this; + } + + /** + * Output suggested packages. + * + * Do not list the ones already installed if installed repository provided. + * + * @param int $mode One of the MODE_* constants from this class + * @param InstalledRepository|null $installedRepo If passed in, suggested packages which are installed already will be skipped + * @param PackageInterface|null $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown + */ + public function output(int $mode, ?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void + { + $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf); + + $suggesters = []; + $suggested = []; + foreach ($suggestedPackages as $suggestion) { + $suggesters[$suggestion['source']][$suggestion['target']] = $suggestion['reason']; + $suggested[$suggestion['target']][$suggestion['source']] = $suggestion['reason']; + } + ksort($suggesters); + ksort($suggested); + + // Simple mode + if ($mode & self::MODE_LIST) { + foreach (array_keys($suggested) as $name) { + $this->io->write(sprintf('%s', $name)); + } + + return; + } + + // Grouped by package + if ($mode & self::MODE_BY_PACKAGE) { + foreach ($suggesters as $suggester => $suggestions) { + $this->io->write(sprintf('%s suggests:', $suggester)); + + foreach ($suggestions as $suggestion => $reason) { + $this->io->write(sprintf(' - %s' . ($reason ? ': %s' : ''), $suggestion, $this->escapeOutput($reason))); + } + $this->io->write(''); + } + } + + // Grouped by suggestion + if ($mode & self::MODE_BY_SUGGESTION) { + // Improve readability in full mode + if ($mode & self::MODE_BY_PACKAGE) { + $this->io->write(str_repeat('-', 78)); + } + foreach ($suggested as $suggestion => $suggesters) { + $this->io->write(sprintf('%s is suggested by:', $suggestion)); + + foreach ($suggesters as $suggester => $reason) { + $this->io->write(sprintf(' - %s' . ($reason ? ': %s' : ''), $suggester, $this->escapeOutput($reason))); + } + $this->io->write(''); + } + } + + if ($onlyDependentsOf) { + $allSuggestedPackages = $this->getFilteredSuggestions($installedRepo); + $diff = count($allSuggestedPackages) - count($suggestedPackages); + if ($diff) { + $this->io->write(''.$diff.' additional suggestions by transitive dependencies can be shown with --all'); + } + } + } + + /** + * Output number of new suggested packages and a hint to use suggest command. + * + * @param InstalledRepository|null $installedRepo If passed in, suggested packages which are installed already will be skipped + * @param PackageInterface|null $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown + */ + public function outputMinimalistic(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void + { + $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf); + if ($suggestedPackages) { + $this->io->writeError(''.count($suggestedPackages).' package suggestions were added by new dependencies, use `composer suggest` to see details.'); + } + } + + /** + * @param InstalledRepository|null $installedRepo If passed in, suggested packages which are installed already will be skipped + * @param PackageInterface|null $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown + * @return mixed[] + */ + private function getFilteredSuggestions(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): array + { + $suggestedPackages = $this->getPackages(); + $installedNames = []; + if (null !== $installedRepo && !empty($suggestedPackages)) { + foreach ($installedRepo->getPackages() as $package) { + $installedNames = array_merge( + $installedNames, + $package->getNames() + ); + } + } + + $sourceFilter = []; + if ($onlyDependentsOf) { + $sourceFilter = array_map(static function ($link): string { + return $link->getTarget(); + }, array_merge($onlyDependentsOf->getRequires(), $onlyDependentsOf->getDevRequires())); + $sourceFilter[] = $onlyDependentsOf->getName(); + } + + $suggestions = []; + foreach ($suggestedPackages as $suggestion) { + if (in_array($suggestion['target'], $installedNames) || (\count($sourceFilter) > 0 && !in_array($suggestion['source'], $sourceFilter))) { + continue; + } + + $suggestions[] = $suggestion; + } + + return $suggestions; + } + + private function escapeOutput(string $string): string + { + return OutputFormatter::escape( + $this->removeControlCharacters($string) + ); + } + + private function removeControlCharacters(string $string): string + { + return Preg::replace( + '/[[:cntrl:]]/', + '', + str_replace("\n", ' ', $string) + ); + } +} diff --git a/vendor/composer/composer/src/Composer/Json/JsonFile.php b/vendor/composer/composer/src/Composer/Json/JsonFile.php new file mode 100644 index 000000000..205f22242 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Json/JsonFile.php @@ -0,0 +1,413 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use JsonSchema\Validator; +use Seld\JsonLint\JsonParser; +use Seld\JsonLint\ParsingException; +use Composer\Util\HttpDownloader; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; + +/** + * Reads/writes json files. + * + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + */ +class JsonFile +{ + public const LAX_SCHEMA = 1; + public const STRICT_SCHEMA = 2; + public const AUTH_SCHEMA = 3; + public const LOCK_SCHEMA = 4; + + /** @deprecated Use \JSON_UNESCAPED_SLASHES */ + public const JSON_UNESCAPED_SLASHES = 64; + /** @deprecated Use \JSON_PRETTY_PRINT */ + public const JSON_PRETTY_PRINT = 128; + /** @deprecated Use \JSON_UNESCAPED_UNICODE */ + public const JSON_UNESCAPED_UNICODE = 256; + + public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json'; + public const LOCK_SCHEMA_PATH = __DIR__ . '/../../../res/composer-lock-schema.json'; + + public const INDENT_DEFAULT = ' '; + + /** @var string */ + private $path; + /** @var ?HttpDownloader */ + private $httpDownloader; + /** @var ?IOInterface */ + private $io; + /** @var string */ + private $indent = self::INDENT_DEFAULT; + + /** + * Initializes json file reader/parser. + * + * @param string $path path to a lockfile + * @param ?HttpDownloader $httpDownloader required for loading http/https json files + * @throws \InvalidArgumentException + */ + public function __construct(string $path, ?HttpDownloader $httpDownloader = null, ?IOInterface $io = null) + { + $this->path = $path; + + if (null === $httpDownloader && Preg::isMatch('{^https?://}i', $path)) { + throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed'); + } + $this->httpDownloader = $httpDownloader; + $this->io = $io; + } + + public function getPath(): string + { + return $this->path; + } + + /** + * Checks whether json file exists. + */ + public function exists(): bool + { + return is_file($this->path); + } + + /** + * Reads json file. + * + * @throws ParsingException + * @throws \RuntimeException + * @return mixed + */ + public function read() + { + try { + if ($this->httpDownloader) { + $json = $this->httpDownloader->get($this->path)->getBody(); + } else { + if (!Filesystem::isReadable($this->path)) { + throw new \RuntimeException('The file "'.$this->path.'" is not readable.'); + } + if ($this->io && $this->io->isDebug()) { + $realpathInfo = ''; + $realpath = realpath($this->path); + if (false !== $realpath && $realpath !== $this->path) { + $realpathInfo = ' (' . $realpath . ')'; + } + $this->io->writeError('Reading ' . $this->path . $realpathInfo); + } + $json = file_get_contents($this->path); + } + } catch (TransportException $e) { + throw new \RuntimeException($e->getMessage(), 0, $e); + } catch (\Exception $e) { + throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage()); + } + + if ($json === false) { + throw new \RuntimeException('Could not read '.$this->path); + } + + $this->indent = self::detectIndenting($json); + + return static::parseJson($json, $this->path); + } + + /** + * Writes json file. + * + * @param mixed[] $hash writes hash into json file + * @param int $options json_encode options + * @throws \UnexpectedValueException|\Exception + * @return void + */ + public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) + { + if ($this->path === 'php://memory') { + file_put_contents($this->path, static::encode($hash, $options, $this->indent)); + + return; + } + + $dir = dirname($this->path); + if (!is_dir($dir)) { + if (file_exists($dir)) { + throw new \UnexpectedValueException( + realpath($dir).' exists and is not a directory.' + ); + } + if (!@mkdir($dir, 0777, true)) { + throw new \UnexpectedValueException( + $dir.' does not exist and could not be created.' + ); + } + } + + $retries = 3; + while ($retries--) { + try { + $this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent). ($options & JSON_PRETTY_PRINT ? "\n" : '')); + break; + } catch (\Exception $e) { + if ($retries > 0) { + usleep(500000); + continue; + } + + throw $e; + } + } + } + + /** + * Modify file properties only if content modified + * + * @return int|false + */ + private function filePutContentsIfModified(string $path, string $content) + { + $currentContent = @file_get_contents($path); + if (false === $currentContent || $currentContent !== $content) { + return file_put_contents($path, $content); + } + + return 0; + } + + /** + * Validates the schema of the current json file according to composer-schema.json rules + * + * @param int $schema a JsonFile::*_SCHEMA constant + * @param string|null $schemaFile a path to the schema file + * @throws JsonValidationException + * @throws ParsingException + * @return true true on success + * + * @phpstan-param self::*_SCHEMA $schema + */ + public function validateSchema(int $schema = self::STRICT_SCHEMA, ?string $schemaFile = null): bool + { + if (!Filesystem::isReadable($this->path)) { + throw new \RuntimeException('The file "'.$this->path.'" is not readable.'); + } + $content = file_get_contents($this->path); + $data = json_decode($content); + + if (null === $data && 'null' !== $content) { + self::validateSyntax($content, $this->path); + } + + return self::validateJsonSchema($this->path, $data, $schema, $schemaFile); + } + + /** + * Validates the schema of the current json file according to composer-schema.json rules + * + * @param mixed $data Decoded JSON data to validate + * @param int $schema a JsonFile::*_SCHEMA constant + * @param string|null $schemaFile a path to the schema file + * @throws JsonValidationException + * @return true true on success + * + * @phpstan-param self::*_SCHEMA $schema + */ + public static function validateJsonSchema(string $source, $data, int $schema, ?string $schemaFile = null): bool + { + $isComposerSchemaFile = false; + if (null === $schemaFile) { + if ($schema === self::LOCK_SCHEMA) { + $schemaFile = self::LOCK_SCHEMA_PATH; + } else { + $isComposerSchemaFile = true; + $schemaFile = self::COMPOSER_SCHEMA_PATH; + } + } + + // Prepend with file:// only when not using a special schema already (e.g. in the phar) + if (false === strpos($schemaFile, '://')) { + $schemaFile = 'file://' . $schemaFile; + } + + $schemaData = (object) ['$ref' => $schemaFile, '$schema' => "https://json-schema.org/draft-04/schema#"]; + + if ($schema === self::STRICT_SCHEMA && $isComposerSchemaFile) { + $schemaData = json_decode((string) file_get_contents($schemaFile)); + $schemaData->additionalProperties = false; + $schemaData->required = ['name', 'description']; + } elseif ($schema === self::AUTH_SCHEMA && $isComposerSchemaFile) { + $schemaData = (object) ['$ref' => $schemaFile.'#/properties/config', '$schema' => "https://json-schema.org/draft-04/schema#"]; + } + + $validator = new Validator(); + // convert assoc arrays to objects + $data = json_decode((string) json_encode($data)); + $validator->validate($data, $schemaData); + + if (!$validator->isValid()) { + $errors = []; + foreach ($validator->getErrors() as $error) { + $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message']; + } + throw new JsonValidationException('"'.$source.'" does not match the expected JSON schema', $errors); + } + + return true; + } + + /** + * Encodes an array into (optionally pretty-printed) JSON + * + * @param mixed $data Data to encode into a formatted JSON string + * @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) + * @param string $indent Indentation string + * @return string Encoded json + */ + public static function encode($data, int $options = \JSON_UNESCAPED_SLASHES | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE, string $indent = self::INDENT_DEFAULT): string + { + $json = json_encode($data, $options); + + if (false === $json) { + self::throwEncodeError(json_last_error()); + } + + if (($options & JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT) { + // Pretty printing and not using default indentation + return Preg::replaceCallback( + '#^ {4,}#m', + static function ($match) use ($indent): string { + return str_repeat($indent, (int) (strlen($match[0]) / 4)); + }, + $json + ); + } + + return $json; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @throws \RuntimeException + * @return never + */ + private static function throwEncodeError(int $code): void + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg); + } + + /** + * Parses json string and returns hash. + * + * @param null|string $json json string + * @param string $file the json file + * + * @throws ParsingException + * @return mixed + */ + public static function parseJson(?string $json, ?string $file = null) + { + if (null === $json) { + return null; + } + $data = json_decode($json, true); + if (null === $data && JSON_ERROR_NONE !== json_last_error()) { + // attempt resolving simple conflicts in lock files so that one can run `composer update --lock` and get a valid lock file + if ($file !== null && str_ends_with($file, '.lock') && str_contains($json, '"content-hash"')) { + $replaced = Preg::replace( + '{\r?\n<<<<<<< [^\r\n]+\r?\n\s+"content-hash": *"[0-9a-f]+", *\r?\n(?:\|{7} [^\r\n]+\r?\n\s+"content-hash": *"[0-9a-f]+", *\r?\n)?=======\r?\n\s+"content-hash": *"[0-9a-f]+", *\r?\n>>>>>>> [^\r\n]+(\r?\n)}', + ' "content-hash": "VCS merge conflict detected. Please run `composer update --lock`.",$1', + $json, + 1, + $count + ); + if ($count === 1) { + $data = json_decode($replaced, true); + if ($data !== null) { + return $data; + } + } + } + + self::validateSyntax($json, $file); + } + + return $data; + } + + /** + * Validates the syntax of a JSON string + * + * @throws \UnexpectedValueException + * @throws ParsingException + * @return bool true on success + */ + protected static function validateSyntax(string $json, ?string $file = null): bool + { + $parser = new JsonParser(); + $result = $parser->lint($json); + if (null === $result) { + if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) { + if ($file === null) { + throw new \UnexpectedValueException('The input is not UTF-8, could not parse as JSON'); + } else { + throw new \UnexpectedValueException('"' . $file . '" is not UTF-8, could not parse as JSON'); + } + } + + return true; + } + + if ($file === null) { + throw new ParsingException( + 'The input does not contain valid JSON' . "\n" . $result->getMessage(), + $result->getDetails() + ); + } else { + throw new ParsingException( + '"' . $file . '" does not contain valid JSON' . "\n" . $result->getMessage(), + $result->getDetails() + ); + } + } + + public static function detectIndenting(?string $json): string + { + if (Preg::isMatchStrictGroups('#^([ \t]+)"#m', $json ?? '', $match)) { + return $match[1]; + } + + return self::INDENT_DEFAULT; + } +} diff --git a/vendor/composer/composer/src/Composer/Json/JsonFormatter.php b/vendor/composer/composer/src/Composer/Json/JsonFormatter.php new file mode 100644 index 000000000..fa1a3c53f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Json/JsonFormatter.php @@ -0,0 +1,132 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +use Composer\Pcre\Preg; + +/** + * Formats json strings used for php < 5.4 because the json_encode doesn't + * supports the flags JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE + * in these versions + * + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + * + * @deprecated Use json_encode or JsonFile::encode() with modern JSON_* flags to configure formatting - this class will be removed in 3.0 + */ +class JsonFormatter +{ + /** + * This code is based on the function found at: + * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ + * + * Originally licensed under MIT by Dave Perrett + * + * @param bool $unescapeUnicode Un escape unicode + * @param bool $unescapeSlashes Un escape slashes + */ + public static function format(string $json, bool $unescapeUnicode, bool $unescapeSlashes): string + { + $result = ''; + $pos = 0; + $strLen = strlen($json); + $indentStr = ' '; + $newLine = "\n"; + $outOfQuotes = true; + $buffer = ''; + $noescape = true; + + for ($i = 0; $i < $strLen; $i++) { + // Grab the next character in the string + $char = substr($json, $i, 1); + + // Are we inside a quoted string? + if ('"' === $char && $noescape) { + $outOfQuotes = !$outOfQuotes; + } + + if (!$outOfQuotes) { + $buffer .= $char; + $noescape = '\\' === $char ? !$noescape : true; + continue; + } + if ('' !== $buffer) { + if ($unescapeSlashes) { + $buffer = str_replace('\\/', '/', $buffer); + } + + if ($unescapeUnicode && function_exists('mb_convert_encoding')) { + // https://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha + $buffer = Preg::replaceCallback('/(\\\\+)u([0-9a-f]{4})/i', static function ($match): string { + $l = strlen($match[1]); + + if ($l % 2) { + $code = hexdec($match[2]); + // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped + // see https://github.com/composer/composer/issues/7510 + if (0xD800 <= $code && 0xDFFF >= $code) { + return $match[0]; + } + + return str_repeat('\\', $l - 1) . mb_convert_encoding( + pack('H*', $match[2]), + 'UTF-8', + 'UCS-2BE' + ); + } + + return $match[0]; + }, $buffer); + } + + $result .= $buffer.$char; + $buffer = ''; + continue; + } + + if (':' === $char) { + // Add a space after the : character + $char .= ' '; + } elseif ('}' === $char || ']' === $char) { + $pos--; + $prevChar = substr($json, $i - 1, 1); + + if ('{' !== $prevChar && '[' !== $prevChar) { + // If this character is the end of an element, + // output a new line and indent the next line + $result .= $newLine; + $result .= str_repeat($indentStr, $pos); + } else { + // Collapse empty {} and [] + $result = rtrim($result); + } + } + + $result .= $char; + + // If the last character was the beginning of an element, + // output a new line and indent the next line + if (',' === $char || '{' === $char || '[' === $char) { + $result .= $newLine; + + if ('{' === $char || '[' === $char) { + $pos++; + } + + $result .= str_repeat($indentStr, $pos); + } + } + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Json/JsonManipulator.php b/vendor/composer/composer/src/Composer/Json/JsonManipulator.php new file mode 100644 index 000000000..b174407f4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Json/JsonManipulator.php @@ -0,0 +1,1031 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +use Composer\Pcre\Preg; +use Composer\Repository\PlatformRepository; + +/** + * @author Jordi Boggiano + */ +class JsonManipulator +{ + /** @var string */ + private const DEFINES = '(?(DEFINE) + (? -? (?= [1-9]|0(?!\d) ) \d++ (?:\.\d++)? (?:[eE] [+-]?+ \d++)? ) + (? true | false | null ) + (? " (?:[^"\\\\]*+ | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " ) + (? \[ (?: (?&json) \s*+ (?: , (?&json) \s*+ )*+ )?+ \s*+ \] ) + (? \s*+ (?&string) \s*+ : (?&json) \s*+ ) + (? \{ (?: (?&pair) (?: , (?&pair) )*+ )?+ \s*+ \} ) + (? \s*+ (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) ) + )'; + + /** @var string */ + private $contents; + /** @var string */ + private $newline; + /** @var string */ + private $indent; + + public function __construct(string $contents) + { + $contents = trim($contents); + if ($contents === '') { + $contents = '{}'; + } + if (!Preg::isMatch('#^\{(.*)\}$#s', $contents)) { + throw new \InvalidArgumentException('The json file must be an object ({})'); + } + $this->newline = false !== strpos($contents, "\r\n") ? "\r\n" : "\n"; + $this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents; + $this->detectIndenting(); + } + + public function getContents(): string + { + return $this->contents . $this->newline; + } + + public function addLink(string $type, string $package, string $constraint, bool $sortPackages = false): bool + { + $decoded = JsonFile::parseJson($this->contents); + + // no link of that type yet + if (!isset($decoded[$type])) { + return $this->addMainKey($type, [$package => $constraint]); + } + + $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. + '(?P'.preg_quote(JsonFile::encode($type)).'\s*:\s*)(?P(?&json))(?P.*)}sx'; + if (!Preg::isMatch($regex, $this->contents, $matches)) { + return false; + } + assert(is_string($matches['start'])); + assert(is_string($matches['value'])); + assert(is_string($matches['end'])); + + $links = $matches['value']; + + // try to find existing link + $packageRegex = str_replace('/', '\\\\?/', preg_quote($package)); + $regex = '{'.self::DEFINES.'"(?P'.$packageRegex.')"(\s*:\s*)(?&string)}ix'; + if (Preg::isMatch($regex, $links, $packageMatches)) { + assert(is_string($packageMatches['package'])); + // update existing link + $existingPackage = $packageMatches['package']; + $packageRegex = str_replace('/', '\\\\?/', preg_quote($existingPackage)); + $links = Preg::replaceCallback('{'.self::DEFINES.'"'.$packageRegex.'"(?P\s*:\s*)(?&string)}ix', static function ($m) use ($existingPackage, $constraint): string { + return JsonFile::encode(str_replace('\\/', '/', $existingPackage)) . $m['separator'] . '"' . $constraint . '"'; + }, $links); + } else { + if (Preg::isMatchStrictGroups('#^\s*\{\s*\S+.*?(\s*\}\s*)$#s', $links, $match)) { + // link missing but non empty links + $links = Preg::replace( + '{'.preg_quote($match[1]).'$}', + // addcslashes is used to double up backslashes/$ since preg_replace resolves them as back references otherwise, see #1588 + addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1], '\\$'), + $links + ); + } else { + // links empty + $links = '{' . $this->newline . + $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $this->newline . + $this->indent . '}'; + } + } + + if (true === $sortPackages) { + $requirements = json_decode($links, true); + $this->sortPackages($requirements); + $links = $this->format($requirements); + } + + $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end']; + + return true; + } + + /** + * Sorts packages by importance (platform packages first, then PHP dependencies) and alphabetically. + * + * @link https://getcomposer.org/doc/02-libraries.md#platform-packages + * + * @param array $packages + */ + private function sortPackages(array &$packages = []): void + { + $prefix = static function ($requirement): string { + if (PlatformRepository::isPlatformPackage($requirement)) { + return Preg::replace( + [ + '/^php/', + '/^hhvm/', + '/^ext/', + '/^lib/', + '/^\D/', + ], + [ + '0-$0', + '1-$0', + '2-$0', + '3-$0', + '4-$0', + ], + $requirement + ); + } + + return '5-'.$requirement; + }; + + uksort($packages, static function ($a, $b) use ($prefix): int { + return strnatcmp($prefix($a), $prefix($b)); + }); + } + + /** + * @param array|false $config + */ + public function addRepository(string $name, $config, bool $append = true): bool + { + if ("" !== $name && !$this->doRemoveRepository($name)) { + return false; + } + + if (!$this->doConvertRepositoriesFromAssocToList()) { + return false; + } + + if (is_array($config) && !is_numeric($name) && '' !== $name) { + $config = ['name' => $name] + $config; + } elseif ($config === false) { + $config = [$name => $config]; + } + + return $this->addListItem('repositories', $config, $append); + } + + private function doConvertRepositoriesFromAssocToList(): bool + { + $decoded = json_decode($this->contents, false); + + if (($decoded->repositories ?? null) instanceof \stdClass) { + // delete from bottom to top, to ensure keys stay the same + $entriesToRevert = array_reverse(array_keys((array) $decoded->repositories)); + + foreach ($entriesToRevert as $entryKey) { + if (!$this->removeSubNode('repositories', (string) $entryKey)) { + return false; + } + } + + $this->changeEmptyMainKeyFromAssocToList('repositories'); + + // re-add in order + foreach (((array) $decoded->repositories) as $repositoryName => $repository) { + if (!$repository instanceof \stdClass) { + if (!$this->addListItem('repositories', [$repositoryName => $repository], true)) { + return false; + } + } elseif (is_numeric($repositoryName)) { + if (!$this->addListItem('repositories', $repository, true)) { + return false; + } + } else { + $repository = (array) $repository; + // prepend name property + $repository = ['name' => $repositoryName] + $repository; + if (!$this->addListItem('repositories', $repository, true)) { + return false; + } + } + } + } + + return true; + } + + public function setRepositoryUrl(string $name, string $url): bool + { + $decoded = JsonFile::parseJson($this->contents); + $repositoryIndex = null; + + foreach ($decoded['repositories'] ?? [] as $index => $repository) { + if ($name === $index) { + $repositoryIndex = $index; + break; + } + + if ($name === ($repository['name'] ?? null)) { + $repositoryIndex = $index; + break; + } + } + + if (null === $repositoryIndex) { + return false; + } + + $listRegex = null; + + if (is_int($repositoryIndex)) { + $listRegex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?"repositories"\s*:\s*\[\s*((?&json)\s*+,\s*+){' . max(0, $repositoryIndex) . '})(?P(?&object))(?P.*)}sx'; + } + + $objectRegex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?"repositories"\s*:\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?' . preg_quote(JsonFile::encode($repositoryIndex)) . '\s*:\s*)(?P(?&object))(?P.*)}sx'; + $matches = null; + + if (($listRegex !== null && Preg::isMatch($listRegex, $this->contents, $matches)) || Preg::isMatch($objectRegex, $this->contents, $matches)) { + assert(isset($matches['start']) && is_string($matches['start'])); + assert(isset($matches['repository']) && is_string($matches['repository'])); + assert(isset($matches['end']) && is_string($matches['end'])); + + // invalid match due to un-regexable content, abort + if (false === @json_decode($matches['repository'])) { + return false; + } + + $repositoryRegex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?"url"\s*:\s*)(?P(?&string))(?P.*)}sx'; + + $this->contents = $matches['start'] . Preg::replaceCallback($repositoryRegex, static function (array $repositoryMatches) use ($url): string { + return $repositoryMatches['start'] . JsonFile::encode($url) . $repositoryMatches['end']; + }, $matches['repository']) . $matches['end']; + + return true; + } + + return false; + } + + /** + * @param array|false $config + */ + public function insertRepository(string $name, $config, string $referenceName, int $offset = 0): bool + { + if ("" !== $name && !$this->doRemoveRepository($name)) { + return false; + } + + if (!$this->doConvertRepositoriesFromAssocToList()) { + return false; + } + + $indexToInsert = null; + $decoded = JsonFile::parseJson($this->contents); + + foreach ($decoded['repositories'] as $repositoryIndex => $repository) { + if (($repository['name'] ?? null) === $referenceName) { + $indexToInsert = $repositoryIndex; + break; + } + + if ($repositoryIndex === $referenceName) { + $indexToInsert = $repositoryIndex; + break; + } + + if ([$referenceName => false] === $repository) { + $indexToInsert = $repositoryIndex; + break; + } + } + + if ($indexToInsert === null) { + return false; + } + + if (is_array($config) && !is_numeric($name) && '' !== $name) { + $config = ['name' => $name] + $config; + } elseif ($config === false) { + $config = ['name' => $config]; + } + + return $this->insertListItem('repositories', $config, $indexToInsert + $offset); + } + + public function removeRepository(string $name): bool + { + return $this->doRemoveRepository($name) && $this->removeMainKeyIfEmpty('repositories'); + } + + private function doRemoveRepository(string $name): bool + { + $decoded = json_decode($this->contents, false); + $isAssoc = ($decoded->repositories ?? null) instanceof \stdClass; + + foreach ((array) ($decoded->repositories ?? []) as $repositoryIndex => $repository) { + if ($repositoryIndex === $name && $isAssoc) { + if (!$this->removeSubNode('repositories', $repositoryIndex)) { + return false; + } + + break; + } + + if (($repository->name ?? null) === $name) { + if ($isAssoc) { + if (!$this->removeSubNode('repositories', (string) $repositoryIndex)) { + return false; + } + } else { + if (!$this->removeListItem('repositories', (int) $repositoryIndex)) { + return false; + } + } + + break; + } + + if ($isAssoc) { + if ($name === $repositoryIndex && false === $repository) { + if (!$this->removeSubNode('repositories', $repositoryIndex)) { + return false; + } + + return true; + } + } else { + $repositoryAsArray = (array) $repository; + + if (false === ($repositoryAsArray[$name] ?? null) && 1 === count($repositoryAsArray)) { + if (!$this->removeListItem('repositories', (int) $repositoryIndex)) { + return false; + } + + return true; + } + } + } + + return true; + } + + /** + * @param mixed $value + */ + public function addConfigSetting(string $name, $value): bool + { + return $this->addSubNode('config', $name, $value); + } + + public function removeConfigSetting(string $name): bool + { + return $this->removeSubNode('config', $name); + } + + /** + * @param mixed $value + */ + public function addProperty(string $name, $value): bool + { + if (strpos($name, 'suggest.') === 0) { + return $this->addSubNode('suggest', substr($name, 8), $value); + } + + if (strpos($name, 'extra.') === 0) { + return $this->addSubNode('extra', substr($name, 6), $value); + } + + if (strpos($name, 'scripts.') === 0) { + return $this->addSubNode('scripts', substr($name, 8), $value); + } + + return $this->addMainKey($name, $value); + } + + public function removeProperty(string $name): bool + { + if (strpos($name, 'suggest.') === 0) { + return $this->removeSubNode('suggest', substr($name, 8)); + } + + if (strpos($name, 'extra.') === 0) { + return $this->removeSubNode('extra', substr($name, 6)); + } + + if (strpos($name, 'scripts.') === 0) { + return $this->removeSubNode('scripts', substr($name, 8)); + } + + if (strpos($name, 'autoload.') === 0) { + return $this->removeSubNode('autoload', substr($name, 9)); + } + + if (strpos($name, 'autoload-dev.') === 0) { + return $this->removeSubNode('autoload-dev', substr($name, 13)); + } + + return $this->removeMainKey($name); + } + + /** + * @param mixed $value + */ + public function addSubNode(string $mainNode, string $name, $value, bool $append = true): bool + { + $decoded = JsonFile::parseJson($this->contents); + + $subName = null; + if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) { + [$name, $subName] = explode('.', $name, 2); + } + + // no main node yet + if (!isset($decoded[$mainNode])) { + if ($subName !== null) { + $this->addMainKey($mainNode, [$name => [$subName => $value]]); + } else { + $this->addMainKey($mainNode, [$name => $value]); + } + + return true; + } + + // main node content not match-able + $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. + preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&object))(?P.*)}sx'; + + try { + if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { + return false; + } + } catch (\RuntimeException $e) { + if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { + return false; + } + throw $e; + } + + assert(is_string($match['start'])); + assert(is_string($match['content'])); + assert(is_string($match['end'])); + + $children = $match['content']; + // invalid match due to un-regexable content, abort + if (!@json_decode($children)) { + return false; + } + + // child exists + $childRegex = '{'.self::DEFINES.'(?P"'.preg_quote($name).'"\s*:\s*)(?P(?&json))(?P,?)}x'; + if (Preg::isMatch($childRegex, $children, $matches)) { + $children = Preg::replaceCallback($childRegex, function ($matches) use ($subName, $value): string { + if ($subName !== null && is_string($matches['content'])) { + $curVal = json_decode($matches['content'], true); + if (!is_array($curVal)) { + $curVal = []; + } + $curVal[$subName] = $value; + $value = $curVal; + } + + return $matches['start'] . $this->format($value, 1) . $matches['end']; + }, $children); + } elseif (Preg::isMatch('#^\{(?P\s*?)(?P\S+.*?)?(?P\s*)\}$#s', $children, $match)) { + $whitespace = $match['trailingspace']; + if (null !== $match['content']) { + if ($subName !== null) { + $value = [$subName => $value]; + } + + // child missing but non empty children + if ($append) { + $children = Preg::replace( + '#'.$whitespace.'}$#', + addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}', '\\$'), + $children + ); + } else { + $whitespace = $match['leadingspace']; + $children = Preg::replace( + '#^{'.$whitespace.'#', + addcslashes('{' . $whitespace . JsonFile::encode($name).': '.$this->format($value, 1) . ',' . $this->newline . $this->indent . $this->indent, '\\$'), + $children + ); + } + } else { + if ($subName !== null) { + $value = [$subName => $value]; + } + + // children present but empty + $children = '{' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}'; + } + } else { + throw new \LogicException('Nothing matched above for: '.$children); + } + + $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string { + return $m['start'] . $children . $m['end']; + }, $this->contents); + + return true; + } + + public function removeSubNode(string $mainNode, string $name): bool + { + $decoded = JsonFile::parseJson($this->contents); + + // no node or empty node + if (empty($decoded[$mainNode])) { + return true; + } + + // no node content match-able + $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. + preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&object))(?P.*)}sx'; + try { + if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { + return false; + } + } catch (\RuntimeException $e) { + if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { + return false; + } + throw $e; + } + + assert(is_string($match['start'])); + assert(is_string($match['content'])); + assert(is_string($match['end'])); + + $children = $match['content']; + + // invalid match due to un-regexable content, abort + if (!@json_decode($children, true)) { + return false; + } + + $subName = null; + if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) { + [$name, $subName] = explode('.', $name, 2); + } + + // no node to remove + if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) { + return true; + } + + // try and find a match for the subkey + $keyRegex = str_replace('/', '\\\\?/', preg_quote($name)); + if (Preg::isMatch('{"'.$keyRegex.'"\s*:}i', $children)) { + // find best match for the value of "name" + if (Preg::isMatchAll('{'.self::DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) { + $bestMatch = ''; + foreach ($matches[0] as $match) { + assert(is_string($match)); + if (strlen($bestMatch) < strlen($match)) { + $bestMatch = $match; + } + } + $childrenClean = Preg::replace('{,\s*'.preg_quote($bestMatch).'}i', '', $children, -1, $count); + if (1 !== $count) { + $childrenClean = Preg::replace('{'.preg_quote($bestMatch).'\s*,?\s*}i', '', $childrenClean, -1, $count); + if (1 !== $count) { + return false; + } + } + } + } else { + $childrenClean = $children; + } + + if (!isset($childrenClean)) { + throw new \InvalidArgumentException("JsonManipulator: \$childrenClean is not defined. Please report at https://github.com/composer/composer/issues/new."); + } + + // no child data left, $name was the only key in + unset($match); + if (Preg::isMatch('#^\{\s*?(?P\S+.*?)?(?P\s*)\}$#s', $childrenClean, $match)) { + if (null === $match['content']) { + $newline = $this->newline; + $indent = $this->indent; + + $this->contents = Preg::replaceCallback($nodeRegex, static function ($matches) use ($indent, $newline): string { + return $matches['start'] . '{' . $newline . $indent . '}' . $matches['end']; + }, $this->contents); + + // we have a subname, so we restore the rest of $name + if ($subName !== null) { + $curVal = json_decode($children, true); + unset($curVal[$name][$subName]); + if ($curVal[$name] === []) { + $curVal[$name] = new \ArrayObject(); + } + $this->addSubNode($mainNode, $name, $curVal[$name]); + } + + return true; + } + } + + $this->contents = Preg::replaceCallback($nodeRegex, function ($matches) use ($name, $subName, $childrenClean): string { + assert(is_string($matches['content'])); + if ($subName !== null) { + $curVal = json_decode($matches['content'], true); + unset($curVal[$name][$subName]); + if ($curVal[$name] === []) { + $curVal[$name] = new \ArrayObject(); + } + $childrenClean = $this->format($curVal, 0, true); + } + + return $matches['start'] . $childrenClean . $matches['end']; + }, $this->contents); + + return true; + } + + /** + * @param mixed $value + */ + public function addListItem(string $mainNode, $value, bool $append = true): bool + { + $decoded = JsonFile::parseJson($this->contents); + + // no main node yet + if (!isset($decoded[$mainNode])) { + if (!$this->addMainKey($mainNode, [])) { + return false; + } + } + + // main node content not match-able + $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. + preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&array))(?P.*)}sx'; + + try { + if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { + return false; + } + } catch (\RuntimeException $e) { + if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { + return false; + } + throw $e; + } + + assert(is_string($match['start'])); + assert(is_string($match['content'])); + assert(is_string($match['end'])); + + $children = $match['content']; + // invalid match due to un-regexable content, abort + if (false === @json_decode($children)) { + return false; + } + + if (Preg::isMatch('#^\[(?P\s*?)(?P\S+.*?)?(?P\s*)\]$#s', $children, $match)) { + $leadingWhitespace = $match['leadingspace']; + $whitespace = $match['trailingspace']; + $leadingItemWhitespace = $this->newline . $this->indent . $this->indent; + $trailingItemWhitespace = $whitespace; + $itemDepth = 1; + + // keep oneline lists as one line + if (!str_contains($whitespace, $this->newline)) { + $leadingItemWhitespace = $leadingWhitespace; + $trailingItemWhitespace = $leadingWhitespace; + $itemDepth = 0; + } + + if (null !== $match['content']) { + // child missing but non empty children + if ($append) { + $children = Preg::replace( + '#'.$whitespace.']$#', + addcslashes(',' . $leadingItemWhitespace . $this->format($value, $itemDepth) . $trailingItemWhitespace . ']', '\\$'), + $children + ); + } else { + $whitespace = $match['leadingspace']; + $children = Preg::replace( + '#^\['.$whitespace.'#', + addcslashes('[' . $whitespace . $this->format($value, $itemDepth) . ',' . $leadingItemWhitespace, '\\$'), + $children + ); + } + } else { + // children present but empty + $children = '[' . $leadingItemWhitespace . $this->format($value, $itemDepth) . $trailingItemWhitespace . ']'; + } + } else { + throw new \LogicException('Nothing matched above for: '.$children); + } + + $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string { + return $m['start'] . $children . $m['end']; + }, $this->contents); + + return true; + } + + /** + * @param mixed $value + */ + public function insertListItem(string $mainNode, $value, int $index): bool + { + if ($index < 0) { + throw new \InvalidArgumentException('Index can only be positive integer'); + } + + if ($index === 0) { + return $this->addListItem($mainNode, $value, false); + } + + $decoded = JsonFile::parseJson($this->contents); + + // no main node yet + if (!isset($decoded[$mainNode])) { + if (!$this->addMainKey($mainNode, [])) { + return false; + } + } + + if (count($decoded[$mainNode]) === $index) { + return $this->addListItem($mainNode, $value, true); + } + + // main node content not match-able + $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. + preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&array))(?P.*)}sx'; + + try { + if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { + return false; + } + } catch (\RuntimeException $e) { + if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { + return false; + } + throw $e; + } + + assert(is_string($match['start'])); + assert(is_string($match['content'])); + assert(is_string($match['end'])); + + $children = $match['content']; + // invalid match due to un-regexable content, abort + if (false === @json_decode($children)) { + return false; + } + + $listSkipToItemRegex = '{'.self::DEFINES.'^(?P\[\s*((?&json)\s*+,\s*?){' . max(0, $index) . '})(?P(\s*))(?P.*)}sx'; + + $children = Preg::replaceCallback($listSkipToItemRegex, function ($m) use ($value): string { + return $m['start'] . $m['space_before_item'] . $this->format($value, 1) . ',' . $m['space_before_item'] . $m['end']; + }, $children); + + $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string { + return $m['start'] . $children . $m['end']; + }, $this->contents); + + return true; + } + + public function removeListItem(string $mainNode, int $nodeIndex): bool + { + // invalid index, that cannot be removed anyway + if ($nodeIndex < 0) { + return true; + } + + $decoded = JsonFile::parseJson($this->contents); + + // no node or empty node + if ([] === $decoded[$mainNode]) { + return true; + } + + // no node content match-able + $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. + preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&array))(?P.*)}sx'; + try { + if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { + return false; + } + } catch (\RuntimeException $e) { + if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { + return false; + } + throw $e; + } + + assert(is_string($match['start'])); + assert(is_string($match['content'])); + assert(is_string($match['end'])); + + $children = $match['content']; + + // invalid match due to un-regexable content, abort + if (false === @json_decode($children, true)) { + return false; + } + + // no node to remove + if (!isset($decoded[$mainNode][$nodeIndex])) { + return true; + } + + $contentRegex = '(?&json)'; + + if ($nodeIndex > 1) { + $startRegex = '(?&json)\s*+(?:,(?&json)\s*+){' . ($nodeIndex - 1) . '}'; + // remove leading array separator in case we might remove the last + $contentRegex = '\s*+,?\s*+' . $contentRegex; + $endRegex = '(?:(\s*+,\s*+(?&json))*(?:\s*+(?&json))?)\s*+'; + } elseif ($nodeIndex > 0) { + $startRegex = '(?&json)\s*+'; + // remove leading array separator in case we might remove the last + $contentRegex = '\s*+,?\s*+' . $contentRegex; + $endRegex = '(?:(\s*+,\s*+(?&json))*(?:\s*+(?&json))?)\s*+'; + } else { + $startRegex = '\s*+'; + // remove trailing array separator when we delete first + $contentRegex = $contentRegex . '\s*+,?\s*+'; + $endRegex = '(?:((?&json)\s*+,\s*+)*(?:\s*+(?&json))?)\s*+'; + } + + if (Preg::isMatch('{'.self::DEFINES.'(?P\[' . $startRegex . ')(?P' . $contentRegex . ')(?P' . $endRegex . '\])}sx', $children, $childMatch)) { + $this->contents = $match['start'] . $childMatch['start'] . $childMatch['end'] . $match['end']; + + return true; + } + + return false; + } + + /** + * @param mixed $content + */ + public function addMainKey(string $key, $content): bool + { + $decoded = JsonFile::parseJson($this->contents); + $content = $this->format($content); + + // key exists already + $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. + '(?P'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))(?P.*)}sx'; + if (isset($decoded[$key]) && Preg::isMatch($regex, $this->contents, $matches)) { + // invalid match due to un-regexable content, abort + if (!@json_decode('{'.$matches['key'].'}')) { + return false; + } + + $this->contents = $matches['start'] . JsonFile::encode($key).': '.$content . $matches['end']; + + return true; + } + + // append at the end of the file and keep whitespace + if (Preg::isMatch('#[^{\s](\s*)\}$#', $this->contents, $match)) { + $this->contents = Preg::replace( + '#'.$match[1].'\}$#', + addcslashes(',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}', '\\$'), + $this->contents + ); + + return true; + } + + // append at the end of the file + $this->contents = Preg::replace( + '#\}$#', + addcslashes($this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}', '\\$'), + $this->contents + ); + + return true; + } + + public function removeMainKey(string $key): bool + { + $decoded = JsonFile::parseJson($this->contents); + + if (!array_key_exists($key, $decoded)) { + return true; + } + + // key exists already + $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. + '(?P'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))\s*,?\s*(?P.*)}sx'; + if (Preg::isMatch($regex, $this->contents, $matches)) { + assert(is_string($matches['start'])); + assert(is_string($matches['removal'])); + assert(is_string($matches['end'])); + + // invalid match due to un-regexable content, abort + if (!@json_decode('{'.$matches['removal'].'}')) { + return false; + } + + // check that we are not leaving a dangling comma on the previous line if the last line was removed + if (Preg::isMatchStrictGroups('#,\s*$#', $matches['start']) && Preg::isMatch('#^\}$#', $matches['end'])) { + $matches['start'] = rtrim(Preg::replace('#,(\s*)$#', '$1', $matches['start']), $this->indent); + } + + $this->contents = $matches['start'] . $matches['end']; + if (Preg::isMatch('#^\{\s*\}\s*$#', $this->contents)) { + $this->contents = "{\n}"; + } + + return true; + } + + return false; + } + + public function changeEmptyMainKeyFromAssocToList(string $key): bool + { + $decoded = JsonFile::parseJson($this->contents); + + if (!array_key_exists($key, $decoded)) { + return true; + } + + $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?'.preg_quote(JsonFile::encode($key)).'\s*:\s*)(?P\{(?P\s*+)\})(?P\s*,?\s*.*)}sx'; + if (Preg::isMatch($regex, $this->contents, $matches)) { + assert(is_string($matches['start'])); + assert(is_string($matches['removal'])); + assert(is_string($matches['end'])); + + // invalid match due to un-regexable content, abort + if (false === @json_decode($matches['removal'])) { + return false; + } + + $this->contents = $matches['start'] . '[' . $matches['removal_space'] . ']' . $matches['end']; + + return true; + } + + return false; + } + + public function removeMainKeyIfEmpty(string $key): bool + { + $decoded = JsonFile::parseJson($this->contents); + + if (!array_key_exists($key, $decoded)) { + return true; + } + + if (is_array($decoded[$key]) && count($decoded[$key]) === 0) { + return $this->removeMainKey($key); + } + + return true; + } + + /** + * @param mixed $data + */ + public function format($data, int $depth = 0, bool $wasObject = false): string + { + if ($data instanceof \stdClass || $data instanceof \ArrayObject) { + $data = (array) $data; + $wasObject = true; + } + + if (is_array($data)) { + if (\count($data) === 0) { + return $wasObject ? '{' . $this->newline . str_repeat($this->indent, $depth + 1) . '}' : '[]'; + } + + if (array_is_list($data)) { + foreach ($data as $key => $val) { + $data[$key] = $this->format($val, $depth + 1); + } + + return '['.implode(', ', $data).']'; + } + + $out = '{' . $this->newline; + $elems = []; + foreach ($data as $key => $val) { + $elems[] = str_repeat($this->indent, $depth + 2) . JsonFile::encode((string) $key). ': '.$this->format($val, $depth + 1); + } + + return $out . implode(','.$this->newline, $elems) . $this->newline . str_repeat($this->indent, $depth + 1) . '}'; + } + + return JsonFile::encode($data); + } + + protected function detectIndenting(): void + { + $this->indent = JsonFile::detectIndenting($this->contents); + } +} diff --git a/vendor/composer/composer/src/Composer/Json/JsonValidationException.php b/vendor/composer/composer/src/Composer/Json/JsonValidationException.php new file mode 100644 index 000000000..28c22fcf9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Json/JsonValidationException.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +use Exception; + +/** + * @author Jordi Boggiano + */ +class JsonValidationException extends Exception +{ + /** + * @var string[] + */ + protected $errors; + + /** + * @param string[] $errors + */ + public function __construct(string $message, array $errors = [], ?Exception $previous = null) + { + $this->errors = $errors; + parent::__construct((string) $message, 0, $previous); + } + + /** + * @return string[] + */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/vendor/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php b/vendor/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php new file mode 100644 index 000000000..f37c57d82 --- /dev/null +++ b/vendor/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php @@ -0,0 +1,206 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\PHPStan; + +use Composer\Config; +use Composer\Json\JsonFile; +use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Type\ArrayType; +use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\DynamicMethodReturnTypeExtension; +use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntegerType; +use PHPStan\Type\MixedType; +use PHPStan\Type\StringType; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypeUtils; +use PHPStan\Type\UnionType; + +final class ConfigReturnTypeExtension implements DynamicMethodReturnTypeExtension +{ + /** @var array */ + private $properties = []; + + public function __construct() + { + $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH)); + /** + * @var string $prop + */ + foreach ($schema['properties']['config']['properties'] as $prop => $conf) { + $type = $this->parseType($conf, $prop); + + $this->properties[$prop] = $type; + } + } + + public function getClass(): string + { + return Config::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return strtolower($methodReflection->getName()) === 'get'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + $args = $methodCall->getArgs(); + + if (count($args) < 1) { + return null; + } + + $keyType = $scope->getType($args[0]->value); + if (method_exists($keyType, 'getConstantStrings')) { // @phpstan-ignore function.alreadyNarrowedType (- depending on PHPStan version, this method will always exist, or not.) + $strings = $keyType->getConstantStrings(); + } else { + // for compat with old phpstan versions, we use a deprecated phpstan method. + $strings = TypeUtils::getConstantStrings($keyType); // @phpstan-ignore staticMethod.deprecated (ignore deprecation) + } + if ($strings !== []) { + $types = []; + foreach ($strings as $string) { + if (!isset($this->properties[$string->getValue()])) { + return null; + } + $types[] = $this->properties[$string->getValue()]; + } + + return TypeCombinator::union(...$types); + } + + return null; + } + + /** + * @param array $def + */ + private function parseType(array $def, string $path): Type + { + if (isset($def['type'])) { + $types = []; + foreach ((array) $def['type'] as $type) { + switch ($type) { + case 'integer': + if (in_array($path, ['process-timeout', 'cache-ttl', 'cache-files-ttl', 'cache-files-maxsize'], true)) { + $types[] = IntegerRangeType::createAllGreaterThanOrEqualTo(0); + } else { + $types[] = new IntegerType(); + } + break; + + case 'string': + if ($path === 'cache-files-maxsize') { + // passthru, skip as it is always converted to int + } elseif ($path === 'discard-changes') { + $types[] = new ConstantStringType('stash'); + } elseif ($path === 'use-parent-dir') { + $types[] = new ConstantStringType('prompt'); + } elseif ($path === 'store-auths') { + $types[] = new ConstantStringType('prompt'); + } elseif ($path === 'platform-check') { + $types[] = new ConstantStringType('php-only'); + } elseif ($path === 'github-protocols') { + $types[] = new UnionType([new ConstantStringType('git'), new ConstantStringType('https'), new ConstantStringType('ssh'), new ConstantStringType('http')]); + } elseif (str_starts_with($path, 'preferred-install')) { + $types[] = new UnionType([new ConstantStringType('source'), new ConstantStringType('dist'), new ConstantStringType('auto')]); + } else { + $types[] = new StringType(); + } + break; + + case 'boolean': + if ($path === 'platform.additionalProperties') { + $types[] = new ConstantBooleanType(false); + } else { + $types[] = new BooleanType(); + } + break; + + case 'object': + $addlPropType = null; + if (isset($def['additionalProperties'])) { + $addlPropType = $this->parseType($def['additionalProperties'], $path.'.additionalProperties'); + } + + if (isset($def['properties'])) { + $keyNames = []; + $valTypes = []; + $optionalKeys = []; + $propIndex = 0; + foreach ($def['properties'] as $propName => $propdef) { + $keyNames[] = new ConstantStringType($propName); + $valType = $this->parseType($propdef, $path.'.'.$propName); + if (!isset($def['required']) || !in_array($propName, $def['required'], true)) { + $valType = TypeCombinator::addNull($valType); + $optionalKeys[] = $propIndex; + } + $valTypes[] = $valType; + $propIndex++; + } + + if ($addlPropType !== null) { + $types[] = new ArrayType(TypeCombinator::union(new StringType(), ...$keyNames), TypeCombinator::union($addlPropType, ...$valTypes)); + } else { + $types[] = new ConstantArrayType($keyNames, $valTypes, [0], $optionalKeys); + } + } else { + $types[] = new ArrayType(new StringType(), $addlPropType ?? new MixedType()); + } + break; + + case 'array': + if (isset($def['items'])) { + $valType = $this->parseType($def['items'], $path.'.items'); + } else { + $valType = new MixedType(); + } + + $types[] = new ArrayType(new IntegerType(), $valType); + break; + + default: + $types[] = new MixedType(); + } + } + + $type = TypeCombinator::union(...$types); + } elseif (isset($def['enum'])) { + $type = TypeCombinator::union(...array_map(static function (string $value): ConstantStringType { + return new ConstantStringType($value); + }, $def['enum'])); + } else { + $type = new MixedType(); + } + + // allow-plugins defaults to null until July 1st 2022 for some BC hackery, but after that it is not nullable anymore + if ($path === 'allow-plugins' && time() < strtotime('2022-07-01')) { + $type = TypeCombinator::addNull($type); + } + + // default null props + if (in_array($path, ['autoloader-suffix', 'gitlab-protocol'], true)) { + $type = TypeCombinator::addNull($type); + } + + return $type; + } +} diff --git a/vendor/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php b/vendor/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php new file mode 100644 index 000000000..58a9e4bba --- /dev/null +++ b/vendor/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php @@ -0,0 +1,69 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\PHPStan; + +use Composer\DependencyResolver\Rule; +use Composer\Package\BasePackage; +use Composer\Package\Link; +use Composer\Semver\Constraint\ConstraintInterface; +use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\DynamicMethodReturnTypeExtension; +use PHPStan\Type\IntegerType; +use PHPStan\Type\StringType; +use PHPStan\Type\Type; +use PHPStan\Type\ObjectType; +use PHPStan\Type\TypeCombinator; +use PhpParser\Node\Identifier; + +final class RuleReasonDataReturnTypeExtension implements DynamicMethodReturnTypeExtension +{ + public function getClass(): string + { + return Rule::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return strtolower($methodReflection->getName()) === 'getreasondata'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $reasonType = $scope->getType(new MethodCall($methodCall->var, new Identifier('getReason'))); + + $types = [ + Rule::RULE_ROOT_REQUIRE => new ConstantArrayType([new ConstantStringType('packageName'), new ConstantStringType('constraint')], [new StringType, new ObjectType(ConstraintInterface::class)]), + Rule::RULE_FIXED => new ConstantArrayType([new ConstantStringType('package')], [new ObjectType(BasePackage::class)]), + Rule::RULE_PACKAGE_CONFLICT => new ObjectType(Link::class), + Rule::RULE_PACKAGE_REQUIRES => new ObjectType(Link::class), + Rule::RULE_PACKAGE_SAME_NAME => TypeCombinator::intersect(new StringType, new AccessoryNonEmptyStringType()), + Rule::RULE_LEARNED => new IntegerType(), + Rule::RULE_PACKAGE_ALIAS => new ObjectType(BasePackage::class), + Rule::RULE_PACKAGE_INVERSE_ALIAS => new ObjectType(BasePackage::class), + ]; + + foreach ($types as $const => $type) { + if ((new ConstantIntegerType($const))->isSuperTypeOf($reasonType)->yes()) { + return $type; + } + } + + return TypeCombinator::union(...$types); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/AliasPackage.php b/vendor/composer/composer/src/Composer/Package/AliasPackage.php new file mode 100644 index 000000000..932ea3658 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/AliasPackage.php @@ -0,0 +1,398 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Semver\Constraint\Constraint; +use Composer\Package\Version\VersionParser; + +/** + * @author Jordi Boggiano + */ +class AliasPackage extends BasePackage +{ + /** @var string */ + protected $version; + /** @var string */ + protected $prettyVersion; + /** @var bool */ + protected $dev; + /** @var bool */ + protected $rootPackageAlias = false; + /** + * @var string + * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + protected $stability; + /** @var bool */ + protected $hasSelfVersionRequires = false; + + /** @var BasePackage */ + protected $aliasOf; + /** @var Link[] */ + protected $requires; + /** @var Link[] */ + protected $devRequires; + /** @var Link[] */ + protected $conflicts; + /** @var Link[] */ + protected $provides; + /** @var Link[] */ + protected $replaces; + + /** + * All descendants' constructors should call this parent constructor + * + * @param BasePackage $aliasOf The package this package is an alias of + * @param string $version The version the alias must report + * @param string $prettyVersion The alias's non-normalized version + */ + public function __construct(BasePackage $aliasOf, string $version, string $prettyVersion) + { + parent::__construct($aliasOf->getName()); + + $this->version = $version; + $this->prettyVersion = $prettyVersion; + $this->aliasOf = $aliasOf; + $this->stability = VersionParser::parseStability($version); + $this->dev = $this->stability === 'dev'; + + foreach (Link::$TYPES as $type) { + $links = $aliasOf->{'get' . ucfirst($type)}(); + $this->{$type} = $this->replaceSelfVersionDependencies($links, $type); + } + } + + /** + * @return BasePackage + */ + public function getAliasOf() + { + return $this->aliasOf; + } + + /** + * @inheritDoc + */ + public function getVersion(): string + { + return $this->version; + } + + /** + * @inheritDoc + */ + public function getStability(): string + { + return $this->stability; + } + + /** + * @inheritDoc + */ + public function getPrettyVersion(): string + { + return $this->prettyVersion; + } + + /** + * @inheritDoc + */ + public function isDev(): bool + { + return $this->dev; + } + + /** + * @inheritDoc + */ + public function getRequires(): array + { + return $this->requires; + } + + /** + * @inheritDoc + * @return array + */ + public function getConflicts(): array + { + return $this->conflicts; + } + + /** + * @inheritDoc + * @return array + */ + public function getProvides(): array + { + return $this->provides; + } + + /** + * @inheritDoc + * @return array + */ + public function getReplaces(): array + { + return $this->replaces; + } + + /** + * @inheritDoc + */ + public function getDevRequires(): array + { + return $this->devRequires; + } + + /** + * Stores whether this is an alias created by an aliasing in the requirements of the root package or not + * + * Use by the policy for sorting manually aliased packages first, see #576 + */ + public function setRootPackageAlias(bool $value): void + { + $this->rootPackageAlias = $value; + } + + /** + * @see setRootPackageAlias + */ + public function isRootPackageAlias(): bool + { + return $this->rootPackageAlias; + } + + /** + * @param Link[] $links + * @param Link::TYPE_* $linkType + * + * @return Link[] + */ + protected function replaceSelfVersionDependencies(array $links, $linkType): array + { + // for self.version requirements, we use the original package's branch name instead, to avoid leaking the magic dev-master-alias to users + $prettyVersion = $this->prettyVersion; + if ($prettyVersion === VersionParser::DEFAULT_BRANCH_ALIAS) { + $prettyVersion = $this->aliasOf->getPrettyVersion(); + } + + if (\in_array($linkType, [Link::TYPE_CONFLICT, Link::TYPE_PROVIDE, Link::TYPE_REPLACE], true)) { + $newLinks = []; + foreach ($links as $link) { + // link is self.version, but must be replacing also the replaced version + if ('self.version' === $link->getPrettyConstraint()) { + $newLinks[] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); + $constraint->setPrettyString($prettyVersion); + } + } + $links = array_merge($links, $newLinks); + } else { + foreach ($links as $index => $link) { + if ('self.version' === $link->getPrettyConstraint()) { + if ($linkType === Link::TYPE_REQUIRE) { + $this->hasSelfVersionRequires = true; + } + $links[$index] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); + $constraint->setPrettyString($prettyVersion); + } + } + } + + return $links; + } + + public function hasSelfVersionRequires(): bool + { + return $this->hasSelfVersionRequires; + } + + public function __toString(): string + { + return parent::__toString().' ('.($this->rootPackageAlias ? 'root ' : ''). 'alias of '.$this->aliasOf->getVersion().')'; + } + + /*************************************** + * Wrappers around the aliased package * + ***************************************/ + + public function getType(): string + { + return $this->aliasOf->getType(); + } + + public function getTargetDir(): ?string + { + return $this->aliasOf->getTargetDir(); + } + + public function getExtra(): array + { + return $this->aliasOf->getExtra(); + } + + public function setInstallationSource(?string $type): void + { + $this->aliasOf->setInstallationSource($type); + } + + public function getInstallationSource(): ?string + { + return $this->aliasOf->getInstallationSource(); + } + + public function getSourceType(): ?string + { + return $this->aliasOf->getSourceType(); + } + + public function getSourceUrl(): ?string + { + return $this->aliasOf->getSourceUrl(); + } + + public function getSourceUrls(): array + { + return $this->aliasOf->getSourceUrls(); + } + + public function getSourceReference(): ?string + { + return $this->aliasOf->getSourceReference(); + } + + public function setSourceReference(?string $reference): void + { + $this->aliasOf->setSourceReference($reference); + } + + public function setSourceMirrors(?array $mirrors): void + { + $this->aliasOf->setSourceMirrors($mirrors); + } + + public function getSourceMirrors(): ?array + { + return $this->aliasOf->getSourceMirrors(); + } + + public function getDistType(): ?string + { + return $this->aliasOf->getDistType(); + } + + public function getDistUrl(): ?string + { + return $this->aliasOf->getDistUrl(); + } + + public function getDistUrls(): array + { + return $this->aliasOf->getDistUrls(); + } + + public function getDistReference(): ?string + { + return $this->aliasOf->getDistReference(); + } + + public function setDistReference(?string $reference): void + { + $this->aliasOf->setDistReference($reference); + } + + public function getDistSha1Checksum(): ?string + { + return $this->aliasOf->getDistSha1Checksum(); + } + + public function setTransportOptions(array $options): void + { + $this->aliasOf->setTransportOptions($options); + } + + public function getTransportOptions(): array + { + return $this->aliasOf->getTransportOptions(); + } + + public function setDistMirrors(?array $mirrors): void + { + $this->aliasOf->setDistMirrors($mirrors); + } + + public function getDistMirrors(): ?array + { + return $this->aliasOf->getDistMirrors(); + } + + public function getAutoload(): array + { + return $this->aliasOf->getAutoload(); + } + + public function getDevAutoload(): array + { + return $this->aliasOf->getDevAutoload(); + } + + public function getIncludePaths(): array + { + return $this->aliasOf->getIncludePaths(); + } + + public function getPhpExt(): ?array + { + return $this->aliasOf->getPhpExt(); + } + + public function getReleaseDate(): ?\DateTimeInterface + { + return $this->aliasOf->getReleaseDate(); + } + + public function getBinaries(): array + { + return $this->aliasOf->getBinaries(); + } + + public function getSuggests(): array + { + return $this->aliasOf->getSuggests(); + } + + public function getNotificationUrl(): ?string + { + return $this->aliasOf->getNotificationUrl(); + } + + public function isDefaultBranch(): bool + { + return $this->aliasOf->isDefaultBranch(); + } + + public function setDistUrl(?string $url): void + { + $this->aliasOf->setDistUrl($url); + } + + public function setDistType(?string $type): void + { + $this->aliasOf->setDistType($type); + } + + public function setSourceDistReferences(string $reference): void + { + $this->aliasOf->setSourceDistReferences($reference); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php b/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php new file mode 100644 index 000000000..995e77443 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php @@ -0,0 +1,50 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use FilterIterator; +use Iterator; +use PharData; +use SplFileInfo; + +/** + * @phpstan-extends FilterIterator> + */ +class ArchivableFilesFilter extends FilterIterator +{ + /** @var string[] */ + private $dirs = []; + + /** + * @return bool true if the current element is acceptable, otherwise false. + */ + public function accept(): bool + { + $file = $this->getInnerIterator()->current(); + if ($file->isDir()) { + $this->dirs[] = (string) $file; + + return false; + } + + return true; + } + + public function addEmptyDir(PharData $phar, string $sources): void + { + foreach ($this->dirs as $filepath) { + $localname = str_replace($sources . "/", '', $filepath); + $phar->addEmptyDir($localname); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php b/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php new file mode 100644 index 000000000..2cf7ffc72 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php @@ -0,0 +1,113 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use FilesystemIterator; +use FilterIterator; +use Iterator; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; + +/** + * A Symfony Finder wrapper which locates files that should go into archives + * + * Handles .gitignore, .gitattributes and .hgignore files as well as composer's + * own exclude rules from composer.json + * + * @author Nils Adermann + * @phpstan-extends FilterIterator> + */ +class ArchivableFilesFinder extends FilterIterator +{ + /** + * @var Finder + */ + protected $finder; + + /** + * Initializes the internal Symfony Finder with appropriate filters + * + * @param string $sources Path to source files to be archived + * @param string[] $excludes Composer's own exclude rules from composer.json + * @param bool $ignoreFilters Ignore filters when looking for files + */ + public function __construct(string $sources, array $excludes, bool $ignoreFilters = false) + { + $fs = new Filesystem(); + + $sourcesRealPath = realpath($sources); + if ($sourcesRealPath === false) { + throw new \RuntimeException('Could not realpath() the source directory "'.$sources.'"'); + } + $sources = $fs->normalizePath($sourcesRealPath); + + if ($ignoreFilters) { + $filters = []; + } else { + $filters = [ + new GitExcludeFilter($sources), + new ComposerExcludeFilter($sources, $excludes), + ]; + } + + $this->finder = new Finder(); + + $filter = static function (\SplFileInfo $file) use ($sources, $filters, $fs): bool { + $realpath = $file->getRealPath(); + if ($realpath === false) { + return false; + } + if ($file->isLink() && strpos($realpath, $sources) !== 0) { + return false; + } + + $relativePath = Preg::replace( + '#^'.preg_quote($sources, '#').'#', + '', + $fs->normalizePath($realpath) + ); + + $exclude = false; + foreach ($filters as $filter) { + $exclude = $filter->filter($relativePath, $exclude); + } + + return !$exclude; + }; + + $this->finder + ->in($sources) + ->filter($filter) + ->ignoreVCS(true) + ->ignoreDotFiles(false) + ->sortByName(); + + parent::__construct($this->finder->getIterator()); + } + + public function accept(): bool + { + /** @var SplFileInfo $current */ + $current = $this->getInnerIterator()->current(); + + if (!$current->isDir()) { + return true; + } + + $iterator = new FilesystemIterator((string) $current, FilesystemIterator::SKIP_DOTS); + + return !$iterator->valid(); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php b/vendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php new file mode 100644 index 000000000..b71cad2a9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php @@ -0,0 +1,288 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Downloader\DownloadManager; +use Composer\Package\RootPackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Loop; +use Composer\Util\SyncHelper; +use Composer\Json\JsonFile; +use Composer\Package\CompletePackageInterface; + +/** + * @author Matthieu Moquet + * @author Till Klampaeckel + */ +class ArchiveManager +{ + /** @var DownloadManager */ + protected $downloadManager; + /** @var Loop */ + protected $loop; + + /** + * @var ArchiverInterface[] + */ + protected $archivers = []; + + /** + * @var bool + */ + protected $overwriteFiles = true; + + /** + * @param DownloadManager $downloadManager A manager used to download package sources + */ + public function __construct(DownloadManager $downloadManager, Loop $loop) + { + $this->downloadManager = $downloadManager; + $this->loop = $loop; + } + + public function addArchiver(ArchiverInterface $archiver): void + { + $this->archivers[] = $archiver; + } + + /** + * Set whether existing archives should be overwritten + * + * @param bool $overwriteFiles New setting + * + * @return $this + */ + public function setOverwriteFiles(bool $overwriteFiles): self + { + $this->overwriteFiles = $overwriteFiles; + + return $this; + } + + /** + * @return array + * @internal + */ + public function getPackageFilenameParts(CompletePackageInterface $package): array + { + $baseName = $package->getArchiveName(); + if (null === $baseName) { + $baseName = Preg::replace('#[^a-z0-9-_]#i', '-', $package->getName()); + } + + $parts = [ + 'base' => $baseName, + ]; + + $distReference = $package->getDistReference(); + if (null !== $distReference && Preg::isMatch('{^[a-f0-9]{40}$}', $distReference)) { + $parts['dist_reference'] = $distReference; + $parts['dist_type'] = $package->getDistType(); + } else { + $parts['version'] = $package->getPrettyVersion(); + $parts['dist_reference'] = $distReference; + } + + $sourceReference = $package->getSourceReference(); + if (null !== $sourceReference) { + $parts['source_reference'] = substr(hash('sha1', $sourceReference), 0, 6); + } + + $parts = array_filter($parts, static function (?string $part) { + return $part !== null; + }); + foreach ($parts as $key => $part) { + $parts[$key] = str_replace('/', '-', $part); + } + + return $parts; + } + + /** + * @param array $parts + * + * @internal + */ + public function getPackageFilenameFromParts(array $parts): string + { + return implode('-', $parts); + } + + /** + * Generate a distinct filename for a particular version of a package. + * + * @param CompletePackageInterface $package The package to get a name for + * + * @return string A filename without an extension + */ + public function getPackageFilename(CompletePackageInterface $package): string + { + return $this->getPackageFilenameFromParts($this->getPackageFilenameParts($package)); + } + + /** + * Create an archive of the specified package. + * + * @param CompletePackageInterface $package The package to archive + * @param string $format The format of the archive (zip, tar, ...) + * @param string $targetDir The directory where to build the archive + * @param string|null $fileName The relative file name to use for the archive, or null to generate + * the package name. Note that the format will be appended to this name + * @param bool $ignoreFilters Ignore filters when looking for files in the package + * @throws \InvalidArgumentException + * @throws \RuntimeException + * @return string The path of the created archive + */ + public function archive(CompletePackageInterface $package, string $format, string $targetDir, ?string $fileName = null, bool $ignoreFilters = false): string + { + if (empty($format)) { + throw new \InvalidArgumentException('Format must be specified'); + } + + // Search for the most appropriate archiver + $usableArchiver = null; + foreach ($this->archivers as $archiver) { + if ($archiver->supports($format, $package->getSourceType())) { + $usableArchiver = $archiver; + break; + } + } + + // Checks the format/source type are supported before downloading the package + if (null === $usableArchiver) { + throw new \RuntimeException(sprintf('No archiver found to support %s format', $format)); + } + + $filesystem = new Filesystem(); + + if ($package instanceof RootPackageInterface) { + $sourcePath = realpath('.'); + } else { + // Directory used to download the sources + $sourcePath = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)); + $filesystem->ensureDirectoryExists($sourcePath); + + try { + // Download sources + $promise = $this->downloadManager->download($package, $sourcePath); + SyncHelper::await($this->loop, $promise); + $promise = $this->downloadManager->install($package, $sourcePath); + SyncHelper::await($this->loop, $promise); + } catch (\Exception $e) { + $filesystem->removeDirectory($sourcePath); + throw $e; + } + + // Check exclude from downloaded composer.json + if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) { + $jsonFile = new JsonFile($composerJsonPath); + $jsonData = $jsonFile->read(); + if (!empty($jsonData['archive']['name'])) { + $package->setArchiveName($jsonData['archive']['name']); + } + if (!empty($jsonData['archive']['exclude'])) { + $package->setArchiveExcludes($jsonData['archive']['exclude']); + } + } + } + + $supportedFormats = $this->getSupportedFormats(); + $packageNameParts = null === $fileName ? + $this->getPackageFilenameParts($package) + : ['base' => $fileName]; + + $packageName = $this->getPackageFilenameFromParts($packageNameParts); + $excludePatterns = $this->buildExcludePatterns($packageNameParts, $supportedFormats); + + // Archive filename + $filesystem->ensureDirectoryExists($targetDir); + $target = realpath($targetDir).'/'.$packageName.'.'.$format; + $filesystem->ensureDirectoryExists(dirname($target)); + + if (!$this->overwriteFiles && file_exists($target)) { + return $target; + } + + // Create the archive + $tempTarget = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)).'.'.$format; + $filesystem->ensureDirectoryExists(dirname($tempTarget)); + + $archivePath = $usableArchiver->archive( + $sourcePath, + $tempTarget, + $format, + array_merge($excludePatterns, $package->getArchiveExcludes()), + $ignoreFilters + ); + $filesystem->rename($archivePath, $target); + + // cleanup temporary download + if (!$package instanceof RootPackageInterface) { + $filesystem->removeDirectory($sourcePath); + } + $filesystem->remove($tempTarget); + + return $target; + } + + /** + * @param string[] $parts + * @param string[] $formats + * + * @return string[] + */ + private function buildExcludePatterns(array $parts, array $formats): array + { + $base = $parts['base']; + if (count($parts) > 1) { + $base .= '-*'; + } + + $patterns = []; + foreach ($formats as $format) { + $patterns[] = "$base.$format"; + } + + return $patterns; + } + + /** + * @return string[] + */ + private function getSupportedFormats(): array + { + // The problem is that the \Composer\Package\Archiver\ArchiverInterface + // doesn't provide method to get the supported formats. + // Supported formats are also hard-coded into the description of the + // --format option. + // See \Composer\Command\ArchiveCommand::configure(). + $formats = []; + foreach ($this->archivers as $archiver) { + $items = []; + switch (get_class($archiver)) { + case ZipArchiver::class: + $items = ['zip']; + break; + + case PharArchiver::class: + $items = ['zip', 'tar', 'tar.gz', 'tar.bz2']; + break; + } + + $formats = array_merge($formats, $items); + } + + return array_unique($formats); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php b/vendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php new file mode 100644 index 000000000..7ebc792d1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php @@ -0,0 +1,44 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +/** + * @author Till Klampaeckel + * @author Matthieu Moquet + * @author Nils Adermann + */ +interface ArchiverInterface +{ + /** + * Create an archive from the sources. + * + * @param string $sources The sources directory + * @param string $target The target file + * @param string $format The format used for archive + * @param string[] $excludes A list of patterns for files to exclude + * @param bool $ignoreFilters Whether to ignore filters when looking for files + * + * @return string The path to the written archive file + */ + public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string; + + /** + * Format supported by the archiver. + * + * @param string $format The archive format + * @param ?string $sourceType The source type (git, svn, hg, etc.) + * + * @return bool true if the format is supported by the archiver + */ + public function supports(string $format, ?string $sourceType): bool; +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php b/vendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php new file mode 100644 index 000000000..e2af2b2d3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php @@ -0,0 +1,152 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Pcre\Preg; +use Symfony\Component\Finder; + +/** + * @author Nils Adermann + */ +abstract class BaseExcludeFilter +{ + /** + * @var string + */ + protected $sourcePath; + + /** + * @var array array of [$pattern, $negate, $stripLeadingSlash] arrays + */ + protected $excludePatterns; + + /** + * @param string $sourcePath Directory containing sources to be filtered + */ + public function __construct(string $sourcePath) + { + $this->sourcePath = $sourcePath; + $this->excludePatterns = []; + } + + /** + * Checks the given path against all exclude patterns in this filter + * + * Negated patterns overwrite exclude decisions of previous filters. + * + * @param string $relativePath The file's path relative to the sourcePath + * @param bool $exclude Whether a previous filter wants to exclude this file + * + * @return bool Whether the file should be excluded + */ + public function filter(string $relativePath, bool $exclude): bool + { + foreach ($this->excludePatterns as $patternData) { + [$pattern, $negate, $stripLeadingSlash] = $patternData; + + if ($stripLeadingSlash) { + $path = substr($relativePath, 1); + } else { + $path = $relativePath; + } + + try { + if (Preg::isMatch($pattern, $path)) { + $exclude = !$negate; + } + } catch (\RuntimeException $e) { + // suppressed + } + } + + return $exclude; + } + + /** + * Processes a file containing exclude rules of different formats per line + * + * @param string[] $lines A set of lines to be parsed + * @param callable $lineParser The parser to be used on each line + * + * @return array Exclude patterns to be used in filter() + */ + protected function parseLines(array $lines, callable $lineParser): array + { + return array_filter( + array_map( + static function ($line) use ($lineParser) { + $line = trim($line); + + if (!$line || 0 === strpos($line, '#')) { + return null; + } + + return $lineParser($line); + }, + $lines + ), + static function ($pattern): bool { + return $pattern !== null; + } + ); + } + + /** + * Generates a set of exclude patterns for filter() from gitignore rules + * + * @param string[] $rules A list of exclude rules in gitignore syntax + * + * @return array Exclude patterns + */ + protected function generatePatterns(array $rules): array + { + $patterns = []; + foreach ($rules as $rule) { + $patterns[] = $this->generatePattern($rule); + } + + return $patterns; + } + + /** + * Generates an exclude pattern for filter() from a gitignore rule + * + * @param string $rule An exclude rule in gitignore syntax + * + * @return array{0: non-empty-string, 1: bool, 2: bool} An exclude pattern + */ + protected function generatePattern(string $rule): array + { + $negate = false; + $pattern = ''; + + if ($rule !== '' && $rule[0] === '!') { + $negate = true; + $rule = ltrim($rule, '!'); + } + + $firstSlashPosition = strpos($rule, '/'); + if (0 === $firstSlashPosition) { + $pattern = '^/'; + } elseif (false === $firstSlashPosition || strlen($rule) - 1 === $firstSlashPosition) { + $pattern = '/'; + } + + $rule = trim($rule, '/'); + + // remove delimiters as well as caret (^) and dollar sign ($) from the regex + $rule = substr(Finder\Glob::toRegex($rule), 2, -2); + + return ['{'.$pattern.$rule.'(?=$|/)}', $negate, false]; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php b/vendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php new file mode 100644 index 000000000..9806b77c2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php @@ -0,0 +1,31 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +/** + * An exclude filter which processes composer's own exclude rules + * + * @author Nils Adermann + */ +class ComposerExcludeFilter extends BaseExcludeFilter +{ + /** + * @param string $sourcePath Directory containing sources to be filtered + * @param string[] $excludeRules An array of exclude rules from composer.json + */ + public function __construct(string $sourcePath, array $excludeRules) + { + parent::__construct($sourcePath); + $this->excludePatterns = $this->generatePatterns($excludeRules); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php b/vendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php new file mode 100644 index 000000000..917f9fced --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php @@ -0,0 +1,65 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Pcre\Preg; + +/** + * An exclude filter that processes gitattributes + * + * It respects export-ignore git attributes + * + * @author Nils Adermann + */ +class GitExcludeFilter extends BaseExcludeFilter +{ + /** + * Parses .gitattributes if it exists + */ + public function __construct(string $sourcePath) + { + parent::__construct($sourcePath); + + if (file_exists($sourcePath.'/.gitattributes')) { + $this->excludePatterns = array_merge( + $this->excludePatterns, + $this->parseLines( + file($sourcePath.'/.gitattributes'), + [$this, 'parseGitAttributesLine'] + ) + ); + } + } + + /** + * Callback parser which finds export-ignore rules in git attribute lines + * + * @param string $line A line from .gitattributes + * + * @return array{0: string, 1: bool, 2: bool}|null An exclude pattern for filter() + */ + public function parseGitAttributesLine(string $line): ?array + { + $parts = Preg::split('#\s+#', $line); + + if (count($parts) === 2 && $parts[1] === 'export-ignore') { + return $this->generatePattern($parts[0]); + } + + if (count($parts) === 2 && $parts[1] === '-export-ignore') { + return $this->generatePattern('!'.$parts[0]); + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php b/vendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php new file mode 100644 index 000000000..861c3c71b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php @@ -0,0 +1,142 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use PharData; + +/** + * @author Till Klampaeckel + * @author Nils Adermann + * @author Matthieu Moquet + */ +class PharArchiver implements ArchiverInterface +{ + /** @var array */ + protected static $formats = [ + 'zip' => \Phar::ZIP, + 'tar' => \Phar::TAR, + 'tar.gz' => \Phar::TAR, + 'tar.bz2' => \Phar::TAR, + ]; + + /** @var array */ + protected static $compressFormats = [ + 'tar.gz' => \Phar::GZ, + 'tar.bz2' => \Phar::BZ2, + ]; + + /** + * @inheritDoc + */ + public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string + { + $sources = realpath($sources); + + // Phar would otherwise load the file which we don't want + if (file_exists($target)) { + unlink($target); + } + + try { + $filename = substr($target, 0, strrpos($target, $format) - 1); + + // Check if compress format + if (isset(static::$compressFormats[$format])) { + // Current compress format supported base on tar + $target = $filename . '.tar'; + } + + $phar = new PharData( + $target, + \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO, + '', + static::$formats[$format] + ); + $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters); + $filesOnly = new ArchivableFilesFilter($files); + $phar->buildFromIterator($filesOnly, $sources); + $filesOnly->addEmptyDir($phar, $sources); + + if (!file_exists($target)) { + $target = $filename . '.' . $format; + unset($phar); + + if ($format === 'tar') { + // create an empty tar file (=10240 null bytes) if the tar file is empty and PharData thus did not write it to disk + file_put_contents($target, str_repeat("\0", 10240)); + } elseif ($format === 'zip') { + // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record) + $eocd = pack( + 'VvvvvVVv', + 0x06054b50, // End of central directory signature + 0, // Number of this disk + 0, // Disk where central directory starts + 0, // Number of central directory records on this disk + 0, // Total number of central directory records + 0, // Size of central directory (bytes) + 0, // Offset of start of central directory + 0 // Comment length + ); + + file_put_contents($target, $eocd); + } elseif ($format === 'tar.gz' || $format === 'tar.bz2') { + if (!PharData::canCompress(static::$compressFormats[$format])) { + throw new \RuntimeException(sprintf('Can not compress to %s format', $format)); + } + if ($format === 'tar.gz' && function_exists('gzcompress')) { + file_put_contents($target, gzcompress(str_repeat("\0", 10240))); + } elseif ($format === 'tar.bz2' && function_exists('bzcompress')) { + file_put_contents($target, bzcompress(str_repeat("\0", 10240))); + } + } + + return $target; + } + + if (isset(static::$compressFormats[$format])) { + // Check can be compressed? + if (!PharData::canCompress(static::$compressFormats[$format])) { + throw new \RuntimeException(sprintf('Can not compress to %s format', $format)); + } + + // Delete old tar + unlink($target); + + // Compress the new tar + $phar->compress(static::$compressFormats[$format]); + + // Make the correct filename + $target = $filename . '.' . $format; + } + + return $target; + } catch (\UnexpectedValueException $e) { + $message = sprintf( + "Could not create archive '%s' from '%s': %s", + $target, + $sources, + $e->getMessage() + ); + + throw new \RuntimeException($message, $e->getCode(), $e); + } + } + + /** + * @inheritDoc + */ + public function supports(string $format, ?string $sourceType): bool + { + return isset(static::$formats[$format]); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php b/vendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php new file mode 100644 index 000000000..bc6829af9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php @@ -0,0 +1,114 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use ZipArchive; + +/** + * @author Jan Prieser + */ +class ZipArchiver implements ArchiverInterface +{ + /** @var array */ + protected static $formats = [ + 'zip' => true, + ]; + + /** + * @inheritDoc + */ + public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string + { + $fs = new Filesystem(); + $sourcesRealpath = realpath($sources); + if (false !== $sourcesRealpath) { + $sources = $sourcesRealpath; + } + unset($sourcesRealpath); + $sources = $fs->normalizePath($sources); + + $zip = new ZipArchive(); + $res = $zip->open($target, ZipArchive::CREATE); + if ($res === true) { + $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters); + foreach ($files as $file) { + /** @var \Symfony\Component\Finder\SplFileInfo $file */ + $filepath = $file->getPathname(); + $relativePath = $file->getRelativePathname(); + + if (Platform::isWindows()) { + $relativePath = strtr($relativePath, '\\', '/'); + } + + if ($file->isDir()) { + $zip->addEmptyDir($relativePath); + } else { + $zip->addFile($filepath, $relativePath); + } + + /** + * setExternalAttributesName() is only available with libzip 0.11.2 or above + */ + if (method_exists($zip, 'setExternalAttributesName')) { + $perms = fileperms($filepath); + + /** + * Ensure to preserve the permission umasks for the filepath in the archive. + */ + $zip->setExternalAttributesName($relativePath, ZipArchive::OPSYS_UNIX, $perms << 16); + } + } + if ($zip->close()) { + if (!file_exists($target)) { + // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record) + $eocd = pack( + 'VvvvvVVv', + 0x06054b50, // End of central directory signature + 0, // Number of this disk + 0, // Disk where central directory starts + 0, // Number of central directory records on this disk + 0, // Total number of central directory records + 0, // Size of central directory (bytes) + 0, // Offset of start of central directory + 0 // Comment length + ); + file_put_contents($target, $eocd); + } + + return $target; + } + } + $message = sprintf( + "Could not create archive '%s' from '%s': %s", + $target, + $sources, + $zip->getStatusString() + ); + throw new \RuntimeException($message); + } + + /** + * @inheritDoc + */ + public function supports(string $format, ?string $sourceType): bool + { + return isset(static::$formats[$format]) && $this->compressionAvailable(); + } + + private function compressionAvailable(): bool + { + return class_exists('ZipArchive'); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/BasePackage.php b/vendor/composer/composer/src/Composer/Package/BasePackage.php new file mode 100644 index 000000000..ecafd2f65 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/BasePackage.php @@ -0,0 +1,293 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Repository\RepositoryInterface; +use Composer\Repository\PlatformRepository; + +/** + * Base class for packages providing name storage and default match implementation + * + * @author Nils Adermann + */ +abstract class BasePackage implements PackageInterface +{ + /** + * @phpstan-var array + * @internal + */ + public static $supportedLinkTypes = [ + 'require' => ['description' => 'requires', 'method' => Link::TYPE_REQUIRE], + 'conflict' => ['description' => 'conflicts', 'method' => Link::TYPE_CONFLICT], + 'provide' => ['description' => 'provides', 'method' => Link::TYPE_PROVIDE], + 'replace' => ['description' => 'replaces', 'method' => Link::TYPE_REPLACE], + 'require-dev' => ['description' => 'requires (for development)', 'method' => Link::TYPE_DEV_REQUIRE], + ]; + + public const STABILITY_STABLE = 0; + public const STABILITY_RC = 5; + public const STABILITY_BETA = 10; + public const STABILITY_ALPHA = 15; + public const STABILITY_DEV = 20; + + public const STABILITIES = [ + 'stable' => self::STABILITY_STABLE, + 'RC' => self::STABILITY_RC, + 'beta' => self::STABILITY_BETA, + 'alpha' => self::STABILITY_ALPHA, + 'dev' => self::STABILITY_DEV, + ]; + + /** + * @deprecated + * @readonly + * @var array, self::STABILITY_*> + * @phpstan-ignore property.readOnlyByPhpDocDefaultValue + */ + public static $stabilities = self::STABILITIES; + + /** + * READ-ONLY: The package id, public for fast access in dependency solver + * @var int + * @internal + */ + public $id; + /** @var string */ + protected $name; + /** @var string */ + protected $prettyName; + /** @var ?RepositoryInterface */ + protected $repository = null; + + /** + * All descendants' constructors should call this parent constructor + * + * @param string $name The package's name + */ + public function __construct(string $name) + { + $this->prettyName = $name; + $this->name = strtolower($name); + $this->id = -1; + } + + /** + * @inheritDoc + */ + public function getName(): string + { + return $this->name; + } + + /** + * @inheritDoc + */ + public function getPrettyName(): string + { + return $this->prettyName; + } + + /** + * @inheritDoc + */ + public function getNames($provides = true): array + { + $names = [ + $this->getName() => true, + ]; + + if ($provides) { + foreach ($this->getProvides() as $link) { + $names[$link->getTarget()] = true; + } + } + + foreach ($this->getReplaces() as $link) { + $names[$link->getTarget()] = true; + } + + return array_keys($names); + } + + /** + * @inheritDoc + */ + public function setId(int $id): void + { + $this->id = $id; + } + + /** + * @inheritDoc + */ + public function getId(): int + { + return $this->id; + } + + /** + * @inheritDoc + */ + public function setRepository(RepositoryInterface $repository): void + { + if ($this->repository && $repository !== $this->repository) { + throw new \LogicException(sprintf( + 'Package "%s" cannot be added to repository "%s" as it is already in repository "%s".', + $this->getPrettyName(), + $repository->getRepoName(), + $this->repository->getRepoName() + )); + } + $this->repository = $repository; + } + + /** + * @inheritDoc + */ + public function getRepository(): ?RepositoryInterface + { + return $this->repository; + } + + /** + * checks if this package is a platform package + */ + public function isPlatform(): bool + { + return $this->getRepository() instanceof PlatformRepository; + } + + /** + * Returns package unique name, constructed from name, version and release type. + */ + public function getUniqueName(): string + { + return $this->getName().'-'.$this->getVersion(); + } + + public function equals(PackageInterface $package): bool + { + $self = $this; + if ($this instanceof AliasPackage) { + $self = $this->getAliasOf(); + } + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + return $package === $self; + } + + /** + * Converts the package into a readable and unique string + */ + public function __toString(): string + { + return $this->getUniqueName(); + } + + public function getPrettyString(): string + { + return $this->getPrettyName().' '.$this->getPrettyVersion(); + } + + /** + * @inheritDoc + */ + public function getFullPrettyVersion(bool $truncate = true, int $displayMode = PackageInterface::DISPLAY_SOURCE_REF_IF_DEV): string + { + if ( + $displayMode === PackageInterface::DISPLAY_SOURCE_REF_IF_DEV + && ( + !$this->isDev() + || ( + !\in_array($this->getSourceType(), ['hg', 'git']) + && ((string) $this->getSourceType() !== '' || (string) $this->getDistReference() === '') + ) + ) + ) { + return $this->getPrettyVersion(); + } + + switch ($displayMode) { + case PackageInterface::DISPLAY_SOURCE_REF_IF_DEV: + $reference = (string) $this->getSourceReference() !== '' ? $this->getSourceReference() : $this->getDistReference(); + break; + case PackageInterface::DISPLAY_SOURCE_REF: + $reference = $this->getSourceReference(); + break; + case PackageInterface::DISPLAY_DIST_REF: + $reference = $this->getDistReference(); + break; + default: + throw new \UnexpectedValueException('Display mode '.$displayMode.' is not supported'); + } + + if (null === $reference) { + return $this->getPrettyVersion(); + } + + // if source reference is a sha1 hash -- truncate + if ($truncate && \strlen($reference) === 40 && $this->getSourceType() !== 'svn') { + return $this->getPrettyVersion() . ' ' . substr($reference, 0, 7); + } + + return $this->getPrettyVersion() . ' ' . $reference; + } + + /** + * @phpstan-return self::STABILITY_* + */ + public function getStabilityPriority(): int + { + return self::STABILITIES[$this->getStability()]; + } + + public function __clone() + { + $this->repository = null; + $this->id = -1; + } + + /** + * Build a regexp from a package name, expanding * globs as required + * + * @param non-empty-string $wrap Wrap the cleaned string by the given string + * @return non-empty-string + */ + public static function packageNameToRegexp(string $allowPattern, string $wrap = '{^%s$}i'): string + { + $cleanedAllowPattern = str_replace('\\*', '.*', preg_quote($allowPattern)); + + return sprintf($wrap, $cleanedAllowPattern); + } + + /** + * Build a regexp from package names, expanding * globs as required + * + * @param string[] $packageNames + * @param non-empty-string $wrap + * @return non-empty-string + */ + public static function packageNamesToRegexp(array $packageNames, string $wrap = '{^(?:%s)$}iD'): string + { + $packageNames = array_map( + static function ($packageName): string { + return BasePackage::packageNameToRegexp($packageName, '%s'); + }, + $packageNames + ); + + return sprintf($wrap, implode('|', $packageNames)); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Comparer/Comparer.php b/vendor/composer/composer/src/Composer/Package/Comparer/Comparer.php new file mode 100644 index 000000000..70a7a28f8 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Comparer/Comparer.php @@ -0,0 +1,152 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Comparer; + +use Composer\Util\Platform; + +/** + * class Comparer + * + * @author Hector Prats + */ +class Comparer +{ + /** @var string Source directory */ + private $source; + /** @var string Target directory */ + private $update; + /** @var array{changed?: string[], removed?: string[], added?: string[]} */ + private $changed; + + public function setSource(string $source): void + { + $this->source = $source; + } + + public function setUpdate(string $update): void + { + $this->update = $update; + } + + /** + * @return array{changed?: string[], removed?: string[], added?: string[]}|false false if no change + */ + public function getChanged(bool $explicated = false) + { + $changed = $this->changed; + if (!count($changed)) { + return false; + } + if ($explicated) { + foreach ($changed as $sectionKey => $itemSection) { + foreach ($itemSection as $itemKey => $item) { + $changed[$sectionKey][$itemKey] = $item.' ('.$sectionKey.')'; + } + } + } + + return $changed; + } + + /** + * @return string empty string if no changes + */ + public function getChangedAsString(bool $toString = false, bool $explicated = false): string + { + $changed = $this->getChanged($explicated); + if (false === $changed) { + return ''; + } + + $strings = []; + foreach ($changed as $sectionKey => $itemSection) { + foreach ($itemSection as $itemKey => $item) { + $strings[] = $item."\r\n"; + } + } + + return trim(implode("\r\n", $strings)); + } + + public function doCompare(): void + { + $source = []; + $destination = []; + $this->changed = []; + $currentDirectory = Platform::getCwd(); + chdir($this->source); + $source = $this->doTree('.', $source); + if (!is_array($source)) { + return; + } + chdir($currentDirectory); + chdir($this->update); + $destination = $this->doTree('.', $destination); + if (!is_array($destination)) { + exit; + } + chdir($currentDirectory); + foreach ($source as $dir => $value) { + foreach ($value as $file => $hash) { + if (isset($destination[$dir][$file])) { + if ($hash !== $destination[$dir][$file]) { + $this->changed['changed'][] = $dir.'/'.$file; + } + } else { + $this->changed['removed'][] = $dir.'/'.$file; + } + } + } + foreach ($destination as $dir => $value) { + foreach ($value as $file => $hash) { + if (!isset($source[$dir][$file])) { + $this->changed['added'][] = $dir.'/'.$file; + } + } + } + } + + /** + * @param mixed[] $array + * + * @return array>|false + */ + private function doTree(string $dir, array &$array) + { + if ($dh = opendir($dir)) { + while ($file = readdir($dh)) { + if ($file !== '.' && $file !== '..') { + if (is_link($dir.'/'.$file)) { + $array[$dir][$file] = readlink($dir.'/'.$file); + } elseif (is_dir($dir.'/'.$file)) { + if (!count($array)) { + $array[0] = 'Temp'; + } + if (!$this->doTree($dir.'/'.$file, $array)) { + return false; + } + } elseif (is_file($dir.'/'.$file) && filesize($dir.'/'.$file)) { + $array[$dir][$file] = hash_file(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', $dir.'/'.$file); + } + } + } + if (count($array) > 1 && isset($array['0'])) { + unset($array['0']); + } + + return $array; + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/CompleteAliasPackage.php b/vendor/composer/composer/src/Composer/Package/CompleteAliasPackage.php new file mode 100644 index 000000000..78106fa3c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/CompleteAliasPackage.php @@ -0,0 +1,167 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * @author Jordi Boggiano + */ +class CompleteAliasPackage extends AliasPackage implements CompletePackageInterface +{ + /** @var CompletePackage */ + protected $aliasOf; + + /** + * All descendants' constructors should call this parent constructor + * + * @param CompletePackage $aliasOf The package this package is an alias of + * @param string $version The version the alias must report + * @param string $prettyVersion The alias's non-normalized version + */ + public function __construct(CompletePackage $aliasOf, string $version, string $prettyVersion) + { + parent::__construct($aliasOf, $version, $prettyVersion); + } + + /** + * @return CompletePackage + */ + public function getAliasOf() + { + return $this->aliasOf; + } + + public function getScripts(): array + { + return $this->aliasOf->getScripts(); + } + + public function setScripts(array $scripts): void + { + $this->aliasOf->setScripts($scripts); + } + + public function getRepositories(): array + { + return $this->aliasOf->getRepositories(); + } + + public function setRepositories(array $repositories): void + { + $this->aliasOf->setRepositories($repositories); + } + + public function getLicense(): array + { + return $this->aliasOf->getLicense(); + } + + public function setLicense(array $license): void + { + $this->aliasOf->setLicense($license); + } + + public function getKeywords(): array + { + return $this->aliasOf->getKeywords(); + } + + public function setKeywords(array $keywords): void + { + $this->aliasOf->setKeywords($keywords); + } + + public function getDescription(): ?string + { + return $this->aliasOf->getDescription(); + } + + public function setDescription(?string $description): void + { + $this->aliasOf->setDescription($description); + } + + public function getHomepage(): ?string + { + return $this->aliasOf->getHomepage(); + } + + public function setHomepage(?string $homepage): void + { + $this->aliasOf->setHomepage($homepage); + } + + public function getAuthors(): array + { + return $this->aliasOf->getAuthors(); + } + + public function setAuthors(array $authors): void + { + $this->aliasOf->setAuthors($authors); + } + + public function getSupport(): array + { + return $this->aliasOf->getSupport(); + } + + public function setSupport(array $support): void + { + $this->aliasOf->setSupport($support); + } + + public function getFunding(): array + { + return $this->aliasOf->getFunding(); + } + + public function setFunding(array $funding): void + { + $this->aliasOf->setFunding($funding); + } + + public function isAbandoned(): bool + { + return $this->aliasOf->isAbandoned(); + } + + public function getReplacementPackage(): ?string + { + return $this->aliasOf->getReplacementPackage(); + } + + public function setAbandoned($abandoned): void + { + $this->aliasOf->setAbandoned($abandoned); + } + + public function getArchiveName(): ?string + { + return $this->aliasOf->getArchiveName(); + } + + public function setArchiveName(?string $name): void + { + $this->aliasOf->setArchiveName($name); + } + + public function getArchiveExcludes(): array + { + return $this->aliasOf->getArchiveExcludes(); + } + + public function setArchiveExcludes(array $excludes): void + { + $this->aliasOf->setArchiveExcludes($excludes); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/CompletePackage.php b/vendor/composer/composer/src/Composer/Package/CompletePackage.php new file mode 100644 index 000000000..0d87082d7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/CompletePackage.php @@ -0,0 +1,246 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * Package containing additional metadata that is not used by the solver + * + * @author Nils Adermann + */ +class CompletePackage extends Package implements CompletePackageInterface +{ + /** @var mixed[] */ + protected $repositories = []; + /** @var string[] */ + protected $license = []; + /** @var string[] */ + protected $keywords = []; + /** @var array */ + protected $authors = []; + /** @var ?string */ + protected $description = null; + /** @var ?string */ + protected $homepage = null; + /** @var array Map of script name to array of handlers */ + protected $scripts = []; + /** @var array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} */ + protected $support = []; + /** @var array */ + protected $funding = []; + /** @var bool|string */ + protected $abandoned = false; + /** @var ?string */ + protected $archiveName = null; + /** @var string[] */ + protected $archiveExcludes = []; + + /** + * @inheritDoc + */ + public function setScripts(array $scripts): void + { + $this->scripts = $scripts; + } + + /** + * @inheritDoc + */ + public function getScripts(): array + { + return $this->scripts; + } + + /** + * @inheritDoc + */ + public function setRepositories(array $repositories): void + { + $this->repositories = $repositories; + } + + /** + * @inheritDoc + */ + public function getRepositories(): array + { + return $this->repositories; + } + + /** + * @inheritDoc + */ + public function setLicense(array $license): void + { + $this->license = $license; + } + + /** + * @inheritDoc + */ + public function getLicense(): array + { + return $this->license; + } + + /** + * @inheritDoc + */ + public function setKeywords(array $keywords): void + { + $this->keywords = $keywords; + } + + /** + * @inheritDoc + */ + public function getKeywords(): array + { + return $this->keywords; + } + + /** + * @inheritDoc + */ + public function setAuthors(array $authors): void + { + $this->authors = $authors; + } + + /** + * @inheritDoc + */ + public function getAuthors(): array + { + return $this->authors; + } + + /** + * @inheritDoc + */ + public function setDescription(?string $description): void + { + $this->description = $description; + } + + /** + * @inheritDoc + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @inheritDoc + */ + public function setHomepage(?string $homepage): void + { + $this->homepage = $homepage; + } + + /** + * @inheritDoc + */ + public function getHomepage(): ?string + { + return $this->homepage; + } + + /** + * @inheritDoc + */ + public function setSupport(array $support): void + { + $this->support = $support; + } + + /** + * @inheritDoc + */ + public function getSupport(): array + { + return $this->support; + } + + /** + * @inheritDoc + */ + public function setFunding(array $funding): void + { + $this->funding = $funding; + } + + /** + * @inheritDoc + */ + public function getFunding(): array + { + return $this->funding; + } + + /** + * @inheritDoc + */ + public function isAbandoned(): bool + { + return (bool) $this->abandoned; + } + + /** + * @inheritDoc + */ + public function setAbandoned($abandoned): void + { + $this->abandoned = $abandoned; + } + + /** + * @inheritDoc + */ + public function getReplacementPackage(): ?string + { + return \is_string($this->abandoned) ? $this->abandoned : null; + } + + /** + * @inheritDoc + */ + public function setArchiveName(?string $name): void + { + $this->archiveName = $name; + } + + /** + * @inheritDoc + */ + public function getArchiveName(): ?string + { + return $this->archiveName; + } + + /** + * @inheritDoc + */ + public function setArchiveExcludes(array $excludes): void + { + $this->archiveExcludes = $excludes; + } + + /** + * @inheritDoc + */ + public function getArchiveExcludes(): array + { + return $this->archiveExcludes; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/CompletePackageInterface.php b/vendor/composer/composer/src/Composer/Package/CompletePackageInterface.php new file mode 100644 index 000000000..eba31cfaf --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/CompletePackageInterface.php @@ -0,0 +1,182 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * Defines package metadata that is not necessarily needed for solving and installing packages + * + * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice. + * + * @author Nils Adermann + */ +interface CompletePackageInterface extends PackageInterface +{ + /** + * Returns the scripts of this package + * + * @return array Map of script name to array of handlers + */ + public function getScripts(): array; + + /** + * @param array $scripts + */ + public function setScripts(array $scripts): void; + + /** + * Returns an array of repositories + * + * @return mixed[] Repositories + */ + public function getRepositories(): array; + + /** + * Set the repositories + * + * @param mixed[] $repositories + */ + public function setRepositories(array $repositories): void; + + /** + * Returns the package license, e.g. MIT, BSD, GPL + * + * @return string[] The package licenses + */ + public function getLicense(): array; + + /** + * Set the license + * + * @param string[] $license + */ + public function setLicense(array $license): void; + + /** + * Returns an array of keywords relating to the package + * + * @return string[] + */ + public function getKeywords(): array; + + /** + * Set the keywords + * + * @param string[] $keywords + */ + public function setKeywords(array $keywords): void; + + /** + * Returns the package description + */ + public function getDescription(): ?string; + + /** + * Set the description + */ + public function setDescription(string $description): void; + + /** + * Returns the package homepage + */ + public function getHomepage(): ?string; + + /** + * Set the homepage + */ + public function setHomepage(string $homepage): void; + + /** + * Returns an array of authors of the package + * + * Each item can contain name/homepage/email keys + * + * @return array + */ + public function getAuthors(): array; + + /** + * Set the authors + * + * @param array $authors + */ + public function setAuthors(array $authors): void; + + /** + * Returns the support information + * + * @return array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} + */ + public function getSupport(): array; + + /** + * Set the support information + * + * @param array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} $support + */ + public function setSupport(array $support): void; + + /** + * Returns an array of funding options for the package + * + * Each item will contain type and url keys + * + * @return array + */ + public function getFunding(): array; + + /** + * Set the funding + * + * @param array $funding + */ + public function setFunding(array $funding): void; + + /** + * Returns if the package is abandoned or not + */ + public function isAbandoned(): bool; + + /** + * If the package is abandoned and has a suggested replacement, this method returns it + */ + public function getReplacementPackage(): ?string; + + /** + * @param bool|string $abandoned + */ + public function setAbandoned($abandoned): void; + + /** + * Returns default base filename for archive + */ + public function getArchiveName(): ?string; + + /** + * Sets default base filename for archive + */ + public function setArchiveName(string $name): void; + + /** + * Returns a list of patterns to exclude from package archives + * + * @return string[] + */ + public function getArchiveExcludes(): array; + + /** + * Sets a list of patterns to be excluded from archives + * + * @param string[] $excludes + */ + public function setArchiveExcludes(array $excludes): void; +} diff --git a/vendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php b/vendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php new file mode 100644 index 000000000..9333bd9a0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php @@ -0,0 +1,172 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Dumper; + +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Package\CompletePackageInterface; +use Composer\Package\RootPackageInterface; + +/** + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + */ +class ArrayDumper +{ + /** + * @return array + */ + public function dump(PackageInterface $package): array + { + $keys = [ + 'binaries' => 'bin', + 'type', + 'extra', + 'installationSource' => 'installation-source', + 'autoload', + 'devAutoload' => 'autoload-dev', + 'notificationUrl' => 'notification-url', + 'includePaths' => 'include-path', + 'phpExt' => 'php-ext', + ]; + + $data = []; + $data['name'] = $package->getPrettyName(); + $data['version'] = $package->getPrettyVersion(); + $data['version_normalized'] = $package->getVersion(); + + if ($package->getTargetDir() !== null) { + $data['target-dir'] = $package->getTargetDir(); + } + + if ($package->getSourceType() !== null) { + $data['source']['type'] = $package->getSourceType(); + $data['source']['url'] = $package->getSourceUrl(); + if (null !== ($value = $package->getSourceReference())) { + $data['source']['reference'] = $value; + } + if ($mirrors = $package->getSourceMirrors()) { + $data['source']['mirrors'] = $mirrors; + } + } + + if ($package->getDistType() !== null) { + $data['dist']['type'] = $package->getDistType(); + $data['dist']['url'] = $package->getDistUrl(); + if (null !== ($value = $package->getDistReference())) { + $data['dist']['reference'] = $value; + } + if (null !== ($value = $package->getDistSha1Checksum())) { + $data['dist']['shasum'] = $value; + } + if ($mirrors = $package->getDistMirrors()) { + $data['dist']['mirrors'] = $mirrors; + } + } + + foreach (BasePackage::$supportedLinkTypes as $type => $opts) { + $links = $package->{'get'.ucfirst($opts['method'])}(); + if (\count($links) === 0) { + continue; + } + foreach ($links as $link) { + $data[$type][$link->getTarget()] = $link->getPrettyConstraint(); + } + ksort($data[$type]); + } + + $packages = $package->getSuggests(); + if (\count($packages) > 0) { + ksort($packages); + $data['suggest'] = $packages; + } + + if ($package->getReleaseDate() instanceof \DateTimeInterface) { + $data['time'] = $package->getReleaseDate()->format(DATE_RFC3339); + } + + if ($package->isDefaultBranch()) { + $data['default-branch'] = true; + } + + $data = $this->dumpValues($package, $keys, $data); + + if ($package instanceof CompletePackageInterface) { + if ($package->getArchiveName()) { + $data['archive']['name'] = $package->getArchiveName(); + } + if ($package->getArchiveExcludes()) { + $data['archive']['exclude'] = $package->getArchiveExcludes(); + } + + $keys = [ + 'scripts', + 'license', + 'authors', + 'description', + 'homepage', + 'keywords', + 'repositories', + 'support', + 'funding', + ]; + + $data = $this->dumpValues($package, $keys, $data); + + if (isset($data['keywords']) && \is_array($data['keywords'])) { + sort($data['keywords']); + } + + if ($package->isAbandoned()) { + $data['abandoned'] = $package->getReplacementPackage() ?: true; + } + } + + if ($package instanceof RootPackageInterface) { + $minimumStability = $package->getMinimumStability(); + if ($minimumStability !== '') { + $data['minimum-stability'] = $minimumStability; + } + } + + if (\count($package->getTransportOptions()) > 0) { + $data['transport-options'] = $package->getTransportOptions(); + } + + return $data; + } + + /** + * @param array $keys + * @param array $data + * + * @return array + */ + private function dumpValues(PackageInterface $package, array $keys, array $data): array + { + foreach ($keys as $method => $key) { + if (is_numeric($method)) { + $method = $key; + } + + $getter = 'get'.ucfirst($method); + $value = $package->{$getter}(); + + if (null !== $value && !(\is_array($value) && 0 === \count($value))) { + $data[$key] = $value; + } + } + + return $data; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Link.php b/vendor/composer/composer/src/Composer/Package/Link.php new file mode 100644 index 000000000..7b19f83a7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Link.php @@ -0,0 +1,140 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Represents a link between two packages, represented by their names + * + * @author Nils Adermann + */ +class Link +{ + public const TYPE_REQUIRE = 'requires'; + public const TYPE_DEV_REQUIRE = 'devRequires'; + public const TYPE_PROVIDE = 'provides'; + public const TYPE_CONFLICT = 'conflicts'; + public const TYPE_REPLACE = 'replaces'; + + /** + * Special type + * @internal + */ + public const TYPE_DOES_NOT_REQUIRE = 'does not require'; + + private const TYPE_UNKNOWN = 'relates to'; + + /** + * Will be converted into a constant once the min PHP version allows this + * + * @internal + * @var string[] + * @phpstan-var array + */ + public static $TYPES = [ + self::TYPE_REQUIRE, + self::TYPE_DEV_REQUIRE, + self::TYPE_PROVIDE, + self::TYPE_CONFLICT, + self::TYPE_REPLACE, + ]; + + /** + * @var string + */ + protected $source; + + /** + * @var string + */ + protected $target; + + /** + * @var ConstraintInterface + */ + protected $constraint; + + /** + * @var string + * @phpstan-var string $description + */ + protected $description; + + /** + * @var ?string + */ + protected $prettyConstraint; + + /** + * Creates a new package link. + * + * @param ConstraintInterface $constraint Constraint applying to the target of this link + * @param self::TYPE_* $description Used to create a descriptive string representation + */ + public function __construct( + string $source, + string $target, + ConstraintInterface $constraint, + $description = self::TYPE_UNKNOWN, + ?string $prettyConstraint = null + ) { + $this->source = strtolower($source); + $this->target = strtolower($target); + $this->constraint = $constraint; + $this->description = self::TYPE_DEV_REQUIRE === $description ? 'requires (for development)' : $description; + $this->prettyConstraint = $prettyConstraint; + } + + public function getDescription(): string + { + return $this->description; + } + + public function getSource(): string + { + return $this->source; + } + + public function getTarget(): string + { + return $this->target; + } + + public function getConstraint(): ConstraintInterface + { + return $this->constraint; + } + + /** + * @throws \UnexpectedValueException If no pretty constraint was provided + */ + public function getPrettyConstraint(): string + { + if (null === $this->prettyConstraint) { + throw new \UnexpectedValueException(sprintf('Link %s has been misconfigured and had no prettyConstraint given.', $this)); + } + + return $this->prettyConstraint; + } + + public function __toString(): string + { + return $this->source.' '.$this->description.' '.$this->target.' ('.$this->constraint.')'; + } + + public function getPrettyString(PackageInterface $sourcePackage): string + { + return $sourcePackage->getPrettyString().' '.$this->description.' '.$this->target.' '.$this->constraint->getPrettyString(); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php b/vendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php new file mode 100644 index 000000000..6dfdbf192 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php @@ -0,0 +1,469 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Package\BasePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\RootPackage; +use Composer\Package\PackageInterface; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Link; +use Composer\Package\RootAliasPackage; +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; + +/** + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + */ +class ArrayLoader implements LoaderInterface +{ + /** @var VersionParser */ + protected $versionParser; + /** @var bool */ + protected $loadOptions; + + public function __construct(?VersionParser $parser = null, bool $loadOptions = false) + { + if (!$parser) { + $parser = new VersionParser; + } + $this->versionParser = $parser; + $this->loadOptions = $loadOptions; + } + + /** + * @inheritDoc + */ + public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage + { + if ($class !== 'Composer\Package\CompletePackage' && $class !== 'Composer\Package\RootPackage') { + trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED); + } + + $package = $this->createObject($config, $class); + + foreach (BasePackage::$supportedLinkTypes as $type => $opts) { + if (!isset($config[$type]) || !is_array($config[$type])) { + continue; + } + $method = 'set'.ucfirst($opts['method']); + $package->{$method}( + $this->parseLinks( + $package->getName(), + $package->getPrettyVersion(), + $opts['method'], + $config[$type] + ) + ); + } + + $package = $this->configureObject($package, $config); + + return $package; + } + + /** + * @param array> $versions + * + * @return list + */ + public function loadPackages(array $versions): array + { + $packages = []; + $linkCache = []; + + foreach ($versions as $version) { + $package = $this->createObject($version, 'Composer\Package\CompletePackage'); + + $this->configureCachedLinks($linkCache, $package, $version); + $package = $this->configureObject($package, $version); + + $packages[] = $package; + } + + return $packages; + } + + /** + * @template PackageClass of CompletePackage + * + * @param mixed[] $config package data + * @param string $class FQCN to be instantiated + * + * @return CompletePackage|RootPackage + * + * @phpstan-param class-string $class + */ + private function createObject(array $config, string $class): CompletePackage + { + if (!isset($config['name'])) { + throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').'); + } + if (!isset($config['version']) || !is_scalar($config['version'])) { + throw new \UnexpectedValueException('Package '.$config['name'].' has no version defined.'); + } + if (!is_string($config['version'])) { + $config['version'] = (string) $config['version']; + } + + // handle already normalized versions + if (isset($config['version_normalized']) && is_string($config['version_normalized'])) { + $version = $config['version_normalized']; + + // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it + if ($version === VersionParser::DEFAULT_BRANCH_ALIAS) { + $version = $this->versionParser->normalize($config['version']); + } + } else { + $version = $this->versionParser->normalize($config['version']); + } + + return new $class($config['name'], $version, $config['version']); + } + + /** + * @param CompletePackage $package + * @param mixed[] $config package data + * + * @return RootPackage|RootAliasPackage|CompletePackage|CompleteAliasPackage + */ + private function configureObject(PackageInterface $package, array $config): BasePackage + { + if (!$package instanceof CompletePackage) { + throw new \LogicException('ArrayLoader expects instances of the Composer\Package\CompletePackage class to function correctly'); + } + + $package->setType(isset($config['type']) ? strtolower($config['type']) : 'library'); + + if (isset($config['target-dir'])) { + $package->setTargetDir($config['target-dir']); + } + + if (isset($config['extra']) && \is_array($config['extra'])) { + $package->setExtra($config['extra']); + } + + if (isset($config['bin'])) { + if (!\is_array($config['bin'])) { + $config['bin'] = [$config['bin']]; + } + foreach ($config['bin'] as $key => $bin) { + $config['bin'][$key] = ltrim($bin, '/'); + } + $package->setBinaries($config['bin']); + } + + if (isset($config['installation-source'])) { + $package->setInstallationSource($config['installation-source']); + } + + if (isset($config['default-branch']) && $config['default-branch'] === true) { + $package->setIsDefaultBranch(true); + } + + if (isset($config['source'])) { + if (!isset($config['source']['type'], $config['source']['url'], $config['source']['reference'])) { + throw new \UnexpectedValueException(sprintf( + "Package %s's source key should be specified as {\"type\": ..., \"url\": ..., \"reference\": ...},\n%s given.", + $config['name'], + json_encode($config['source']) + )); + } + $package->setSourceType($config['source']['type']); + $package->setSourceUrl($config['source']['url']); + $package->setSourceReference(isset($config['source']['reference']) ? (string) $config['source']['reference'] : null); + if (isset($config['source']['mirrors'])) { + $package->setSourceMirrors($config['source']['mirrors']); + } + } + + if (isset($config['dist'])) { + if (!isset($config['dist']['type'], $config['dist']['url'])) { + throw new \UnexpectedValueException(sprintf( + "Package %s's dist key should be specified as ". + "{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...},\n%s given.", + $config['name'], + json_encode($config['dist']) + )); + } + $package->setDistType($config['dist']['type']); + $package->setDistUrl($config['dist']['url']); + $package->setDistReference(isset($config['dist']['reference']) ? (string) $config['dist']['reference'] : null); + $package->setDistSha1Checksum($config['dist']['shasum'] ?? null); + if (isset($config['dist']['mirrors'])) { + $package->setDistMirrors($config['dist']['mirrors']); + } + } + + if (isset($config['suggest']) && \is_array($config['suggest'])) { + foreach ($config['suggest'] as $target => $reason) { + if ('self.version' === trim($reason)) { + $config['suggest'][$target] = $package->getPrettyVersion(); + } + } + $package->setSuggests($config['suggest']); + } + + if (isset($config['autoload'])) { + $package->setAutoload($config['autoload']); + } + + if (isset($config['autoload-dev'])) { + $package->setDevAutoload($config['autoload-dev']); + } + + if (isset($config['include-path'])) { + $package->setIncludePaths($config['include-path']); + } + + if (isset($config['php-ext'])) { + $package->setPhpExt($config['php-ext']); + } + + if (!empty($config['time'])) { + $time = Preg::isMatch('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time']; + + try { + $date = new \DateTime($time, new \DateTimeZone('UTC')); + $package->setReleaseDate($date); + } catch (\Exception $e) { + } + } + + if (!empty($config['notification-url'])) { + $package->setNotificationUrl($config['notification-url']); + } + + if ($package instanceof CompletePackageInterface) { + if (!empty($config['archive']['name'])) { + $package->setArchiveName($config['archive']['name']); + } + if (!empty($config['archive']['exclude'])) { + $package->setArchiveExcludes($config['archive']['exclude']); + } + + if (isset($config['scripts']) && \is_array($config['scripts'])) { + foreach ($config['scripts'] as $event => $listeners) { + $config['scripts'][$event] = (array) $listeners; + } + foreach (['composer', 'php', 'putenv'] as $reserved) { + if (isset($config['scripts'][$reserved])) { + trigger_error('The `'.$reserved.'` script name is reserved for internal use, please avoid defining it', E_USER_DEPRECATED); + } + } + $package->setScripts($config['scripts']); + } + + if (!empty($config['description']) && \is_string($config['description'])) { + $package->setDescription($config['description']); + } + + if (!empty($config['homepage']) && \is_string($config['homepage'])) { + $package->setHomepage($config['homepage']); + } + + if (!empty($config['keywords']) && \is_array($config['keywords'])) { + $package->setKeywords(array_map('strval', $config['keywords'])); + } + + if (!empty($config['license'])) { + $package->setLicense(\is_array($config['license']) ? $config['license'] : [$config['license']]); + } + + if (!empty($config['authors']) && \is_array($config['authors'])) { + $package->setAuthors($config['authors']); + } + + if (isset($config['support']) && \is_array($config['support'])) { + $package->setSupport($config['support']); + } + + if (!empty($config['funding']) && \is_array($config['funding'])) { + $package->setFunding($config['funding']); + } + + if (isset($config['abandoned'])) { + $package->setAbandoned($config['abandoned']); + } + } + + if ($this->loadOptions && isset($config['transport-options'])) { + $package->setTransportOptions($config['transport-options']); + } + + if ($aliasNormalized = $this->getBranchAlias($config)) { + $prettyAlias = Preg::replace('{(\.9{7})+}', '.x', $aliasNormalized); + + if ($package instanceof RootPackage) { + return new RootAliasPackage($package, $aliasNormalized, $prettyAlias); + } + + return new CompleteAliasPackage($package, $aliasNormalized, $prettyAlias); + } + + return $package; + } + + /** + * @param array>>> $linkCache + * @param mixed[] $config + */ + private function configureCachedLinks(array &$linkCache, PackageInterface $package, array $config): void + { + $name = $package->getName(); + $prettyVersion = $package->getPrettyVersion(); + + foreach (BasePackage::$supportedLinkTypes as $type => $opts) { + if (isset($config[$type])) { + $method = 'set'.ucfirst($opts['method']); + + $links = []; + foreach ($config[$type] as $prettyTarget => $constraint) { + $target = strtolower($prettyTarget); + + // recursive links are not supported + if ($target === $name) { + continue; + } + + if ($constraint === 'self.version') { + $links[$target] = $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint); + } else { + if (!isset($linkCache[$name][$type][$target][$constraint])) { + $linkCache[$name][$type][$target][$constraint] = [$target, $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint)]; + } + + [$target, $link] = $linkCache[$name][$type][$target][$constraint]; + $links[$target] = $link; + } + } + + $package->{$method}($links); + } + } + } + + /** + * @param string $source source package name + * @param string $sourceVersion source package version (pretty version ideally) + * @param string $description link description (e.g. requires, replaces, ..) + * @param array $links array of package name => constraint mappings + * + * @return Link[] + * + * @phpstan-param Link::TYPE_* $description + */ + public function parseLinks(string $source, string $sourceVersion, string $description, array $links): array + { + $res = []; + foreach ($links as $target => $constraint) { + if (!is_string($constraint)) { + continue; + } + $target = strtolower((string) $target); + $res[$target] = $this->createLink($source, $sourceVersion, $description, $target, $constraint); + } + + return $res; + } + + /** + * @param string $source source package name + * @param string $sourceVersion source package version (pretty version ideally) + * @param Link::TYPE_* $description link description (e.g. requires, replaces, ..) + * @param string $target target package name + * @param string $prettyConstraint constraint string + */ + private function createLink(string $source, string $sourceVersion, string $description, string $target, string $prettyConstraint): Link + { + if (!\is_string($prettyConstraint)) { + throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.\get_debug_type($prettyConstraint) . ' (' . var_export($prettyConstraint, true) . ')'); + } + if ('self.version' === $prettyConstraint) { + $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion); + } else { + $parsedConstraint = $this->versionParser->parseConstraints($prettyConstraint); + } + + return new Link($source, $target, $parsedConstraint, $description, $prettyConstraint); + } + + /** + * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists + * + * @param mixed[] $config the entire package config + * + * @return string|null normalized version of the branch alias or null if there is none + */ + public function getBranchAlias(array $config): ?string + { + if (!isset($config['version']) || !is_scalar($config['version'])) { + throw new \UnexpectedValueException('no/invalid version defined'); + } + if (!is_string($config['version'])) { + $config['version'] = (string) $config['version']; + } + + if (strpos($config['version'], 'dev-') !== 0 && '-dev' !== substr($config['version'], -4)) { + return null; + } + + if (isset($config['extra']['branch-alias']) && \is_array($config['extra']['branch-alias'])) { + foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { + $sourceBranch = (string) $sourceBranch; + + // ensure it is an alias to a -dev package + if ('-dev' !== substr($targetBranch, -4)) { + continue; + } + + // normalize without -dev and ensure it's a numeric branch that is parseable + if ($targetBranch === VersionParser::DEFAULT_BRANCH_ALIAS) { + $validatedTargetBranch = VersionParser::DEFAULT_BRANCH_ALIAS; + } else { + $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4)); + } + if ('-dev' !== substr($validatedTargetBranch, -4)) { + continue; + } + + // ensure that it is the current branch aliasing itself + if (strtolower($config['version']) !== strtolower($sourceBranch)) { + continue; + } + + // If using numeric aliases ensure the alias is a valid subversion + if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) + && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) + && (stripos($targetPrefix, $sourcePrefix) !== 0) + ) { + continue; + } + + return $validatedTargetBranch; + } + } + + if ( + isset($config['default-branch']) + && $config['default-branch'] === true + && false === $this->versionParser->parseNumericAliasPrefix(Preg::replace('{^v}', '', $config['version'])) + ) { + return VersionParser::DEFAULT_BRANCH_ALIAS; + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php b/vendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php new file mode 100644 index 000000000..51f0db895 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php @@ -0,0 +1,63 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +/** + * @author Jordi Boggiano + */ +class InvalidPackageException extends \Exception +{ + /** @var list */ + private $errors; + /** @var list */ + private $warnings; + /** @var mixed[] package config */ + private $data; + + /** + * @param list $errors + * @param list $warnings + * @param mixed[] $data + */ + public function __construct(array $errors, array $warnings, array $data) + { + $this->errors = $errors; + $this->warnings = $warnings; + $this->data = $data; + parent::__construct("Invalid package information: \n".implode("\n", array_merge($errors, $warnings))); + } + + /** + * @return mixed[] + */ + public function getData(): array + { + return $this->data; + } + + /** + * @return list + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return list + */ + public function getWarnings(): array + { + return $this->warnings; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php b/vendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php new file mode 100644 index 000000000..5dbd17c51 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Json\JsonFile; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\RootPackage; +use Composer\Package\RootAliasPackage; + +/** + * @author Konstantin Kudryashiv + */ +class JsonLoader +{ + /** @var LoaderInterface */ + private $loader; + + public function __construct(LoaderInterface $loader) + { + $this->loader = $loader; + } + + /** + * @param string|JsonFile $json A filename, json string or JsonFile instance to load the package from + * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage + */ + public function load($json): BasePackage + { + if ($json instanceof JsonFile) { + $config = $json->read(); + } elseif (file_exists($json)) { + $config = JsonFile::parseJson(file_get_contents($json), $json); + } elseif (is_string($json)) { + $config = JsonFile::parseJson($json); + } else { + throw new \InvalidArgumentException(sprintf( + "JsonLoader: Unknown \$json parameter %s. Please report at https://github.com/composer/composer/issues/new.", + get_debug_type($json) + )); + } + + return $this->loader->load($config); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.php b/vendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.php new file mode 100644 index 000000000..cbf1b4c34 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.php @@ -0,0 +1,39 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Package\CompletePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\RootAliasPackage; +use Composer\Package\RootPackage; +use Composer\Package\BasePackage; + +/** + * Defines a loader that takes an array to create package instances + * + * @author Jordi Boggiano + */ +interface LoaderInterface +{ + /** + * Converts a package from an array to a real instance + * + * @param mixed[] $config package data + * @param string $class FQCN to be instantiated + * + * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage + * + * @phpstan-param class-string $class + */ + public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage; +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php b/vendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php new file mode 100644 index 000000000..1e278a1d0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php @@ -0,0 +1,315 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Package\BasePackage; +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Package\RootAliasPackage; +use Composer\Pcre\Preg; +use Composer\Repository\RepositoryFactory; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Package\RootPackage; +use Composer\Repository\RepositoryManager; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; + +/** + * ArrayLoader built for the sole purpose of loading the root package + * + * Sets additional defaults and loads repositories + * + * @author Jordi Boggiano + */ +class RootPackageLoader extends ArrayLoader +{ + /** + * @var RepositoryManager + */ + private $manager; + + /** + * @var Config + */ + private $config; + + /** + * @var VersionGuesser + */ + private $versionGuesser; + + /** + * @var IOInterface|null + */ + private $io; + + public function __construct(RepositoryManager $manager, Config $config, ?VersionParser $parser = null, ?VersionGuesser $versionGuesser = null, ?IOInterface $io = null) + { + parent::__construct($parser); + + $this->manager = $manager; + $this->config = $config; + if (null === $versionGuesser) { + $processExecutor = new ProcessExecutor($io); + $processExecutor->enableAsync(); + $versionGuesser = new VersionGuesser($config, $processExecutor, $this->versionParser); + } + $this->versionGuesser = $versionGuesser; + $this->io = $io; + } + + /** + * @inheritDoc + * + * @return RootPackage|RootAliasPackage + * + * @phpstan-param class-string $class + */ + public function load(array $config, string $class = 'Composer\Package\RootPackage', ?string $cwd = null): BasePackage + { + if ($class !== 'Composer\Package\RootPackage') { + trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED); + } + + if (!isset($config['name'])) { + $config['name'] = '__root__'; + } elseif ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) { + throw new \RuntimeException('Your package name '.$err); + } + $autoVersioned = false; + if (!isset($config['version'])) { + $commit = null; + + // override with env var if available + if (Platform::getEnv('COMPOSER_ROOT_VERSION')) { + $config['version'] = $this->versionGuesser->getRootVersionFromEnv(); + } else { + $versionData = $this->versionGuesser->guessVersion($config, $cwd ?? Platform::getCwd(true)); + if ($versionData) { + $config['version'] = $versionData['pretty_version']; + $config['version_normalized'] = $versionData['version']; + $commit = $versionData['commit']; + } + } + + if (!isset($config['version'])) { + if ($this->io !== null && $config['name'] !== '__root__' && 'project' !== ($config['type'] ?? '')) { + $this->io->warning( + sprintf( + "Composer could not detect the root package (%s) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version", + $config['name'] + ) + ); + } + $config['version'] = '1.0.0'; + $autoVersioned = true; + } + + if ($commit) { + $config['source'] = [ + 'type' => '', + 'url' => '', + 'reference' => $commit, + ]; + $config['dist'] = [ + 'type' => '', + 'url' => '', + 'reference' => $commit, + ]; + } + } + + /** @var RootPackage|RootAliasPackage $package */ + $package = parent::load($config, $class); + if ($package instanceof RootAliasPackage) { + $realPackage = $package->getAliasOf(); + } else { + $realPackage = $package; + } + + if (!$realPackage instanceof RootPackage) { + throw new \LogicException('Expecting a Composer\Package\RootPackage at this point'); + } + + if ($autoVersioned) { + $realPackage->replaceVersion($realPackage->getVersion(), RootPackage::DEFAULT_PRETTY_VERSION); + } + + if (isset($config['minimum-stability'])) { + $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability'])); + } + + $aliases = []; + $stabilityFlags = []; + $references = []; + foreach (['require', 'require-dev'] as $linkType) { + if (isset($config[$linkType])) { + $linkInfo = BasePackage::$supportedLinkTypes[$linkType]; + $method = 'get'.ucfirst($linkInfo['method']); + $links = []; + foreach ($realPackage->{$method}() as $link) { + $links[$link->getTarget()] = $link->getConstraint()->getPrettyString(); + } + $aliases = $this->extractAliases($links, $aliases); + $stabilityFlags = self::extractStabilityFlags($links, $realPackage->getMinimumStability(), $stabilityFlags); + $references = self::extractReferences($links, $references); + + if (isset($links[$config['name']])) { + throw new \RuntimeException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL . + 'Did you accidentally name your root package after an external package?', $config['name'])); + } + } + } + + foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { + if (isset($config[$linkType])) { + foreach ($config[$linkType] as $linkName => $constraint) { + if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) { + throw new \RuntimeException($linkType.'.'.$err); + } + } + } + } + + $realPackage->setAliases($aliases); + $realPackage->setStabilityFlags($stabilityFlags); + $realPackage->setReferences($references); + + if (isset($config['prefer-stable'])) { + $realPackage->setPreferStable((bool) $config['prefer-stable']); + } + + if (isset($config['config'])) { + $realPackage->setConfig($config['config']); + } + + $repos = RepositoryFactory::defaultRepos(null, $this->config, $this->manager); + foreach ($repos as $repo) { + $this->manager->addRepository($repo); + } + $realPackage->setRepositories($this->config->getRepositories()); + + return $package; + } + + /** + * @param array $requires + * @param list $aliases + * + * @return list + */ + private function extractAliases(array $requires, array $aliases): array + { + foreach ($requires as $reqName => $reqVersion) { + if (Preg::isMatchStrictGroups('{(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *,)}', $reqVersion, $match)) { + $aliases[] = [ + 'package' => strtolower($reqName), + 'version' => $this->versionParser->normalize($match[1], $reqVersion), + 'alias' => $match[2], + 'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion), + ]; + } elseif (strpos($reqVersion, ' as ') !== false) { + throw new \UnexpectedValueException('Invalid alias definition in "'.$reqName.'": "'.$reqVersion.'". Aliases should be in the form "exact-version as other-exact-version".'); + } + } + + return $aliases; + } + + /** + * @internal + * + * @param array $requires + * @param array $stabilityFlags + * @param key-of $minimumStability + * + * @return array + * + * @phpstan-param array $stabilityFlags + * @phpstan-return array + */ + public static function extractStabilityFlags(array $requires, string $minimumStability, array $stabilityFlags): array + { + $stabilities = BasePackage::STABILITIES; + $minimumStability = $stabilities[$minimumStability]; + foreach ($requires as $reqName => $reqVersion) { + $constraints = []; + + // extract all sub-constraints in case it is an OR/AND multi-constraint + $orSplit = Preg::split('{\s*\|\|?\s*}', trim($reqVersion)); + foreach ($orSplit as $orConstraint) { + $andSplit = Preg::split('{(?< ,]) *(? $stability) { + continue; + } + $stabilityFlags[$name] = $stability; + $matched = true; + } + } + + if ($matched) { + continue; + } + + foreach ($constraints as $constraint) { + // infer flags for requirements that have an explicit -dev or -beta version specified but only + // for those that are more unstable than the minimumStability or existing flags + $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $constraint); + if (Preg::isMatch('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) { + $name = strtolower($reqName); + $stability = $stabilities[$stabilityName]; + if ((isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) || ($minimumStability > $stability)) { + continue; + } + $stabilityFlags[$name] = $stability; + } + } + } + + return $stabilityFlags; + } + + /** + * @internal + * + * @param array $requires + * @param array $references + * + * @return array + */ + public static function extractReferences(array $requires, array $references): array + { + foreach ($requires as $reqName => $reqVersion) { + $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion); + if (Preg::isMatchStrictGroups('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === VersionParser::parseStability($reqVersion)) { + $name = strtolower($reqName); + $references[$name] = $match[1]; + } + } + + return $references; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php b/vendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php new file mode 100644 index 000000000..80b03fc6f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -0,0 +1,790 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Package\BasePackage; +use Composer\Pcre\Preg; +use Composer\Semver\Constraint\Constraint; +use Composer\Package\Version\VersionParser; +use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\MatchNoneConstraint; +use Composer\Semver\Intervals; +use Composer\Spdx\SpdxLicenses; + +/** + * @author Jordi Boggiano + */ +class ValidatingArrayLoader implements LoaderInterface +{ + public const CHECK_ALL = 3; + public const CHECK_UNBOUND_CONSTRAINTS = 1; + public const CHECK_STRICT_CONSTRAINTS = 2; + + /** @var LoaderInterface */ + private $loader; + /** @var VersionParser */ + private $versionParser; + /** @var list */ + private $errors; + /** @var list */ + private $warnings; + /** @var mixed[] */ + private $config; + /** @var int One or more of self::CHECK_* constants */ + private $flags; + + /** + * @param true $strictName + */ + public function __construct(LoaderInterface $loader, bool $strictName = true, ?VersionParser $parser = null, int $flags = 0) + { + $this->loader = $loader; + $this->versionParser = $parser ?? new VersionParser(); + $this->flags = $flags; + + if ($strictName !== true) { // @phpstan-ignore-line + trigger_error('$strictName must be set to true in ValidatingArrayLoader\'s constructor as of 2.2, and it will be removed in 3.0', E_USER_DEPRECATED); + } + } + + /** + * @inheritDoc + */ + public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage + { + $this->errors = []; + $this->warnings = []; + $this->config = $config; + + $this->validateString('name', true); + if (isset($config['name']) && null !== ($err = self::hasPackageNamingError($config['name']))) { + $this->errors[] = 'name : '.$err; + } + + if (isset($this->config['version'])) { + if (!is_scalar($this->config['version'])) { + $this->validateString('version'); + } else { + if (!is_string($this->config['version'])) { + $this->config['version'] = (string) $this->config['version']; + } + try { + $this->versionParser->normalize($this->config['version']); + } catch (\Exception $e) { + $this->errors[] = 'version : invalid value ('.$this->config['version'].'): '.$e->getMessage(); + unset($this->config['version']); + } + } + } + + if (isset($this->config['config']['platform'])) { + foreach ((array) $this->config['config']['platform'] as $key => $platform) { + if (false === $platform) { + continue; + } + if (!is_string($platform)) { + $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.get_debug_type($platform).' '.var_export($platform, true).'): expected string or false'; + continue; + } + try { + $this->versionParser->normalize($platform); + } catch (\Exception $e) { + $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.$platform.'): '.$e->getMessage(); + } + } + } + + $this->validateRegex('type', '[A-Za-z0-9-]+'); + $this->validateString('target-dir'); + $this->validateArray('extra'); + + if (isset($this->config['bin'])) { + if (is_string($this->config['bin'])) { + $this->validateString('bin'); + } else { + $this->validateFlatArray('bin'); + } + } + + $this->validateArray('scripts'); // TODO validate event names & listener syntax + $this->validateString('description'); + $this->validateUrl('homepage'); + $this->validateFlatArray('keywords', '[\p{N}\p{L} ._-]+'); + + $releaseDate = null; + $this->validateString('time'); + if (isset($this->config['time'])) { + try { + $releaseDate = new \DateTime($this->config['time'], new \DateTimeZone('UTC')); + } catch (\Exception $e) { + $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage(); + unset($this->config['time']); + } + } + + if (isset($this->config['license'])) { + // validate main data types + if (is_array($this->config['license']) || is_string($this->config['license'])) { + $licenses = (array) $this->config['license']; + + foreach ($licenses as $index => $license) { + if (!is_string($license)) { + $this->warnings[] = sprintf( + 'License %s should be a string.', + json_encode($license) + ); + unset($licenses[$index]); + } + } + + // check for license validity on newly updated branches/tags + if (null === $releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days')) { + $licenseValidator = new SpdxLicenses(); + foreach ($licenses as $license) { + // replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer + if ('proprietary' === $license) { + continue; + } + $licenseToValidate = str_replace('proprietary', 'MIT', $license); + if (!$licenseValidator->validate($licenseToValidate)) { + if ($licenseValidator->validate(trim($licenseToValidate))) { + $this->warnings[] = sprintf( + 'License %s must not contain extra spaces, make sure to trim it.', + json_encode($license) + ); + } else { + $this->warnings[] = sprintf( + 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL . + 'If the software is closed-source, you may use "proprietary" as license.', + json_encode($license) + ); + } + } + } + } + + $this->config['license'] = array_values($licenses); + } else { + $this->warnings[] = sprintf( + 'License must be a string or array of strings, got %s.', + json_encode($this->config['license']) + ); + unset($this->config['license']); + } + } + + if ($this->validateArray('authors')) { + foreach ($this->config['authors'] as $key => $author) { + if (!is_array($author)) { + $this->errors[] = 'authors.'.$key.' : should be an array, '.get_debug_type($author).' given'; + unset($this->config['authors'][$key]); + continue; + } + foreach (['homepage', 'email', 'name', 'role'] as $authorData) { + if (isset($author[$authorData]) && !is_string($author[$authorData])) { + $this->errors[] = 'authors.'.$key.'.'.$authorData.' : invalid value, must be a string'; + unset($this->config['authors'][$key][$authorData]); + } + } + if (isset($author['homepage']) && !$this->filterUrl($author['homepage'])) { + $this->warnings[] = 'authors.'.$key.'.homepage : invalid value ('.$author['homepage'].'), must be an http/https URL'; + unset($this->config['authors'][$key]['homepage']); + } + if (isset($author['email']) && false === filter_var($author['email'], FILTER_VALIDATE_EMAIL)) { + $this->warnings[] = 'authors.'.$key.'.email : invalid value ('.$author['email'].'), must be a valid email address'; + unset($this->config['authors'][$key]['email']); + } + if (\count($this->config['authors'][$key]) === 0) { + unset($this->config['authors'][$key]); + } + } + if (\count($this->config['authors']) === 0) { + unset($this->config['authors']); + } + } + + if ($this->validateArray('support') && !empty($this->config['support'])) { + foreach (['issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss', 'chat', 'security'] as $key) { + if (isset($this->config['support'][$key]) && !is_string($this->config['support'][$key])) { + $this->errors[] = 'support.'.$key.' : invalid value, must be a string'; + unset($this->config['support'][$key]); + } + } + + if (isset($this->config['support']['email']) && !filter_var($this->config['support']['email'], FILTER_VALIDATE_EMAIL)) { + $this->warnings[] = 'support.email : invalid value ('.$this->config['support']['email'].'), must be a valid email address'; + unset($this->config['support']['email']); + } + + if (isset($this->config['support']['irc']) && !$this->filterUrl($this->config['support']['irc'], ['irc', 'ircs'])) { + $this->warnings[] = 'support.irc : invalid value ('.$this->config['support']['irc'].'), must be a irc:/// or ircs:// URL'; + unset($this->config['support']['irc']); + } + + foreach (['issues', 'forum', 'wiki', 'source', 'docs', 'chat', 'security'] as $key) { + if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) { + $this->warnings[] = 'support.'.$key.' : invalid value ('.$this->config['support'][$key].'), must be an http/https URL'; + unset($this->config['support'][$key]); + } + } + if (empty($this->config['support'])) { + unset($this->config['support']); + } + } + + if ($this->validateArray('funding') && !empty($this->config['funding'])) { + foreach ($this->config['funding'] as $key => $fundingOption) { + if (!is_array($fundingOption)) { + $this->errors[] = 'funding.'.$key.' : should be an array, '.get_debug_type($fundingOption).' given'; + unset($this->config['funding'][$key]); + continue; + } + foreach (['type', 'url'] as $fundingData) { + if (isset($fundingOption[$fundingData]) && !is_string($fundingOption[$fundingData])) { + $this->errors[] = 'funding.'.$key.'.'.$fundingData.' : invalid value, must be a string'; + unset($this->config['funding'][$key][$fundingData]); + } + } + if (isset($fundingOption['url']) && !$this->filterUrl($fundingOption['url'])) { + $this->warnings[] = 'funding.'.$key.'.url : invalid value ('.$fundingOption['url'].'), must be an http/https URL'; + unset($this->config['funding'][$key]['url']); + } + if (empty($this->config['funding'][$key])) { + unset($this->config['funding'][$key]); + } + } + if (empty($this->config['funding'])) { + unset($this->config['funding']); + } + } + + if (isset($this->config['php-ext']) && $this->validateArray('php-ext')) { + if (!in_array($this->config['type'] ?? '', ['php-ext', 'php-ext-zend'], true)) { + $this->errors[] = 'php-ext can only be set by packages of type "php-ext" or "php-ext-zend" which must be C extensions'; + unset($this->config['php-ext']); + } + + $phpExt = &$this->config['php-ext']; + + if (isset($phpExt['extension-name']) && !is_string($phpExt['extension-name'])) { + $this->errors[] = 'php-ext.extension-name : should be a string, '.get_debug_type($phpExt['extension-name']).' given'; + unset($phpExt['extension-name']); + } + + if (isset($phpExt['priority']) && !is_int($phpExt['priority'])) { + $this->errors[] = 'php-ext.priority : should be an integer, '.get_debug_type($phpExt['priority']).' given'; + unset($phpExt['priority']); + } + + if (isset($phpExt['support-zts']) && !is_bool($phpExt['support-zts'])) { + $this->errors[] = 'php-ext.support-zts : should be a boolean, '.get_debug_type($phpExt['support-zts']).' given'; + unset($phpExt['support-zts']); + } + + if (isset($phpExt['support-nts']) && !is_bool($phpExt['support-nts'])) { + $this->errors[] = 'php-ext.support-nts : should be a boolean, '.get_debug_type($phpExt['support-nts']).' given'; + unset($phpExt['support-nts']); + } + + if (isset($phpExt['build-path']) && !is_string($phpExt['build-path']) && !is_null($phpExt['build-path'])) { + $this->errors[] = 'php-ext.build-path : should be a string or null, '.get_debug_type($phpExt['build-path']).' given'; + unset($phpExt['build-path']); + } + + if (isset($phpExt['download-url-method'])) { + if (!is_array($phpExt['download-url-method']) && !is_string($phpExt['download-url-method'])) { + $this->errors[] = 'php-ext.download-url-method : should be an array or a string, '.get_debug_type($phpExt['download-url-method']).' given'; + unset($phpExt['download-url-method']); + } else { + $validDownloadUrlMethods = ['composer-default', 'pre-packaged-source', 'pre-packaged-binary']; + $definedDownloadUrlMethods = is_array($phpExt['download-url-method']) ? $phpExt['download-url-method'] : [$phpExt['download-url-method']]; + + if ([] === $definedDownloadUrlMethods) { + $this->errors[] = 'php-ext.download-url-method : must contain at least one element'; + unset($phpExt['download-url-method']); + } else { + foreach ($definedDownloadUrlMethods as $key => $downloadUrlMethod) { + if (!is_string($downloadUrlMethod)) { + $this->errors[] = 'php-ext.download-url-method.'.$key.' : should be a string, '.get_debug_type($downloadUrlMethod).' given'; + unset($phpExt['download-url-method']); + } elseif (!in_array($downloadUrlMethod, $validDownloadUrlMethods, true)) { + $this->errors[] = 'php-ext.download-url-method.'.$key.' : invalid value ('.$downloadUrlMethod.'), must be one of ' . implode(', ', $validDownloadUrlMethods); + unset($phpExt['download-url-method']); + } + } + } + } + } + + if (isset($phpExt['os-families']) && isset($phpExt['os-families-exclude'])) { + $this->errors[] = 'php-ext : os-families and os-families-exclude cannot both be specified'; + unset($phpExt['os-families'], $phpExt['os-families-exclude']); + } else { + $validOsFamilies = ['windows', 'bsd', 'darwin', 'solaris', 'linux', 'unknown']; + + foreach (['os-families', 'os-families-exclude'] as $fieldName) { + if (isset($phpExt[$fieldName])) { + if (!is_array($phpExt[$fieldName])) { + $this->errors[] = 'php-ext.'.$fieldName.' : should be an array, '.get_debug_type($phpExt[$fieldName]).' given'; + unset($phpExt[$fieldName]); + } elseif ([] === $phpExt[$fieldName]) { + $this->errors[] = 'php-ext.'.$fieldName.' : must contain at least one element'; + unset($phpExt[$fieldName]); + } else { + foreach ($phpExt[$fieldName] as $key => $osFamily) { + if (!is_string($osFamily)) { + $this->errors[] = 'php-ext.'.$fieldName.'.'.$key.' : should be a string, '.get_debug_type($osFamily).' given'; + unset($phpExt[$fieldName][$key]); + } elseif (!in_array($osFamily, $validOsFamilies, true)) { + $this->errors[] = 'php-ext.'.$fieldName.'.'.$key.' : invalid value ('.$osFamily.'), must be one of '.implode(', ', $validOsFamilies); + unset($phpExt[$fieldName][$key]); + } + } + if ([] === $phpExt[$fieldName]) { + unset($phpExt[$fieldName]); + } + } + } + } + } + + if (isset($phpExt['configure-options'])) { + if (!is_array($phpExt['configure-options'])) { + $this->errors[] = 'php-ext.configure-options : should be an array, '.get_debug_type($phpExt['configure-options']).' given'; + unset($phpExt['configure-options']); + } else { + foreach ($phpExt['configure-options'] as $key => $option) { + if (!is_array($option)) { + $this->errors[] = 'php-ext.configure-options.'.$key.' : should be an array, '.get_debug_type($option).' given'; + unset($phpExt['configure-options'][$key]); + continue; + } + + if (!isset($option['name'])) { + $this->errors[] = 'php-ext.configure-options.'.$key.'.name : must be present'; + unset($phpExt['configure-options'][$key]); + continue; + } + + if (!is_string($option['name'])) { + $this->errors[] = 'php-ext.configure-options.'.$key.'.name : should be a string, '.get_debug_type($option['name']).' given'; + unset($phpExt['configure-options'][$key]); + continue; + } + + if (isset($option['needs-value']) && !is_bool($option['needs-value'])) { + $this->errors[] = 'php-ext.configure-options.'.$key.'.needs-value : should be a boolean, '.get_debug_type($option['needs-value']).' given'; + unset($phpExt['configure-options'][$key]['needs-value']); + } + + if (isset($option['description']) && !is_string($option['description'])) { + $this->errors[] = 'php-ext.configure-options.'.$key.'.description : should be a string, '.get_debug_type($option['description']).' given'; + unset($phpExt['configure-options'][$key]['description']); + } + } + + if ([] === $phpExt['configure-options']) { + unset($phpExt['configure-options']); + } + } + } + + // If php-ext is now empty, unset it + if ([] === $phpExt) { + unset($this->config['php-ext']); + } + + unset($phpExt); + } + + $unboundConstraint = new Constraint('=', '10000000-dev'); + + foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { + if ($this->validateArray($linkType) && isset($this->config[$linkType])) { + foreach ($this->config[$linkType] as $package => $constraint) { + $package = (string) $package; + if (isset($this->config['name']) && 0 === strcasecmp($package, $this->config['name'])) { + $this->errors[] = $linkType.'.'.$package.' : a package cannot set a '.$linkType.' on itself'; + unset($this->config[$linkType][$package]); + continue; + } + if ($err = self::hasPackageNamingError($package, true)) { + $this->warnings[] = $linkType.'.'.$err; + } elseif (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $package)) { + $this->errors[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]'; + } + if (!is_string($constraint)) { + $this->errors[] = $linkType.'.'.$package.' : invalid value, must be a string containing a version constraint'; + unset($this->config[$linkType][$package]); + } elseif ('self.version' !== $constraint) { + try { + $linkConstraint = $this->versionParser->parseConstraints($constraint); + } catch (\Exception $e) { + $this->errors[] = $linkType.'.'.$package.' : invalid version constraint ('.$e->getMessage().')'; + unset($this->config[$linkType][$package]); + continue; + } + + // check requires for unbound constraints on non-platform packages + if ( + ($this->flags & self::CHECK_UNBOUND_CONSTRAINTS) + && 'require' === $linkType + && $linkConstraint->matches($unboundConstraint) + && !PlatformRepository::isPlatformPackage($package) + ) { + $this->warnings[] = $linkType.'.'.$package.' : unbound version constraints ('.$constraint.') should be avoided'; + } elseif ( + // check requires for exact constraints + ($this->flags & self::CHECK_STRICT_CONSTRAINTS) + && 'require' === $linkType + && $linkConstraint instanceof Constraint && in_array($linkConstraint->getOperator(), ['==', '='], true) + && (new Constraint('>=', '1.0.0.0-dev'))->matches($linkConstraint) + ) { + $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning'; + } + + $compacted = Intervals::compactConstraint($linkConstraint); + if ($compacted instanceof MatchNoneConstraint) { + $this->warnings[] = $linkType.'.'.$package.' : this version constraint cannot possibly match anything ('.$constraint.')'; + } + } + + if ($linkType === 'conflict' && isset($this->config['replace']) && $keys = array_intersect_key($this->config['replace'], $this->config['conflict'])) { + $this->errors[] = $linkType.'.'.$package.' : you cannot conflict with a package that is also replaced, as replace already creates an implicit conflict rule'; + unset($this->config[$linkType][$package]); + } + } + } + } + + if ($this->validateArray('suggest') && isset($this->config['suggest'])) { + foreach ($this->config['suggest'] as $package => $description) { + if (!is_string($description)) { + $this->errors[] = 'suggest.'.$package.' : invalid value, must be a string describing why the package is suggested'; + unset($this->config['suggest'][$package]); + } + } + } + + if ($this->validateString('minimum-stability') && isset($this->config['minimum-stability'])) { + if (!isset(BasePackage::STABILITIES[strtolower($this->config['minimum-stability'])]) && $this->config['minimum-stability'] !== 'RC') { + $this->errors[] = 'minimum-stability : invalid value ('.$this->config['minimum-stability'].'), must be one of '.implode(', ', array_keys(BasePackage::STABILITIES)); + unset($this->config['minimum-stability']); + } + } + + if ($this->validateArray('autoload') && isset($this->config['autoload'])) { + $types = ['psr-0', 'psr-4', 'classmap', 'files', 'exclude-from-classmap']; + foreach ($this->config['autoload'] as $type => $typeConfig) { + if (!in_array($type, $types)) { + $this->errors[] = 'autoload : invalid value ('.$type.'), must be one of '.implode(', ', $types); + unset($this->config['autoload'][$type]); + } + if ($type === 'psr-4') { + foreach ($typeConfig as $namespace => $dirs) { + if ($namespace !== '' && '\\' !== substr((string) $namespace, -1)) { + $this->errors[] = 'autoload.psr-4 : invalid value ('.$namespace.'), namespaces must end with a namespace separator, should be '.$namespace.'\\\\'; + } + } + } + } + } + + if (isset($this->config['autoload']['psr-4']) && isset($this->config['target-dir'])) { + $this->errors[] = 'target-dir : this can not be used together with the autoload.psr-4 setting, remove target-dir to upgrade to psr-4'; + // Unset the psr-4 setting, since unsetting target-dir might + // interfere with other settings. + unset($this->config['autoload']['psr-4']); + } + + foreach (['source', 'dist'] as $srcType) { + if ($this->validateArray($srcType) && !empty($this->config[$srcType])) { + if (!isset($this->config[$srcType]['type'])) { + $this->errors[] = $srcType . '.type : must be present'; + } + if (!isset($this->config[$srcType]['url'])) { + $this->errors[] = $srcType . '.url : must be present'; + } + if ($srcType === 'source' && !isset($this->config[$srcType]['reference'])) { + $this->errors[] = $srcType . '.reference : must be present'; + } + if (isset($this->config[$srcType]['type']) && !is_string($this->config[$srcType]['type'])) { + $this->errors[] = $srcType . '.type : should be a string, '.get_debug_type($this->config[$srcType]['type']).' given'; + } + if (isset($this->config[$srcType]['url']) && !is_string($this->config[$srcType]['url'])) { + $this->errors[] = $srcType . '.url : should be a string, '.get_debug_type($this->config[$srcType]['url']).' given'; + } + if (isset($this->config[$srcType]['reference']) && !is_string($this->config[$srcType]['reference']) && !is_int($this->config[$srcType]['reference'])) { + $this->errors[] = $srcType . '.reference : should be a string or int, '.get_debug_type($this->config[$srcType]['reference']).' given'; + } + if (isset($this->config[$srcType]['reference']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['reference'])) { + $this->errors[] = $srcType . '.reference : must not start with a "-", "'.$this->config[$srcType]['reference'].'" given'; + } + if (isset($this->config[$srcType]['url']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['url'])) { + $this->errors[] = $srcType . '.url : must not start with a "-", "'.$this->config[$srcType]['url'].'" given'; + } + } + } + + // TODO validate repositories + // TODO validate package repositories' packages using this recursively + + $this->validateFlatArray('include-path'); + $this->validateArray('transport-options'); + + // branch alias validation + if (isset($this->config['extra']['branch-alias'])) { + if (!is_array($this->config['extra']['branch-alias'])) { + $this->errors[] = 'extra.branch-alias : must be an array of versions => aliases'; + } else { + foreach ($this->config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { + if (!is_string($targetBranch)) { + $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.json_encode($targetBranch).') must be a string, "'.get_debug_type($targetBranch).'" received.'; + unset($this->config['extra']['branch-alias'][$sourceBranch]); + + continue; + } + + // ensure it is an alias to a -dev package + if ('-dev' !== substr($targetBranch, -4)) { + $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must end in -dev'; + unset($this->config['extra']['branch-alias'][$sourceBranch]); + + continue; + } + + // normalize without -dev and ensure it's a numeric branch that is parseable + $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4)); + if ('-dev' !== substr($validatedTargetBranch, -4)) { + $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev'; + unset($this->config['extra']['branch-alias'][$sourceBranch]); + + continue; + } + + // If using numeric aliases ensure the alias is a valid subversion + if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) + && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) + && (stripos($targetPrefix, $sourcePrefix) !== 0) + ) { + $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version'; + unset($this->config['extra']['branch-alias'][$sourceBranch]); + } + } + } + } + + if ($this->errors) { + throw new InvalidPackageException($this->errors, $this->warnings, $config); + } + + $package = $this->loader->load($this->config, $class); + $this->config = []; + + return $package; + } + + /** + * @return list + */ + public function getWarnings(): array + { + return $this->warnings; + } + + /** + * @return list + */ + public function getErrors(): array + { + return $this->errors; + } + + public static function hasPackageNamingError(string $name, bool $isLink = false): ?string + { + if (PlatformRepository::isPlatformPackage($name)) { + return null; + } + + if (!Preg::isMatch('{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD', $name)) { + return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$".'; + } + + $reservedNames = ['nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9']; + $bits = explode('/', strtolower($name)); + if (in_array($bits[0], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) { + return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.'; + } + + if (Preg::isMatch('{\.json$}', $name)) { + return $name.' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.'; + } + + if (Preg::isMatch('{[A-Z]}', $name)) { + if ($isLink) { + return $name.' is invalid, it should not contain uppercase characters. Please use '.strtolower($name).' instead.'; + } + + $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); + $suggestName = strtolower($suggestName); + + return $name.' is invalid, it should not contain uppercase characters. We suggest using '.$suggestName.' instead.'; + } + + return null; + } + + /** + * @phpstan-param non-empty-string $property + * @phpstan-param non-empty-string $regex + */ + private function validateRegex(string $property, string $regex, bool $mandatory = false): bool + { + if (!$this->validateString($property, $mandatory)) { + return false; + } + + if (!Preg::isMatch('{^'.$regex.'$}u', $this->config[$property])) { + $message = $property.' : invalid value ('.$this->config[$property].'), must match '.$regex; + if ($mandatory) { + $this->errors[] = $message; + } else { + $this->warnings[] = $message; + } + unset($this->config[$property]); + + return false; + } + + return true; + } + + /** + * @phpstan-param non-empty-string $property + */ + private function validateString(string $property, bool $mandatory = false): bool + { + if (isset($this->config[$property]) && !is_string($this->config[$property])) { + $this->errors[] = $property.' : should be a string, '.get_debug_type($this->config[$property]).' given'; + unset($this->config[$property]); + + return false; + } + + if (!isset($this->config[$property]) || trim($this->config[$property]) === '') { + if ($mandatory) { + $this->errors[] = $property.' : must be present'; + } + unset($this->config[$property]); + + return false; + } + + return true; + } + + /** + * @phpstan-param non-empty-string $property + */ + private function validateArray(string $property, bool $mandatory = false): bool + { + if (isset($this->config[$property]) && !is_array($this->config[$property])) { + $this->errors[] = $property.' : should be an array, '.get_debug_type($this->config[$property]).' given'; + unset($this->config[$property]); + + return false; + } + + if (!isset($this->config[$property]) || !count($this->config[$property])) { + if ($mandatory) { + $this->errors[] = $property.' : must be present and contain at least one element'; + } + unset($this->config[$property]); + + return false; + } + + return true; + } + + /** + * @phpstan-param non-empty-string $property + * @phpstan-param non-empty-string|null $regex + */ + private function validateFlatArray(string $property, ?string $regex = null, bool $mandatory = false): bool + { + if (!$this->validateArray($property, $mandatory)) { + return false; + } + + $pass = true; + foreach ($this->config[$property] as $key => $value) { + if (!is_string($value) && !is_numeric($value)) { + $this->errors[] = $property.'.'.$key.' : must be a string or int, '.get_debug_type($value).' given'; + unset($this->config[$property][$key]); + $pass = false; + + continue; + } + + if ($regex && !Preg::isMatch('{^'.$regex.'$}u', (string) $value)) { + $this->warnings[] = $property.'.'.$key.' : invalid value ('.$value.'), must match '.$regex; + unset($this->config[$property][$key]); + $pass = false; + } + } + + return $pass; + } + + /** + * @phpstan-param non-empty-string $property + */ + private function validateUrl(string $property, bool $mandatory = false): bool + { + if (!$this->validateString($property, $mandatory)) { + return false; + } + + if (!$this->filterUrl($this->config[$property])) { + $this->warnings[] = $property.' : invalid value ('.$this->config[$property].'), must be an http/https URL'; + unset($this->config[$property]); + + return false; + } + + return true; + } + + /** + * @param mixed $value + * @param string[] $schemes + */ + private function filterUrl($value, array $schemes = ['http', 'https']): bool + { + if ($value === '') { + return true; + } + + $bits = parse_url($value); + if (empty($bits['scheme']) || empty($bits['host'])) { + return false; + } + + if (!in_array($bits['scheme'], $schemes, true)) { + return false; + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Locker.php b/vendor/composer/composer/src/Composer/Package/Locker.php new file mode 100644 index 000000000..900273f9d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Locker.php @@ -0,0 +1,633 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Json\JsonFile; +use Composer\Installer\InstallationManager; +use Composer\Pcre\Preg; +use Composer\Repository\InstalledRepository; +use Composer\Repository\LockArrayRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Util\ProcessExecutor; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Version\VersionParser; +use Composer\Plugin\PluginInterface; +use Composer\Util\Git as GitUtil; +use Composer\IO\IOInterface; +use Seld\JsonLint\ParsingException; + +/** + * Reads/writes project lockfile (composer.lock). + * + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + */ +class Locker +{ + /** @var JsonFile */ + private $lockFile; + /** @var InstallationManager */ + private $installationManager; + /** @var string */ + private $hash; + /** @var string */ + private $contentHash; + /** @var ArrayLoader */ + private $loader; + /** @var ArrayDumper */ + private $dumper; + /** @var ProcessExecutor */ + private $process; + /** @var mixed[]|null */ + private $lockDataCache = null; + /** @var bool */ + private $virtualFileWritten = false; + + /** + * Initializes packages locker. + * + * @param JsonFile $lockFile lockfile loader + * @param InstallationManager $installationManager installation manager instance + * @param string $composerFileContents The contents of the composer file + */ + public function __construct(IOInterface $io, JsonFile $lockFile, InstallationManager $installationManager, string $composerFileContents, ?ProcessExecutor $process = null) + { + $this->lockFile = $lockFile; + $this->installationManager = $installationManager; + $this->hash = hash('md5', $composerFileContents); + $this->contentHash = self::getContentHash($composerFileContents); + $this->loader = new ArrayLoader(null, true); + $this->dumper = new ArrayDumper(); + $this->process = $process ?? new ProcessExecutor($io); + } + + /** + * @internal + */ + public function getJsonFile(): JsonFile + { + return $this->lockFile; + } + + /** + * Returns the md5 hash of the sorted content of the composer file. + * + * @param string $composerFileContents The contents of the composer file. + */ + public static function getContentHash(string $composerFileContents): string + { + $content = JsonFile::parseJson($composerFileContents, 'composer.json'); + + $relevantKeys = [ + 'name', + 'version', + 'require', + 'require-dev', + 'conflict', + 'replace', + 'provide', + 'minimum-stability', + 'prefer-stable', + 'repositories', + 'extra', + ]; + + $relevantContent = []; + + foreach (array_intersect($relevantKeys, array_keys($content)) as $key) { + $relevantContent[$key] = $content[$key]; + } + if (isset($content['config']['platform'])) { + $relevantContent['config']['platform'] = $content['config']['platform']; + } + + ksort($relevantContent); + + return hash('md5', JsonFile::encode($relevantContent, 0)); + } + + /** + * Checks whether locker has been locked (lockfile found). + */ + public function isLocked(): bool + { + if (!$this->virtualFileWritten && !$this->lockFile->exists()) { + return false; + } + + $data = $this->getLockData(); + + return isset($data['packages']); + } + + /** + * Checks whether the lock file is still up to date with the current hash + */ + public function isFresh(): bool + { + $lock = $this->lockFile->read(); + + if (!empty($lock['content-hash'])) { + // There is a content hash key, use that instead of the file hash + return $this->contentHash === $lock['content-hash']; + } + + // BC support for old lock files without content-hash + if (!empty($lock['hash'])) { + return $this->hash === $lock['hash']; + } + + // should not be reached unless the lock file is corrupted, so assume it's out of date + return false; + } + + /** + * Searches and returns an array of locked packages, retrieved from registered repositories. + * + * @param bool $withDevReqs true to retrieve the locked dev packages + * @throws \RuntimeException + */ + public function getLockedRepository(bool $withDevReqs = false): LockArrayRepository + { + $lockData = $this->getLockData(); + $packages = new LockArrayRepository(); + + $lockedPackages = $lockData['packages']; + if ($withDevReqs) { + if (isset($lockData['packages-dev'])) { + $lockedPackages = array_merge($lockedPackages, $lockData['packages-dev']); + } else { + throw new \RuntimeException('The lock file does not contain require-dev information, run install with the --no-dev option or delete it and run composer update to generate a new lock file.'); + } + } + + if (empty($lockedPackages)) { + return $packages; + } + + if (isset($lockedPackages[0]['name'])) { + $packageByName = []; + foreach ($lockedPackages as $info) { + $package = $this->loader->load($info); + $packages->addPackage($package); + $packageByName[$package->getName()] = $package; + + if ($package instanceof AliasPackage) { + $packageByName[$package->getAliasOf()->getName()] = $package->getAliasOf(); + } + } + + if (isset($lockData['aliases'])) { + foreach ($lockData['aliases'] as $alias) { + if (isset($packageByName[$alias['package']])) { + $aliasPkg = new CompleteAliasPackage($packageByName[$alias['package']], $alias['alias_normalized'], $alias['alias']); + $aliasPkg->setRootPackageAlias(true); + $packages->addPackage($aliasPkg); + } + } + } + + return $packages; + } + + throw new \RuntimeException('Your composer.lock is invalid. Run "composer update" to generate a new one.'); + } + + /** + * @return string[] Names of dependencies installed through require-dev + */ + public function getDevPackageNames(): array + { + $names = []; + $lockData = $this->getLockData(); + if (isset($lockData['packages-dev'])) { + foreach ($lockData['packages-dev'] as $package) { + $names[] = strtolower($package['name']); + } + } + + return $names; + } + + /** + * Returns the platform requirements stored in the lock file + * + * @param bool $withDevReqs if true, the platform requirements from the require-dev block are also returned + * @return Link[] + */ + public function getPlatformRequirements(bool $withDevReqs = false): array + { + $lockData = $this->getLockData(); + $requirements = []; + + if (!empty($lockData['platform'])) { + $requirements = $this->loader->parseLinks( + '__root__', + '1.0.0', + Link::TYPE_REQUIRE, + $lockData['platform'] ?? [] + ); + } + + if ($withDevReqs && !empty($lockData['platform-dev'])) { + $devRequirements = $this->loader->parseLinks( + '__root__', + '1.0.0', + Link::TYPE_REQUIRE, + $lockData['platform-dev'] ?? [] + ); + + $requirements = array_merge($requirements, $devRequirements); + } + + return $requirements; + } + + /** + * @return key-of + */ + public function getMinimumStability(): string + { + $lockData = $this->getLockData(); + + return $lockData['minimum-stability'] ?? 'stable'; + } + + /** + * @return array + */ + public function getStabilityFlags(): array + { + $lockData = $this->getLockData(); + + return $lockData['stability-flags'] ?? []; + } + + public function getPreferStable(): ?bool + { + $lockData = $this->getLockData(); + + // return null if not set to allow caller logic to choose the + // right behavior since old lock files have no prefer-stable + return $lockData['prefer-stable'] ?? null; + } + + public function getPreferLowest(): ?bool + { + $lockData = $this->getLockData(); + + // return null if not set to allow caller logic to choose the + // right behavior since old lock files have no prefer-lowest + return $lockData['prefer-lowest'] ?? null; + } + + /** + * @return array + */ + public function getPlatformOverrides(): array + { + $lockData = $this->getLockData(); + + return $lockData['platform-overrides'] ?? []; + } + + /** + * @return string[][] + * + * @phpstan-return list + */ + public function getAliases(): array + { + $lockData = $this->getLockData(); + + return $lockData['aliases'] ?? []; + } + + /** + * @return string + */ + public function getPluginApi() + { + $lockData = $this->getLockData(); + + return $lockData['plugin-api-version'] ?? '1.1.0'; + } + + /** + * @return array + */ + public function getLockData(): array + { + if (null !== $this->lockDataCache) { + return $this->lockDataCache; + } + + if (!$this->lockFile->exists()) { + throw new \LogicException('No lockfile found. Unable to read locked packages'); + } + + return $this->lockDataCache = $this->lockFile->read(); + } + + /** + * Locks provided data into lockfile. + * + * @param PackageInterface[] $packages array of packages + * @param PackageInterface[]|null $devPackages array of dev packages or null if installed without --dev + * @param array $platformReqs array of package name => constraint for required platform packages + * @param array $platformDevReqs array of package name => constraint for dev-required platform packages + * @param string[][] $aliases array of aliases + * @param array $stabilityFlags + * @param array $platformOverrides + * @param bool $write Whether to actually write data to disk, useful in tests and for --dry-run + * + * @phpstan-param list $aliases + */ + public function setLockData(array $packages, ?array $devPackages, array $platformReqs, array $platformDevReqs, array $aliases, string $minimumStability, array $stabilityFlags, bool $preferStable, bool $preferLowest, array $platformOverrides, bool $write = true): bool + { + // keep old default branch names normalized to DEFAULT_BRANCH_ALIAS for BC as that is how Composer 1 outputs the lock file + // when loading the lock file the version is anyway ignored in Composer 2, so it has no adverse effect + $aliases = array_map(static function ($alias): array { + if (in_array($alias['version'], ['dev-master', 'dev-trunk', 'dev-default'], true)) { + $alias['version'] = VersionParser::DEFAULT_BRANCH_ALIAS; + } + + return $alias; + }, $aliases); + + $lock = [ + '_readme' => ['This file locks the dependencies of your project to a known state', + 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies', + 'This file is @gener'.'ated automatically', ], + 'content-hash' => $this->contentHash, + 'packages' => $this->lockPackages($packages), + 'packages-dev' => null, + 'aliases' => $aliases, + 'minimum-stability' => $minimumStability, + 'stability-flags' => $stabilityFlags, + 'prefer-stable' => $preferStable, + 'prefer-lowest' => $preferLowest, + ]; + + if (null !== $devPackages) { + $lock['packages-dev'] = $this->lockPackages($devPackages); + } + + $lock['platform'] = $platformReqs; + $lock['platform-dev'] = $platformDevReqs; + if (\count($platformOverrides) > 0) { + $lock['platform-overrides'] = $platformOverrides; + } + $lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION; + + $lock = $this->fixupJsonDataType($lock); + + try { + $isLocked = $this->isLocked(); + } catch (ParsingException $e) { + $isLocked = false; + } + if (!$isLocked || $lock !== $this->getLockData()) { + if ($write) { + $this->lockFile->write($lock); + $this->lockDataCache = null; + $this->virtualFileWritten = false; + } else { + $this->virtualFileWritten = true; + $this->lockDataCache = JsonFile::parseJson(JsonFile::encode($lock)); + } + + return true; + } + + return false; + } + + /** + * Updates the lock file's hash in-place from a given composer.json's JsonFile + * + * This does not reload or require any packages, and retains the filemtime of the lock file. + * + * Use this only to update the lock file hash after updating a composer.json in ways that are guaranteed NOT to impact the dependency resolution. + * + * This is a risky method, use carefully. + * + * @param (callable(array): array)|null $dataProcessor Receives the lock data and can process it before it gets written to disk + */ + public function updateHash(JsonFile $composerJson, ?callable $dataProcessor = null): void + { + $contents = file_get_contents($composerJson->getPath()); + if (false === $contents) { + throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.'); + } + + $lockMtime = filemtime($this->lockFile->getPath()); + $lockData = $this->lockFile->read(); + $lockData['content-hash'] = Locker::getContentHash($contents); + if ($dataProcessor !== null) { + $lockData = $dataProcessor($lockData); + } + + $this->lockFile->write($this->fixupJsonDataType($lockData)); + $this->lockDataCache = null; + $this->virtualFileWritten = false; + if (is_int($lockMtime)) { + @touch($this->lockFile->getPath(), $lockMtime); + } + } + + /** + * Ensures correct data types and ordering for the JSON lock format + * + * @param array $lockData + * @return array + */ + private function fixupJsonDataType(array $lockData): array + { + foreach (['stability-flags', 'platform', 'platform-dev'] as $key) { + if (isset($lockData[$key]) && is_array($lockData[$key]) && \count($lockData[$key]) === 0) { + $lockData[$key] = new \stdClass(); + } + } + + if (is_array($lockData['stability-flags'])) { + ksort($lockData['stability-flags']); + } + + return $lockData; + } + + /** + * @param PackageInterface[] $packages + * + * @return mixed[][] + * + * @phpstan-return list> + */ + private function lockPackages(array $packages): array + { + $locked = []; + + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + continue; + } + + $name = $package->getPrettyName(); + $version = $package->getPrettyVersion(); + + if (!$name || !$version) { + throw new \LogicException(sprintf( + 'Package "%s" has no version or name and can not be locked', + $package + )); + } + + $spec = $this->dumper->dump($package); + unset($spec['version_normalized']); + + // always move time to the end of the package definition + $time = $spec['time'] ?? null; + unset($spec['time']); + if ($package->isDev() && $package->getInstallationSource() === 'source') { + // use the exact commit time of the current reference if it's a dev package + $time = $this->getPackageTime($package) ?: $time; + } + if (null !== $time) { + $spec['time'] = $time; + } + + unset($spec['installation-source']); + + $locked[] = $spec; + } + + usort($locked, static function ($a, $b) { + $comparison = strcmp($a['name'], $b['name']); + + if (0 !== $comparison) { + return $comparison; + } + + // If it is the same package, compare the versions to make the order deterministic + return strcmp($a['version'], $b['version']); + }); + + return $locked; + } + + /** + * Returns the packages's datetime for its source reference. + * + * @param PackageInterface $package The package to scan. + * @return string|null The formatted datetime or null if none was found. + */ + private function getPackageTime(PackageInterface $package): ?string + { + if (!function_exists('proc_open')) { + return null; + } + + $path = $this->installationManager->getInstallPath($package); + if ($path === null) { + return null; + } + $path = realpath($path); + $sourceType = $package->getSourceType(); + $datetime = null; + + if ($path && in_array($sourceType, ['git', 'hg'])) { + $sourceRef = $package->getSourceReference() ?: $package->getDistReference(); + switch ($sourceType) { + case 'git': + GitUtil::cleanEnv($this->process); + + $command = GitUtil::buildRevListCommand($this->process, array_merge(['-n1', '--format=%ct', (string) $sourceRef], GitUtil::getNoShowSignatureFlags($this->process))); + if (0 === $this->process->execute($command, $output, $path)) { + $output = trim(GitUtil::parseRevListOutput($output, $this->process)); + if (Preg::isMatch('{^\s*\d+\s*$}', $output)) { + $datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC')); + } + } + break; + + case 'hg': + if (0 === $this->process->execute(['hg', 'log', '--template', '{date|hgdate}', '-r', (string) $sourceRef], $output, $path) && Preg::isMatch('{^\s*(\d+)\s*}', $output, $match)) { + $datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC')); + } + break; + } + } + + return $datetime ? $datetime->format(DATE_RFC3339) : null; + } + + /** + * @return array + */ + public function getMissingRequirementInfo(RootPackageInterface $package, bool $includeDev): array + { + $missingRequirementInfo = []; + $missingRequirements = false; + $sets = [['repo' => $this->getLockedRepository(false), 'method' => 'getRequires', 'description' => 'Required']]; + if ($includeDev === true) { + $sets[] = ['repo' => $this->getLockedRepository(true), 'method' => 'getDevRequires', 'description' => 'Required (in require-dev)']; + } + $rootRepo = new RootPackageRepository(clone $package); + + foreach ($sets as $set) { + $installedRepo = new InstalledRepository([$set['repo'], $rootRepo]); + + foreach (call_user_func([$package, $set['method']]) as $link) { + if (PlatformRepository::isPlatformPackage($link->getTarget())) { + continue; + } + if ($link->getPrettyConstraint() === 'self.version') { + continue; + } + if ($installedRepo->findPackagesWithReplacersAndProviders($link->getTarget(), $link->getConstraint()) === []) { + $results = $installedRepo->findPackagesWithReplacersAndProviders($link->getTarget()); + + if ($results !== []) { + $provider = reset($results); + $description = $provider->getPrettyVersion(); + if ($provider->getName() !== $link->getTarget()) { + foreach (['getReplaces' => 'replaced as %s by %s', 'getProvides' => 'provided as %s by %s'] as $method => $text) { + foreach (call_user_func([$provider, $method]) as $providerLink) { + if ($providerLink->getTarget() === $link->getTarget()) { + $description = sprintf($text, $providerLink->getPrettyConstraint(), $provider->getPrettyName().' '.$provider->getPrettyVersion()); + break 2; + } + } + } + } + $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is in the lock file as "'.$description.'" but that does not satisfy your constraint "'.$link->getPrettyConstraint().'".'; + } else { + $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is not present in the lock file.'; + } + $missingRequirements = true; + } + } + } + + if ($missingRequirements) { + $missingRequirementInfo[] = 'This usually happens when composer files are incorrectly merged or the composer.json file is manually edited.'; + $missingRequirementInfo[] = 'Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md'; + $missingRequirementInfo[] = 'and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r'; + } + + return $missingRequirementInfo; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Package.php b/vendor/composer/composer/src/Composer/Package/Package.php new file mode 100644 index 000000000..0ed5da03a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Package.php @@ -0,0 +1,724 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; +use Composer\Util\ComposerMirror; + +/** + * Core package definitions that are needed to resolve dependencies and install packages + * + * @author Nils Adermann + * + * @phpstan-import-type AutoloadRules from PackageInterface + * @phpstan-import-type DevAutoloadRules from PackageInterface + * @phpstan-import-type PhpExtConfig from PackageInterface + */ +class Package extends BasePackage +{ + /** @var string */ + protected $type; + /** @var ?string */ + protected $targetDir; + /** @var 'source'|'dist'|null */ + protected $installationSource; + /** @var ?string */ + protected $sourceType; + /** @var ?string */ + protected $sourceUrl; + /** @var ?string */ + protected $sourceReference; + /** @var ?list */ + protected $sourceMirrors; + /** @var ?non-empty-string */ + protected $distType; + /** @var ?non-empty-string */ + protected $distUrl; + /** @var ?string */ + protected $distReference; + /** @var ?string */ + protected $distSha1Checksum; + /** @var ?list */ + protected $distMirrors; + /** @var string */ + protected $version; + /** @var string */ + protected $prettyVersion; + /** @var ?\DateTimeInterface */ + protected $releaseDate; + /** @var mixed[] */ + protected $extra = []; + /** @var string[] */ + protected $binaries = []; + /** @var bool */ + protected $dev; + /** + * @var string + * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + protected $stability; + /** @var ?string */ + protected $notificationUrl; + + /** @var array */ + protected $requires = []; + /** @var array */ + protected $conflicts = []; + /** @var array */ + protected $provides = []; + /** @var array */ + protected $replaces = []; + /** @var array */ + protected $devRequires = []; + /** @var array */ + protected $suggests = []; + /** + * @var array + * @phpstan-var AutoloadRules + */ + protected $autoload = []; + /** + * @var array + * @phpstan-var DevAutoloadRules + */ + protected $devAutoload = []; + /** @var string[] */ + protected $includePaths = []; + /** @var bool */ + protected $isDefaultBranch = false; + /** @var mixed[] */ + protected $transportOptions = []; + /** + * @var array|null + * @phpstan-var PhpExtConfig|null + */ + protected $phpExt = null; + + /** + * Creates a new in memory package. + * + * @param string $name The package's name + * @param string $version The package's version + * @param string $prettyVersion The package's non-normalized version + */ + public function __construct(string $name, string $version, string $prettyVersion) + { + parent::__construct($name); + + $this->version = $version; + $this->prettyVersion = $prettyVersion; + + $this->stability = VersionParser::parseStability($version); + $this->dev = $this->stability === 'dev'; + } + + /** + * @inheritDoc + */ + public function isDev(): bool + { + return $this->dev; + } + + public function setType(string $type): void + { + $this->type = $type; + } + + /** + * @inheritDoc + */ + public function getType(): string + { + return $this->type ?: 'library'; + } + + /** + * @inheritDoc + */ + public function getStability(): string + { + return $this->stability; + } + + public function setTargetDir(?string $targetDir): void + { + $this->targetDir = $targetDir; + } + + /** + * @inheritDoc + */ + public function getTargetDir(): ?string + { + if (null === $this->targetDir) { + return null; + } + + return ltrim(Preg::replace('{ (?:^|[\\\\/]+) \.\.? (?:[\\\\/]+|$) (?:\.\.? (?:[\\\\/]+|$) )*}x', '/', $this->targetDir), '/'); + } + + /** + * @param mixed[] $extra + */ + public function setExtra(array $extra): void + { + $this->extra = $extra; + } + + /** + * @inheritDoc + */ + public function getExtra(): array + { + return $this->extra; + } + + /** + * @param string[] $binaries + */ + public function setBinaries(array $binaries): void + { + $this->binaries = $binaries; + } + + /** + * @inheritDoc + */ + public function getBinaries(): array + { + return $this->binaries; + } + + /** + * @inheritDoc + */ + public function setInstallationSource(?string $type): void + { + $this->installationSource = $type; + } + + /** + * @inheritDoc + */ + public function getInstallationSource(): ?string + { + return $this->installationSource; + } + + public function setSourceType(?string $type): void + { + $this->sourceType = $type; + } + + /** + * @inheritDoc + */ + public function getSourceType(): ?string + { + return $this->sourceType; + } + + public function setSourceUrl(?string $url): void + { + $this->sourceUrl = $url; + } + + /** + * @inheritDoc + */ + public function getSourceUrl(): ?string + { + return $this->sourceUrl; + } + + public function setSourceReference(?string $reference): void + { + $this->sourceReference = $reference; + } + + /** + * @inheritDoc + */ + public function getSourceReference(): ?string + { + return $this->sourceReference; + } + + public function setSourceMirrors(?array $mirrors): void + { + $this->sourceMirrors = $mirrors; + } + + /** + * @inheritDoc + */ + public function getSourceMirrors(): ?array + { + return $this->sourceMirrors; + } + + /** + * @inheritDoc + */ + public function getSourceUrls(): array + { + return $this->getUrls($this->sourceUrl, $this->sourceMirrors, $this->sourceReference, $this->sourceType, 'source'); + } + + public function setDistType(?string $type): void + { + $this->distType = $type === '' ? null : $type; + } + + /** + * @inheritDoc + */ + public function getDistType(): ?string + { + return $this->distType; + } + + public function setDistUrl(?string $url): void + { + $this->distUrl = $url === '' ? null : $url; + } + + /** + * @inheritDoc + */ + public function getDistUrl(): ?string + { + return $this->distUrl; + } + + public function setDistReference(?string $reference): void + { + $this->distReference = $reference; + } + + /** + * @inheritDoc + */ + public function getDistReference(): ?string + { + return $this->distReference; + } + + public function setDistSha1Checksum(?string $sha1checksum): void + { + $this->distSha1Checksum = $sha1checksum; + } + + /** + * @inheritDoc + */ + public function getDistSha1Checksum(): ?string + { + return $this->distSha1Checksum; + } + + public function setDistMirrors(?array $mirrors): void + { + $this->distMirrors = $mirrors; + } + + /** + * @inheritDoc + */ + public function getDistMirrors(): ?array + { + return $this->distMirrors; + } + + /** + * @inheritDoc + */ + public function getDistUrls(): array + { + return $this->getUrls($this->distUrl, $this->distMirrors, $this->distReference, $this->distType, 'dist'); + } + + /** + * @inheritDoc + */ + public function getTransportOptions(): array + { + return $this->transportOptions; + } + + /** + * @inheritDoc + */ + public function setTransportOptions(array $options): void + { + $this->transportOptions = $options; + } + + /** + * @inheritDoc + */ + public function getVersion(): string + { + return $this->version; + } + + /** + * @inheritDoc + */ + public function getPrettyVersion(): string + { + return $this->prettyVersion; + } + + public function setReleaseDate(?\DateTimeInterface $releaseDate): void + { + $this->releaseDate = $releaseDate; + } + + /** + * @inheritDoc + */ + public function getReleaseDate(): ?\DateTimeInterface + { + return $this->releaseDate; + } + + /** + * Set the required packages + * + * @param array $requires A set of package links + */ + public function setRequires(array $requires): void + { + if (isset($requires[0])) { // @phpstan-ignore-line + $requires = $this->convertLinksToMap($requires, 'setRequires'); + } + + $this->requires = $requires; + } + + /** + * @inheritDoc + */ + public function getRequires(): array + { + return $this->requires; + } + + /** + * Set the conflicting packages + * + * @param array $conflicts A set of package links + */ + public function setConflicts(array $conflicts): void + { + if (isset($conflicts[0])) { // @phpstan-ignore-line + $conflicts = $this->convertLinksToMap($conflicts, 'setConflicts'); + } + + $this->conflicts = $conflicts; + } + + /** + * @inheritDoc + * @return array + */ + public function getConflicts(): array + { + return $this->conflicts; + } + + /** + * Set the provided virtual packages + * + * @param array $provides A set of package links + */ + public function setProvides(array $provides): void + { + if (isset($provides[0])) { // @phpstan-ignore-line + $provides = $this->convertLinksToMap($provides, 'setProvides'); + } + + $this->provides = $provides; + } + + /** + * @inheritDoc + * @return array + */ + public function getProvides(): array + { + return $this->provides; + } + + /** + * Set the packages this one replaces + * + * @param array $replaces A set of package links + */ + public function setReplaces(array $replaces): void + { + if (isset($replaces[0])) { // @phpstan-ignore-line + $replaces = $this->convertLinksToMap($replaces, 'setReplaces'); + } + + $this->replaces = $replaces; + } + + /** + * @inheritDoc + * @return array + */ + public function getReplaces(): array + { + return $this->replaces; + } + + /** + * Set the recommended packages + * + * @param array $devRequires A set of package links + */ + public function setDevRequires(array $devRequires): void + { + if (isset($devRequires[0])) { // @phpstan-ignore-line + $devRequires = $this->convertLinksToMap($devRequires, 'setDevRequires'); + } + + $this->devRequires = $devRequires; + } + + /** + * @inheritDoc + */ + public function getDevRequires(): array + { + return $this->devRequires; + } + + /** + * Set the suggested packages + * + * @param array $suggests A set of package names/comments + */ + public function setSuggests(array $suggests): void + { + $this->suggests = $suggests; + } + + /** + * @inheritDoc + */ + public function getSuggests(): array + { + return $this->suggests; + } + + /** + * Set the autoload mapping + * + * @param array $autoload Mapping of autoloading rules + * + * @phpstan-param AutoloadRules $autoload + */ + public function setAutoload(array $autoload): void + { + $this->autoload = $autoload; + } + + /** + * @inheritDoc + */ + public function getAutoload(): array + { + return $this->autoload; + } + + /** + * Set the dev autoload mapping + * + * @param array $devAutoload Mapping of dev autoloading rules + * + * @phpstan-param DevAutoloadRules $devAutoload + */ + public function setDevAutoload(array $devAutoload): void + { + $this->devAutoload = $devAutoload; + } + + /** + * @inheritDoc + */ + public function getDevAutoload(): array + { + return $this->devAutoload; + } + + /** + * Sets the list of paths added to PHP's include path. + * + * @param string[] $includePaths List of directories. + */ + public function setIncludePaths(array $includePaths): void + { + $this->includePaths = $includePaths; + } + + /** + * @inheritDoc + */ + public function getIncludePaths(): array + { + return $this->includePaths; + } + + /** + * Sets the settings for php extension packages + * + * + * @phpstan-param PhpExtConfig|null $phpExt + */ + public function setPhpExt(?array $phpExt): void + { + $this->phpExt = $phpExt; + } + + /** + * @inheritDoc + */ + public function getPhpExt(): ?array + { + return $this->phpExt; + } + + /** + * Sets the notification URL + */ + public function setNotificationUrl(string $notificationUrl): void + { + $this->notificationUrl = $notificationUrl; + } + + /** + * @inheritDoc + */ + public function getNotificationUrl(): ?string + { + return $this->notificationUrl; + } + + public function setIsDefaultBranch(bool $defaultBranch): void + { + $this->isDefaultBranch = $defaultBranch; + } + + /** + * @inheritDoc + */ + public function isDefaultBranch(): bool + { + return $this->isDefaultBranch; + } + + /** + * @inheritDoc + */ + public function setSourceDistReferences(string $reference): void + { + $this->setSourceReference($reference); + + // only bitbucket, github and gitlab have auto generated dist URLs that easily allow replacing the reference in the dist URL + // TODO generalize this a bit for self-managed/on-prem versions? Some kind of replace token in dist urls which allow this? + if ( + $this->getDistUrl() !== null + && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $this->getDistUrl()) + ) { + $this->setDistReference($reference); + $this->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $this->getDistUrl())); + } elseif ($this->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it + $this->setDistReference($reference); + } + } + + /** + * Replaces current version and pretty version with passed values. + * It also sets stability. + * + * @param string $version The package's normalized version + * @param string $prettyVersion The package's non-normalized version + */ + public function replaceVersion(string $version, string $prettyVersion): void + { + $this->version = $version; + $this->prettyVersion = $prettyVersion; + + $this->stability = VersionParser::parseStability($version); + $this->dev = $this->stability === 'dev'; + } + + /** + * @param mixed[]|null $mirrors + * + * @return list + * + * @phpstan-param list|null $mirrors + */ + protected function getUrls(?string $url, ?array $mirrors, ?string $ref, ?string $type, string $urlType): array + { + if (!$url) { + return []; + } + + if ($urlType === 'dist' && false !== strpos($url, '%')) { + $url = ComposerMirror::processUrl($url, $this->name, $this->version, $ref, $type, $this->prettyVersion); + } + + $urls = [$url]; + if ($mirrors) { + foreach ($mirrors as $mirror) { + if ($urlType === 'dist') { + $mirrorUrl = ComposerMirror::processUrl($mirror['url'], $this->name, $this->version, $ref, $type, $this->prettyVersion); + } elseif ($urlType === 'source' && $type === 'git') { + $mirrorUrl = ComposerMirror::processGitUrl($mirror['url'], $this->name, $url, $type); + } elseif ($urlType === 'source' && $type === 'hg') { + $mirrorUrl = ComposerMirror::processHgUrl($mirror['url'], $this->name, $url, $type); + } else { + continue; + } + if (!\in_array($mirrorUrl, $urls)) { + $func = $mirror['preferred'] ? 'array_unshift' : 'array_push'; + $func($urls, $mirrorUrl); + } + } + } + + return $urls; + } + + /** + * @param array $links + * @return array + */ + private function convertLinksToMap(array $links, string $source): array + { + trigger_error('Package::'.$source.' must be called with a map of lowercased package name => Link object, got a indexed array, this is deprecated and you should fix your usage.'); + $newLinks = []; + foreach ($links as $link) { + $newLinks[$link->getTarget()] = $link; + } + + return $newLinks; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/PackageInterface.php b/vendor/composer/composer/src/Composer/Package/PackageInterface.php new file mode 100644 index 000000000..0bfece430 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/PackageInterface.php @@ -0,0 +1,394 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Repository\RepositoryInterface; + +/** + * Defines the essential information a package has that is used during solving/installation + * + * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice. + * + * @author Jordi Boggiano + * + * @phpstan-type AutoloadRules array{psr-0?: array, psr-4?: array, classmap?: list, files?: list, exclude-from-classmap?: list} + * @phpstan-type DevAutoloadRules array{psr-0?: array, psr-4?: array, classmap?: list, files?: list} + * @phpstan-type PhpExtConfig array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, build-path?: string|null, download-url-method?: string|list, os-families?: non-empty-list, os-families-exclude?: non-empty-list, configure-options?: list} + */ +interface PackageInterface +{ + public const DISPLAY_SOURCE_REF_IF_DEV = 0; + public const DISPLAY_SOURCE_REF = 1; + public const DISPLAY_DIST_REF = 2; + + /** + * Returns the package's name without version info, thus not a unique identifier + * + * @return string package name + */ + public function getName(): string; + + /** + * Returns the package's pretty (i.e. with proper case) name + * + * @return string package name + */ + public function getPrettyName(): string; + + /** + * Returns a set of names that could refer to this package + * + * No version or release type information should be included in any of the + * names. Provided or replaced package names need to be returned as well. + * + * @param bool $provides Whether provided names should be included + * + * @return string[] An array of strings referring to this package + */ + public function getNames(bool $provides = true): array; + + /** + * Allows the solver to set an id for this package to refer to it. + */ + public function setId(int $id): void; + + /** + * Retrieves the package's id set through setId + * + * @return int The previously set package id + */ + public function getId(): int; + + /** + * Returns whether the package is a development virtual package or a concrete one + */ + public function isDev(): bool; + + /** + * Returns the package type, e.g. library + * + * @return string The package type + */ + public function getType(): string; + + /** + * Returns the package targetDir property + * + * @return ?string The package targetDir + */ + public function getTargetDir(): ?string; + + /** + * Returns the package extra data + * + * @return mixed[] The package extra data + */ + public function getExtra(): array; + + /** + * Sets source from which this package was installed (source/dist). + * + * @param ?string $type source/dist + * @phpstan-param 'source'|'dist'|null $type + */ + public function setInstallationSource(?string $type): void; + + /** + * Returns source from which this package was installed (source/dist). + * + * @return ?string source/dist + * @phpstan-return 'source'|'dist'|null + */ + public function getInstallationSource(): ?string; + + /** + * Returns the repository type of this package, e.g. git, svn + * + * @return ?string The repository type + */ + public function getSourceType(): ?string; + + /** + * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git + * + * @return ?string The repository url + */ + public function getSourceUrl(): ?string; + + /** + * Returns the repository urls of this package including mirrors, e.g. git://github.com/naderman/composer.git + * + * @return list + */ + public function getSourceUrls(): array; + + /** + * Returns the repository reference of this package, e.g. master, 1.0.0 or a commit hash for git + * + * @return ?string The repository reference + */ + public function getSourceReference(): ?string; + + /** + * Returns the source mirrors of this package + * + * @return ?list + */ + public function getSourceMirrors(): ?array; + + /** + * @param null|list $mirrors + */ + public function setSourceMirrors(?array $mirrors): void; + + /** + * Returns the type of the distribution archive of this version, e.g. zip, tarball + * + * @return ?string The repository type + */ + public function getDistType(): ?string; + + /** + * Returns the url of the distribution archive of this version + * + * @return ?non-empty-string + */ + public function getDistUrl(): ?string; + + /** + * Returns the urls of the distribution archive of this version, including mirrors + * + * @return non-empty-string[] + */ + public function getDistUrls(): array; + + /** + * Returns the reference of the distribution archive of this version, e.g. master, 1.0.0 or a commit hash for git + */ + public function getDistReference(): ?string; + + /** + * Returns the sha1 checksum for the distribution archive of this version + * + * Can be an empty string which should be treated as null + */ + public function getDistSha1Checksum(): ?string; + + /** + * Returns the dist mirrors of this package + * + * @return ?list + */ + public function getDistMirrors(): ?array; + + /** + * @param null|list $mirrors + */ + public function setDistMirrors(?array $mirrors): void; + + /** + * Returns the version of this package + * + * @return string version + */ + public function getVersion(): string; + + /** + * Returns the pretty (i.e. non-normalized) version string of this package + * + * @return string version + */ + public function getPrettyVersion(): string; + + /** + * Returns the pretty version string plus a git or hg commit hash of this package + * + * @see getPrettyVersion + * + * @param bool $truncate If the source reference is a sha1 hash, truncate it + * @param int $displayMode One of the DISPLAY_ constants on this interface determining display of references + * @return string version + * + * @phpstan-param self::DISPLAY_SOURCE_REF_IF_DEV|self::DISPLAY_SOURCE_REF|self::DISPLAY_DIST_REF $displayMode + */ + public function getFullPrettyVersion(bool $truncate = true, int $displayMode = self::DISPLAY_SOURCE_REF_IF_DEV): string; + + /** + * Returns the release date of the package + */ + public function getReleaseDate(): ?\DateTimeInterface; + + /** + * Returns the stability of this package: one of (dev, alpha, beta, RC, stable) + * + * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + public function getStability(): string; + + /** + * Returns a set of links to packages which need to be installed before + * this package can be installed + * + * @return array A map of package links defining required packages, indexed by the require package's name + */ + public function getRequires(): array; + + /** + * Returns a set of links to packages which must not be installed at the + * same time as this package + * + * @return Link[] An array of package links defining conflicting packages + */ + public function getConflicts(): array; + + /** + * Returns a set of links to virtual packages that are provided through + * this package + * + * @return Link[] An array of package links defining provided packages + */ + public function getProvides(): array; + + /** + * Returns a set of links to packages which can alternatively be + * satisfied by installing this package + * + * @return Link[] An array of package links defining replaced packages + */ + public function getReplaces(): array; + + /** + * Returns a set of links to packages which are required to develop + * this package. These are installed if in dev mode. + * + * @return array A map of package links defining packages required for development, indexed by the require package's name + */ + public function getDevRequires(): array; + + /** + * Returns a set of package names and reasons why they are useful in + * combination with this package. + * + * @return array An array of package suggestions with descriptions + * @phpstan-return array + */ + public function getSuggests(): array; + + /** + * Returns an associative array of autoloading rules + * + * {"": {""}} + * + * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to + * directories for autoloading using the type specified. + * + * @return array Mapping of autoloading rules + * @phpstan-return AutoloadRules + */ + public function getAutoload(): array; + + /** + * Returns an associative array of dev autoloading rules + * + * {"": {""}} + * + * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to + * directories for autoloading using the type specified. + * + * @return array Mapping of dev autoloading rules + * @phpstan-return DevAutoloadRules + */ + public function getDevAutoload(): array; + + /** + * Returns a list of directories which should get added to PHP's + * include path. + * + * @return string[] + */ + public function getIncludePaths(): array; + + /** + * Returns the settings for php extension packages + * + * + * @phpstan-return PhpExtConfig|null + */ + public function getPhpExt(): ?array; + + /** + * Stores a reference to the repository that owns the package + */ + public function setRepository(RepositoryInterface $repository): void; + + /** + * Returns a reference to the repository that owns the package + */ + public function getRepository(): ?RepositoryInterface; + + /** + * Returns the package binaries + * + * @return string[] + */ + public function getBinaries(): array; + + /** + * Returns package unique name, constructed from name and version. + */ + public function getUniqueName(): string; + + /** + * Returns the package notification url + */ + public function getNotificationUrl(): ?string; + + /** + * Converts the package into a readable and unique string + */ + public function __toString(): string; + + /** + * Converts the package into a pretty readable string + */ + public function getPrettyString(): string; + + public function isDefaultBranch(): bool; + + /** + * Returns a list of options to download package dist files + * + * @return mixed[] + */ + public function getTransportOptions(): array; + + /** + * Configures the list of options to download package dist files + * + * @param mixed[] $options + */ + public function setTransportOptions(array $options): void; + + public function setSourceReference(?string $reference): void; + + public function setDistUrl(?string $url): void; + + public function setDistType(?string $type): void; + + public function setDistReference(?string $reference): void; + + /** + * Set dist and source references and update dist URL for ones that contain a reference + */ + public function setSourceDistReferences(string $reference): void; +} diff --git a/vendor/composer/composer/src/Composer/Package/RootAliasPackage.php b/vendor/composer/composer/src/Composer/Package/RootAliasPackage.php new file mode 100644 index 000000000..57768d574 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/RootAliasPackage.php @@ -0,0 +1,223 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * @author Jordi Boggiano + */ +class RootAliasPackage extends CompleteAliasPackage implements RootPackageInterface +{ + /** @var RootPackage */ + protected $aliasOf; + + /** + * All descendants' constructors should call this parent constructor + * + * @param RootPackage $aliasOf The package this package is an alias of + * @param string $version The version the alias must report + * @param string $prettyVersion The alias's non-normalized version + */ + public function __construct(RootPackage $aliasOf, string $version, string $prettyVersion) + { + parent::__construct($aliasOf, $version, $prettyVersion); + } + + /** + * @return RootPackage + */ + public function getAliasOf() + { + return $this->aliasOf; + } + + /** + * @inheritDoc + */ + public function getAliases(): array + { + return $this->aliasOf->getAliases(); + } + + /** + * @inheritDoc + */ + public function getMinimumStability(): string + { + return $this->aliasOf->getMinimumStability(); + } + + /** + * @inheritDoc + */ + public function getStabilityFlags(): array + { + return $this->aliasOf->getStabilityFlags(); + } + + /** + * @inheritDoc + */ + public function getReferences(): array + { + return $this->aliasOf->getReferences(); + } + + /** + * @inheritDoc + */ + public function getPreferStable(): bool + { + return $this->aliasOf->getPreferStable(); + } + + /** + * @inheritDoc + */ + public function getConfig(): array + { + return $this->aliasOf->getConfig(); + } + + /** + * @inheritDoc + */ + public function setRequires(array $requires): void + { + $this->requires = $this->replaceSelfVersionDependencies($requires, Link::TYPE_REQUIRE); + + $this->aliasOf->setRequires($requires); + } + + /** + * @inheritDoc + */ + public function setDevRequires(array $devRequires): void + { + $this->devRequires = $this->replaceSelfVersionDependencies($devRequires, Link::TYPE_DEV_REQUIRE); + + $this->aliasOf->setDevRequires($devRequires); + } + + /** + * @inheritDoc + */ + public function setConflicts(array $conflicts): void + { + $this->conflicts = $this->replaceSelfVersionDependencies($conflicts, Link::TYPE_CONFLICT); + $this->aliasOf->setConflicts($conflicts); + } + + /** + * @inheritDoc + */ + public function setProvides(array $provides): void + { + $this->provides = $this->replaceSelfVersionDependencies($provides, Link::TYPE_PROVIDE); + $this->aliasOf->setProvides($provides); + } + + /** + * @inheritDoc + */ + public function setReplaces(array $replaces): void + { + $this->replaces = $this->replaceSelfVersionDependencies($replaces, Link::TYPE_REPLACE); + $this->aliasOf->setReplaces($replaces); + } + + /** + * @inheritDoc + */ + public function setAutoload(array $autoload): void + { + $this->aliasOf->setAutoload($autoload); + } + + /** + * @inheritDoc + */ + public function setDevAutoload(array $devAutoload): void + { + $this->aliasOf->setDevAutoload($devAutoload); + } + + /** + * @inheritDoc + */ + public function setStabilityFlags(array $stabilityFlags): void + { + $this->aliasOf->setStabilityFlags($stabilityFlags); + } + + /** + * @inheritDoc + */ + public function setMinimumStability(string $minimumStability): void + { + $this->aliasOf->setMinimumStability($minimumStability); + } + + /** + * @inheritDoc + */ + public function setPreferStable(bool $preferStable): void + { + $this->aliasOf->setPreferStable($preferStable); + } + + /** + * @inheritDoc + */ + public function setConfig(array $config): void + { + $this->aliasOf->setConfig($config); + } + + /** + * @inheritDoc + */ + public function setReferences(array $references): void + { + $this->aliasOf->setReferences($references); + } + + /** + * @inheritDoc + */ + public function setAliases(array $aliases): void + { + $this->aliasOf->setAliases($aliases); + } + + /** + * @inheritDoc + */ + public function setSuggests(array $suggests): void + { + $this->aliasOf->setSuggests($suggests); + } + + /** + * @inheritDoc + */ + public function setExtra(array $extra): void + { + $this->aliasOf->setExtra($extra); + } + + public function __clone() + { + parent::__clone(); + $this->aliasOf = clone $this->aliasOf; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/RootPackage.php b/vendor/composer/composer/src/Composer/Package/RootPackage.php new file mode 100644 index 000000000..a0f8e659f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/RootPackage.php @@ -0,0 +1,132 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * The root package represents the project's composer.json and contains additional metadata + * + * @author Jordi Boggiano + */ +class RootPackage extends CompletePackage implements RootPackageInterface +{ + public const DEFAULT_PRETTY_VERSION = '1.0.0+no-version-set'; + + /** @var key-of */ + protected $minimumStability = 'stable'; + /** @var bool */ + protected $preferStable = false; + /** @var array Map of package name to stability constant */ + protected $stabilityFlags = []; + /** @var mixed[] */ + protected $config = []; + /** @var array Map of package name to reference/commit hash */ + protected $references = []; + /** @var list */ + protected $aliases = []; + + /** + * @inheritDoc + */ + public function setMinimumStability(string $minimumStability): void + { + $this->minimumStability = $minimumStability; + } + + /** + * @inheritDoc + */ + public function getMinimumStability(): string + { + return $this->minimumStability; + } + + /** + * @inheritDoc + */ + public function setStabilityFlags(array $stabilityFlags): void + { + $this->stabilityFlags = $stabilityFlags; + } + + /** + * @inheritDoc + */ + public function getStabilityFlags(): array + { + return $this->stabilityFlags; + } + + /** + * @inheritDoc + */ + public function setPreferStable(bool $preferStable): void + { + $this->preferStable = $preferStable; + } + + /** + * @inheritDoc + */ + public function getPreferStable(): bool + { + return $this->preferStable; + } + + /** + * @inheritDoc + */ + public function setConfig(array $config): void + { + $this->config = $config; + } + + /** + * @inheritDoc + */ + public function getConfig(): array + { + return $this->config; + } + + /** + * @inheritDoc + */ + public function setReferences(array $references): void + { + $this->references = $references; + } + + /** + * @inheritDoc + */ + public function getReferences(): array + { + return $this->references; + } + + /** + * @inheritDoc + */ + public function setAliases(array $aliases): void + { + $this->aliases = $aliases; + } + + /** + * @inheritDoc + */ + public function getAliases(): array + { + return $this->aliases; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/RootPackageInterface.php b/vendor/composer/composer/src/Composer/Package/RootPackageInterface.php new file mode 100644 index 000000000..8a08060f8 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/RootPackageInterface.php @@ -0,0 +1,173 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * Defines additional fields that are only needed for the root package + * + * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice. + * + * @author Jordi Boggiano + * + * @phpstan-import-type AutoloadRules from PackageInterface + * @phpstan-import-type DevAutoloadRules from PackageInterface + */ +interface RootPackageInterface extends CompletePackageInterface +{ + /** + * Returns a set of package names and their aliases + * + * @return list + */ + public function getAliases(): array; + + /** + * Returns the minimum stability of the package + * + * @return key-of + */ + public function getMinimumStability(): string; + + /** + * Returns the stability flags to apply to dependencies + * + * array('foo/bar' => 'dev') + * + * @return array + */ + public function getStabilityFlags(): array; + + /** + * Returns a set of package names and source references that must be enforced on them + * + * array('foo/bar' => 'abcd1234') + * + * @return array + */ + public function getReferences(): array; + + /** + * Returns true if the root package prefers picking stable packages over unstable ones + */ + public function getPreferStable(): bool; + + /** + * Returns the root package's configuration + * + * @return mixed[] + */ + public function getConfig(): array; + + /** + * Set the required packages + * + * @param Link[] $requires A set of package links + */ + public function setRequires(array $requires): void; + + /** + * Set the recommended packages + * + * @param Link[] $devRequires A set of package links + */ + public function setDevRequires(array $devRequires): void; + + /** + * Set the conflicting packages + * + * @param Link[] $conflicts A set of package links + */ + public function setConflicts(array $conflicts): void; + + /** + * Set the provided virtual packages + * + * @param Link[] $provides A set of package links + */ + public function setProvides(array $provides): void; + + /** + * Set the packages this one replaces + * + * @param Link[] $replaces A set of package links + */ + public function setReplaces(array $replaces): void; + + /** + * Set the autoload mapping + * + * @param array $autoload Mapping of autoloading rules + * @phpstan-param AutoloadRules $autoload + */ + public function setAutoload(array $autoload): void; + + /** + * Set the dev autoload mapping + * + * @param array $devAutoload Mapping of dev autoloading rules + * @phpstan-param DevAutoloadRules $devAutoload + */ + public function setDevAutoload(array $devAutoload): void; + + /** + * Set the stabilityFlags + * + * @phpstan-param array $stabilityFlags + */ + public function setStabilityFlags(array $stabilityFlags): void; + + /** + * Set the minimumStability + * + * @phpstan-param key-of $minimumStability + */ + public function setMinimumStability(string $minimumStability): void; + + /** + * Set the preferStable + */ + public function setPreferStable(bool $preferStable): void; + + /** + * Set the config + * + * @param mixed[] $config + */ + public function setConfig(array $config): void; + + /** + * Set the references + * + * @param array $references + */ + public function setReferences(array $references): void; + + /** + * Set the aliases + * + * @param list $aliases + */ + public function setAliases(array $aliases): void; + + /** + * Set the suggested packages + * + * @param array $suggests A set of package names/comments + */ + public function setSuggests(array $suggests): void; + + /** + * @param mixed[] $extra + */ + public function setExtra(array $extra): void; +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php b/vendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php new file mode 100644 index 000000000..7e0182a6e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php @@ -0,0 +1,49 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Package\BasePackage; + +/** + * @author Jordi Boggiano + */ +class StabilityFilter +{ + /** + * Checks if any of the provided package names in the given stability match the configured acceptable stability and flags + * + * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value + * @phpstan-param array, BasePackage::STABILITY_*> $acceptableStabilities + * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array $stabilityFlags + * @param string[] $names The package name(s) to check for stability flags + * @param key-of $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' + * @return bool true if any package name is acceptable + */ + public static function isPackageAcceptable(array $acceptableStabilities, array $stabilityFlags, array $names, string $stability): bool + { + foreach ($names as $name) { + // allow if package matches the package-specific stability flag + if (isset($stabilityFlags[$name])) { + if (BasePackage::STABILITIES[$stability] <= $stabilityFlags[$name]) { + return true; + } + } elseif (isset($acceptableStabilities[$stability])) { + // allow if package matches the global stability requirement and has no exception + return true; + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/VersionBumper.php b/vendor/composer/composer/src/Composer/Package/Version/VersionBumper.php new file mode 100644 index 000000000..d6265b55c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/VersionBumper.php @@ -0,0 +1,128 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Package\PackageInterface; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Pcre\Preg; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Intervals; +use Composer\Util\Platform; + +/** + * @author Jordi Boggiano + * @internal + */ +class VersionBumper +{ + /** + * Given a constraint, this returns a new constraint with + * the lower bound bumped to match the given package's version. + * + * For example: + * * ^1.0 + 1.2.1 -> ^1.2.1 + * * ^1.2 + 1.2.0 -> ^1.2 + * * ^1.2.0 + 1.3.0 -> ^1.3.0 + * * ^1.2 || ^2.3 + 1.3.0 -> ^1.3 || ^2.3 + * * ^1.2 || ^2.3 + 2.4.0 -> ^1.2 || ^2.4 + * * ^3@dev + 3.2.99999-dev -> ^3.2@dev + * * ~2 + 2.0-beta.1 -> ~2 + * * ~2.0.0 + 2.0.3 -> ~2.0.3 + * * ~2.0 + 2.0.3 -> ^2.0.3 + * * dev-master + dev-master -> dev-master + * * * + 1.2.3 -> >=1.2.3 + */ + public function bumpRequirement(ConstraintInterface $constraint, PackageInterface $package): string + { + $parser = new VersionParser(); + $prettyConstraint = $constraint->getPrettyString(); + if (str_starts_with($constraint->getPrettyString(), 'dev-')) { + return $prettyConstraint; + } + + $version = $package->getVersion(); + if (str_starts_with($package->getVersion(), 'dev-')) { + $loader = new ArrayLoader($parser); + $dumper = new ArrayDumper(); + $extra = $loader->getBranchAlias($dumper->dump($package)); + + // dev packages without branch alias cannot be processed + if (null === $extra || $extra === VersionParser::DEFAULT_BRANCH_ALIAS) { + return $prettyConstraint; + } + + $version = $extra; + } + + $intervals = Intervals::get($constraint); + + // complex constraints with branch names are not bumped + if (\count($intervals['branches']['names']) > 0) { + return $prettyConstraint; + } + + $major = Preg::replace('{^([1-9][0-9]*|0\.\d+).*}', '$1', $version); + $versionWithoutSuffix = Preg::replace('{(?:\.(?:0|9999999))+(-dev)?$}', '', $version); + $newPrettyConstraint = '^'.$versionWithoutSuffix; + + // not a simple stable version, abort + if (!Preg::isMatch('{^\^\d+(\.\d+)*$}', $newPrettyConstraint)) { + return $prettyConstraint; + } + + $pattern = '{ + (?<=,|\ |\||^) # leading separator + (?P + \^v?'.$major.'(?:\.\d+)* # e.g. ^2.anything + | ~v?'.$major.'(?:\.\d+){1,3} # e.g. ~2.2 or ~2.2.2 or ~2.2.2.2 + | v?'.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc + | >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc + | \* # full wildcard + ) + (?=,|$|\ |\||@) # trailing separator + }x'; + if (Preg::isMatchAllWithOffsets($pattern, $prettyConstraint, $matches)) { + $modified = $prettyConstraint; + foreach (array_reverse($matches['constraint']) as $match) { + assert(is_string($match[0])); + $suffix = ''; + if (substr_count($match[0], '.') === 2 && substr_count($versionWithoutSuffix, '.') === 1) { + $suffix = '.0'; + } + if (str_starts_with($match[0], '~') && substr_count($match[0], '.') !== 1) { + // take as many version bits from the current version as we have in the constraint to bump it without making it more specific + $versionBits = explode('.', $versionWithoutSuffix); + $versionBits = array_pad($versionBits, substr_count($match[0], '.') + 1, '0'); + $replacement = '~'.implode('.', array_slice($versionBits, 0, substr_count($match[0], '.') + 1)); + } elseif ($match[0] === '*' || str_starts_with($match[0], '>=')) { + $replacement = '>='.$versionWithoutSuffix.$suffix; + } else { + $replacement = $newPrettyConstraint.$suffix; + } + $modified = substr_replace($modified, $replacement, $match[1], Platform::strlen($match[0])); + } + + // if it is strictly equal to the previous one then no need to change anything + $newConstraint = $parser->parseConstraints($modified); + if (Intervals::isSubsetOf($newConstraint, $constraint) && Intervals::isSubsetOf($constraint, $newConstraint)) { + return $prettyConstraint; + } + + return $modified; + } + + return $prettyConstraint; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php b/vendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php new file mode 100644 index 000000000..58201854c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php @@ -0,0 +1,448 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Repository\Vcs\HgDriver; +use Composer\IO\NullIO; +use Composer\Semver\VersionParser as SemverVersionParser; +use Composer\Util\Git as GitUtil; +use Composer\Util\HttpDownloader; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\Svn as SvnUtil; +use Symfony\Component\Process\Process; + +/** + * Try to guess the current version number based on different VCS configuration. + * + * @author Jordi Boggiano + * @author Samuel Roze + * + * @phpstan-type Version array{version: string, commit: string|null, pretty_version: string|null}|array{version: string, commit: string|null, pretty_version: string|null, feature_version: string|null, feature_pretty_version: string|null} + */ +class VersionGuesser +{ + /** + * @var Config + */ + private $config; + + /** + * @var ProcessExecutor + */ + private $process; + + /** + * @var SemverVersionParser + */ + private $versionParser; + + /** + * @var IOInterface|null + */ + private $io; + + public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser, ?IOInterface $io = null) + { + $this->config = $config; + $this->process = $process; + $this->versionParser = $versionParser; + $this->io = $io; + } + + /** + * @param array $packageConfig + * @param string $path Path to guess into + * + * @phpstan-return Version|null + */ + public function guessVersion(array $packageConfig, string $path): ?array + { + if (!function_exists('proc_open')) { + return null; + } + + // bypass version guessing in bash completions as it takes time to create + // new processes and the root version is usually not that important + if (Platform::isInputCompletionProcess()) { + return null; + } + + $versionData = $this->guessGitVersion($packageConfig, $path); + if (null !== $versionData['version']) { + return $this->postprocess($versionData); + } + + $versionData = $this->guessHgVersion($packageConfig, $path); + if (null !== $versionData && null !== $versionData['version']) { + return $this->postprocess($versionData); + } + + $versionData = $this->guessFossilVersion($path); + if (null !== $versionData['version']) { + return $this->postprocess($versionData); + } + + $versionData = $this->guessSvnVersion($packageConfig, $path); + if (null !== $versionData && null !== $versionData['version']) { + return $this->postprocess($versionData); + } + + return null; + } + + /** + * @phpstan-param Version $versionData + * + * @phpstan-return Version + */ + private function postprocess(array $versionData): array + { + if (!empty($versionData['feature_version']) && $versionData['feature_version'] === $versionData['version'] && $versionData['feature_pretty_version'] === $versionData['pretty_version']) { + unset($versionData['feature_version'], $versionData['feature_pretty_version']); + } + + if ('-dev' === substr($versionData['version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['version'])) { + $versionData['pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['version']); + } + + if (!empty($versionData['feature_version']) && '-dev' === substr($versionData['feature_version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['feature_version'])) { + $versionData['feature_pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['feature_version']); + } + + return $versionData; + } + + /** + * @param array $packageConfig + * + * @return array{version: string|null, commit: string|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null} + */ + private function guessGitVersion(array $packageConfig, string $path): array + { + GitUtil::cleanEnv($this->process); + $commit = null; + $version = null; + $prettyVersion = null; + $featureVersion = null; + $featurePrettyVersion = null; + $isDetached = false; + + // try to fetch current version from git branch + if (0 === $this->process->execute(['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], $output, $path)) { + $branches = []; + $isFeatureBranch = false; + + // find current branch and collect all branch names + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && Preg::isMatchStrictGroups('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\(HEAD detached at \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) { + if ( + $match[1] === '(no branch)' + || strpos($match[1], '(detached ') === 0 + || strpos($match[1], '(HEAD detached at') === 0 + ) { + $version = 'dev-' . $match[2]; + $prettyVersion = $version; + $isFeatureBranch = true; + $isDetached = true; + } else { + $version = $this->versionParser->normalizeBranch($match[1]); + $prettyVersion = 'dev-' . $match[1]; + $isFeatureBranch = $this->isFeatureBranch($packageConfig, $match[1]); + } + + $commit = $match[2]; + } + + if ($branch && !Preg::isMatchStrictGroups('{^ *.+/HEAD }', $branch)) { + if (Preg::isMatchStrictGroups('{^(?:\* )? *((?:remotes/(?:origin|upstream)/)?[^\s/]+) *([a-f0-9]+) .*$}', $branch, $match)) { + $branches[] = $match[1]; + } + } + } + + if ($isFeatureBranch) { + $featureVersion = $version; + $featurePrettyVersion = $prettyVersion; + + // try to find the best (nearest) version branch to assume this feature's version + $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['git', 'rev-list', '%candidate%..%branch%'], $path); + $version = $result['version']; + $prettyVersion = $result['pretty_version']; + } + } + GitUtil::checkForRepoOwnershipError($this->process->getErrorOutput(), $path, $this->io); + + if (!$version || $isDetached) { + $result = $this->versionFromGitTags($path); + if ($result) { + $version = $result['version']; + $prettyVersion = $result['pretty_version']; + $featureVersion = null; + $featurePrettyVersion = null; + } + } + + if (null === $commit) { + $command = GitUtil::buildRevListCommand($this->process, array_merge(['--format=%H', '-n1', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process))); + if (0 === $this->process->execute($command, $output, $path)) { + $commit = trim(GitUtil::parseRevListOutput($output, $this->process)) ?: null; + } + } + + if ($featureVersion) { + return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion, 'feature_version' => $featureVersion, 'feature_pretty_version' => $featurePrettyVersion]; + } + + return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion]; + } + + /** + * @return array{version: string, pretty_version: string}|null + */ + private function versionFromGitTags(string $path): ?array + { + // try to fetch current version from git tags + if (0 === $this->process->execute(['git', 'describe', '--exact-match', '--tags'], $output, $path)) { + try { + $version = $this->versionParser->normalize(trim($output)); + + return ['version' => $version, 'pretty_version' => trim($output)]; + } catch (\Exception $e) { + } + } + + return null; + } + + /** + * @param array $packageConfig + * + * @return array{version: string|null, commit: ''|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null}|null + */ + private function guessHgVersion(array $packageConfig, string $path): ?array + { + // try to fetch current version from hg branch + if (0 === $this->process->execute(['hg', 'branch'], $output, $path)) { + $branch = trim($output); + $version = $this->versionParser->normalizeBranch($branch); + $isFeatureBranch = 0 === strpos($version, 'dev-'); + + if (VersionParser::DEFAULT_BRANCH_ALIAS === $version) { + return ['version' => $version, 'commit' => null, 'pretty_version' => 'dev-'.$branch]; + } + + if (!$isFeatureBranch) { + return ['version' => $version, 'commit' => null, 'pretty_version' => $version]; + } + + // re-use the HgDriver to fetch branches (this properly includes bookmarks) + $io = new NullIO(); + $driver = new HgDriver(['url' => $path], $io, $this->config, new HttpDownloader($io, $this->config), $this->process); + $branches = array_map('strval', array_keys($driver->getBranches())); + + // try to find the best (nearest) version branch to assume this feature's version + $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['hg', 'log', '-r', 'not ancestors(\'%candidate%\') and ancestors(\'%branch%\')', '--template', '"{node}\\n"'], $path); + $result['commit'] = ''; + $result['feature_version'] = $version; + $result['feature_pretty_version'] = $version; + + return $result; + } + + return null; + } + + /** + * @param array $packageConfig + * @param list $branches + * @param list $scmCmdline + * + * @return array{version: string|null, pretty_version: string|null} + */ + private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, array $scmCmdline, string $path): array + { + $prettyVersion = $version; + + // ignore feature branches if they have no branch-alias or self.version is used + // and find the branch they came from to use as a version instead + if (!isset($packageConfig['extra']['branch-alias'][$version]) + || strpos(json_encode($packageConfig), '"self.version"') + ) { + $branch = Preg::replace('{^dev-}', '', $version); + $length = PHP_INT_MAX; + + // return directly, if branch is configured to be non-feature branch + if (!$this->isFeatureBranch($packageConfig, $branch)) { + return ['version' => $version, 'pretty_version' => $prettyVersion]; + } + + // sort local branches first then remote ones + // and sort numeric branches below named ones, to make sure if the branch has the same distance from main and 1.10 and 1.9 for example, 1.9 is picked + // and sort using natural sort so that 1.10 will appear before 1.9 + usort($branches, static function ($a, $b): int { + $aRemote = 0 === strpos($a, 'remotes/'); + $bRemote = 0 === strpos($b, 'remotes/'); + + if ($aRemote !== $bRemote) { + return $aRemote ? 1 : -1; + } + + return strnatcasecmp($b, $a); + }); + + $promises = []; + $this->process->setMaxJobs(30); + try { + $lastIndex = -1; + foreach ($branches as $index => $candidate) { + $candidateVersion = Preg::replace('{^remotes/\S+/}', '', $candidate); + + // do not compare against itself or other feature branches + if ($candidate === $branch || $this->isFeatureBranch($packageConfig, $candidateVersion)) { + continue; + } + + $cmdLine = array_map(static function (string $component) use ($candidate, $branch) { + return str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $component); + }, $scmCmdline); + $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$lastIndex, $index, &$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void { + if (!$process->isSuccessful()) { + return; + } + + $output = $process->getOutput(); + // overwrite existing if we have a shorter diff, or we have an equal diff and an index that comes later in the array (i.e. older version) + // as newer versions typically have more commits, if the feature branch is based on a newer branch it should have a longer diff to the old version + // but if it doesn't and they have equal diffs, then it probably is based on the old version + if (strlen($output) < $length || (strlen($output) === $length && $lastIndex < $index)) { + $lastIndex = $index; + $length = strlen($output); + $version = $this->versionParser->normalizeBranch($candidateVersion); + $prettyVersion = 'dev-' . $candidateVersion; + if ($length === 0) { + foreach ($promises as $promise) { + // to support react/promise 2.x we wrap the promise in a resolve() call for safety + \React\Promise\resolve($promise)->cancel(); + } + } + } + }); + } + + $this->process->wait(); + } finally { + $this->process->resetMaxJobs(); + } + } + + return ['version' => $version, 'pretty_version' => $prettyVersion]; + } + + /** + * @param array $packageConfig + */ + private function isFeatureBranch(array $packageConfig, ?string $branchName): bool + { + $nonFeatureBranches = ''; + if (!empty($packageConfig['non-feature-branches'])) { + $nonFeatureBranches = implode('|', $packageConfig['non-feature-branches']); + } + + return !Preg::isMatch('{^(' . $nonFeatureBranches . '|master|main|latest|next|current|support|tip|trunk|default|develop|\d+\..+)$}', $branchName, $match); + } + + /** + * @return array{version: string|null, commit: '', pretty_version: string|null} + */ + private function guessFossilVersion(string $path): array + { + $version = null; + $prettyVersion = null; + + // try to fetch current version from fossil + if (0 === $this->process->execute(['fossil', 'branch', 'list'], $output, $path)) { + $branch = trim($output); + $version = $this->versionParser->normalizeBranch($branch); + $prettyVersion = 'dev-' . $branch; + } + + // try to fetch current version from fossil tags + if (0 === $this->process->execute(['fossil', 'tag', 'list'], $output, $path)) { + try { + $version = $this->versionParser->normalize(trim($output)); + $prettyVersion = trim($output); + } catch (\Exception $e) { + } + } + + return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; + } + + /** + * @param array $packageConfig + * + * @return array{version: string, commit: '', pretty_version: string}|null + */ + private function guessSvnVersion(array $packageConfig, string $path): ?array + { + SvnUtil::cleanEnv(); + + // try to fetch current version from svn + if (0 === $this->process->execute(['svn', 'info', '--xml'], $output, $path)) { + $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk'; + $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches'; + $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags'; + + $urlPattern = '#.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))#'; + + if (Preg::isMatch($urlPattern, $output, $matches)) { + if (isset($matches[2], $matches[3]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) { + // we are in a branches path + $version = $this->versionParser->normalizeBranch($matches[3]); + $prettyVersion = 'dev-' . $matches[3]; + + return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; + } + + assert(is_string($matches[1])); + $prettyVersion = trim($matches[1]); + if ($prettyVersion === 'trunk') { + $version = 'dev-trunk'; + } else { + $version = $this->versionParser->normalize($prettyVersion); + } + + return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; + } + } + + return null; + } + + public function getRootVersionFromEnv(): string + { + $version = Platform::getEnv('COMPOSER_ROOT_VERSION'); + if (!is_string($version) || $version === '') { + throw new \RuntimeException('COMPOSER_ROOT_VERSION not set or empty'); + } + if (Preg::isMatch('{^(\d+(?:\.\d+)*)-dev$}i', $version, $match)) { + $version = $match[1].'.x-dev'; + } + + return $version; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/VersionParser.php b/vendor/composer/composer/src/Composer/Package/Version/VersionParser.php new file mode 100644 index 000000000..b2859c134 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/VersionParser.php @@ -0,0 +1,94 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Pcre\Preg; +use Composer\Repository\PlatformRepository; +use Composer\Semver\VersionParser as SemverVersionParser; +use Composer\Semver\Semver; +use Composer\Semver\Constraint\ConstraintInterface; + +class VersionParser extends SemverVersionParser +{ + public const DEFAULT_BRANCH_ALIAS = '9999999-dev'; + + /** @var array Constraint parsing cache */ + private static $constraints = []; + + /** + * @inheritDoc + */ + public function parseConstraints($constraints): ConstraintInterface + { + if (!isset(self::$constraints[$constraints])) { + self::$constraints[$constraints] = parent::parseConstraints($constraints); + } + + return self::$constraints[$constraints]; + } + + /** + * Parses an array of strings representing package/version pairs. + * + * The parsing results in an array of arrays, each of which + * contain a 'name' key with value and optionally a 'version' key with value. + * + * @param string[] $pairs a set of package/version pairs separated by ":", "=" or " " + * + * @return list + */ + public function parseNameVersionPairs(array $pairs): array + { + $pairs = array_values($pairs); + $result = []; + + for ($i = 0, $count = count($pairs); $i < $count; $i++) { + $pair = Preg::replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', trim($pairs[$i])); + if (false === strpos($pair, ' ') && isset($pairs[$i + 1]) && false === strpos($pairs[$i + 1], '/') && !Preg::isMatch('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $pairs[$i + 1]) && !PlatformRepository::isPlatformPackage($pairs[$i + 1])) { + $pair .= ' '.$pairs[$i + 1]; + $i++; + } + + if (strpos($pair, ' ')) { + [$name, $version] = explode(' ', $pair, 2); + $result[] = ['name' => $name, 'version' => $version]; + } else { + $result[] = ['name' => $pair]; + } + } + + return $result; + } + + public static function isUpgrade(string $normalizedFrom, string $normalizedTo): bool + { + if ($normalizedFrom === $normalizedTo) { + return true; + } + + if (in_array($normalizedFrom, ['dev-master', 'dev-trunk', 'dev-default'], true)) { + $normalizedFrom = VersionParser::DEFAULT_BRANCH_ALIAS; + } + if (in_array($normalizedTo, ['dev-master', 'dev-trunk', 'dev-default'], true)) { + $normalizedTo = VersionParser::DEFAULT_BRANCH_ALIAS; + } + + if (strpos($normalizedFrom, 'dev-') === 0 || strpos($normalizedTo, 'dev-') === 0) { + return true; + } + + $sorted = Semver::sort([$normalizedTo, $normalizedFrom]); + + return $sorted[0] === $normalizedFrom; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/VersionSelector.php b/vendor/composer/composer/src/Composer/Package/Version/VersionSelector.php new file mode 100644 index 000000000..520c1401f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/VersionSelector.php @@ -0,0 +1,272 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; +use Composer\Composer; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Pcre\Preg; +use Composer\Repository\RepositorySet; +use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Selects the best possible version for a package + * + * @author Ryan Weaver + * @author Jordi Boggiano + */ +class VersionSelector +{ + /** @var RepositorySet */ + private $repositorySet; + + /** @var array */ + private $platformConstraints = []; + + /** @var VersionParser */ + private $parser; + + /** + * @param PlatformRepository $platformRepo If passed in, the versions found will be filtered against their requirements to eliminate any not matching the current platform packages + */ + public function __construct(RepositorySet $repositorySet, ?PlatformRepository $platformRepo = null) + { + $this->repositorySet = $repositorySet; + if ($platformRepo) { + foreach ($platformRepo->getPackages() as $package) { + $this->platformConstraints[$package->getName()][] = new Constraint('==', $package->getVersion()); + } + } + } + + /** + * Given a package name and optional version, returns the latest PackageInterface + * that matches. + * + * @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter + * @param IOInterface|null $io If passed, warnings will be output there in case versions cannot be selected due to platform requirements + * @param callable(PackageInterface):bool|bool $showWarnings + * @return PackageInterface|false + */ + public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = true) + { + if (!isset(BasePackage::STABILITIES[$preferredStability])) { + // If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version + throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got '.$preferredStability); + } + + if (null === $platformRequirementFilter) { + $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); + } elseif (!($platformRequirementFilter instanceof PlatformRequirementFilterInterface)) { + trigger_error('VersionSelector::findBestCandidate with ignored platform reqs as bool|array is deprecated since Composer 2.2, use an instance of PlatformRequirementFilterInterface instead.', E_USER_DEPRECATED); + $platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($platformRequirementFilter); + } + + $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null; + $candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint, $repoSetFlags); + + $minPriority = BasePackage::STABILITIES[$preferredStability]; + usort($candidates, static function (PackageInterface $a, PackageInterface $b) use ($minPriority) { + $aPriority = $a->getStabilityPriority(); + $bPriority = $b->getStabilityPriority(); + + // A is less stable than our preferred stability, + // and B is more stable than A, select B + if ($minPriority < $aPriority && $bPriority < $aPriority) { + return 1; + } + + // A is less stable than our preferred stability, + // and B is less stable than A, select A + if ($minPriority < $aPriority && $aPriority < $bPriority) { + return -1; + } + + // A is more stable than our preferred stability, + // and B is less stable than preferred stability, select A + if ($minPriority >= $aPriority && $minPriority < $bPriority) { + return -1; + } + + // select highest version of the two + return version_compare($b->getVersion(), $a->getVersion()); + }); + + if (count($this->platformConstraints) > 0 && !($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter)) { + /** @var array $alreadyWarnedNames */ + $alreadyWarnedNames = []; + /** @var array $alreadySeenNames */ + $alreadySeenNames = []; + + foreach ($candidates as $pkg) { + $reqs = $pkg->getRequires(); + $skip = false; + foreach ($reqs as $name => $link) { + if (!PlatformRepository::isPlatformPackage($name) || $platformRequirementFilter->isIgnored($name)) { + continue; + } + if (isset($this->platformConstraints[$name])) { + foreach ($this->platformConstraints[$name] as $providedConstraint) { + if ($link->getConstraint()->matches($providedConstraint)) { + // constraint satisfied, go to next require + continue 2; + } + if ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter && $platformRequirementFilter->isUpperBoundIgnored($name)) { + $filteredConstraint = $platformRequirementFilter->filterConstraint($name, $link->getConstraint()); + if ($filteredConstraint->matches($providedConstraint)) { + // constraint satisfied with the upper bound ignored, go to next require + continue 2; + } + } + } + + // constraint not satisfied + $reason = 'is not satisfied by your platform'; + } else { + // Package requires a platform package that is unknown on current platform. + // It means that current platform cannot validate this constraint and so package is not installable. + $reason = 'is missing from your platform'; + } + + $isLatestVersion = !isset($alreadySeenNames[$pkg->getName()]); + $alreadySeenNames[$pkg->getName()] = true; + if ($io !== null && ($showWarnings === true || (is_callable($showWarnings) && $showWarnings($pkg)))) { + $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()]); + $alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()] = true; + $latest = $isLatestVersion ? "'s latest version" : ''; + $io->writeError( + 'Cannot use '.$pkg->getPrettyName().$latest.' '.$pkg->getPrettyVersion().' as it '.$link->getDescription().' '.$link->getTarget().' '.$link->getPrettyConstraint().' which '.$reason.'.', + true, + $isFirstWarning ? IOInterface::NORMAL : IOInterface::VERBOSE + ); + } + + // skip candidate + $skip = true; + } + + if ($skip) { + continue; + } + + $package = $pkg; + break; + } + } else { + $package = count($candidates) > 0 ? $candidates[0] : null; + } + + if (!isset($package)) { + return false; + } + + // if we end up with 9999999-dev as selected package, make sure we use the original version instead of the alias + if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $package = $package->getAliasOf(); + } + + return $package; + } + + /** + * Given a concrete version, this returns a ^ constraint (when possible) + * that should be used, for example, in composer.json. + * + * For example: + * * 1.2.1 -> ^1.2 + * * 1.2.1.2 -> ^1.2 + * * 1.2 -> ^1.2 + * * v3.2.1 -> ^3.2 + * * 2.0-beta.1 -> ^2.0@beta + * * dev-master -> ^2.1@dev (dev version with alias) + * * dev-master -> dev-master (dev versions are untouched) + */ + public function findRecommendedRequireVersion(PackageInterface $package): string + { + // Extensions which are versioned in sync with PHP should rather be required as "*" to simplify + // the requires and have only one required version to change when bumping the php requirement + if (0 === strpos($package->getName(), 'ext-')) { + $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; + $extVersion = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3)); + if ($phpVersion === $extVersion) { + return '*'; + } + } + + $version = $package->getVersion(); + if (!$package->isDev()) { + return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability()); + } + + $loader = new ArrayLoader($this->getParser()); + $dumper = new ArrayDumper(); + $extra = $loader->getBranchAlias($dumper->dump($package)); + if ($extra && $extra !== VersionParser::DEFAULT_BRANCH_ALIAS) { + $extra = Preg::replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count); + if ($count > 0) { + $extra = str_replace('.9999999', '.0', $extra); + + return $this->transformVersion($extra, $extra, 'dev'); + } + } + + return $package->getPrettyVersion(); + } + + private function transformVersion(string $version, string $prettyVersion, string $stability): string + { + // attempt to transform 2.1.1 to 2.1 + // this allows you to upgrade through minor versions + $semanticVersionParts = explode('.', $version); + + // check to see if we have a semver-looking version + if (count($semanticVersionParts) === 4 && Preg::isMatch('{^\d+\D?}', $semanticVersionParts[3])) { + // remove the last parts (i.e. the patch version number and any extra) + if ($semanticVersionParts[0] === '0') { + unset($semanticVersionParts[3]); + } else { + unset($semanticVersionParts[2], $semanticVersionParts[3]); + } + $version = implode('.', $semanticVersionParts); + } else { + return $prettyVersion; + } + + // append stability flag if not default + if ($stability !== 'stable') { + $version .= '@'.$stability; + } + + // 2.1 -> ^2.1 + return '^' . $version; + } + + private function getParser(): VersionParser + { + if ($this->parser === null) { + $this->parser = new VersionParser(); + } + + return $this->parser; + } +} diff --git a/vendor/composer/composer/src/Composer/PartialComposer.php b/vendor/composer/composer/src/Composer/PartialComposer.php new file mode 100644 index 000000000..bd4f817e0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/PartialComposer.php @@ -0,0 +1,130 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Package\RootPackageInterface; +use Composer\Util\Loop; +use Composer\Repository\RepositoryManager; +use Composer\Installer\InstallationManager; +use Composer\EventDispatcher\EventDispatcher; + +/** + * @author Jordi Boggiano + */ +class PartialComposer +{ + /** + * @var bool + */ + private $global = false; + + /** + * @var RootPackageInterface + */ + private $package; + + /** + * @var Loop + */ + private $loop; + + /** + * @var RepositoryManager + */ + private $repositoryManager; + + /** + * @var InstallationManager + */ + private $installationManager; + + /** + * @var Config + */ + private $config; + + /** + * @var EventDispatcher + */ + private $eventDispatcher; + + public function setPackage(RootPackageInterface $package): void + { + $this->package = $package; + } + + public function getPackage(): RootPackageInterface + { + return $this->package; + } + + public function setConfig(Config $config): void + { + $this->config = $config; + } + + public function getConfig(): Config + { + return $this->config; + } + + public function setLoop(Loop $loop): void + { + $this->loop = $loop; + } + + public function getLoop(): Loop + { + return $this->loop; + } + + public function setRepositoryManager(RepositoryManager $manager): void + { + $this->repositoryManager = $manager; + } + + public function getRepositoryManager(): RepositoryManager + { + return $this->repositoryManager; + } + + public function setInstallationManager(InstallationManager $manager): void + { + $this->installationManager = $manager; + } + + public function getInstallationManager(): InstallationManager + { + return $this->installationManager; + } + + public function setEventDispatcher(EventDispatcher $eventDispatcher): void + { + $this->eventDispatcher = $eventDispatcher; + } + + public function getEventDispatcher(): EventDispatcher + { + return $this->eventDispatcher; + } + + public function isGlobal(): bool + { + return $this->global; + } + + public function setGlobal(): void + { + $this->global = true; + } +} diff --git a/vendor/composer/composer/src/Composer/Platform/HhvmDetector.php b/vendor/composer/composer/src/Composer/Platform/HhvmDetector.php new file mode 100644 index 000000000..284b0ba8a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Platform/HhvmDetector.php @@ -0,0 +1,61 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Process\ExecutableFinder; + +class HhvmDetector +{ + /** @var string|false|null */ + private static $hhvmVersion = null; + /** @var ?ExecutableFinder */ + private $executableFinder; + /** @var ?ProcessExecutor */ + private $processExecutor; + + public function __construct(?ExecutableFinder $executableFinder = null, ?ProcessExecutor $processExecutor = null) + { + $this->executableFinder = $executableFinder; + $this->processExecutor = $processExecutor; + } + + public function reset(): void + { + self::$hhvmVersion = null; + } + + public function getVersion(): ?string + { + if (null !== self::$hhvmVersion) { + return self::$hhvmVersion ?: null; + } + + self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; + if (self::$hhvmVersion === null && !Platform::isWindows()) { + self::$hhvmVersion = false; + $this->executableFinder = $this->executableFinder ?: new ExecutableFinder(); + $hhvmPath = $this->executableFinder->find('hhvm'); + if ($hhvmPath !== null) { + $this->processExecutor = $this->processExecutor ?? new ProcessExecutor(); + $exitCode = $this->processExecutor->execute([$hhvmPath, '--php', '-d', 'hhvm.jit=0', '-r', 'echo HHVM_VERSION;'], self::$hhvmVersion); + if ($exitCode !== 0) { + self::$hhvmVersion = false; + } + } + } + + return self::$hhvmVersion ?: null; + } +} diff --git a/vendor/composer/composer/src/Composer/Platform/Runtime.php b/vendor/composer/composer/src/Composer/Platform/Runtime.php new file mode 100644 index 000000000..940c02d0f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Platform/Runtime.php @@ -0,0 +1,106 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +class Runtime +{ + /** + * @param class-string $class + */ + public function hasConstant(string $constant, ?string $class = null): bool + { + return defined(ltrim($class.'::'.$constant, ':')); + } + + /** + * @param class-string $class + * + * @return mixed + */ + public function getConstant(string $constant, ?string $class = null) + { + return constant(ltrim($class.'::'.$constant, ':')); + } + + public function hasFunction(string $fn): bool + { + return function_exists($fn); + } + + /** + * @param mixed[] $arguments + * + * @return mixed + */ + public function invoke(callable $callable, array $arguments = []) + { + return $callable(...$arguments); + } + + /** + * @param class-string $class + */ + public function hasClass(string $class): bool + { + return class_exists($class, false); + } + + /** + * @template T of object + * @param mixed[] $arguments + * + * @phpstan-param class-string $class + * @phpstan-return T + * + * @throws \ReflectionException + */ + public function construct(string $class, array $arguments = []): object + { + if (empty($arguments)) { + return new $class; + } + + $refl = new \ReflectionClass($class); + + return $refl->newInstanceArgs($arguments); + } + + /** @return string[] */ + public function getExtensions(): array + { + return get_loaded_extensions(); + } + + public function getExtensionVersion(string $extension): string + { + $version = phpversion($extension); + if ($version === false) { + $version = '0'; + } + + return $version; + } + + /** + * @throws \ReflectionException + */ + public function getExtensionInfo(string $extension): string + { + $reflector = new \ReflectionExtension($extension); + + ob_start(); + $reflector->info(); + + return ob_get_clean(); + } +} diff --git a/vendor/composer/composer/src/Composer/Platform/Version.php b/vendor/composer/composer/src/Composer/Platform/Version.php new file mode 100644 index 000000000..56abb1bb1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Platform/Version.php @@ -0,0 +1,92 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +use Composer\Pcre\Preg; + +/** + * @author Lars Strojny + */ +class Version +{ + /** + * @param bool $isFips Set by the method + * + * @param-out bool $isFips + */ + public static function parseOpenssl(string $opensslVersion, ?bool &$isFips): ?string + { + $isFips = false; + + if (!Preg::isMatchStrictGroups('/^(?[0-9.]+)(?[a-z]{0,2})(?(?:-?(?:dev|pre|alpha|beta|rc|fips)[\d]*)*)(?:-\w+)?(?: \(.+?\))?$/', $opensslVersion, $matches)) { + return null; + } + + // OpenSSL 1 used 1.2.3a style versioning, 3+ uses semver + $patch = ''; + if (version_compare($matches['version'], '3.0.0', '<')) { + $patch = '.'.self::convertAlphaVersionToIntVersion($matches['patch']); + } + + $isFips = strpos($matches['suffix'], 'fips') !== false; + $suffix = strtr('-'.ltrim($matches['suffix'], '-'), ['-fips' => '', '-pre' => '-alpha']); + + return rtrim($matches['version'].$patch.$suffix, '-'); + } + + public static function parseLibjpeg(string $libjpegVersion): ?string + { + if (!Preg::isMatchStrictGroups('/^(?\d+)(?[a-z]*)$/', $libjpegVersion, $matches)) { + return null; + } + + return $matches['major'].'.'.self::convertAlphaVersionToIntVersion($matches['minor']); + } + + public static function parseZoneinfoVersion(string $zoneinfoVersion): ?string + { + if (!Preg::isMatchStrictGroups('/^(?\d{4})(?[a-z]*)$/', $zoneinfoVersion, $matches)) { + return null; + } + + return $matches['year'].'.'.self::convertAlphaVersionToIntVersion($matches['revision']); + } + + /** + * "" => 0, "a" => 1, "zg" => 33 + */ + private static function convertAlphaVersionToIntVersion(string $alpha): int + { + return strlen($alpha) * (-ord('a') + 1) + array_sum(array_map('ord', str_split($alpha))); + } + + public static function convertLibxpmVersionId(int $versionId): string + { + return self::convertVersionId($versionId, 100); + } + + public static function convertOpenldapVersionId(int $versionId): string + { + return self::convertVersionId($versionId, 100); + } + + private static function convertVersionId(int $versionId, int $base): string + { + return sprintf( + '%d.%d.%d', + $versionId / ($base * $base), + (int) ($versionId / $base) % $base, + $versionId % $base + ); + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/Capability/Capability.php b/vendor/composer/composer/src/Composer/Plugin/Capability/Capability.php new file mode 100644 index 000000000..b2237b44b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/Capability/Capability.php @@ -0,0 +1,23 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin\Capability; + +/** + * Marker interface for Plugin capabilities. + * Every new Capability which is added to the Plugin API must implement this interface. + * + * @api + */ +interface Capability +{ +} diff --git a/vendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php b/vendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php new file mode 100644 index 000000000..884b9554d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php @@ -0,0 +1,33 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin\Capability; + +/** + * Commands Provider Interface + * + * This capability will receive an array with 'composer' and 'io' keys as + * constructor argument. Those contain Composer\Composer and Composer\IO\IOInterface + * instances. It also contains a 'plugin' key containing the plugin instance that + * created the capability. + * + * @author Jérémy Derussé + */ +interface CommandProvider extends Capability +{ + /** + * Retrieves an array of commands + * + * @return \Composer\Command\BaseCommand[] + */ + public function getCommands(); +} diff --git a/vendor/composer/composer/src/Composer/Plugin/Capable.php b/vendor/composer/composer/src/Composer/Plugin/Capable.php new file mode 100644 index 000000000..9a45833c8 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/Capable.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +/** + * Plugins which need to expose various implementations + * of the Composer Plugin Capabilities must have their + * declared Plugin class implementing this interface. + * + * @api + */ +interface Capable +{ + /** + * Method by which a Plugin announces its API implementations, through an array + * with a special structure. + * + * The key must be a string, representing a fully qualified class/interface name + * which Composer Plugin API exposes. + * The value must be a string as well, representing the fully qualified class name + * of the implementing class. + * + * @tutorial + * + * return array( + * 'Composer\Plugin\Capability\CommandProvider' => 'My\CommandProvider', + * 'Composer\Plugin\Capability\Validator' => 'My\Validator', + * ); + * + * @return string[] + */ + public function getCapabilities(); +} diff --git a/vendor/composer/composer/src/Composer/Plugin/CommandEvent.php b/vendor/composer/composer/src/Composer/Plugin/CommandEvent.php new file mode 100644 index 000000000..ece2cf5e3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/CommandEvent.php @@ -0,0 +1,80 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * An event for all commands. + * + * @author Nils Adermann + */ +class CommandEvent extends Event +{ + /** + * @var string + */ + private $commandName; + + /** + * @var InputInterface + */ + private $input; + + /** + * @var OutputInterface + */ + private $output; + + /** + * Constructor. + * + * @param string $name The event name + * @param string $commandName The command name + * @param mixed[] $args Arguments passed by the user + * @param mixed[] $flags Optional flags to pass data not as argument + */ + public function __construct(string $name, string $commandName, InputInterface $input, OutputInterface $output, array $args = [], array $flags = []) + { + parent::__construct($name, $args, $flags); + $this->commandName = $commandName; + $this->input = $input; + $this->output = $output; + } + + /** + * Returns the command input interface + */ + public function getInput(): InputInterface + { + return $this->input; + } + + /** + * Retrieves the command output interface + */ + public function getOutput(): OutputInterface + { + return $this->output; + } + + /** + * Retrieves the name of the command being run + */ + public function getCommandName(): string + { + return $this->commandName; + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PluginBlockedException.php b/vendor/composer/composer/src/Composer/Plugin/PluginBlockedException.php new file mode 100644 index 000000000..a23815eaa --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PluginBlockedException.php @@ -0,0 +1,19 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use UnexpectedValueException; + +class PluginBlockedException extends UnexpectedValueException +{ +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PluginEvents.php b/vendor/composer/composer/src/Composer/Plugin/PluginEvents.php new file mode 100644 index 000000000..5a82018c0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PluginEvents.php @@ -0,0 +1,82 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +/** + * The Plugin Events. + * + * @author Nils Adermann + */ +class PluginEvents +{ + /** + * The INIT event occurs after a Composer instance is done being initialized + * + * The event listener method receives a + * Composer\EventDispatcher\Event instance. + * + * @var string + */ + public const INIT = 'init'; + + /** + * The COMMAND event occurs as a command begins + * + * The event listener method receives a + * Composer\Plugin\CommandEvent instance. + * + * @var string + */ + public const COMMAND = 'command'; + + /** + * The PRE_FILE_DOWNLOAD event occurs before downloading a file + * + * The event listener method receives a + * Composer\Plugin\PreFileDownloadEvent instance. + * + * @var string + */ + public const PRE_FILE_DOWNLOAD = 'pre-file-download'; + + /** + * The POST_FILE_DOWNLOAD event occurs after downloading a package dist file + * + * The event listener method receives a + * Composer\Plugin\PostFileDownloadEvent instance. + * + * @var string + */ + public const POST_FILE_DOWNLOAD = 'post-file-download'; + + /** + * The PRE_COMMAND_RUN event occurs before a command is executed and lets you modify the input arguments/options + * + * The event listener method receives a + * Composer\Plugin\PreCommandRunEvent instance. + * + * @var string + */ + public const PRE_COMMAND_RUN = 'pre-command-run'; + + /** + * The PRE_POOL_CREATE event occurs before the Pool of packages is created, and lets + * you filter the list of packages which is going to enter the Solver + * + * The event listener method receives a + * Composer\Plugin\PrePoolCreateEvent instance. + * + * @var string + */ + public const PRE_POOL_CREATE = 'pre-pool-create'; +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PluginInterface.php b/vendor/composer/composer/src/Composer/Plugin/PluginInterface.php new file mode 100644 index 000000000..8bf3879f2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PluginInterface.php @@ -0,0 +1,63 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\Composer; +use Composer\IO\IOInterface; + +/** + * Plugin interface + * + * @author Nils Adermann + */ +interface PluginInterface +{ + /** + * Version number of the internal composer-plugin-api package + * + * This is used to denote the API version of Plugin specific + * features, but is also bumped to a new major if Composer + * includes a major break in internal APIs which are susceptible + * to be used by plugins. + * + * @var string + */ + public const PLUGIN_API_VERSION = '2.9.0'; + + /** + * Apply plugin modifications to Composer + * + * @return void + */ + public function activate(Composer $composer, IOInterface $io); + + /** + * Remove any hooks from Composer + * + * This will be called when a plugin is deactivated before being + * uninstalled, but also before it gets upgraded to a new version + * so the old one can be deactivated and the new one activated. + * + * @return void + */ + public function deactivate(Composer $composer, IOInterface $io); + + /** + * Prepare the plugin to be uninstalled + * + * This will be called after deactivate. + * + * @return void + */ + public function uninstall(Composer $composer, IOInterface $io); +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PluginManager.php b/vendor/composer/composer/src/Composer/Plugin/PluginManager.php new file mode 100644 index 000000000..af2cdbbf1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PluginManager.php @@ -0,0 +1,804 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\Composer; +use Composer\EventDispatcher\EventSubscriberInterface; +use Composer\Installer\InstallerInterface; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackage; +use Composer\Package\Locker; +use Composer\Package\Package; +use Composer\Package\RootPackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\PartialComposer; +use Composer\Pcre\Preg; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\InstalledRepository; +use Composer\Repository\RepositoryUtils; +use Composer\Repository\RootPackageRepository; +use Composer\Package\PackageInterface; +use Composer\Package\Link; +use Composer\Semver\Constraint\Constraint; +use Composer\Plugin\Capability\Capability; +use Composer\Util\PackageSorter; + +/** + * Plugin manager + * + * @author Nils Adermann + * @author Jordi Boggiano + */ +class PluginManager +{ + /** @var Composer */ + protected $composer; + /** @var IOInterface */ + protected $io; + /** @var PartialComposer|null */ + protected $globalComposer; + /** @var VersionParser */ + protected $versionParser; + /** @var bool|'local'|'global' */ + protected $disablePlugins = false; + + /** @var array */ + protected $plugins = []; + /** @var array> */ + protected $registeredPlugins = []; + + /** + * @var array|null + */ + private $allowPluginRules; + + /** + * @var array|null + */ + private $allowGlobalPluginRules; + + /** @var bool */ + private $runningInGlobalDir = false; + + /** @var int */ + private static $classCounter = 0; + + /** + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + */ + public function __construct(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = false) + { + $this->io = $io; + $this->composer = $composer; + $this->globalComposer = $globalComposer; + $this->versionParser = new VersionParser(); + $this->disablePlugins = $disablePlugins; + $this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'), $composer->getLocker()); + $this->allowGlobalPluginRules = $this->parseAllowedPlugins($globalComposer !== null ? $globalComposer->getConfig()->get('allow-plugins') : false); + } + + public function setRunningInGlobalDir(bool $runningInGlobalDir): void + { + $this->runningInGlobalDir = $runningInGlobalDir; + } + + /** + * Loads all plugins from currently installed plugin packages + */ + public function loadInstalledPlugins(): void + { + if (!$this->arePluginsDisabled('local')) { + $repo = $this->composer->getRepositoryManager()->getLocalRepository(); + $this->loadRepository($repo, false, $this->composer->getPackage()); + } + + if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { + $this->loadRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true); + } + } + + /** + * Deactivate all plugins from currently installed plugin packages + */ + public function deactivateInstalledPlugins(): void + { + if (!$this->arePluginsDisabled('local')) { + $repo = $this->composer->getRepositoryManager()->getLocalRepository(); + $this->deactivateRepository($repo, false); + } + + if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { + $this->deactivateRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true); + } + } + + /** + * Gets all currently active plugin instances + * + * @return array plugins + */ + public function getPlugins(): array + { + return $this->plugins; + } + + /** + * Gets all currently active plugin instances + * + * @internal + * @return array Plugin package names which are currently active + */ + public function getRegisteredPlugins(): array + { + return array_keys($this->registeredPlugins); + } + + /** + * Gets global composer or null when main composer is not fully loaded + */ + public function getGlobalComposer(): ?PartialComposer + { + return $this->globalComposer; + } + + /** + * Register a plugin package, activate it etc. + * + * If it's of type composer-installer it is registered as an installer + * instead for BC + * + * @param bool $failOnMissingClasses By default this silently skips plugins that can not be found, but if set to true it fails with an exception + * @param bool $isGlobalPlugin Set to true to denote plugins which are installed in the global Composer directory + * + * @throws \UnexpectedValueException + */ + public function registerPackage(PackageInterface $package, bool $failOnMissingClasses = false, bool $isGlobalPlugin = false): void + { + if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) { + $this->io->writeError('The "'.$package->getName().'" plugin was not loaded as plugins are disabled.'); + + return; + } + + if ($package->getType() === 'composer-plugin') { + $requiresComposer = null; + foreach ($package->getRequires() as $link) { /** @var Link $link */ + if ('composer-plugin-api' === $link->getTarget()) { + $requiresComposer = $link->getConstraint(); + break; + } + } + + if (!$requiresComposer) { + throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); + } + + $currentPluginApiVersion = $this->getPluginApiVersion(); + $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion)); + + if ($requiresComposer->getPrettyString() === $this->getPluginApiVersion()) { + $this->io->writeError('The "' . $package->getName() . '" plugin requires composer-plugin-api '.$this->getPluginApiVersion().', this *WILL* break in the future and it should be fixed ASAP (require ^'.$this->getPluginApiVersion().' instead for example).'); + } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) { + $this->io->writeError('The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); + + return; + } + + if ($package->getName() === 'symfony/flex' && Preg::isMatch('{^[0-9.]+$}', $package->getVersion()) && version_compare($package->getVersion(), '1.9.8', '<')) { + $this->io->writeError('The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it is not compatible with Composer 2+. Make sure to update it to version 1.9.8 or greater.'); + + return; + } + } + + if (!$this->isPluginAllowed($package->getName(), $isGlobalPlugin, true === ($package->getExtra()['plugin-optional'] ?? false))) { + $this->io->writeError('Skipped loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'as it is not in config.allow-plugins', true, IOInterface::DEBUG); + + return; + } + + $oldInstallerPlugin = ($package->getType() === 'composer-installer'); + + if (isset($this->registeredPlugins[$package->getName()])) { + return; + } + + $extra = $package->getExtra(); + if (empty($extra['class'])) { + throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); + } + $classes = is_array($extra['class']) ? $extra['class'] : [$extra['class']]; + + $localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); + $globalRepo = $this->globalComposer !== null ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; + + $rootPackage = clone $this->composer->getPackage(); + + // clear files autoload rules from the root package as the root dependencies are not + // necessarily all present yet when booting this runtime autoloader + $rootPackageAutoloads = $rootPackage->getAutoload(); + $rootPackageAutoloads['files'] = []; + $rootPackage->setAutoload($rootPackageAutoloads); + $rootPackageAutoloads = $rootPackage->getDevAutoload(); + $rootPackageAutoloads['files'] = []; + $rootPackage->setDevAutoload($rootPackageAutoloads); + unset($rootPackageAutoloads); + + $rootPackageRepo = new RootPackageRepository($rootPackage); + $installedRepo = new InstalledRepository([$localRepo, $rootPackageRepo]); + if ($globalRepo) { + $installedRepo->addRepository($globalRepo); + } + + $autoloadPackages = [$package->getName() => $package]; + $autoloadPackages = $this->collectDependencies($installedRepo, $autoloadPackages, $package); + + $generator = $this->composer->getAutoloadGenerator(); + $autoloads = [[$rootPackage, '']]; + foreach ($autoloadPackages as $autoloadPackage) { + if ($autoloadPackage === $rootPackage) { + continue; + } + + $installPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage)); + if ($installPath === null) { + continue; + } + $autoloads[] = [$autoloadPackage, $installPath]; + } + + $map = $generator->parseAutoloads($autoloads, $rootPackage); + $classLoader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir')); + $classLoader->register(false); + + foreach ($map['files'] as $fileIdentifier => $file) { + // exclude laminas/laminas-zendframework-bridge:src/autoload.php as it breaks Composer in some conditions + // see https://github.com/composer/composer/issues/10349 and https://github.com/composer/composer/issues/10401 + // this hack can be removed once this deprecated package stop being installed + if ($fileIdentifier === '7e9bd612cc444b3eed788ebbe46263a0') { + continue; + } + \Composer\Autoload\composerRequire($fileIdentifier, $file); + } + + foreach ($classes as $class) { + if (class_exists($class, false)) { + $class = trim($class, '\\'); + $path = $classLoader->findFile($class); + $code = file_get_contents($path); + $separatorPos = strrpos($class, '\\'); + $className = $class; + if ($separatorPos) { + $className = substr($class, $separatorPos + 1); + } + $code = Preg::replace('{^((?:(?:final|readonly)\s+)*(?:\s*))class\s+('.preg_quote($className).')}mi', '$1class $2_composer_tmp'.self::$classCounter, $code, 1); + $code = strtr($code, [ + '__FILE__' => var_export($path, true), + '__DIR__' => var_export(dirname($path), true), + '__CLASS__' => var_export($class, true), + ]); + $code = Preg::replace('/^\s*<\?(php)?/i', '', $code, 1); + eval($code); + $class .= '_composer_tmp'.self::$classCounter; + self::$classCounter++; + } + + if ($oldInstallerPlugin) { + if (!is_a($class, 'Composer\Installer\InstallerInterface', true)) { + throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Installer\InstallerInterface'); + } + $this->io->writeError('Loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'which is a legacy composer-installer built for Composer 1.x, it is likely to cause issues as you are running Composer 2.x.'); + $installer = new $class($this->io, $this->composer); + $this->composer->getInstallationManager()->addInstaller($installer); + $this->registeredPlugins[$package->getName()][] = $installer; + } elseif (class_exists($class)) { + if (!is_a($class, 'Composer\Plugin\PluginInterface', true)) { + throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Plugin\PluginInterface'); + } + $plugin = new $class(); + $this->addPlugin($plugin, $isGlobalPlugin, $package); + $this->registeredPlugins[$package->getName()][] = $plugin; + } elseif ($failOnMissingClasses) { + throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class); + } + } + } + + /** + * Deactivates a plugin package + * + * If it's of type composer-installer it is unregistered from the installers + * instead for BC + * + * @throws \UnexpectedValueException + */ + public function deactivatePackage(PackageInterface $package): void + { + if (!isset($this->registeredPlugins[$package->getName()])) { + return; + } + + $plugins = $this->registeredPlugins[$package->getName()]; + foreach ($plugins as $plugin) { + if ($plugin instanceof InstallerInterface) { + $this->composer->getInstallationManager()->removeInstaller($plugin); + } else { + $this->removePlugin($plugin); + } + } + unset($this->registeredPlugins[$package->getName()]); + } + + /** + * Uninstall a plugin package + * + * If it's of type composer-installer it is unregistered from the installers + * instead for BC + * + * @throws \UnexpectedValueException + */ + public function uninstallPackage(PackageInterface $package): void + { + if (!isset($this->registeredPlugins[$package->getName()])) { + return; + } + + $plugins = $this->registeredPlugins[$package->getName()]; + foreach ($plugins as $plugin) { + if ($plugin instanceof InstallerInterface) { + $this->composer->getInstallationManager()->removeInstaller($plugin); + } else { + $this->removePlugin($plugin); + $this->uninstallPlugin($plugin); + } + } + unset($this->registeredPlugins[$package->getName()]); + } + + /** + * Returns the version of the internal composer-plugin-api package. + */ + protected function getPluginApiVersion(): string + { + return PluginInterface::PLUGIN_API_VERSION; + } + + /** + * Adds a plugin, activates it and registers it with the event dispatcher + * + * Ideally plugin packages should be registered via registerPackage, but if you use Composer + * programmatically and want to register a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + * @param ?PackageInterface $sourcePackage Package from which the plugin comes from + */ + public function addPlugin(PluginInterface $plugin, bool $isGlobalPlugin = false, ?PackageInterface $sourcePackage = null): void + { + if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) { + return; + } + + if ($sourcePackage === null) { + trigger_error('Calling PluginManager::addPlugin without $sourcePackage is deprecated, if you are using this please get in touch with us to explain the use case', E_USER_DEPRECATED); + } elseif (!$this->isPluginAllowed($sourcePackage->getName(), $isGlobalPlugin, true === ($sourcePackage->getExtra()['plugin-optional'] ?? false))) { + $this->io->writeError('Skipped loading "'.get_class($plugin).' from '.$sourcePackage->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').' as it is not in config.allow-plugins', true, IOInterface::DEBUG); + + return; + } + + $details = []; + if ($sourcePackage) { + $details[] = 'from '.$sourcePackage->getName(); + } + if ($isGlobalPlugin || $this->runningInGlobalDir) { + $details[] = 'installed globally'; + } + $this->io->writeError('Loading plugin '.get_class($plugin).($details ? ' ('.implode(', ', $details).')' : ''), true, IOInterface::DEBUG); + $this->plugins[] = $plugin; + $plugin->activate($this->composer, $this->io); + + if ($plugin instanceof EventSubscriberInterface) { + $this->composer->getEventDispatcher()->addSubscriber($plugin); + } + } + + /** + * Removes a plugin, deactivates it and removes any listener the plugin has set on the plugin instance + * + * Ideally plugin packages should be deactivated via deactivatePackage, but if you use Composer + * programmatically and want to deregister a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + */ + public function removePlugin(PluginInterface $plugin): void + { + $index = array_search($plugin, $this->plugins, true); + if ($index === false) { + return; + } + + $this->io->writeError('Unloading plugin '.get_class($plugin), true, IOInterface::DEBUG); + unset($this->plugins[$index]); + $plugin->deactivate($this->composer, $this->io); + + $this->composer->getEventDispatcher()->removeListener($plugin); + } + + /** + * Notifies a plugin it is being uninstalled and should clean up + * + * Ideally plugin packages should be uninstalled via uninstallPackage, but if you use Composer + * programmatically and want to deregister a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + */ + public function uninstallPlugin(PluginInterface $plugin): void + { + $this->io->writeError('Uninstalling plugin '.get_class($plugin), true, IOInterface::DEBUG); + $plugin->uninstall($this->composer, $this->io); + } + + /** + * Load all plugins and installers from a repository + * + * If a plugin requires another plugin, the required one will be loaded first + * + * Note that plugins in the specified repository that rely on events that + * have fired prior to loading will be missed. This means you likely want to + * call this method as early as possible. + * + * @param RepositoryInterface $repo Repository to scan for plugins to install + * + * @phpstan-param ($isGlobalRepo is true ? null : RootPackageInterface) $rootPackage + * + * @throws \RuntimeException + */ + private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo, ?RootPackageInterface $rootPackage = null): void + { + $packages = $repo->getPackages(); + + $weights = []; + foreach ($packages as $package) { + if ($package->getType() === 'composer-plugin') { + $extra = $package->getExtra(); + if ($package->getName() === 'composer/installers' || true === ($extra['plugin-modifies-install-path'] ?? false)) { + $weights[$package->getName()] = -10000; + } + } + } + + $sortedPackages = PackageSorter::sortPackages($packages, $weights); + if (!$isGlobalRepo) { + $requiredPackages = RepositoryUtils::filterRequiredPackages($packages, $rootPackage, true); + } + + foreach ($sortedPackages as $package) { + if (!($package instanceof CompletePackage)) { + continue; + } + + if (!in_array($package->getType(), ['composer-plugin', 'composer-installer'], true)) { + continue; + } + + if ( + !$isGlobalRepo + && !in_array($package, $requiredPackages, true) + && !$this->isPluginAllowed($package->getName(), false, true, false) + ) { + $this->io->writeError('The "'.$package->getName().'" plugin was not loaded as it is not listed in allow-plugins and is not required by the root package anymore.'); + continue; + } + + if ('composer-plugin' === $package->getType()) { + $this->registerPackage($package, false, $isGlobalRepo); + // Backward compatibility + } elseif ('composer-installer' === $package->getType()) { + $this->registerPackage($package, false, $isGlobalRepo); + } + } + } + + /** + * Deactivate all plugins and installers from a repository + * + * If a plugin requires another plugin, the required one will be deactivated last + * + * @param RepositoryInterface $repo Repository to scan for plugins to install + */ + private function deactivateRepository(RepositoryInterface $repo, bool $isGlobalRepo): void + { + $packages = $repo->getPackages(); + $sortedPackages = array_reverse(PackageSorter::sortPackages($packages)); + + foreach ($sortedPackages as $package) { + if (!($package instanceof CompletePackage)) { + continue; + } + if ('composer-plugin' === $package->getType()) { + $this->deactivatePackage($package); + // Backward compatibility + } elseif ('composer-installer' === $package->getType()) { + $this->deactivatePackage($package); + } + } + } + + /** + * Recursively generates a map of package names to packages for all deps + * + * @param InstalledRepository $installedRepo Set of local repos + * @param array $collected Current state of the map for recursion + * @param PackageInterface $package The package to analyze + * + * @return array Map of package names to packages + */ + private function collectDependencies(InstalledRepository $installedRepo, array $collected, PackageInterface $package): array + { + foreach ($package->getRequires() as $requireLink) { + foreach ($installedRepo->findPackagesWithReplacersAndProviders($requireLink->getTarget()) as $requiredPackage) { + if (!isset($collected[$requiredPackage->getName()])) { + $collected[$requiredPackage->getName()] = $requiredPackage; + $collected = $this->collectDependencies($installedRepo, $collected, $requiredPackage); + } + } + } + + return $collected; + } + + /** + * Retrieves the path a package is installed to. + * + * @param bool $global Whether this is a global package + * + * @return string|null Install path + */ + private function getInstallPath(PackageInterface $package, bool $global = false): ?string + { + if (!$global) { + return $this->composer->getInstallationManager()->getInstallPath($package); + } + + assert(null !== $this->globalComposer); + + return $this->globalComposer->getInstallationManager()->getInstallPath($package); + } + + /** + * @throws \RuntimeException On empty or non-string implementation class name value + * @return null|string The fully qualified class of the implementation or null if Plugin is not of Capable type or does not provide it + */ + protected function getCapabilityImplementationClassName(PluginInterface $plugin, string $capability): ?string + { + if (!($plugin instanceof Capable)) { + return null; + } + + $capabilities = (array) $plugin->getCapabilities(); + + if (!empty($capabilities[$capability]) && is_string($capabilities[$capability]) && trim($capabilities[$capability])) { + return trim($capabilities[$capability]); + } + + if ( + array_key_exists($capability, $capabilities) + && (empty($capabilities[$capability]) || !is_string($capabilities[$capability]) || !trim($capabilities[$capability])) + ) { + throw new \UnexpectedValueException('Plugin '.get_class($plugin).' provided invalid capability class name(s), got '.var_export($capabilities[$capability], true)); + } + + return null; + } + + /** + * @template CapabilityClass of Capability + * @param class-string $capabilityClassName The fully qualified name of the API interface which the plugin may provide + * an implementation of. + * @param array $ctorArgs Arguments passed to Capability's constructor. + * Keeping it an array will allow future values to be passed w\o changing the signature. + * @phpstan-param class-string $capabilityClassName + * @phpstan-return null|CapabilityClass + */ + public function getPluginCapability(PluginInterface $plugin, $capabilityClassName, array $ctorArgs = []): ?Capability + { + if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capabilityClassName)) { + if (!class_exists($capabilityClass)) { + throw new \RuntimeException("Cannot instantiate Capability, as class $capabilityClass from plugin ".get_class($plugin)." does not exist."); + } + + $ctorArgs['plugin'] = $plugin; + $capabilityObj = new $capabilityClass($ctorArgs); + + // FIXME these could use is_a and do the check *before* instantiating once drop support for php<5.3.9 + if (!$capabilityObj instanceof Capability || !$capabilityObj instanceof $capabilityClassName) { + throw new \RuntimeException( + 'Class ' . $capabilityClass . ' must implement both Composer\Plugin\Capability\Capability and '. $capabilityClassName . '.' + ); + } + + return $capabilityObj; + } + + return null; + } + + /** + * @template CapabilityClass of Capability + * @param class-string $capabilityClassName The fully qualified name of the API interface which the plugin may provide + * an implementation of. + * @param array $ctorArgs Arguments passed to Capability's constructor. + * Keeping it an array will allow future values to be passed w\o changing the signature. + * @return CapabilityClass[] + */ + public function getPluginCapabilities($capabilityClassName, array $ctorArgs = []): array + { + $capabilities = []; + foreach ($this->getPlugins() as $plugin) { + $capability = $this->getPluginCapability($plugin, $capabilityClassName, $ctorArgs); + if (null !== $capability) { + $capabilities[] = $capability; + } + } + + return $capabilities; + } + + /** + * @param array|bool $allowPluginsConfig + * @return array|null + */ + private function parseAllowedPlugins($allowPluginsConfig, ?Locker $locker = null): ?array + { + if ([] === $allowPluginsConfig && $locker !== null && $locker->isLocked() && version_compare($locker->getPluginApi(), '2.2.0', '<')) { + return null; + } + + if (true === $allowPluginsConfig) { + return ['{}' => true]; + } + + if (false === $allowPluginsConfig) { + return ['{}' => false]; + } + + $rules = []; + foreach ($allowPluginsConfig as $pattern => $allow) { + $rules[BasePackage::packageNameToRegexp($pattern)] = $allow; + } + + return $rules; + } + + /** + * @internal + * + * @param 'local'|'global' $type + * @return bool + */ + public function arePluginsDisabled($type) + { + return $this->disablePlugins === true || $this->disablePlugins === $type; + } + + /** + * @internal + */ + public function disablePlugins(): void + { + $this->disablePlugins = true; + } + + /** + * @internal + */ + public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = false, bool $prompt = true): bool + { + if ($isGlobalPlugin) { + $rules = &$this->allowGlobalPluginRules; + } else { + $rules = &$this->allowPluginRules; + } + + // This is a BC mode for lock files created pre-Composer-2.2 where the expectation of + // an allow-plugins config being present cannot be made. + if ($rules === null) { + if (!$this->io->isInteractive()) { + $this->io->writeError('For additional security you should declare the allow-plugins config with a list of packages names that are allowed to run code. See https://getcomposer.org/allow-plugins'); + $this->io->writeError('This warning will become an exception once you run composer update!'); + + $rules = ['{}' => true]; + + // if no config is defined we allow all plugins for BC + return true; + } + + // keep going and prompt the user + $rules = []; + } + + foreach ($rules as $pattern => $allow) { + if (Preg::isMatch($pattern, $package)) { + return $allow === true; + } + } + + if ($package === 'composer/package-versions-deprecated') { + return false; + } + + if ($this->io->isInteractive() && $prompt) { + $composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer; + + $this->io->writeError(''.$package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins'); + $attempts = 0; + while (true) { + // do not allow more than 5 prints of the help message, at some point assume the + // input is not interactive and bail defaulting to a disabled plugin + $default = '?'; + if ($attempts > 5) { + $this->io->writeError('Too many failed prompts, aborting.'); + break; + } + + switch ($answer = $this->io->ask('Do you trust "'.$package.'" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] ', $default)) { + case 'y': + case 'n': + case 'd': + $allow = $answer === 'y'; + + // persist answer in current rules to avoid prompting again if the package gets reloaded + $rules[BasePackage::packageNameToRegexp($package)] = $allow; + + // persist answer in composer.json if it wasn't simply discarded + if ($answer === 'y' || $answer === 'n') { + $allowPlugins = $composer->getConfig()->get('allow-plugins'); + if (is_array($allowPlugins)) { + $allowPlugins[$package] = $allow; + if ($composer->getConfig()->get('sort-packages')) { + ksort($allowPlugins); + } + $composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins', $allowPlugins); + $composer->getConfig()->merge(['config' => ['allow-plugins' => $allowPlugins]]); + } + } + + return $allow; + + case '?': + default: + $attempts++; + $this->io->writeError([ + 'y - add package to allow-plugins in composer.json and let it run immediately', + 'n - add package (as disallowed) to allow-plugins in composer.json to suppress further prompts', + 'd - discard this, do not change composer.json and do not allow the plugin to run', + '? - print help', + ]); + break; + } + } + } elseif ($optional) { + return false; + } + + throw new PluginBlockedException( + $package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is blocked by your allow-plugins config. You may add it to the list if you consider it safe.'.PHP_EOL. + 'You can run "composer '.($isGlobalPlugin || $this->runningInGlobalDir ? 'global ' : '').'config --no-plugins allow-plugins.'.$package.' [true|false]" to enable it (true) or disable it explicitly and suppress this exception (false)'.PHP_EOL. + 'See https://getcomposer.org/allow-plugins' + ); + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php b/vendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php new file mode 100644 index 000000000..d879030bb --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php @@ -0,0 +1,139 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Composer\Package\PackageInterface; + +/** + * The post file download event. + * + * @author Nils Adermann + */ +class PostFileDownloadEvent extends Event +{ + /** + * @var string + */ + private $fileName; + + /** + * @var string|null + */ + private $checksum; + + /** + * @var string + */ + private $url; + + /** + * @var mixed + */ + private $context; + + /** + * @var string + */ + private $type; + + /** + * Constructor. + * + * @param string $name The event name + * @param string|null $fileName The file name + * @param string|null $checksum The checksum + * @param string $url The processed url + * @param string $type The type (package or metadata). + * @param mixed $context Additional context for the download. + */ + public function __construct(string $name, ?string $fileName, ?string $checksum, string $url, string $type, $context = null) + { + /** @phpstan-ignore instanceof.alwaysFalse, booleanAnd.alwaysFalse */ + if ($context === null && $type instanceof PackageInterface) { + $context = $type; + $type = 'package'; + trigger_error('PostFileDownloadEvent::__construct should receive a $type=package and the package object in $context since Composer 2.1.', E_USER_DEPRECATED); + } + + parent::__construct($name); + $this->fileName = $fileName; + $this->checksum = $checksum; + $this->url = $url; + $this->context = $context; + $this->type = $type; + } + + /** + * Retrieves the target file name location. + * + * If this download is of type metadata, null is returned. + */ + public function getFileName(): ?string + { + return $this->fileName; + } + + /** + * Gets the checksum. + */ + public function getChecksum(): ?string + { + return $this->checksum; + } + + /** + * Gets the processed URL. + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * Returns the context of this download, if any. + * + * If this download is of type package, the package object is returned. If + * this download is of type metadata, an array{response: Response, repository: RepositoryInterface} is returned. + * + * @return mixed + */ + public function getContext() + { + return $this->context; + } + + /** + * Get the package. + * + * If this download is of type metadata, null is returned. + * + * @return PackageInterface|null The package. + * @deprecated Use getContext instead + */ + public function getPackage(): ?PackageInterface + { + trigger_error('PostFileDownloadEvent::getPackage is deprecated since Composer 2.1, use getContext instead.', E_USER_DEPRECATED); + $context = $this->getContext(); + + return $context instanceof PackageInterface ? $context : null; + } + + /** + * Returns the type of this download (package, metadata). + */ + public function getType(): string + { + return $this->type; + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php b/vendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php new file mode 100644 index 000000000..7bd7a4626 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php @@ -0,0 +1,63 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Symfony\Component\Console\Input\InputInterface; + +/** + * The pre command run event. + * + * @author Jordi Boggiano + */ +class PreCommandRunEvent extends Event +{ + /** + * @var InputInterface + */ + private $input; + + /** + * @var string + */ + private $command; + + /** + * Constructor. + * + * @param string $name The event name + * @param string $command The command about to be executed + */ + public function __construct(string $name, InputInterface $input, string $command) + { + parent::__construct($name); + $this->input = $input; + $this->command = $command; + } + + /** + * Returns the console input + */ + public function getInput(): InputInterface + { + return $this->input; + } + + /** + * Returns the command about to be executed + */ + public function getCommand(): string + { + return $this->command; + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php b/vendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php new file mode 100644 index 000000000..be368826c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php @@ -0,0 +1,158 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Composer\Util\HttpDownloader; + +/** + * The pre file download event. + * + * @author Nils Adermann + */ +class PreFileDownloadEvent extends Event +{ + /** + * @var HttpDownloader + */ + private $httpDownloader; + + /** + * @var non-empty-string + */ + private $processedUrl; + + /** + * @var string|null + */ + private $customCacheKey; + + /** + * @var string + */ + private $type; + + /** + * @var mixed + */ + private $context; + + /** + * @var mixed[] + */ + private $transportOptions = []; + + /** + * Constructor. + * + * @param string $name The event name + * @param mixed $context + * @param non-empty-string $processedUrl + */ + public function __construct(string $name, HttpDownloader $httpDownloader, string $processedUrl, string $type, $context = null) + { + parent::__construct($name); + $this->httpDownloader = $httpDownloader; + $this->processedUrl = $processedUrl; + $this->type = $type; + $this->context = $context; + } + + public function getHttpDownloader(): HttpDownloader + { + return $this->httpDownloader; + } + + /** + * Retrieves the processed URL that will be downloaded. + * + * @return non-empty-string + */ + public function getProcessedUrl(): string + { + return $this->processedUrl; + } + + /** + * Sets the processed URL that will be downloaded. + * + * @param non-empty-string $processedUrl New processed URL + */ + public function setProcessedUrl(string $processedUrl): void + { + $this->processedUrl = $processedUrl; + } + + /** + * Retrieves a custom package cache key for this download. + */ + public function getCustomCacheKey(): ?string + { + return $this->customCacheKey; + } + + /** + * Sets a custom package cache key for this download. + * + * @param string|null $customCacheKey New cache key + */ + public function setCustomCacheKey(?string $customCacheKey): void + { + $this->customCacheKey = $customCacheKey; + } + + /** + * Returns the type of this download (package, metadata). + */ + public function getType(): string + { + return $this->type; + } + + /** + * Returns the context of this download, if any. + * + * If this download is of type package, the package object is returned. + * If the type is metadata, an array{repository: RepositoryInterface} is returned. + * + * @return mixed + */ + public function getContext() + { + return $this->context; + } + + /** + * Returns transport options for the download. + * + * Only available for events with type metadata, for packages set the transport options on the package itself. + * + * @return mixed[] + */ + public function getTransportOptions(): array + { + return $this->transportOptions; + } + + /** + * Sets transport options for the download. + * + * Only available for events with type metadata, for packages set the transport options on the package itself. + * + * @param mixed[] $options + */ + public function setTransportOptions(array $options): void + { + $this->transportOptions = $options; + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php b/vendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php new file mode 100644 index 000000000..e7ea7a06c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php @@ -0,0 +1,173 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Composer\Repository\RepositoryInterface; +use Composer\DependencyResolver\Request; +use Composer\Package\BasePackage; + +/** + * The pre command run event. + * + * @author Jordi Boggiano + */ +class PrePoolCreateEvent extends Event +{ + /** + * @var RepositoryInterface[] + */ + private $repositories; + /** + * @var Request + */ + private $request; + /** + * @var int[] array of stability => BasePackage::STABILITY_* value + * @phpstan-var array + */ + private $acceptableStabilities; + /** + * @var int[] array of package name => BasePackage::STABILITY_* value + * @phpstan-var array + */ + private $stabilityFlags; + /** + * @var array[] of package => version => [alias, alias_normalized] + * @phpstan-var array> + */ + private $rootAliases; + /** + * @var string[] + * @phpstan-var array + */ + private $rootReferences; + /** + * @var BasePackage[] + */ + private $packages; + /** + * @var BasePackage[] + */ + private $unacceptableFixedPackages; + + /** + * @param string $name The event name + * @param RepositoryInterface[] $repositories + * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value + * @param int[] $stabilityFlags array of package name => BasePackage::STABILITY_* value + * @param array[] $rootAliases array of package => version => [alias, alias_normalized] + * @param string[] $rootReferences + * @param BasePackage[] $packages + * @param BasePackage[] $unacceptableFixedPackages + * + * @phpstan-param array $acceptableStabilities + * @phpstan-param array $stabilityFlags + * @phpstan-param array> $rootAliases + * @phpstan-param array $rootReferences + */ + public function __construct(string $name, array $repositories, Request $request, array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, array $packages, array $unacceptableFixedPackages) + { + parent::__construct($name); + + $this->repositories = $repositories; + $this->request = $request; + $this->acceptableStabilities = $acceptableStabilities; + $this->stabilityFlags = $stabilityFlags; + $this->rootAliases = $rootAliases; + $this->rootReferences = $rootReferences; + $this->packages = $packages; + $this->unacceptableFixedPackages = $unacceptableFixedPackages; + } + + /** + * @return RepositoryInterface[] + */ + public function getRepositories(): array + { + return $this->repositories; + } + + public function getRequest(): Request + { + return $this->request; + } + + /** + * @return int[] array of stability => BasePackage::STABILITY_* value + * @phpstan-return array + */ + public function getAcceptableStabilities(): array + { + return $this->acceptableStabilities; + } + + /** + * @return int[] array of package name => BasePackage::STABILITY_* value + * @phpstan-return array + */ + public function getStabilityFlags(): array + { + return $this->stabilityFlags; + } + + /** + * @return array[] of package => version => [alias, alias_normalized] + * @phpstan-return array> + */ + public function getRootAliases(): array + { + return $this->rootAliases; + } + + /** + * @return string[] + * @phpstan-return array + */ + public function getRootReferences(): array + { + return $this->rootReferences; + } + + /** + * @return BasePackage[] + */ + public function getPackages(): array + { + return $this->packages; + } + + /** + * @return BasePackage[] + */ + public function getUnacceptableFixedPackages(): array + { + return $this->unacceptableFixedPackages; + } + + /** + * @param BasePackage[] $packages + */ + public function setPackages(array $packages): void + { + $this->packages = $packages; + } + + /** + * @param BasePackage[] $packages + */ + public function setUnacceptableFixedPackages(array $packages): void + { + $this->unacceptableFixedPackages = $packages; + } +} diff --git a/vendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php b/vendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php new file mode 100644 index 000000000..9cbc74ec5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php @@ -0,0 +1,93 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Question; + +use Composer\Pcre\Preg; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Question\Question; + +/** + * Represents a yes/no question + * Enforces strict responses rather than non-standard answers counting as default + * Based on Symfony\Component\Console\Question\ConfirmationQuestion + * + * @author Theo Tonge + */ +class StrictConfirmationQuestion extends Question +{ + /** @var non-empty-string */ + private $trueAnswerRegex; + /** @var non-empty-string */ + private $falseAnswerRegex; + + /** + * Constructor.s + * + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param non-empty-string $trueAnswerRegex A regex to match the "yes" answer + * @param non-empty-string $falseAnswerRegex A regex to match the "no" answer + */ + public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y(?:es)?$/i', string $falseAnswerRegex = '/^no?$/i') + { + parent::__construct($question, $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->falseAnswerRegex = $falseAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + $this->setValidator($this->getDefaultValidator()); + } + + /** + * Returns the default answer normalizer. + */ + private function getDefaultNormalizer(): callable + { + $default = $this->getDefault(); + $trueRegex = $this->trueAnswerRegex; + $falseRegex = $this->falseAnswerRegex; + + return static function ($answer) use ($default, $trueRegex, $falseRegex) { + if (is_bool($answer)) { + return $answer; + } + if (empty($answer) && !empty($default)) { + return $default; + } + + if (Preg::isMatch($trueRegex, $answer)) { + return true; + } + + if (Preg::isMatch($falseRegex, $answer)) { + return false; + } + + return null; + }; + } + + /** + * Returns the default answer validator. + */ + private function getDefaultValidator(): callable + { + return static function ($answer): bool { + if (!is_bool($answer)) { + throw new InvalidArgumentException('Please answer yes, y, no, or n.'); + } + + return $answer; + }; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php b/vendor/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php new file mode 100644 index 000000000..528b4ab76 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php @@ -0,0 +1,34 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\Advisory\SecurityAdvisory; + +/** + * Repositories that allow fetching security advisory data + * + * @author Jordi Boggiano + * @internal + */ +interface AdvisoryProviderInterface +{ + public function hasSecurityAdvisories(): bool; + + /** + * @param array $packageConstraintMap Map of package name to constraint (can be MatchAllConstraint to fetch all advisories) + * @return ($allowPartialAdvisories is true ? array{namesFound: string[], advisories: array>} : array{namesFound: string[], advisories: array>}) + */ + public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array; +} diff --git a/vendor/composer/composer/src/Composer/Repository/ArrayRepository.php b/vendor/composer/composer/src/Composer/Repository/ArrayRepository.php new file mode 100644 index 000000000..71a26ecc3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/ArrayRepository.php @@ -0,0 +1,341 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\PackageInterface; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\StabilityFilter; +use Composer\Pcre\Preg; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; + +/** + * A repository implementation that simply stores packages in an array + * + * @author Nils Adermann + */ +class ArrayRepository implements RepositoryInterface +{ + /** @var ?array */ + protected $packages = null; + + /** + * @var ?array indexed by package unique name and used to cache hasPackage calls + */ + protected $packageMap = null; + + /** + * @param array $packages + */ + public function __construct(array $packages = []) + { + foreach ($packages as $package) { + $this->addPackage($package); + } + } + + public function getRepoName() + { + return 'array repo (defining '.$this->count().' package'.($this->count() > 1 ? 's' : '').')'; + } + + /** + * @inheritDoc + */ + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) + { + $packages = $this->getPackages(); + + $result = []; + $namesFound = []; + foreach ($packages as $package) { + if (array_key_exists($package->getName(), $packageNameMap)) { + if ( + (!$packageNameMap[$package->getName()] || $packageNameMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) + && StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability()) + && !isset($alreadyLoaded[$package->getName()][$package->getVersion()]) + ) { + // add selected packages which match stability requirements + $result[spl_object_hash($package)] = $package; + // add the aliased package for packages where the alias matches + if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) { + $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); + } + } + + $namesFound[$package->getName()] = true; + } + } + + // add aliases of packages that were selected, even if the aliases did not match + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + if (isset($result[spl_object_hash($package->getAliasOf())])) { + $result[spl_object_hash($package)] = $package; + } + } + } + + return ['namesFound' => array_keys($namesFound), 'packages' => $result]; + } + + /** + * @inheritDoc + */ + public function findPackage(string $name, $constraint) + { + $name = strtolower($name); + + if (!$constraint instanceof ConstraintInterface) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($constraint); + } + + foreach ($this->getPackages() as $package) { + if ($name === $package->getName()) { + $pkgConstraint = new Constraint('==', $package->getVersion()); + if ($constraint->matches($pkgConstraint)) { + return $package; + } + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function findPackages(string $name, $constraint = null) + { + // normalize name + $name = strtolower($name); + $packages = []; + + if (null !== $constraint && !$constraint instanceof ConstraintInterface) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($constraint); + } + + foreach ($this->getPackages() as $package) { + if ($name === $package->getName()) { + if (null === $constraint || $constraint->matches(new Constraint('==', $package->getVersion()))) { + $packages[] = $package; + } + } + } + + return $packages; + } + + /** + * @inheritDoc + */ + public function search(string $query, int $mode = 0, ?string $type = null) + { + if ($mode === self::SEARCH_FULLTEXT) { + $regex = '{(?:'.implode('|', Preg::split('{\s+}', preg_quote($query))).')}i'; + } else { + // vendor/name searches expect the caller to have preg_quoted the query + $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i'; + } + + $matches = []; + foreach ($this->getPackages() as $package) { + $name = $package->getName(); + if ($mode === self::SEARCH_VENDOR) { + [$name] = explode('/', $name); + } + if (isset($matches[$name])) { + continue; + } + if (null !== $type && $package->getType() !== $type) { + continue; + } + + if (Preg::isMatch($regex, $name) + || ($mode === self::SEARCH_FULLTEXT && $package instanceof CompletePackageInterface && Preg::isMatch($regex, implode(' ', (array) $package->getKeywords()) . ' ' . $package->getDescription())) + ) { + if ($mode === self::SEARCH_VENDOR) { + $matches[$name] = [ + 'name' => $name, + 'description' => null, + ]; + } else { + $matches[$name] = [ + 'name' => $package->getPrettyName(), + 'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : null, + ]; + + if ($package instanceof CompletePackageInterface && $package->isAbandoned()) { + $matches[$name]['abandoned'] = $package->getReplacementPackage() ?: true; + } + } + } + } + + return array_values($matches); + } + + /** + * @inheritDoc + */ + public function hasPackage(PackageInterface $package) + { + if ($this->packageMap === null) { + $this->packageMap = []; + foreach ($this->getPackages() as $repoPackage) { + $this->packageMap[$repoPackage->getUniqueName()] = $repoPackage; + } + } + + return isset($this->packageMap[$package->getUniqueName()]); + } + + /** + * Adds a new package to the repository + * + * @return void + */ + public function addPackage(PackageInterface $package) + { + if (!$package instanceof BasePackage) { + throw new \InvalidArgumentException('Only subclasses of BasePackage are supported'); + } + if (null === $this->packages) { + $this->initialize(); + } + $package->setRepository($this); + $this->packages[] = $package; + + if ($package instanceof AliasPackage) { + $aliasedPackage = $package->getAliasOf(); + if (null === $aliasedPackage->getRepository()) { + $this->addPackage($aliasedPackage); + } + } + + // invalidate package map cache + $this->packageMap = null; + } + + /** + * @inheritDoc + */ + public function getProviders(string $packageName) + { + $result = []; + + foreach ($this->getPackages() as $candidate) { + if (isset($result[$candidate->getName()])) { + continue; + } + foreach ($candidate->getProvides() as $link) { + if ($packageName === $link->getTarget()) { + $result[$candidate->getName()] = [ + 'name' => $candidate->getName(), + 'description' => $candidate instanceof CompletePackageInterface ? $candidate->getDescription() : null, + 'type' => $candidate->getType(), + ]; + continue 2; + } + } + } + + return $result; + } + + /** + * @return AliasPackage|CompleteAliasPackage + */ + protected function createAliasPackage(BasePackage $package, string $alias, string $prettyAlias) + { + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + if ($package instanceof CompletePackage) { + return new CompleteAliasPackage($package, $alias, $prettyAlias); + } + + return new AliasPackage($package, $alias, $prettyAlias); + } + + /** + * Removes package from repository. + * + * @param PackageInterface $package package instance + * + * @return void + */ + public function removePackage(PackageInterface $package) + { + $packageId = $package->getUniqueName(); + + foreach ($this->getPackages() as $key => $repoPackage) { + if ($packageId === $repoPackage->getUniqueName()) { + array_splice($this->packages, $key, 1); + + // invalidate package map cache + $this->packageMap = null; + + return; + } + } + } + + /** + * @inheritDoc + */ + public function getPackages() + { + if (null === $this->packages) { + $this->initialize(); + } + + if (null === $this->packages) { + throw new \LogicException('initialize failed to initialize the packages array'); + } + + return $this->packages; + } + + /** + * Returns the number of packages in this repository + * + * @return 0|positive-int Number of packages + */ + public function count(): int + { + if (null === $this->packages) { + $this->initialize(); + } + + return count($this->packages); + } + + /** + * Initializes the packages array. Mostly meant as an extension point. + * + * @return void + */ + protected function initialize() + { + $this->packages = []; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/ArtifactRepository.php b/vendor/composer/composer/src/Composer/Repository/ArtifactRepository.php new file mode 100644 index 000000000..fcd897049 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/ArtifactRepository.php @@ -0,0 +1,140 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\BasePackage; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Loader\LoaderInterface; +use Composer\Util\Platform; +use Composer\Util\Tar; +use Composer\Util\Zip; + +/** + * @author Serge Smertin + */ +class ArtifactRepository extends ArrayRepository implements ConfigurableRepositoryInterface +{ + /** @var LoaderInterface */ + protected $loader; + + /** @var string */ + protected $lookup; + /** @var array{url: string} */ + protected $repoConfig; + /** @var IOInterface */ + private $io; + + /** + * @param array{url: string} $repoConfig + */ + public function __construct(array $repoConfig, IOInterface $io) + { + parent::__construct(); + if (!extension_loaded('zip')) { + throw new \RuntimeException('The artifact repository requires PHP\'s zip extension'); + } + + $this->loader = new ArrayLoader(); + $this->lookup = Platform::expandPath($repoConfig['url']); + $this->io = $io; + $this->repoConfig = $repoConfig; + } + + public function getRepoName() + { + return 'artifact repo ('.$this->lookup.')'; + } + + public function getRepoConfig() + { + return $this->repoConfig; + } + + protected function initialize() + { + parent::initialize(); + + $this->scanDirectory($this->lookup); + } + + private function scanDirectory(string $path): void + { + $io = $this->io; + + $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); + $iterator = new \RecursiveIteratorIterator($directory); + $regex = new \RegexIterator($iterator, '/^.+\.(zip|tar|gz|tgz)$/i'); + foreach ($regex as $file) { + /* @var $file \SplFileInfo */ + if (!$file->isFile()) { + continue; + } + + $package = $this->getComposerInformation($file); + if (!$package) { + $io->writeError("File {$file->getBasename()} doesn't seem to hold a package", true, IOInterface::VERBOSE); + continue; + } + + $template = 'Found package %s (%s) in file %s'; + $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE); + + $this->addPackage($package); + } + } + + private function getComposerInformation(\SplFileInfo $file): ?BasePackage + { + $json = null; + $fileType = null; + $fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION); + if (in_array($fileExtension, ['gz', 'tar', 'tgz'], true)) { + $fileType = 'tar'; + } elseif ($fileExtension === 'zip') { + $fileType = 'zip'; + } else { + throw new \RuntimeException('Files with "'.$fileExtension.'" extensions aren\'t supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.'); + } + + try { + if ($fileType === 'tar') { + $json = Tar::getComposerJson($file->getPathname()); + } else { + $json = Zip::getComposerJson($file->getPathname()); + } + } catch (\Exception $exception) { + $this->io->write('Failed loading package '.$file->getPathname().': '.$exception->getMessage(), false, IOInterface::VERBOSE); + } + + if (null === $json) { + return null; + } + + $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json'); + $package['dist'] = [ + 'type' => $fileType, + 'url' => strtr($file->getPathname(), '\\', '/'), + 'shasum' => hash_file('sha1', $file->getRealPath()), + ]; + + try { + $package = $this->loader->load($package); + } catch (\UnexpectedValueException $e) { + throw new \UnexpectedValueException('Failed loading package in '.$file.': '.$e->getMessage(), 0, $e); + } + + return $package; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php b/vendor/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php new file mode 100644 index 000000000..1784dadc1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php @@ -0,0 +1,55 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; + +/** + * Provides getCanonicalPackages() to various repository implementations + * + * @internal + */ +trait CanonicalPackagesTrait +{ + /** + * Get unique packages (at most one package of each name), with aliases resolved and removed. + * + * @return PackageInterface[] + */ + public function getCanonicalPackages() + { + $packages = $this->getPackages(); + + // get at most one package of each name, preferring non-aliased ones + $packagesByName = []; + foreach ($packages as $package) { + if (!isset($packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) { + $packagesByName[$package->getName()] = $package; + } + } + + $canonicalPackages = []; + + // unfold aliased packages + foreach ($packagesByName as $package) { + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + $canonicalPackages[] = $package; + } + + return $canonicalPackages; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/ComposerRepository.php b/vendor/composer/composer/src/Composer/Repository/ComposerRepository.php new file mode 100644 index 000000000..50f112450 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/ComposerRepository.php @@ -0,0 +1,1791 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\Advisory\SecurityAdvisory; +use Composer\Package\BasePackage; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\StabilityFilter; +use Composer\Json\JsonFile; +use Composer\Cache; +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Plugin\PostFileDownloadEvent; +use Composer\Semver\CompilingMatcher; +use Composer\Util\HttpDownloader; +use Composer\Util\Loop; +use Composer\Plugin\PluginEvents; +use Composer\Plugin\PreFileDownloadEvent; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Downloader\TransportException; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Util\Http\Response; +use Composer\MetadataMinifier\MetadataMinifier; +use Composer\Util\Url; +use React\Promise\PromiseInterface; + +/** + * @author Jordi Boggiano + */ +class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface, AdvisoryProviderInterface +{ + /** + * @var mixed[] + * @phpstan-var array{url: string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} + */ + private $repoConfig; + /** @var mixed[] */ + private $options; + /** @var non-empty-string */ + private $url; + /** @var non-empty-string */ + private $baseUrl; + /** @var IOInterface */ + private $io; + /** @var HttpDownloader */ + private $httpDownloader; + /** @var Loop */ + private $loop; + /** @var Cache */ + protected $cache; + /** @var ?non-empty-string */ + protected $notifyUrl = null; + /** @var ?non-empty-string */ + protected $searchUrl = null; + /** @var ?non-empty-string a URL containing %package% which can be queried to get providers of a given name */ + protected $providersApiUrl = null; + /** @var bool */ + protected $hasProviders = false; + /** @var ?non-empty-string */ + protected $providersUrl = null; + /** @var ?non-empty-string */ + protected $listUrl = null; + /** @var bool Indicates whether a comprehensive list of packages this repository might provide is expressed in the repository root. **/ + protected $hasAvailablePackageList = false; + /** @var ?array */ + protected $availablePackages = null; + /** @var ?array */ + protected $availablePackagePatterns = null; + /** @var ?non-empty-string */ + protected $lazyProvidersUrl = null; + /** @var ?array */ + protected $providerListing; + /** @var ArrayLoader */ + protected $loader; + /** @var bool */ + private $allowSslDowngrade = false; + /** @var ?EventDispatcher */ + private $eventDispatcher; + /** @var ?array> */ + private $sourceMirrors; + /** @var ?list */ + private $distMirrors; + /** @var bool */ + private $degradedMode = false; + /** @var mixed[]|true */ + private $rootData; + /** @var bool */ + private $hasPartialPackages = false; + /** @var ?array */ + private $partialPackagesByName = null; + /** @var bool */ + private $displayedWarningAboutNonMatchingPackageIndex = false; + /** @var array{metadata: bool, api-url: string|null}|null */ + private $securityAdvisoryConfig = null; + + /** + * @var array list of package names which are fresh and can be loaded from the cache directly in case loadPackage is called several times + * useful for v2 metadata repositories with lazy providers + * @phpstan-var array + */ + private $freshMetadataUrls = []; + + /** + * @var array list of package names which returned a 404 and should not be re-fetched in case loadPackage is called several times + * useful for v2 metadata repositories with lazy providers + * @phpstan-var array + */ + private $packagesNotFoundCache = []; + + /** + * @var VersionParser + */ + private $versionParser; + + /** + * @param array $repoConfig + * @phpstan-param array{url: non-empty-string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} $repoConfig + */ + public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null) + { + parent::__construct(); + if (!Preg::isMatch('{^[\w.]+\??://}', $repoConfig['url'])) { + if (($localFilePath = realpath($repoConfig['url'])) !== false) { + // it is a local path, add file scheme + $repoConfig['url'] = 'file://'.$localFilePath; + } else { + // otherwise, assume http as the default protocol + $repoConfig['url'] = 'http://'.$repoConfig['url']; + } + } + $repoConfig['url'] = rtrim($repoConfig['url'], '/'); + if ($repoConfig['url'] === '') { + throw new \InvalidArgumentException('The repository url must not be an empty string'); + } + + if (str_starts_with($repoConfig['url'], 'https?')) { + $repoConfig['url'] = (extension_loaded('openssl') ? 'https' : 'http') . substr($repoConfig['url'], 6); + } + + $urlBits = parse_url(strtr($repoConfig['url'], '\\', '/')); + if ($urlBits === false || empty($urlBits['scheme'])) { + throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']); + } + + if (!isset($repoConfig['options'])) { + $repoConfig['options'] = []; + } + if (isset($repoConfig['allow_ssl_downgrade']) && true === $repoConfig['allow_ssl_downgrade']) { + $this->allowSslDowngrade = true; + } + + $this->options = $repoConfig['options']; + $this->url = $repoConfig['url']; + + // force url for packagist.org to repo.packagist.org + if (Preg::isMatch('{^(?Phttps?)://packagist\.org/?$}i', $this->url, $match)) { + $this->url = $match['proto'].'://repo.packagist.org'; + } + + $baseUrl = rtrim(Preg::replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); + assert($baseUrl !== ''); + $this->baseUrl = $baseUrl; + $this->io = $io; + $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)), 'a-z0-9.$~_'); + $this->cache->setReadOnly($config->get('cache-read-only')); + $this->versionParser = new VersionParser(); + $this->loader = new ArrayLoader($this->versionParser); + $this->httpDownloader = $httpDownloader; + $this->eventDispatcher = $eventDispatcher; + $this->repoConfig = $repoConfig; + $this->loop = new Loop($this->httpDownloader); + } + + public function getRepoName() + { + return 'composer repo ('.Url::sanitize($this->url).')'; + } + + public function getRepoConfig() + { + return $this->repoConfig; + } + + /** + * @inheritDoc + */ + public function findPackage(string $name, $constraint) + { + // this call initializes loadRootServerFile which is needed for the rest below to work + $hasProviders = $this->hasProviders(); + + $name = strtolower($name); + if (!$constraint instanceof ConstraintInterface) { + $constraint = $this->versionParser->parseConstraints($constraint); + } + + if ($this->lazyProvidersUrl) { + if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { + return $this->filterPackages($this->whatProvides($name), $constraint, true); + } + + if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) { + return null; + } + + $packages = $this->loadAsyncPackages([$name => $constraint]); + + if (count($packages['packages']) > 0) { + return reset($packages['packages']); + } + + return null; + } + + if ($hasProviders) { + foreach ($this->getProviderNames() as $providerName) { + if ($name === $providerName) { + return $this->filterPackages($this->whatProvides($providerName), $constraint, true); + } + } + + return null; + } + + return parent::findPackage($name, $constraint); + } + + /** + * @inheritDoc + */ + public function findPackages(string $name, $constraint = null) + { + // this call initializes loadRootServerFile which is needed for the rest below to work + $hasProviders = $this->hasProviders(); + + $name = strtolower($name); + if (null !== $constraint && !$constraint instanceof ConstraintInterface) { + $constraint = $this->versionParser->parseConstraints($constraint); + } + + if ($this->lazyProvidersUrl) { + if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { + return $this->filterPackages($this->whatProvides($name), $constraint); + } + + if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) { + return []; + } + + $result = $this->loadAsyncPackages([$name => $constraint]); + + return $result['packages']; + } + + if ($hasProviders) { + foreach ($this->getProviderNames() as $providerName) { + if ($name === $providerName) { + return $this->filterPackages($this->whatProvides($providerName), $constraint); + } + } + + return []; + } + + return parent::findPackages($name, $constraint); + } + + /** + * @param array $packages + * + * @return BasePackage|array|null + */ + private function filterPackages(array $packages, ?ConstraintInterface $constraint = null, bool $returnFirstMatch = false) + { + if (null === $constraint) { + if ($returnFirstMatch) { + return reset($packages); + } + + return $packages; + } + + $filteredPackages = []; + + foreach ($packages as $package) { + $pkgConstraint = new Constraint('==', $package->getVersion()); + + if ($constraint->matches($pkgConstraint)) { + if ($returnFirstMatch) { + return $package; + } + + $filteredPackages[] = $package; + } + } + + if ($returnFirstMatch) { + return null; + } + + return $filteredPackages; + } + + public function getPackages() + { + $hasProviders = $this->hasProviders(); + + if ($this->lazyProvidersUrl) { + if (is_array($this->availablePackages) && !$this->availablePackagePatterns) { + $packageMap = []; + foreach ($this->availablePackages as $name) { + $packageMap[$name] = new MatchAllConstraint(); + } + + $result = $this->loadAsyncPackages($packageMap); + + return array_values($result['packages']); + } + + if ($this->hasPartialPackages()) { + if (!is_array($this->partialPackagesByName)) { + throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName'); + } + + return $this->createPackages($this->partialPackagesByName, 'packages.json inline packages'); + } + + throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getPackageNames instead.'); + } + + if ($hasProviders) { + throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getPackageNames instead.'); + } + + return parent::getPackages(); + } + + /** + * @param string|null $packageFilter Package pattern filter which can include "*" as a wildcard + * + * @return string[] + */ + public function getPackageNames(?string $packageFilter = null) + { + $hasProviders = $this->hasProviders(); + + $filterResults = + /** + * @param list $results + * @return list + */ + static function (array $results): array { + return $results; + } + ; + if (null !== $packageFilter && '' !== $packageFilter) { + $packageFilterRegex = BasePackage::packageNameToRegexp($packageFilter); + $filterResults = + /** + * @param list $results + * @return list + */ + static function (array $results) use ($packageFilterRegex): array { + /** @var list $results */ + return Preg::grep($packageFilterRegex, $results); + } + ; + } + + if ($this->lazyProvidersUrl) { + if (is_array($this->availablePackages)) { + return $filterResults(array_keys($this->availablePackages)); + } + + if ($this->listUrl) { + // no need to call $filterResults here as the $packageFilter is applied in the function itself + return $this->loadPackageList($packageFilter); + } + + if ($this->hasPartialPackages() && $this->partialPackagesByName !== null) { + return $filterResults(array_keys($this->partialPackagesByName)); + } + + return []; + } + + if ($hasProviders) { + return $filterResults($this->getProviderNames()); + } + + $names = []; + foreach ($this->getPackages() as $package) { + $names[] = $package->getPrettyName(); + } + + return $filterResults($names); + } + + /** + * @return list + */ + private function getVendorNames(): array + { + $cacheKey = 'vendor-list.txt'; + $cacheAge = $this->cache->getAge($cacheKey); + if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) { + $cachedData = explode("\n", $cachedData); + + return $cachedData; + } + + $names = $this->getPackageNames(); + + $uniques = []; + foreach ($names as $name) { + $uniques[explode('/', $name, 2)[0]] = true; + } + + $vendors = array_keys($uniques); + + if (!$this->cache->isReadOnly()) { + $this->cache->write($cacheKey, implode("\n", $vendors)); + } + + return $vendors; + } + + /** + * @return list + */ + private function loadPackageList(?string $packageFilter = null): array + { + if (null === $this->listUrl) { + throw new \LogicException('Make sure to call loadRootServerFile before loadPackageList'); + } + + $url = $this->listUrl; + if (is_string($packageFilter) && $packageFilter !== '') { + $url .= '?filter='.urlencode($packageFilter); + $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); + + return $result['packageNames']; + } + + $cacheKey = 'package-list.txt'; + $cacheAge = $this->cache->getAge($cacheKey); + if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) { + $cachedData = explode("\n", $cachedData); + + return $cachedData; + } + + $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); + if (!$this->cache->isReadOnly()) { + $this->cache->write($cacheKey, implode("\n", $result['packageNames'])); + } + + return $result['packageNames']; + } + + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) + { + // this call initializes loadRootServerFile which is needed for the rest below to work + $hasProviders = $this->hasProviders(); + + if (!$hasProviders && !$this->hasPartialPackages() && null === $this->lazyProvidersUrl) { + return parent::loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + } + + $packages = []; + $namesFound = []; + + if ($hasProviders || $this->hasPartialPackages()) { + foreach ($packageNameMap as $name => $constraint) { + $matches = []; + + // if a repo has no providers but only partial packages and the partial packages are missing + // then we don't want to call whatProvides as it would try to load from the providers and fail + if (!$hasProviders && !isset($this->partialPackagesByName[$name])) { + continue; + } + + $candidates = $this->whatProvides($name, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + foreach ($candidates as $candidate) { + if ($candidate->getName() !== $name) { + throw new \LogicException('whatProvides should never return a package with a different name than the requested one'); + } + $namesFound[$name] = true; + + if (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { + $matches[spl_object_hash($candidate)] = $candidate; + if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf(); + } + } + } + + // add aliases of matched packages even if they did not match the constraint + foreach ($candidates as $candidate) { + if ($candidate instanceof AliasPackage) { + if (isset($matches[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate)] = $candidate; + } + } + } + $packages = array_merge($packages, $matches); + + unset($packageNameMap[$name]); + } + } + + if ($this->lazyProvidersUrl && count($packageNameMap)) { + if ($this->hasAvailablePackageList) { + foreach ($packageNameMap as $name => $constraint) { + if (!$this->lazyProvidersRepoContains(strtolower($name))) { + unset($packageNameMap[$name]); + } + } + } + + $result = $this->loadAsyncPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + $packages = array_merge($packages, $result['packages']); + $namesFound = array_merge($namesFound, $result['namesFound']); + } + + return ['namesFound' => array_keys($namesFound), 'packages' => $packages]; + } + + /** + * @inheritDoc + */ + public function search(string $query, int $mode = 0, ?string $type = null) + { + $this->loadRootServerFile(600); + + if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) { + $url = str_replace(['%query%', '%type%'], [urlencode($query), $type], $this->searchUrl); + + $search = $this->httpDownloader->get($url, $this->options)->decodeJson(); + + if (empty($search['results'])) { + return []; + } + + $results = []; + foreach ($search['results'] as $result) { + // do not show virtual packages in results as they are not directly useful from a composer perspective + if (!empty($result['virtual'])) { + continue; + } + + $results[] = $result; + } + + return $results; + } + + if ($mode === self::SEARCH_VENDOR) { + $results = []; + $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i'; + + $vendorNames = $this->getVendorNames(); + foreach (Preg::grep($regex, $vendorNames) as $name) { + $results[] = ['name' => $name, 'description' => '']; + } + + return $results; + } + + if ($this->hasProviders() || $this->lazyProvidersUrl) { + // optimize search for "^foo/bar" where at least "^foo/" is present by loading this directly from the listUrl if present + if (Preg::isMatchStrictGroups('{^\^(?P(?P[a-z0-9_.-]+)/[a-z0-9_.-]*)\*?$}i', $query, $match) && $this->listUrl !== null) { + $url = $this->listUrl . '?vendor='.urlencode($match['vendor']).'&filter='.urlencode($match['query'].'*'); + $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); + + $results = []; + foreach ($result['packageNames'] as $name) { + $results[] = ['name' => $name, 'description' => '']; + } + + return $results; + } + + $results = []; + $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i'; + + $packageNames = $this->getPackageNames(); + foreach (Preg::grep($regex, $packageNames) as $name) { + $results[] = ['name' => $name, 'description' => '']; + } + + return $results; + } + + return parent::search($query, $mode); + } + + public function hasSecurityAdvisories(): bool + { + $this->loadRootServerFile(600); + + return $this->securityAdvisoryConfig !== null && ($this->securityAdvisoryConfig['metadata'] || $this->securityAdvisoryConfig['api-url'] !== null); + } + + /** + * @inheritDoc + */ + public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array + { + $this->loadRootServerFile(600); + if (null === $this->securityAdvisoryConfig) { + return ['namesFound' => [], 'advisories' => []]; + } + + $advisories = []; + $namesFound = []; + + $apiUrl = $this->securityAdvisoryConfig['api-url']; + + // respect available-package-patterns / available-packages directives from the repo + if ($this->hasAvailablePackageList) { + foreach ($packageConstraintMap as $name => $constraint) { + if (!$this->lazyProvidersRepoContains(strtolower($name))) { + unset($packageConstraintMap[$name]); + } + } + } + + $parser = new VersionParser(); + /** + * @param array $data + * @param string $name + * @return ($allowPartialAdvisories is false ? SecurityAdvisory|null : PartialSecurityAdvisory|SecurityAdvisory|null) + */ + $create = function (array $data, string $name) use ($parser, $allowPartialAdvisories, &$packageConstraintMap): ?PartialSecurityAdvisory { + $advisory = PartialSecurityAdvisory::create($name, $data, $parser); + if (!$allowPartialAdvisories && !$advisory instanceof SecurityAdvisory) { + throw new \RuntimeException('Advisory for '.$name.' could not be loaded as a full advisory from '.$this->getRepoName() . PHP_EOL . var_export($data, true)); + } + if (!$advisory->affectedVersions->matches($packageConstraintMap[$name])) { + return null; + } + + return $advisory; + }; + + if ($this->securityAdvisoryConfig['metadata'] && ($allowPartialAdvisories || $apiUrl === null)) { + $promises = []; + foreach ($packageConstraintMap as $name => $constraint) { + $name = strtolower($name); + + // skip platform packages, root package and composer-plugin-api + if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { + continue; + } + + $promises[] = $this->startCachedAsyncDownload($name, $name) + ->then(static function (array $spec) use (&$advisories, &$namesFound, &$packageConstraintMap, $name, $create): void { + [$response] = $spec; + + if (!isset($response['security-advisories']) || !is_array($response['security-advisories'])) { + return; + } + + $namesFound[$name] = true; + if (count($response['security-advisories']) > 0) { + $advisories[$name] = array_values(array_filter(array_map( + static function ($data) use ($name, $create) { + return $create($data, $name); + }, + $response['security-advisories'] + ))); + } + unset($packageConstraintMap[$name]); + }); + } + + $this->loop->wait($promises); + } + + if ($apiUrl !== null && count($packageConstraintMap) > 0) { + $options = $this->options; + $options['http']['method'] = 'POST'; + if (isset($options['http']['header'])) { + $options['http']['header'] = (array) $options['http']['header']; + } + $options['http']['header'][] = 'Content-type: application/x-www-form-urlencoded'; + $options['http']['timeout'] = 10; + $options['http']['content'] = http_build_query(['packages' => array_keys($packageConstraintMap)]); + + $response = $this->httpDownloader->get($apiUrl, $options); + $warned = false; + /** @var string $name */ + foreach ($response->decodeJson()['advisories'] as $name => $list) { + if (!isset($packageConstraintMap[$name])) { + if (!$warned) { + $this->io->writeError(''.$this->getRepoName().' returned names which were not requested in response to the security-advisories API. '.$name.' was not requested but is present in the response. Requested names were: '.implode(', ', array_keys($packageConstraintMap)).''); + $warned = true; + } + continue; + } + if (count($list) > 0) { + $advisories[$name] = array_values(array_filter(array_map( + static function ($data) use ($name, $create) { + return $create($data, $name); + }, + $list + ))); + } + $namesFound[$name] = true; + } + } + + return ['namesFound' => array_keys($namesFound), 'advisories' => array_filter($advisories, static function ($adv): bool { return \count($adv) > 0; })]; + } + + public function getProviders(string $packageName) + { + $this->loadRootServerFile(); + $result = []; + + if ($this->providersApiUrl) { + try { + $apiResult = $this->httpDownloader->get(str_replace('%package%', $packageName, $this->providersApiUrl), $this->options)->decodeJson(); + } catch (TransportException $e) { + if ($e->getStatusCode() === 404) { + return $result; + } + throw $e; + } + + foreach ($apiResult['providers'] as $provider) { + $result[$provider['name']] = $provider; + } + + return $result; + } + + if ($this->hasPartialPackages()) { + if (!is_array($this->partialPackagesByName)) { + throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName'); + } + foreach ($this->partialPackagesByName as $versions) { + foreach ($versions as $candidate) { + if (isset($result[$candidate['name']]) || !isset($candidate['provide'][$packageName])) { + continue; + } + $result[$candidate['name']] = [ + 'name' => $candidate['name'], + 'description' => $candidate['description'] ?? '', + 'type' => $candidate['type'] ?? '', + ]; + } + } + } + + if ($this->packages) { + $result = array_merge($result, parent::getProviders($packageName)); + } + + return $result; + } + + /** + * @return string[] + */ + private function getProviderNames(): array + { + $this->loadRootServerFile(); + + if (null === $this->providerListing) { + $data = $this->loadRootServerFile(); + if (is_array($data)) { + $this->loadProviderListings($data); + } + } + + if ($this->lazyProvidersUrl) { + // Can not determine list of provided packages for lazy repositories + return []; + } + + if (null !== $this->providersUrl && null !== $this->providerListing) { + return array_keys($this->providerListing); + } + + return []; + } + + protected function configurePackageTransportOptions(PackageInterface $package): void + { + foreach ($package->getDistUrls() as $url) { + if (strpos($url, $this->baseUrl) === 0) { + $package->setTransportOptions($this->options); + + return; + } + } + } + + private function hasProviders(): bool + { + $this->loadRootServerFile(); + + return $this->hasProviders; + } + + /** + * @param string $name package name + * @param array|null $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*>|null $acceptableStabilities + * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array|null $stabilityFlags + * @param array> $alreadyLoaded + * + * @return array + */ + private function whatProvides(string $name, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array + { + $packagesSource = null; + if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) { + // skip platform packages, root package and composer-plugin-api + if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { + return []; + } + + if (null === $this->providerListing) { + $data = $this->loadRootServerFile(); + if (is_array($data)) { + $this->loadProviderListings($data); + } + } + + $useLastModifiedCheck = false; + if ($this->lazyProvidersUrl && !isset($this->providerListing[$name])) { + $hash = null; + $url = str_replace('%package%', $name, $this->lazyProvidersUrl); + $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; + $useLastModifiedCheck = true; + } elseif ($this->providersUrl) { + // package does not exist in this repo + if (!isset($this->providerListing[$name])) { + return []; + } + + $hash = $this->providerListing[$name]['sha256']; + $url = str_replace(['%package%', '%hash%'], [$name, $hash], $this->providersUrl); + $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; + } else { + return []; + } + + $packages = null; + if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) { + $packages = json_decode($this->cache->read($cacheKey), true); + $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')'; + } elseif ($useLastModifiedCheck) { + if ($contents = $this->cache->read($cacheKey)) { + $contents = json_decode($contents, true); + // we already loaded some packages from this file, so assume it is fresh and avoid fetching it again + if (isset($alreadyLoaded[$name])) { + $packages = $contents; + $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')'; + } elseif (isset($contents['last-modified'])) { + $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']); + $packages = true === $response ? $contents : $response; + $packagesSource = true === $response ? 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')' : 'downloaded file ('.Url::sanitize($url).')'; + } + } + } + + if (!$packages) { + try { + $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck); + $packagesSource = 'downloaded file ('.Url::sanitize($url).')'; + } catch (TransportException $e) { + // 404s are acceptable for lazy provider repos + if ($this->lazyProvidersUrl && in_array($e->getStatusCode(), [404, 499], true)) { + $packages = ['packages' => []]; + $packagesSource = 'not-found file ('.Url::sanitize($url).')'; + if ($e->getStatusCode() === 499) { + $this->io->error('' . $e->getMessage() . ''); + } + } else { + throw $e; + } + } + } + + $loadingPartialPackage = false; + } else { + $packages = ['packages' => ['versions' => $this->partialPackagesByName[$name]]]; + $packagesSource = 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')'; + $loadingPartialPackage = true; + } + + $result = []; + $versionsToLoad = []; + foreach ($packages['packages'] as $versions) { + foreach ($versions as $version) { + $normalizedName = strtolower($version['name']); + + // only load the actual named package, not other packages that might find themselves in the same file + if ($normalizedName !== $name) { + continue; + } + + if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$normalizedName])) { + continue; + } + + if (!isset($versionsToLoad[$version['uid']])) { + if (!isset($version['version_normalized'])) { + $version['version_normalized'] = $this->versionParser->normalize($version['version']); + } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) { + // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it + $version['version_normalized'] = $this->versionParser->normalize($version['version']); + } + + // avoid loading packages which have already been loaded + if (isset($alreadyLoaded[$name][$version['version_normalized']])) { + continue; + } + + if ($this->isVersionAcceptable(null, $normalizedName, $version, $acceptableStabilities, $stabilityFlags)) { + $versionsToLoad[$version['uid']] = $version; + } + } + } + } + + // load acceptable packages in the providers + $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource); + $uids = array_keys($versionsToLoad); + + foreach ($loadedPackages as $index => $package) { + $package->setRepository($this); + $uid = $uids[$index]; + + if ($package instanceof AliasPackage) { + $aliased = $package->getAliasOf(); + $aliased->setRepository($this); + + $result[$uid] = $aliased; + $result[$uid.'-alias'] = $package; + } else { + $result[$uid] = $package; + } + } + + return $result; + } + + /** + * @inheritDoc + */ + protected function initialize() + { + parent::initialize(); + + $repoData = $this->loadDataFromServer(); + + foreach ($this->createPackages($repoData, 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')') as $package) { + $this->addPackage($package); + } + } + + /** + * Adds a new package to the repository + */ + public function addPackage(PackageInterface $package) + { + parent::addPackage($package); + $this->configurePackageTransportOptions($package); + } + + /** + * @param array $packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only + * packages matching it will be loaded + * @param array|null $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*>|null $acceptableStabilities + * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array|null $stabilityFlags + * @param array> $alreadyLoaded + * + * @return array{namesFound: array, packages: array} + */ + private function loadAsyncPackages(array $packageNames, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array + { + $this->loadRootServerFile(); + + $packages = []; + $namesFound = []; + $promises = []; + + if (null === $this->lazyProvidersUrl) { + throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url'); + } + + // load ~dev versions of the packages as well if needed + foreach ($packageNames as $name => $constraint) { + if ($acceptableStabilities === null || $stabilityFlags === null || StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], 'dev')) { + $packageNames[$name.'~dev'] = $constraint; + } + // if only dev stability is requested, we skip loading the non dev file + if (isset($acceptableStabilities['dev']) && count($acceptableStabilities) === 1 && count($stabilityFlags) === 0) { + unset($packageNames[$name]); + } + } + + foreach ($packageNames as $name => $constraint) { + $name = strtolower($name); + + $realName = Preg::replace('{~dev$}', '', $name); + // skip platform packages, root package and composer-plugin-api + if (PlatformRepository::isPlatformPackage($realName) || '__root__' === $realName) { + continue; + } + + $promises[] = $this->startCachedAsyncDownload($name, $realName) + ->then(function (array $spec) use (&$packages, &$namesFound, $realName, $constraint, $acceptableStabilities, $stabilityFlags, $alreadyLoaded): void { + [$response, $packagesSource] = $spec; + if (null === $response || !isset($response['packages'][$realName])) { + return; + } + + $versions = $response['packages'][$realName]; + + if (isset($response['minified']) && $response['minified'] === 'composer/2.0') { + $versions = MetadataMinifier::expand($versions); + } + + $namesFound[$realName] = true; + $versionsToLoad = []; + foreach ($versions as $version) { + if (!isset($version['version_normalized'])) { + $version['version_normalized'] = $this->versionParser->normalize($version['version']); + } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) { + // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it + $version['version_normalized'] = $this->versionParser->normalize($version['version']); + } + + // avoid loading packages which have already been loaded + if (isset($alreadyLoaded[$realName][$version['version_normalized']])) { + continue; + } + + if ($this->isVersionAcceptable($constraint, $realName, $version, $acceptableStabilities, $stabilityFlags)) { + $versionsToLoad[] = $version; + } + } + + $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource); + foreach ($loadedPackages as $package) { + $package->setRepository($this); + $packages[spl_object_hash($package)] = $package; + + if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { + $package->getAliasOf()->setRepository($this); + $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); + } + } + }); + } + + $this->loop->wait($promises); + + return ['namesFound' => $namesFound, 'packages' => $packages]; + } + + /** + * @phpstan-return PromiseInterface + */ + private function startCachedAsyncDownload(string $fileName, ?string $packageName = null): PromiseInterface + { + if (null === $this->lazyProvidersUrl) { + throw new \LogicException('startCachedAsyncDownload only supports v2 protocol composer repos with a metadata-url'); + } + + $name = strtolower($fileName); + $packageName = $packageName ?? $name; + + $url = str_replace('%package%', $name, $this->lazyProvidersUrl); + $cacheKey = 'provider-'.strtr($name, '/', '~').'.json'; + + $lastModified = null; + if ($contents = $this->cache->read($cacheKey)) { + $contents = json_decode($contents, true); + $lastModified = $contents['last-modified'] ?? null; + } + + return $this->asyncFetchFile($url, $cacheKey, $lastModified) + ->then(static function ($response) use ($url, $cacheKey, $contents, $packageName): array { + $packagesSource = 'downloaded file ('.Url::sanitize($url).')'; + + if (true === $response) { + $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')'; + $response = $contents; + } + + if (!isset($response['packages'][$packageName]) && !isset($response['security-advisories'])) { + return [null, $packagesSource]; + } + + return [$response, $packagesSource]; + }); + } + + /** + * @param string $name package name (must be lowercased already) + * @param array $versionData + * @param array|null $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*>|null $acceptableStabilities + * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array|null $stabilityFlags + */ + private function isVersionAcceptable(?ConstraintInterface $constraint, string $name, array $versionData, ?array $acceptableStabilities = null, ?array $stabilityFlags = null): bool + { + $versions = [$versionData['version_normalized']]; + + if ($alias = $this->loader->getBranchAlias($versionData)) { + $versions[] = $alias; + } + + foreach ($versions as $version) { + if (null !== $acceptableStabilities && null !== $stabilityFlags && !StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], VersionParser::parseStability($version))) { + continue; + } + + if ($constraint && !CompilingMatcher::match($constraint, Constraint::OP_EQ, $version)) { + continue; + } + + return true; + } + + return false; + } + + private function getPackagesJsonUrl(): string + { + $jsonUrlParts = parse_url(strtr($this->url, '\\', '/')); + + if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) { + return $this->url; + } + + return $this->url . '/packages.json'; + } + + /** + * @return array<'providers'|'provider-includes'|'packages'|'providers-url'|'notify-batch'|'search'|'mirrors'|'providers-lazy-url'|'metadata-url'|'available-packages'|'available-package-patterns', mixed>|true + */ + protected function loadRootServerFile(?int $rootMaxAge = null) + { + if (null !== $this->rootData) { + return $this->rootData; + } + + if (!extension_loaded('openssl') && strpos($this->url, 'https') === 0) { + throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url); + } + + if ($cachedData = $this->cache->read('packages.json')) { + $cachedData = json_decode($cachedData, true); + if ($rootMaxAge !== null && ($age = $this->cache->getAge('packages.json')) !== false && $age <= $rootMaxAge) { + $data = $cachedData; + } elseif (isset($cachedData['last-modified'])) { + $response = $this->fetchFileIfLastModified($this->getPackagesJsonUrl(), 'packages.json', $cachedData['last-modified']); + $data = true === $response ? $cachedData : $response; + } + } + + if (!isset($data)) { + $data = $this->fetchFile($this->getPackagesJsonUrl(), 'packages.json', null, true); + } + + if (!empty($data['notify-batch'])) { + $this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']); + } elseif (!empty($data['notify'])) { + $this->notifyUrl = $this->canonicalizeUrl($data['notify']); + } + + if (!empty($data['search'])) { + $this->searchUrl = $this->canonicalizeUrl($data['search']); + } + + if (!empty($data['mirrors'])) { + foreach ($data['mirrors'] as $mirror) { + if (!empty($mirror['git-url'])) { + $this->sourceMirrors['git'][] = ['url' => $mirror['git-url'], 'preferred' => !empty($mirror['preferred'])]; + } + if (!empty($mirror['hg-url'])) { + $this->sourceMirrors['hg'][] = ['url' => $mirror['hg-url'], 'preferred' => !empty($mirror['preferred'])]; + } + if (!empty($mirror['dist-url'])) { + $this->distMirrors[] = [ + 'url' => $this->canonicalizeUrl($mirror['dist-url']), + 'preferred' => !empty($mirror['preferred']), + ]; + } + } + } + + if (!empty($data['providers-lazy-url'])) { + $this->lazyProvidersUrl = $this->canonicalizeUrl($data['providers-lazy-url']); + $this->hasProviders = true; + + $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); + } + + // metadata-url indicates V2 repo protocol so it takes over from all the V1 types + // V2 only has lazyProviders and possibly partial packages, but no ability to process anything else, + // V2 also supports async loading + if (!empty($data['metadata-url'])) { + $this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']); + $this->providersUrl = null; + $this->hasProviders = false; + $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); + $this->allowSslDowngrade = false; + + // provides a list of package names that are available in this repo + // this disables lazy-provider behavior in the sense that if a list is available we assume it is finite and won't search for other packages in that repo + // while if no list is there lazyProvidersUrl is used when looking for any package name to see if the repo knows it + if (!empty($data['available-packages'])) { + $availPackages = array_map('strtolower', $data['available-packages']); + $this->availablePackages = array_combine($availPackages, $availPackages); + $this->hasAvailablePackageList = true; + } + + // Provides a list of package name patterns (using * wildcards to match any substring, e.g. "vendor/*") that are available in this repo + // Disables lazy-provider behavior as with available-packages, but may allow much more compact expression of packages covered by this repository. + // Over-specifying covered packages is safe, but may result in increased traffic to your repository. + if (!empty($data['available-package-patterns'])) { + $this->availablePackagePatterns = array_map(static function ($pattern): string { + return BasePackage::packageNameToRegexp($pattern); + }, $data['available-package-patterns']); + $this->hasAvailablePackageList = true; + } + + // Remove legacy keys as most repos need to be compatible with Composer v1 + // as well but we are not interested in the old format anymore at this point + unset($data['providers-url'], $data['providers'], $data['providers-includes']); + + if (isset($data['security-advisories']) && is_array($data['security-advisories'])) { + $this->securityAdvisoryConfig = [ + 'metadata' => $data['security-advisories']['metadata'] ?? false, + 'api-url' => isset($data['security-advisories']['api-url']) && is_string($data['security-advisories']['api-url']) ? $this->canonicalizeUrl($data['security-advisories']['api-url']) : null, + ]; + if ($this->securityAdvisoryConfig['api-url'] === null && !$this->hasAvailablePackageList) { + throw new \UnexpectedValueException('Invalid security advisory configuration on '.$this->getRepoName().': If the repository does not provide a security-advisories.api-url then available-packages or available-package-patterns are required to be provided for performance reason.'); + } + } + } + + if ($this->allowSslDowngrade) { + $this->url = str_replace('https://', 'http://', $this->url); + $this->baseUrl = str_replace('https://', 'http://', $this->baseUrl); + } + + if (!empty($data['providers-url'])) { + $this->providersUrl = $this->canonicalizeUrl($data['providers-url']); + $this->hasProviders = true; + } + + if (!empty($data['list'])) { + $this->listUrl = $this->canonicalizeUrl($data['list']); + } + + if (!empty($data['providers']) || !empty($data['providers-includes'])) { + $this->hasProviders = true; + } + + if (!empty($data['providers-api'])) { + $this->providersApiUrl = $this->canonicalizeUrl($data['providers-api']); + } + + return $this->rootData = $data; + } + + /** + * @return non-empty-string + */ + private function canonicalizeUrl(string $url): string + { + if (strlen($url) === 0) { + throw new \InvalidArgumentException('Expected a string with a value and not an empty string'); + } + + if (str_starts_with($url, '/')) { + if (Preg::isMatch('{^[^:]++://[^/]*+}', $this->url, $matches)) { + return $matches[0] . $url; + } + + return $this->url; + } + + return $url; + } + + /** + * @return mixed[] + */ + private function loadDataFromServer(): array + { + $data = $this->loadRootServerFile(); + if (true === $data) { + throw new \LogicException('loadRootServerFile should not return true during initialization'); + } + + return $this->loadIncludes($data); + } + + private function hasPartialPackages(): bool + { + if ($this->hasPartialPackages && null === $this->partialPackagesByName) { + $this->initializePartialPackages(); + } + + return $this->hasPartialPackages; + } + + /** + * @param array{providers?: mixed[], provider-includes?: mixed[]} $data + */ + private function loadProviderListings($data): void + { + if (isset($data['providers'])) { + if (!is_array($this->providerListing)) { + $this->providerListing = []; + } + $this->providerListing = array_merge($this->providerListing, $data['providers']); + } + + if ($this->providersUrl && isset($data['provider-includes'])) { + $includes = $data['provider-includes']; + foreach ($includes as $include => $metadata) { + $url = $this->baseUrl . '/' . str_replace('%hash%', $metadata['sha256'], $include); + $cacheKey = str_replace(['%hash%','$'], '', $include); + if ($this->cache->sha256($cacheKey) === $metadata['sha256']) { + $includedData = json_decode($this->cache->read($cacheKey), true); + } else { + $includedData = $this->fetchFile($url, $cacheKey, $metadata['sha256']); + } + + $this->loadProviderListings($includedData); + } + } + } + + /** + * @param mixed[] $data + * + * @return mixed[] + */ + private function loadIncludes(array $data): array + { + $packages = []; + + // legacy repo handling + if (!isset($data['packages']) && !isset($data['includes'])) { + foreach ($data as $pkg) { + if (isset($pkg['versions']) && is_array($pkg['versions'])) { + foreach ($pkg['versions'] as $metadata) { + $packages[] = $metadata; + } + } + } + + return $packages; + } + + if (isset($data['packages'])) { + foreach ($data['packages'] as $package => $versions) { + $packageName = strtolower((string) $package); + foreach ($versions as $version => $metadata) { + $packages[] = $metadata; + if (!$this->displayedWarningAboutNonMatchingPackageIndex && $packageName !== strtolower((string) ($metadata['name'] ?? ''))) { + $this->displayedWarningAboutNonMatchingPackageIndex = true; + $this->io->writeError(sprintf("Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s", $package, $metadata['name'] ?? '', $this->baseUrl)); + } + } + } + } + + if (isset($data['includes'])) { + foreach ($data['includes'] as $include => $metadata) { + if (isset($metadata['sha1']) && $this->cache->sha1((string) $include) === $metadata['sha1']) { + $includedData = json_decode($this->cache->read((string) $include), true); + } else { + $includedData = $this->fetchFile($include); + } + $packages = array_merge($packages, $this->loadIncludes($includedData)); + } + } + + return $packages; + } + + /** + * @param mixed[] $packages + * + * @return list + */ + private function createPackages(array $packages, ?string $source = null): array + { + if (!$packages) { + return []; + } + + try { + foreach ($packages as &$data) { + if (!isset($data['notification-url'])) { + $data['notification-url'] = $this->notifyUrl; + } + } + + $packageInstances = $this->loader->loadPackages($packages); + + foreach ($packageInstances as $package) { + if (isset($this->sourceMirrors[$package->getSourceType()])) { + $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]); + } + $package->setDistMirrors($this->distMirrors); + $this->configurePackageTransportOptions($package); + } + + return $packageInstances; + } catch (\Exception $e) { + throw new \RuntimeException('Could not load packages in '.$this->getRepoName().($source ? ' from '.$source : '').': ['.get_class($e).'] '.$e->getMessage(), 0, $e); + } + } + + /** + * @return array + */ + protected function fetchFile(string $filename, ?string $cacheKey = null, ?string $sha256 = null, bool $storeLastModifiedTime = false) + { + if ('' === $filename) { + throw new \InvalidArgumentException('$filename should not be an empty string'); + } + + if (null === $cacheKey) { + $cacheKey = $filename; + $filename = $this->baseUrl.'/'.$filename; + } + + // url-encode $ signs in URLs as bad proxies choke on them + if (($pos = strpos($filename, '$')) && Preg::isMatch('{^https?://}i', $filename)) { + $filename = substr($filename, 0, $pos) . '%24' . substr($filename, $pos + 1); + } + + $retries = 3; + while ($retries--) { + try { + $options = $this->options; + if ($this->eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); + $preFileDownloadEvent->setTransportOptions($this->options); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $filename = $preFileDownloadEvent->getProcessedUrl(); + $options = $preFileDownloadEvent->getTransportOptions(); + } + + $response = $this->httpDownloader->get($filename, $options); + $json = (string) $response->getBody(); + if ($sha256 && $sha256 !== hash('sha256', $json)) { + // undo downgrade before trying again if http seems to be hijacked or modifying content somehow + if ($this->allowSslDowngrade) { + $this->url = str_replace('http://', 'https://', $this->url); + $this->baseUrl = str_replace('http://', 'https://', $this->baseUrl); + $filename = str_replace('http://', 'https://', $filename); + } + + if ($retries > 0) { + usleep(100000); + + continue; + } + + // TODO use scarier wording once we know for sure it doesn't do false positives anymore + throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.'); + } + + if ($this->eventDispatcher) { + $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, $sha256, $filename, 'metadata', ['response' => $response, 'repository' => $this]); + $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); + } + + $data = $response->decodeJson(); + HttpDownloader::outputWarnings($this->io, $this->url, $data); + + if ($cacheKey && !$this->cache->isReadOnly()) { + if ($storeLastModifiedTime) { + $lastModifiedDate = $response->getHeader('last-modified'); + if ($lastModifiedDate) { + $data['last-modified'] = $lastModifiedDate; + $json = JsonFile::encode($data, 0); + } + } + $this->cache->write($cacheKey, $json); + } + + $response->collect(); + + break; + } catch (\Exception $e) { + if ($e instanceof \LogicException) { + throw $e; + } + + if ($e instanceof TransportException && $e->getStatusCode() === 404) { + throw $e; + } + + if ($e instanceof RepositorySecurityException) { + throw $e; + } + + if ($cacheKey && ($contents = $this->cache->read($cacheKey))) { + if (!$this->degradedMode) { + $this->io->writeError(''.$this->url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date'); + } + $this->degradedMode = true; + $data = JsonFile::parseJson($contents, $this->cache->getRoot().$cacheKey); + + break; + } + + throw $e; + } + } + + if (!isset($data)) { + throw new \LogicException("ComposerRepository: Undefined \$data. Please report at https://github.com/composer/composer/issues/new."); + } + + return $data; + } + + /** + * @return array|true + */ + private function fetchFileIfLastModified(string $filename, string $cacheKey, string $lastModifiedTime) + { + if ('' === $filename) { + throw new \InvalidArgumentException('$filename should not be an empty string'); + } + + try { + $options = $this->options; + if ($this->eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); + $preFileDownloadEvent->setTransportOptions($this->options); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $filename = $preFileDownloadEvent->getProcessedUrl(); + $options = $preFileDownloadEvent->getTransportOptions(); + } + + if (isset($options['http']['header'])) { + $options['http']['header'] = (array) $options['http']['header']; + } + $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime; + $response = $this->httpDownloader->get($filename, $options); + $json = (string) $response->getBody(); + if ($json === '' && $response->getStatusCode() === 304) { + return true; + } + + if ($this->eventDispatcher) { + $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]); + $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); + } + + $data = $response->decodeJson(); + HttpDownloader::outputWarnings($this->io, $this->url, $data); + + $lastModifiedDate = $response->getHeader('last-modified'); + $response->collect(); + if ($lastModifiedDate) { + $data['last-modified'] = $lastModifiedDate; + $json = JsonFile::encode($data, 0); + } + if (!$this->cache->isReadOnly()) { + $this->cache->write($cacheKey, $json); + } + + return $data; + } catch (\Exception $e) { + if ($e instanceof \LogicException) { + throw $e; + } + + if ($e instanceof TransportException && $e->getStatusCode() === 404) { + throw $e; + } + + if (!$this->degradedMode) { + $this->io->writeError(''.$this->url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date'); + } + $this->degradedMode = true; + + return true; + } + } + + /** + * @phpstan-return PromiseInterface|true> true if the response was a 304 and the cache is fresh, otherwise it returns the decoded json + */ + private function asyncFetchFile(string $filename, string $cacheKey, ?string $lastModifiedTime = null): PromiseInterface + { + if ('' === $filename) { + throw new \InvalidArgumentException('$filename should not be an empty string'); + } + + if (isset($this->packagesNotFoundCache[$filename])) { + return \React\Promise\resolve(['packages' => []]); + } + + if (isset($this->freshMetadataUrls[$filename]) && $lastModifiedTime) { + // make it look like we got a 304 response + /** @var PromiseInterface $promise */ + $promise = \React\Promise\resolve(true); + + return $promise; + } + + $httpDownloader = $this->httpDownloader; + $options = $this->options; + if ($this->eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); + $preFileDownloadEvent->setTransportOptions($this->options); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $filename = $preFileDownloadEvent->getProcessedUrl(); + $options = $preFileDownloadEvent->getTransportOptions(); + } + + if ($lastModifiedTime) { + if (isset($options['http']['header'])) { + $options['http']['header'] = (array) $options['http']['header']; + } + $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime; + } + + $io = $this->io; + $url = $this->url; + $cache = $this->cache; + $degradedMode = &$this->degradedMode; + $eventDispatcher = $this->eventDispatcher; + + /** + * @return array|true true if the response was a 304 and the cache is fresh + */ + $accept = function ($response) use ($io, $url, $filename, $cache, $cacheKey, $eventDispatcher) { + // package not found is acceptable for a v2 protocol repository + if ($response->getStatusCode() === 404) { + $this->packagesNotFoundCache[$filename] = true; + + return ['packages' => []]; + } + + $json = (string) $response->getBody(); + if ($json === '' && $response->getStatusCode() === 304) { + $this->freshMetadataUrls[$filename] = true; + + return true; + } + + if ($eventDispatcher) { + $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]); + $eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); + } + + $data = $response->decodeJson(); + HttpDownloader::outputWarnings($io, $url, $data); + + $lastModifiedDate = $response->getHeader('last-modified'); + $response->collect(); + if ($lastModifiedDate) { + $data['last-modified'] = $lastModifiedDate; + $json = JsonFile::encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + if (!$cache->isReadOnly()) { + $cache->write($cacheKey, $json); + } + $this->freshMetadataUrls[$filename] = true; + + return $data; + }; + + $reject = function ($e) use ($filename, $accept, $io, $url, &$degradedMode, $lastModifiedTime) { + if ($e instanceof TransportException && $e->getStatusCode() === 404) { + $this->packagesNotFoundCache[$filename] = true; + + return false; + } + + if (!$degradedMode) { + $io->writeError(''.$url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date'); + } + $degradedMode = true; + + // if the file is in the cache, we fake a 304 Not Modified to allow the process to continue + if ($lastModifiedTime) { + return $accept(new Response(['url' => $url], 304, [], '')); + } + + // special error code returned when network is being artificially disabled + if ($e instanceof TransportException && $e->getStatusCode() === 499) { + return $accept(new Response(['url' => $url], 404, [], '')); + } + + throw $e; + }; + + return $httpDownloader->add($filename, $options)->then($accept, $reject); + } + + /** + * This initializes the packages key of a partial packages.json that contain some packages inlined + a providers-lazy-url + * + * This should only be called once + */ + private function initializePartialPackages(): void + { + $rootData = $this->loadRootServerFile(); + if ($rootData === true) { + return; + } + + $this->partialPackagesByName = []; + foreach ($rootData['packages'] as $package => $versions) { + foreach ($versions as $version) { + $versionPackageName = strtolower((string) ($version['name'] ?? '')); + $this->partialPackagesByName[$versionPackageName][] = $version; + if (!$this->displayedWarningAboutNonMatchingPackageIndex && $versionPackageName !== strtolower($package)) { + $this->io->writeError(sprintf("Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s", $package, $version['name'] ?? '', $this->baseUrl)); + $this->displayedWarningAboutNonMatchingPackageIndex = true; + } + } + } + + // wipe rootData as it is fully consumed at this point and this saves some memory + $this->rootData = true; + } + + /** + * Checks if the package name is present in this lazy providers repo + * + * @return bool true if the package name is present in availablePackages or matched by availablePackagePatterns + */ + protected function lazyProvidersRepoContains(string $name) + { + if (!$this->hasAvailablePackageList) { + throw new \LogicException('lazyProvidersRepoContains should not be called unless hasAvailablePackageList is true'); + } + + if (is_array($this->availablePackages) && isset($this->availablePackages[$name])) { + return true; + } + + if (is_array($this->availablePackagePatterns)) { + foreach ($this->availablePackagePatterns as $providerRegex) { + if (Preg::isMatch($providerRegex, $name)) { + return true; + } + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/CompositeRepository.php b/vendor/composer/composer/src/Composer/Repository/CompositeRepository.php new file mode 100644 index 000000000..8ee53261e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/CompositeRepository.php @@ -0,0 +1,203 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; + +/** + * Composite repository. + * + * @author Beau Simensen + */ +class CompositeRepository implements RepositoryInterface +{ + /** + * List of repositories + * @var RepositoryInterface[] + */ + private $repositories; + + /** + * Constructor + * @param RepositoryInterface[] $repositories + */ + public function __construct(array $repositories) + { + $this->repositories = []; + foreach ($repositories as $repo) { + $this->addRepository($repo); + } + } + + public function getRepoName(): string + { + return 'composite repo ('.implode(', ', array_map(static function ($repo): string { + return $repo->getRepoName(); + }, $this->repositories)).')'; + } + + /** + * Returns all the wrapped repositories + * + * @return RepositoryInterface[] + */ + public function getRepositories(): array + { + return $this->repositories; + } + + /** + * @inheritDoc + */ + public function hasPackage(PackageInterface $package): bool + { + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + if ($repository->hasPackage($package)) { + return true; + } + } + + return false; + } + + /** + * @inheritDoc + */ + public function findPackage($name, $constraint): ?BasePackage + { + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $package = $repository->findPackage($name, $constraint); + if (null !== $package) { + return $package; + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function findPackages($name, $constraint = null): array + { + $packages = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $packages[] = $repository->findPackages($name, $constraint); + } + + return $packages ? array_merge(...$packages) : []; + } + + /** + * @inheritDoc + */ + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array + { + $packages = []; + $namesFound = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $result = $repository->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + $packages[] = $result['packages']; + $namesFound[] = $result['namesFound']; + } + + return [ + 'packages' => $packages ? array_merge(...$packages) : [], + 'namesFound' => $namesFound ? array_unique(array_merge(...$namesFound)) : [], + ]; + } + + /** + * @inheritDoc + */ + public function search(string $query, int $mode = 0, ?string $type = null): array + { + $matches = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $matches[] = $repository->search($query, $mode, $type); + } + + return \count($matches) > 0 ? array_merge(...$matches) : []; + } + + /** + * @inheritDoc + */ + public function getPackages(): array + { + $packages = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $packages[] = $repository->getPackages(); + } + + return $packages ? array_merge(...$packages) : []; + } + + /** + * @inheritDoc + */ + public function getProviders($packageName): array + { + $results = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $results[] = $repository->getProviders($packageName); + } + + return $results ? array_merge(...$results) : []; + } + + public function removePackage(PackageInterface $package): void + { + foreach ($this->repositories as $repository) { + if ($repository instanceof WritableRepositoryInterface) { + $repository->removePackage($package); + } + } + } + + /** + * @inheritDoc + */ + public function count(): int + { + $total = 0; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $total += $repository->count(); + } + + return $total; + } + + /** + * Add a repository. + */ + public function addRepository(RepositoryInterface $repository): void + { + if ($repository instanceof self) { + foreach ($repository->getRepositories() as $repo) { + $this->addRepository($repo); + } + } else { + $this->repositories[] = $repository; + } + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php b/vendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php new file mode 100644 index 000000000..a56540f03 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Configurable repository interface. + * + * @author Lukas Homza + */ +interface ConfigurableRepositoryInterface +{ + /** + * @return mixed[] + */ + public function getRepoConfig(); +} diff --git a/vendor/composer/composer/src/Composer/Repository/FilesystemRepository.php b/vendor/composer/composer/src/Composer/Repository/FilesystemRepository.php new file mode 100644 index 000000000..cf021bfd4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/FilesystemRepository.php @@ -0,0 +1,417 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Json\JsonFile; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\PackageInterface; +use Composer\Package\RootAliasPackage; +use Composer\Package\RootPackageInterface; +use Composer\Package\AliasPackage; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Installer\InstallationManager; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; + +/** + * Filesystem repository. + * + * @author Konstantin Kudryashov + * @author Jordi Boggiano + */ +class FilesystemRepository extends WritableArrayRepository +{ + /** @var JsonFile */ + protected $file; + /** @var bool */ + private $dumpVersions; + /** @var ?RootPackageInterface */ + private $rootPackage; + /** @var Filesystem */ + private $filesystem; + /** @var bool|null */ + private $devMode = null; + + /** + * Initializes filesystem repository. + * + * @param JsonFile $repositoryFile repository json file + * @param ?RootPackageInterface $rootPackage Must be provided if $dumpVersions is true + */ + public function __construct(JsonFile $repositoryFile, bool $dumpVersions = false, ?RootPackageInterface $rootPackage = null, ?Filesystem $filesystem = null) + { + parent::__construct(); + $this->file = $repositoryFile; + $this->dumpVersions = $dumpVersions; + $this->rootPackage = $rootPackage; + $this->filesystem = $filesystem ?: new Filesystem; + if ($dumpVersions && !$rootPackage) { + throw new \InvalidArgumentException('Expected a root package instance if $dumpVersions is true'); + } + } + + /** + * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown + */ + public function getDevMode() + { + return $this->devMode; + } + + /** + * Initializes repository (reads file, or remote address). + */ + protected function initialize() + { + parent::initialize(); + + if (!$this->file->exists()) { + return; + } + + try { + $data = $this->file->read(); + if (isset($data['packages'])) { + $packages = $data['packages']; + } else { + $packages = $data; + } + + if (isset($data['dev-package-names'])) { + $this->setDevPackageNames($data['dev-package-names']); + } + if (isset($data['dev'])) { + $this->devMode = $data['dev']; + } + + if (!is_array($packages)) { + throw new \UnexpectedValueException('Could not parse package list from the repository'); + } + } catch (\Exception $e) { + throw new InvalidRepositoryException('Invalid repository data in '.$this->file->getPath().', packages could not be loaded: ['.get_class($e).'] '.$e->getMessage()); + } + + $loader = new ArrayLoader(null, true); + foreach ($packages as $packageData) { + $package = $loader->load($packageData); + $this->addPackage($package); + } + } + + public function reload() + { + $this->packages = null; + $this->initialize(); + } + + /** + * Writes writable repository. + */ + public function write(bool $devMode, InstallationManager $installationManager) + { + $data = ['packages' => [], 'dev' => $devMode, 'dev-package-names' => []]; + $dumper = new ArrayDumper(); + + // make sure the directory is created so we can realpath it + // as realpath() does some additional normalizations with network paths that normalizePath does not + // and we need to find shortest path correctly + $repoDir = dirname($this->file->getPath()); + $this->filesystem->ensureDirectoryExists($repoDir); + + $repoDir = $this->filesystem->normalizePath(realpath($repoDir)); + $installPaths = []; + + foreach ($this->getCanonicalPackages() as $package) { + $pkgArray = $dumper->dump($package); + $path = $installationManager->getInstallPath($package); + $installPath = null; + if ('' !== $path && null !== $path) { + $normalizedPath = $this->filesystem->normalizePath($this->filesystem->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path); + $installPath = $this->filesystem->findShortestPath($repoDir, $normalizedPath, true); + } + $installPaths[$package->getName()] = $installPath; + + $pkgArray['install-path'] = $installPath; + $data['packages'][] = $pkgArray; + + // only write to the files the names which are really installed, as we receive the full list + // of dev package names before they get installed during composer install + if (in_array($package->getName(), $this->devPackageNames, true)) { + $data['dev-package-names'][] = $package->getName(); + } + } + + sort($data['dev-package-names']); + usort($data['packages'], static function ($a, $b): int { + return strcmp($a['name'], $b['name']); + }); + + $this->file->write($data); + + if ($this->dumpVersions) { + $versions = $this->generateInstalledVersions($installationManager, $installPaths, $devMode, $repoDir); + + $this->filesystem->filePutContentsIfModified($repoDir.'/installed.php', 'dumpToPhpCode($versions) . ';'."\n"); + $installedVersionsClass = file_get_contents(__DIR__.'/../InstalledVersions.php'); + + // this normally should not happen but during upgrades of Composer when it is installed in the project it is a possibility + if ($installedVersionsClass !== false) { + $this->filesystem->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass); + + // make sure the in memory state is up to date with on disk + \Composer\InstalledVersions::reload($versions); + + // make sure the selfDir matches the expected data at runtime if the class was loaded from the vendor dir, as it may have been + // loaded from the Composer sources, causing packages to appear twice in that case if the installed.php is loaded in addition to the + // in memory loaded data from above + try { + $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'selfDir'); + (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true); + $reflProp->setValue(null, strtr($repoDir, '\\', '/')); + + $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'installedIsLocalDir'); + (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true); + $reflProp->setValue(null, true); + } catch (\ReflectionException $e) { + if (!Preg::isMatch('{Property .*? does not exist}i', $e->getMessage())) { + throw $e; + } + // noop, if outdated class is loaded we do not want to cause trouble + } + } + } + } + + /** + * As we load the file from vendor dir during bootstrap, we need to make sure it contains only expected code before executing it + * + * @internal + */ + public static function safelyLoadInstalledVersions(string $path): bool + { + $installedVersionsData = @file_get_contents($path); + $pattern = <<<'REGEX' +{(?(DEFINE) + (? -? \s*+ \d++ (?:\.\d++)? ) + (? true | false | null ) + (? (?&string) (?: \s*+ \. \s*+ (?&string))*+ ) + (? (?: " (?:[^"\\$]*+ | \\ ["\\0] )* " | ' (?:[^'\\]*+ | \\ ['\\] )* ' ) ) + (? array\( \s*+ (?: (?:(?&number)|(?&strings)) \s*+ => \s*+ (?: (?:__DIR__ \s*+ \. \s*+)? (?&strings) | (?&value) ) \s*+, \s*+ )*+ \s*+ \) ) + (? (?: (?&number) | (?&boolean) | (?&strings) | (?&array) ) ) +) +^<\?php\s++return\s++(?&array)\s*+;$}ix +REGEX; + if (is_string($installedVersionsData) && Preg::isMatch($pattern, trim($installedVersionsData))) { + \Composer\InstalledVersions::reload(eval('?>'.Preg::replace('{=>\s*+__DIR__\s*+\.\s*+([\'"])}', '=> '.var_export(dirname($path), true).' . $1', $installedVersionsData))); + + return true; + } + + return false; + } + + /** + * @param array $array + */ + private function dumpToPhpCode(array $array = [], int $level = 0): string + { + $lines = "array(\n"; + $level++; + + foreach ($array as $key => $value) { + $lines .= str_repeat(' ', $level); + $lines .= is_int($key) ? $key . ' => ' : var_export($key, true) . ' => '; + + if (is_array($value)) { + if (!empty($value)) { + $lines .= $this->dumpToPhpCode($value, $level); + } else { + $lines .= "array(),\n"; + } + } elseif ($key === 'install_path' && is_string($value)) { + if ($this->filesystem->isAbsolutePath($value)) { + $lines .= var_export($value, true) . ",\n"; + } else { + $lines .= "__DIR__ . " . var_export('/' . $value, true) . ",\n"; + } + } elseif (is_string($value)) { + $lines .= var_export($value, true) . ",\n"; + } elseif (is_bool($value)) { + $lines .= ($value ? 'true' : 'false') . ",\n"; + } elseif (is_null($value)) { + $lines .= "null,\n"; + } else { + throw new \UnexpectedValueException('Unexpected type '.get_debug_type($value)); + } + } + + $lines .= str_repeat(' ', $level - 1) . ')' . ($level - 1 === 0 ? '' : ",\n"); + + return $lines; + } + + /** + * @param array $installPaths + * + * @return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + private function generateInstalledVersions(InstallationManager $installationManager, array $installPaths, bool $devMode, string $repoDir): array + { + $devPackages = array_flip($this->devPackageNames); + $packages = $this->getPackages(); + if (null === $this->rootPackage) { + throw new \LogicException('It should not be possible to dump packages if no root package is given'); + } + $packages[] = $rootPackage = $this->rootPackage; + + while ($rootPackage instanceof RootAliasPackage) { + $rootPackage = $rootPackage->getAliasOf(); + $packages[] = $rootPackage; + } + $versions = [ + 'root' => $this->dumpRootPackage($rootPackage, $installPaths, $devMode, $repoDir, $devPackages), + 'versions' => [], + ]; + + // add real installed packages + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + continue; + } + + $versions['versions'][$package->getName()] = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages); + } + + // add provided/replaced packages + foreach ($packages as $package) { + $isDevPackage = isset($devPackages[$package->getName()]); + foreach ($package->getReplaces() as $replace) { + // exclude platform replaces as when they are really there we can not check for their presence + if (PlatformRepository::isPlatformPackage($replace->getTarget())) { + continue; + } + if (!isset($versions['versions'][$replace->getTarget()]['dev_requirement'])) { + $versions['versions'][$replace->getTarget()]['dev_requirement'] = $isDevPackage; + } elseif (!$isDevPackage) { + $versions['versions'][$replace->getTarget()]['dev_requirement'] = false; + } + $replaced = $replace->getPrettyConstraint(); + if ($replaced === 'self.version') { + $replaced = $package->getPrettyVersion(); + } + if (!isset($versions['versions'][$replace->getTarget()]['replaced']) || !in_array($replaced, $versions['versions'][$replace->getTarget()]['replaced'], true)) { + $versions['versions'][$replace->getTarget()]['replaced'][] = $replaced; + } + } + foreach ($package->getProvides() as $provide) { + // exclude platform provides as when they are really there we can not check for their presence + if (PlatformRepository::isPlatformPackage($provide->getTarget())) { + continue; + } + if (!isset($versions['versions'][$provide->getTarget()]['dev_requirement'])) { + $versions['versions'][$provide->getTarget()]['dev_requirement'] = $isDevPackage; + } elseif (!$isDevPackage) { + $versions['versions'][$provide->getTarget()]['dev_requirement'] = false; + } + $provided = $provide->getPrettyConstraint(); + if ($provided === 'self.version') { + $provided = $package->getPrettyVersion(); + } + if (!isset($versions['versions'][$provide->getTarget()]['provided']) || !in_array($provided, $versions['versions'][$provide->getTarget()]['provided'], true)) { + $versions['versions'][$provide->getTarget()]['provided'][] = $provided; + } + } + } + + // add aliases + foreach ($packages as $package) { + if (!$package instanceof AliasPackage) { + continue; + } + $versions['versions'][$package->getName()]['aliases'][] = $package->getPrettyVersion(); + if ($package instanceof RootPackageInterface) { + $versions['root']['aliases'][] = $package->getPrettyVersion(); + } + } + + ksort($versions['versions']); + ksort($versions); + + foreach ($versions['versions'] as $name => $version) { + foreach (['aliases', 'replaced', 'provided'] as $key) { + if (isset($versions['versions'][$name][$key])) { + sort($versions['versions'][$name][$key], SORT_NATURAL); + } + } + } + + return $versions; + } + + /** + * @param array $installPaths + * @param array $devPackages + * @return array{pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev_requirement: bool} + */ + private function dumpInstalledPackage(PackageInterface $package, array $installPaths, string $repoDir, array $devPackages): array + { + $reference = null; + if ($package->getInstallationSource()) { + $reference = $package->getInstallationSource() === 'source' ? $package->getSourceReference() : $package->getDistReference(); + } + if (null === $reference) { + $reference = ($package->getSourceReference() ?: $package->getDistReference()) ?: null; + } + + if ($package instanceof RootPackageInterface) { + $to = $this->filesystem->normalizePath(realpath(Platform::getCwd())); + $installPath = $this->filesystem->findShortestPath($repoDir, $to, true); + } else { + $installPath = $installPaths[$package->getName()]; + } + + $data = [ + 'pretty_version' => $package->getPrettyVersion(), + 'version' => $package->getVersion(), + 'reference' => $reference, + 'type' => $package->getType(), + 'install_path' => $installPath, + 'aliases' => [], + 'dev_requirement' => isset($devPackages[$package->getName()]), + ]; + + return $data; + } + + /** + * @param array $installPaths + * @param array $devPackages + * @return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + private function dumpRootPackage(RootPackageInterface $package, array $installPaths, bool $devMode, string $repoDir, array $devPackages) + { + $data = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages); + + return [ + 'name' => $package->getName(), + 'pretty_version' => $data['pretty_version'], + 'version' => $data['version'], + 'reference' => $data['reference'], + 'type' => $data['type'], + 'install_path' => $data['install_path'], + 'aliases' => $data['aliases'], + 'dev' => $devMode, + ]; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/FilterRepository.php b/vendor/composer/composer/src/Composer/Repository/FilterRepository.php new file mode 100644 index 000000000..bd9b54d9a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/FilterRepository.php @@ -0,0 +1,234 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\PackageInterface; +use Composer\Package\BasePackage; +use Composer\Pcre\Preg; + +/** + * Filters which packages are seen as canonical on this repo by loadPackages + * + * @author Jordi Boggiano + */ +class FilterRepository implements RepositoryInterface, AdvisoryProviderInterface +{ + /** @var ?string */ + private $only = null; + /** @var ?non-empty-string */ + private $exclude = null; + /** @var bool */ + private $canonical = true; + /** @var RepositoryInterface */ + private $repo; + + /** + * @param array{only?: array, exclude?: array, canonical?: bool} $options + */ + public function __construct(RepositoryInterface $repo, array $options) + { + if (isset($options['only'])) { + if (!is_array($options['only'])) { + throw new \InvalidArgumentException('"only" key for repository '.$repo->getRepoName().' should be an array'); + } + $this->only = BasePackage::packageNamesToRegexp($options['only']); + } + if (isset($options['exclude'])) { + if (!is_array($options['exclude'])) { + throw new \InvalidArgumentException('"exclude" key for repository '.$repo->getRepoName().' should be an array'); + } + $this->exclude = BasePackage::packageNamesToRegexp($options['exclude']); + } + if ($this->exclude && $this->only) { + throw new \InvalidArgumentException('Only one of "only" and "exclude" can be specified for repository '.$repo->getRepoName()); + } + if (isset($options['canonical'])) { + if (!is_bool($options['canonical'])) { + throw new \InvalidArgumentException('"canonical" key for repository '.$repo->getRepoName().' should be a boolean'); + } + $this->canonical = $options['canonical']; + } + + $this->repo = $repo; + } + + public function getRepoName(): string + { + return $this->repo->getRepoName(); + } + + /** + * Returns the wrapped repositories + */ + public function getRepository(): RepositoryInterface + { + return $this->repo; + } + + /** + * @inheritDoc + */ + public function hasPackage(PackageInterface $package): bool + { + return $this->repo->hasPackage($package); + } + + /** + * @inheritDoc + */ + public function findPackage($name, $constraint): ?BasePackage + { + if (!$this->isAllowed($name)) { + return null; + } + + return $this->repo->findPackage($name, $constraint); + } + + /** + * @inheritDoc + */ + public function findPackages($name, $constraint = null): array + { + if (!$this->isAllowed($name)) { + return []; + } + + return $this->repo->findPackages($name, $constraint); + } + + /** + * @inheritDoc + */ + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array + { + foreach ($packageNameMap as $name => $constraint) { + if (!$this->isAllowed($name)) { + unset($packageNameMap[$name]); + } + } + + if (!$packageNameMap) { + return ['namesFound' => [], 'packages' => []]; + } + + $result = $this->repo->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + if (!$this->canonical) { + $result['namesFound'] = []; + } + + return $result; + } + + /** + * @inheritDoc + */ + public function search(string $query, int $mode = 0, ?string $type = null): array + { + $result = []; + + foreach ($this->repo->search($query, $mode, $type) as $package) { + if ($this->isAllowed($package['name'])) { + $result[] = $package; + } + } + + return $result; + } + + /** + * @inheritDoc + */ + public function getPackages(): array + { + $result = []; + foreach ($this->repo->getPackages() as $package) { + if ($this->isAllowed($package->getName())) { + $result[] = $package; + } + } + + return $result; + } + + /** + * @inheritDoc + */ + public function getProviders($packageName): array + { + $result = []; + foreach ($this->repo->getProviders($packageName) as $name => $provider) { + if ($this->isAllowed($provider['name'])) { + $result[$name] = $provider; + } + } + + return $result; + } + + /** + * @inheritDoc + */ + public function count(): int + { + if ($this->repo->count() > 0) { + return count($this->getPackages()); + } + + return 0; + } + + public function hasSecurityAdvisories(): bool + { + if (!$this->repo instanceof AdvisoryProviderInterface) { + return false; + } + + return $this->repo->hasSecurityAdvisories(); + } + + /** + * @inheritDoc + */ + public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array + { + if (!$this->repo instanceof AdvisoryProviderInterface) { + return ['namesFound' => [], 'advisories' => []]; + } + + foreach ($packageConstraintMap as $name => $constraint) { + if (!$this->isAllowed($name)) { + unset($packageConstraintMap[$name]); + } + } + + return $this->repo->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories); + } + + private function isAllowed(string $name): bool + { + if (!$this->only && !$this->exclude) { + return true; + } + + if ($this->only) { + return Preg::isMatch($this->only, $name); + } + + if ($this->exclude === null) { + return true; + } + + return !Preg::isMatch($this->exclude, $name); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php b/vendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php new file mode 100644 index 000000000..971276fbd --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php @@ -0,0 +1,38 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Installed array repository. + * + * This is used as an in-memory InstalledRepository mostly for testing purposes + * + * @author Jordi Boggiano + */ +class InstalledArrayRepository extends WritableArrayRepository implements InstalledRepositoryInterface +{ + public function getRepoName(): string + { + return 'installed '.parent::getRepoName(); + } + + /** + * @inheritDoc + */ + public function isFresh(): bool + { + // this is not a completely correct implementation but there is no way to + // distinguish an empty repo and a newly created one given this is all in-memory + return $this->count() === 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php b/vendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php new file mode 100644 index 000000000..393a38422 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php @@ -0,0 +1,34 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Installed filesystem repository. + * + * @author Jordi Boggiano + */ +class InstalledFilesystemRepository extends FilesystemRepository implements InstalledRepositoryInterface +{ + public function getRepoName() + { + return 'installed '.parent::getRepoName(); + } + + /** + * @inheritDoc + */ + public function isFresh() + { + return !$this->file->exists(); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/InstalledRepository.php b/vendor/composer/composer/src/Composer/Repository/InstalledRepository.php new file mode 100644 index 000000000..3520fdec6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InstalledRepository.php @@ -0,0 +1,277 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Package\RootPackageInterface; +use Composer\Package\Link; + +/** + * Installed repository is a composite of all installed repo types. + * + * The main use case is tagging a repo as an "installed" repository, and offering a way to get providers/replacers easily. + * + * Installed repos are LockArrayRepository, InstalledRepositoryInterface, RootPackageRepository and PlatformRepository + * + * @author Jordi Boggiano + */ +class InstalledRepository extends CompositeRepository +{ + /** + * @param ConstraintInterface|string|null $constraint + * + * @return BasePackage[] + */ + public function findPackagesWithReplacersAndProviders(string $name, $constraint = null): array + { + $name = strtolower($name); + + if (null !== $constraint && !$constraint instanceof ConstraintInterface) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($constraint); + } + + $matches = []; + foreach ($this->getRepositories() as $repo) { + foreach ($repo->getPackages() as $candidate) { + if ($name === $candidate->getName()) { + if (null === $constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { + $matches[] = $candidate; + } + continue; + } + + foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) { + if ( + $name === $link->getTarget() + && ($constraint === null || $constraint->matches($link->getConstraint())) + ) { + $matches[] = $candidate; + continue 2; + } + } + } + } + + return $matches; + } + + /** + * Returns a list of links causing the requested needle packages to be installed, as an associative array with the + * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship + * as values. If recursive lookup was requested a third value is returned containing an identically formed array up + * to the root package. That third value will be false in case a circular recursion was detected. + * + * @param string|string[] $needle The package name(s) to inspect. + * @param ConstraintInterface|null $constraint Optional constraint to filter by. + * @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed. + * @param bool $recurse Whether to recursively expand the requirement tree up to the root package. + * @param string[] $packagesFound Used internally when recurring + * + * @return array[] An associative array of arrays as described above. + * @phpstan-return array|false}> + */ + public function getDependents($needle, ?ConstraintInterface $constraint = null, bool $invert = false, bool $recurse = true, ?array $packagesFound = null): array + { + $needles = array_map('strtolower', (array) $needle); + $results = []; + + // initialize the array with the needles before any recursion occurs + if (null === $packagesFound) { + $packagesFound = $needles; + } + + // locate root package for use below + $rootPackage = null; + foreach ($this->getPackages() as $package) { + if ($package instanceof RootPackageInterface) { + $rootPackage = $package; + break; + } + } + + // Loop over all currently installed packages. + foreach ($this->getPackages() as $package) { + $links = $package->getRequires(); + + // each loop needs its own "tree" as we want to show the complete dependent set of every needle + // without warning all the time about finding circular deps + $packagesInTree = $packagesFound; + + // Replacements are considered valid reasons for a package to be installed during forward resolution + if (!$invert) { + $links += $package->getReplaces(); + + // On forward search, check if any replaced package was required and add the replaced + // packages to the list of needles. Contrary to the cross-reference link check below, + // replaced packages are the target of links. + foreach ($package->getReplaces() as $link) { + foreach ($needles as $needle) { + if ($link->getSource() === $needle) { + if ($constraint === null || ($link->getConstraint()->matches($constraint) === true)) { + // already displayed this node's dependencies, cutting short + if (in_array($link->getTarget(), $packagesInTree)) { + $results[] = [$package, $link, false]; + continue; + } + $packagesInTree[] = $link->getTarget(); + $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : []; + $results[] = [$package, $link, $dependents]; + $needles[] = $link->getTarget(); + } + } + } + } + unset($needle); + } + + // Require-dev is only relevant for the root package + if ($package instanceof RootPackageInterface) { + $links += $package->getDevRequires(); + } + + // Cross-reference all discovered links to the needles + foreach ($links as $link) { + foreach ($needles as $needle) { + if ($link->getTarget() === $needle) { + if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) { + // already displayed this node's dependencies, cutting short + if (in_array($link->getSource(), $packagesInTree)) { + $results[] = [$package, $link, false]; + continue; + } + $packagesInTree[] = $link->getSource(); + $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : []; + $results[] = [$package, $link, $dependents]; + } + } + } + } + + // When inverting, we need to check for conflicts of the needles against installed packages + if ($invert && in_array($package->getName(), $needles, true)) { + foreach ($package->getConflicts() as $link) { + foreach ($this->findPackages($link->getTarget()) as $pkg) { + $version = new Constraint('=', $pkg->getVersion()); + if ($link->getConstraint()->matches($version) === $invert) { + $results[] = [$package, $link, false]; + } + } + } + } + + // List conflicts against X as they may explain why the current version was selected, or explain why it is rejected if the conflict matched when inverting + foreach ($package->getConflicts() as $link) { + if (in_array($link->getTarget(), $needles, true)) { + foreach ($this->findPackages($link->getTarget()) as $pkg) { + $version = new Constraint('=', $pkg->getVersion()); + if ($link->getConstraint()->matches($version) === $invert) { + $results[] = [$package, $link, false]; + } + } + } + } + + // When inverting, we need to check for conflicts of the needles' requirements against installed packages + if ($invert && $constraint && in_array($package->getName(), $needles, true) && $constraint->matches(new Constraint('=', $package->getVersion()))) { + foreach ($package->getRequires() as $link) { + if (PlatformRepository::isPlatformPackage($link->getTarget())) { + if ($this->findPackage($link->getTarget(), $link->getConstraint())) { + continue; + } + + $platformPkg = $this->findPackage($link->getTarget(), '*'); + $description = $platformPkg ? 'but '.$platformPkg->getPrettyVersion().' is installed' : 'but it is missing'; + $results[] = [$package, new Link($package->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_REQUIRE, $link->getPrettyConstraint().' '.$description), false]; + + continue; + } + + foreach ($this->getPackages() as $pkg) { + if (!in_array($link->getTarget(), $pkg->getNames())) { + continue; + } + + $version = new Constraint('=', $pkg->getVersion()); + + if ($link->getTarget() !== $pkg->getName()) { + foreach (array_merge($pkg->getReplaces(), $pkg->getProvides()) as $prov) { + if ($link->getTarget() === $prov->getTarget()) { + $version = $prov->getConstraint(); + break; + } + } + } + + if (!$link->getConstraint()->matches($version)) { + // if we have a root package (we should but can not guarantee..) we show + // the root requires as well to perhaps allow to find an issue there + if ($rootPackage) { + foreach (array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) { + if (in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) { + $results[] = [$package, $link, false]; + $results[] = [$rootPackage, $rootReq, false]; + continue 3; + } + } + + $results[] = [$package, $link, false]; + $results[] = [$rootPackage, new Link($rootPackage->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_DOES_NOT_REQUIRE, 'but ' . $pkg->getPrettyVersion() . ' is installed'), false]; + } else { + // no root so let's just print whatever we found + $results[] = [$package, $link, false]; + } + } + + continue 2; + } + } + } + } + + ksort($results); + + return $results; + } + + public function getRepoName(): string + { + return 'installed repo ('.implode(', ', array_map(static function ($repo): string { + return $repo->getRepoName(); + }, $this->getRepositories())).')'; + } + + /** + * @inheritDoc + */ + public function addRepository(RepositoryInterface $repository): void + { + if ( + $repository instanceof LockArrayRepository + || $repository instanceof InstalledRepositoryInterface + || $repository instanceof RootPackageRepository + || $repository instanceof PlatformRepository + ) { + parent::addRepository($repository); + + return; + } + + throw new \LogicException('An InstalledRepository can not contain a repository of type '.get_class($repository).' ('.$repository->getRepoName().')'); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php b/vendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php new file mode 100644 index 000000000..3b89d1da2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php @@ -0,0 +1,33 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Installable repository interface. + * + * Just used to tag installed repositories so the base classes can act differently on Alias packages + * + * @author Jordi Boggiano + */ +interface InstalledRepositoryInterface extends WritableRepositoryInterface +{ + /** + * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown + */ + public function getDevMode(); + + /** + * @return bool true if packages were never installed in this repository + */ + public function isFresh(); +} diff --git a/vendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php b/vendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php new file mode 100644 index 000000000..9dbd6f0de --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Exception thrown when a package repository is utterly broken + * + * @author Jordi Boggiano + */ +class InvalidRepositoryException extends \Exception +{ +} diff --git a/vendor/composer/composer/src/Composer/Repository/LockArrayRepository.php b/vendor/composer/composer/src/Composer/Repository/LockArrayRepository.php new file mode 100644 index 000000000..da0775895 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/LockArrayRepository.php @@ -0,0 +1,30 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Lock array repository. + * + * Regular array repository, only uses a different type to identify the lock file as the source of info + * + * @author Nils Adermann + */ +class LockArrayRepository extends ArrayRepository +{ + use CanonicalPackagesTrait; + + public function getRepoName(): string + { + return 'lock repo'; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/PackageRepository.php b/vendor/composer/composer/src/Composer/Repository/PackageRepository.php new file mode 100644 index 000000000..73eb1a9db --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/PackageRepository.php @@ -0,0 +1,109 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\Advisory\SecurityAdvisory; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Loader\ValidatingArrayLoader; +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; + +/** + * Package repository. + * + * @author Jordi Boggiano + */ +class PackageRepository extends ArrayRepository implements AdvisoryProviderInterface +{ + /** @var mixed[] */ + private $config; + + /** @var mixed[] */ + private $securityAdvisories; + + /** + * Initializes filesystem repository. + * + * @param array{package: mixed[]} $config package definition + */ + public function __construct(array $config) + { + parent::__construct(); + $this->config = $config['package']; + + // make sure we have an array of package definitions + if (!is_numeric(key($this->config))) { + $this->config = [$this->config]; + } + + $this->securityAdvisories = $config['security-advisories'] ?? []; + } + + /** + * Initializes repository (reads file, or remote address). + */ + protected function initialize(): void + { + parent::initialize(); + + $loader = new ValidatingArrayLoader(new ArrayLoader(null, true), true); + foreach ($this->config as $package) { + try { + $package = $loader->load($package); + } catch (\Exception $e) { + throw new InvalidRepositoryException('A repository of type "package" contains an invalid package definition: '.$e->getMessage()."\n\nInvalid package definition:\n".json_encode($package)); + } + + $this->addPackage($package); + } + } + + public function getRepoName(): string + { + return Preg::replace('{^array }', 'package ', parent::getRepoName()); + } + + public function hasSecurityAdvisories(): bool + { + return count($this->securityAdvisories) > 0; + } + + /** + * @todo not sure if this is a good idea, just helped setting up the test fixtures + */ + public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array + { + $parser = new VersionParser(); + + $advisories = []; + foreach ($this->securityAdvisories as $packageName => $packageAdvisories) { + if (isset($packageConstraintMap[$packageName])) { + $advisories[$packageName] = array_values(array_filter(array_map(function (array $data) use ($packageName, $allowPartialAdvisories, $packageConstraintMap, $parser) { + $advisory = PartialSecurityAdvisory::create($packageName, $data, $parser); + if (!$allowPartialAdvisories && !$advisory instanceof SecurityAdvisory) { + throw new \RuntimeException('Advisory for '.$packageName.' could not be loaded as a full advisory from '.$this->getRepoName() . PHP_EOL . var_export($data, true)); + } + + if (!$advisory->affectedVersions->matches($packageConstraintMap[$packageName])) { + return null; + } + + return $advisory; + }, $packageAdvisories))); + } + } + + return ['advisories' => array_filter($advisories, static function ($adv): bool { return \count($adv) > 0; }), 'namesFound' => array_keys($advisories)]; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/PathRepository.php b/vendor/composer/composer/src/Composer/Repository/PathRepository.php new file mode 100644 index 000000000..a37acde31 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/PathRepository.php @@ -0,0 +1,254 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Config; +use Composer\EventDispatcher\EventDispatcher; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; +use Composer\Util\HttpDownloader; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\Util\Url; +use Composer\Util\Git as GitUtil; + +/** + * This repository allows installing local packages that are not necessarily under their own VCS. + * + * The local packages will be symlinked when possible, else they will be copied. + * + * @code + * "require": { + * "/": "*" + * }, + * "repositories": [ + * { + * "type": "path", + * "url": "../../relative/path/to/package/" + * }, + * { + * "type": "path", + * "url": "/absolute/path/to/package/" + * }, + * { + * "type": "path", + * "url": "/absolute/path/to/several/packages/*" + * }, + * { + * "type": "path", + * "url": "../../relative/path/to/package/", + * "options": { + * "symlink": false + * } + * }, + * { + * "type": "path", + * "url": "../../relative/path/to/package/", + * "options": { + * "reference": "none" + * } + * }, + * ] + * @endcode + * + * @author Samuel Roze + * @author Johann Reinke + */ +class PathRepository extends ArrayRepository implements ConfigurableRepositoryInterface +{ + /** + * @var ArrayLoader + */ + private $loader; + + /** + * @var VersionGuesser + */ + private $versionGuesser; + + /** + * @var string + */ + private $url; + + /** + * @var mixed[] + * @phpstan-var array{url: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array}} + */ + private $repoConfig; + + /** + * @var ProcessExecutor + */ + private $process; + + /** + * @var array{symlink?: bool, reference?: string, relative?: bool, versions?: array} + */ + private $options; + + /** + * Initializes path repository. + * + * @param array{url?: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array}} $repoConfig + */ + public function __construct(array $repoConfig, IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null) + { + if (!isset($repoConfig['url'])) { + throw new \RuntimeException('You must specify the `url` configuration for the path repository'); + } + + $this->loader = new ArrayLoader(null, true); + $this->url = Platform::expandPath($repoConfig['url']); + $this->process = $process ?? new ProcessExecutor($io); + $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser(), $io); + $this->repoConfig = $repoConfig; + $this->options = $repoConfig['options'] ?? []; + if (!isset($this->options['relative'])) { + $filesystem = new Filesystem(); + $this->options['relative'] = !$filesystem->isAbsolutePath($this->url); + } + + parent::__construct(); + } + + public function getRepoName(): string + { + return 'path repo ('.Url::sanitize($this->repoConfig['url']).')'; + } + + public function getRepoConfig(): array + { + return $this->repoConfig; + } + + /** + * Initializes path repository. + * + * This method will basically read the folder and add the found package. + */ + protected function initialize(): void + { + parent::initialize(); + + $urlMatches = $this->getUrlMatches(); + + if (empty($urlMatches)) { + if (Preg::isMatch('{[*{}]}', $this->url)) { + $url = $this->url; + while (Preg::isMatch('{[*{}]}', $url)) { + $url = dirname($url); + } + // the parent directory before any wildcard exists, so we assume it is correctly configured but simply empty + if (is_dir($url)) { + return; + } + } + + throw new \RuntimeException('The `url` supplied for the path (' . $this->url . ') repository does not exist'); + } + + foreach ($urlMatches as $url) { + $path = realpath($url) . DIRECTORY_SEPARATOR; + $composerFilePath = $path.'composer.json'; + + if (!file_exists($composerFilePath)) { + continue; + } + + $json = file_get_contents($composerFilePath); + $package = JsonFile::parseJson($json, $composerFilePath); + $package['dist'] = [ + 'type' => 'path', + 'url' => $url, + ]; + $reference = $this->options['reference'] ?? 'auto'; + if ('none' === $reference) { + $package['dist']['reference'] = null; + } elseif ('config' === $reference || 'auto' === $reference) { + $package['dist']['reference'] = hash('sha1', $json . serialize($this->options)); + } + + // copy symlink/relative options to transport options + $package['transport-options'] = array_intersect_key($this->options, ['symlink' => true, 'relative' => true]); + // use the version provided as option if available + if (isset($package['name'], $this->options['versions'][$package['name']])) { + $package['version'] = $this->options['versions'][$package['name']]; + } + + // carry over the root package version if this path repo is in the same git repository as root package + if (!isset($package['version']) && ($rootVersion = Platform::getEnv('COMPOSER_ROOT_VERSION'))) { + if ( + 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref1, $path) + && 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref2) + && $ref1 === $ref2 + ) { + $package['version'] = $this->versionGuesser->getRootVersionFromEnv(); + } + } + + $output = ''; + $command = GitUtil::buildRevListCommand($this->process, array_merge(['-n1', '--format=%H', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process))); + if ('auto' === $reference && is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute($command, $output, $path)) { + $package['dist']['reference'] = trim(GitUtil::parseRevListOutput($output, $this->process)); + } + + if (!isset($package['version'])) { + $versionData = $this->versionGuesser->guessVersion($package, $path); + if (is_array($versionData) && $versionData['pretty_version']) { + // if there is a feature branch detected, we add a second packages with the feature branch version + if (!empty($versionData['feature_pretty_version'])) { + $package['version'] = $versionData['feature_pretty_version']; + $this->addPackage($this->loader->load($package)); + } + + $package['version'] = $versionData['pretty_version']; + } else { + $package['version'] = 'dev-main'; + } + } + + try { + $this->addPackage($this->loader->load($package)); + } catch (\Exception $e) { + throw new \RuntimeException('Failed loading the package in '.$composerFilePath, 0, $e); + } + } + } + + /** + * Get a list of all (possibly relative) path names matching given url (supports globbing). + * + * @return string[] + */ + private function getUrlMatches(): array + { + $flags = GLOB_MARK | GLOB_ONLYDIR; + + if (defined('GLOB_BRACE')) { + $flags |= GLOB_BRACE; + } elseif (strpos($this->url, '{') !== false || strpos($this->url, '}') !== false) { + throw new \RuntimeException('The operating system does not support GLOB_BRACE which is required for the url '. $this->url); + } + + // Ensure environment-specific path separators are normalized to URL separators + return array_map(static function ($val): string { + return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/'); + }, glob($this->url, $flags)); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/PearRepository.php b/vendor/composer/composer/src/Composer/Repository/PearRepository.php new file mode 100644 index 000000000..facc41787 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/PearRepository.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Builds list of package from PEAR channel. + * + * Packages read from channel are named as 'pear-{channelName}/{packageName}' + * and has aliased as 'pear-{channelAlias}/{packageName}' + * + * @author Benjamin Eberlei + * @author Jordi Boggiano + * @deprecated + * @private + */ +class PearRepository extends ArrayRepository +{ + public function __construct() + { + throw new \InvalidArgumentException('The PEAR repository has been removed from Composer 2.x'); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/PlatformRepository.php b/vendor/composer/composer/src/Composer/Repository/PlatformRepository.php new file mode 100644 index 000000000..640c415c2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/PlatformRepository.php @@ -0,0 +1,770 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Composer; +use Composer\Package\CompletePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Link; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; +use Composer\Platform\HhvmDetector; +use Composer\Platform\Runtime; +use Composer\Platform\Version; +use Composer\Plugin\PluginInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Util\Silencer; +use Composer\XdebugHandler\XdebugHandler; + +/** + * @author Jordi Boggiano + */ +class PlatformRepository extends ArrayRepository +{ + /** + * @deprecated use PlatformRepository::isPlatformPackage(string $name) instead + * @private + */ + public const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer(?:-(?:plugin|runtime)-api)?)$}iD'; + + /** + * @var ?string + */ + private static $lastSeenPlatformPhp = null; + + /** + * @var VersionParser + */ + private $versionParser; + + /** + * Defines overrides so that the platform can be mocked + * + * Keyed by package name (lowercased) + * + * @var array + */ + private $overrides = []; + + /** + * Stores which packages have been disabled and their actual version + * + * @var array + */ + private $disabledPackages = []; + + /** @var Runtime */ + private $runtime; + /** @var HhvmDetector */ + private $hhvmDetector; + + /** + * @param array $overrides + */ + public function __construct(array $packages = [], array $overrides = [], ?Runtime $runtime = null, ?HhvmDetector $hhvmDetector = null) + { + $this->runtime = $runtime ?: new Runtime(); + $this->hhvmDetector = $hhvmDetector ?: new HhvmDetector(); + foreach ($overrides as $name => $version) { + if (!is_string($version) && false !== $version) { // @phpstan-ignore-line + throw new \UnexpectedValueException('config.platform.'.$name.' should be a string or false, but got '.get_debug_type($version).' '.var_export($version, true)); + } + if ($name === 'php' && $version === false) { + throw new \UnexpectedValueException('config.platform.'.$name.' cannot be set to false as you cannot disable php entirely.'); + } + $this->overrides[strtolower($name)] = ['name' => $name, 'version' => $version]; + } + parent::__construct($packages); + } + + public function getRepoName(): string + { + return 'platform repo'; + } + + public function isPlatformPackageDisabled(string $name): bool + { + return isset($this->disabledPackages[$name]); + } + + /** + * @return array + */ + public function getDisabledPackages(): array + { + return $this->disabledPackages; + } + + protected function initialize(): void + { + parent::initialize(); + + $libraries = []; + + $this->versionParser = new VersionParser(); + + // Add each of the override versions as options. + // Later we might even replace the extensions instead. + foreach ($this->overrides as $override) { + // Check that it's a platform package. + if (!self::isPlatformPackage($override['name'])) { + throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']); + } + + if ($override['version'] !== false) { + $this->addOverriddenPackage($override); + } + } + + $prettyVersion = Composer::getVersion(); + $version = $this->versionParser->normalize($prettyVersion); + $composer = new CompletePackage('composer', $version, $prettyVersion); + $composer->setDescription('Composer package'); + $this->addPackage($composer); + + $prettyVersion = PluginInterface::PLUGIN_API_VERSION; + $version = $this->versionParser->normalize($prettyVersion); + $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion); + $composerPluginApi->setDescription('The Composer Plugin API'); + $this->addPackage($composerPluginApi); + + $prettyVersion = Composer::RUNTIME_API_VERSION; + $version = $this->versionParser->normalize($prettyVersion); + $composerRuntimeApi = new CompletePackage('composer-runtime-api', $version, $prettyVersion); + $composerRuntimeApi->setDescription('The Composer Runtime API'); + $this->addPackage($composerRuntimeApi); + + try { + $prettyVersion = $this->runtime->getConstant('PHP_VERSION'); + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $this->runtime->getConstant('PHP_VERSION')); + $version = $this->versionParser->normalize($prettyVersion); + } + + $php = new CompletePackage('php', $version, $prettyVersion); + $php->setDescription('The PHP interpreter'); + $this->addPackage($php); + + if ($this->runtime->getConstant('PHP_DEBUG')) { + $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion); + $phpdebug->setDescription('The PHP interpreter, with debugging symbols'); + $this->addPackage($phpdebug); + } + + if ($this->runtime->hasConstant('PHP_ZTS') && $this->runtime->getConstant('PHP_ZTS')) { + $phpzts = new CompletePackage('php-zts', $version, $prettyVersion); + $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety'); + $this->addPackage($phpzts); + } + + if ($this->runtime->getConstant('PHP_INT_SIZE') === 8) { + $php64 = new CompletePackage('php-64bit', $version, $prettyVersion); + $php64->setDescription('The PHP interpreter, 64bit'); + $this->addPackage($php64); + } + + // The AF_INET6 constant is only defined if ext-sockets is available but + // IPv6 support might still be available. + if ($this->runtime->hasConstant('AF_INET6') || Silencer::call([$this->runtime, 'invoke'], 'inet_pton', ['::']) !== false) { + $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion); + $phpIpv6->setDescription('The PHP interpreter, with IPv6 support'); + $this->addPackage($phpIpv6); + } + + $loadedExtensions = $this->runtime->getExtensions(); + + // Extensions scanning + foreach ($loadedExtensions as $name) { + if (in_array($name, ['standard', 'Core'])) { + continue; + } + + $this->addExtension($name, $this->runtime->getExtensionVersion($name)); + } + + // Check for Xdebug in a restarted process + if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) { + $this->addExtension('xdebug', $prettyVersion); + } + + // Another quick loop, just for possible libraries + // Doing it this way to know that functions or constants exist before + // relying on them. + foreach ($loadedExtensions as $name) { + switch ($name) { + case 'amqp': + $info = $this->runtime->getExtensionInfo($name); + + // librabbitmq version => 0.9.0 + if (Preg::isMatch('/^librabbitmq version => (?.+)$/im', $info, $librabbitmqMatches)) { + $this->addLibrary($libraries, $name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version'); + } + + // AMQP protocol version => 0-9-1 + if (Preg::isMatchStrictGroups('/^AMQP protocol version => (?.+)$/im', $info, $protocolMatches)) { + $this->addLibrary($libraries, $name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version'); + } + break; + + case 'bz2': + $info = $this->runtime->getExtensionInfo($name); + + // BZip2 Version => 1.0.6, 6-Sept-2010 + if (Preg::isMatch('/^BZip2 Version => (?.*),/im', $info, $matches)) { + $this->addLibrary($libraries, $name, $matches['version']); + } + break; + + case 'curl': + $curlVersion = $this->runtime->invoke('curl_version'); + $this->addLibrary($libraries, $name, $curlVersion['version']); + + $info = $this->runtime->getExtensionInfo($name); + + // SSL Version => OpenSSL/1.0.1t + if (Preg::isMatchStrictGroups('{^SSL Version => (?[^/]+)/(?.+)$}im', $info, $sslMatches)) { + $library = strtolower($sslMatches['library']); + if ($library === 'openssl') { + $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips); + $this->addLibrary($libraries, $name.'-openssl'.($isFips ? '-fips' : ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', [], $isFips ? ['curl-openssl'] : []); + } else { + if (str_starts_with($library, '(securetransport)') && Preg::isMatch('{^\(securetransport\) ([a-z0-9]+)}', $library, $securetransportMatches)) { + $shortlib = 'securetransport'; + $sslLib = 'curl-'.$securetransportMatches[1]; + } else { + $shortlib = $library; + $sslLib = 'curl-openssl'; + } + $this->addLibrary($libraries, $name.'-'.$shortlib, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', [$sslLib]); + } + } + + // libSSH Version => libssh2/1.4.3 + if (Preg::isMatchStrictGroups('{^libSSH Version => (?[^/]+)/(?.+?)(?:/.*)?$}im', $info, $sshMatches)) { + $this->addLibrary($libraries, $name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version'); + } + + // ZLib Version => 1.2.8 + if (Preg::isMatchStrictGroups('{^ZLib Version => (?.+)$}im', $info, $zlibMatches)) { + $this->addLibrary($libraries, $name.'-zlib', $zlibMatches['version'], 'curl zlib version'); + } + break; + + case 'date': + $info = $this->runtime->getExtensionInfo($name); + + // timelib version => 2018.03 + if (Preg::isMatchStrictGroups('/^timelib version => (?.+)$/im', $info, $timelibMatches)) { + $this->addLibrary($libraries, $name.'-timelib', $timelibMatches['version'], 'date timelib version'); + } + + // Timezone Database => internal + if (Preg::isMatchStrictGroups('/^Timezone Database => (?internal|external)$/im', $info, $zoneinfoSourceMatches)) { + $external = $zoneinfoSourceMatches['source'] === 'external'; + if (Preg::isMatchStrictGroups('/^"Olson" Timezone Database Version => (?.+?)(?:\.system)?$/im', $info, $zoneinfoMatches)) { + // If the timezonedb is provided by ext/timezonedb, register that version as a replacement + if ($external && in_array('timezonedb', $loadedExtensions, true)) { + $this->addLibrary($libraries, 'timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', [$name.'-zoneinfo']); + } else { + $this->addLibrary($libraries, $name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date'); + } + } + } + break; + + case 'fileinfo': + $info = $this->runtime->getExtensionInfo($name); + + // libmagic => 537 + if (Preg::isMatch('/^libmagic => (?.+)$/im', $info, $magicMatches)) { + $this->addLibrary($libraries, $name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version'); + } + break; + + case 'gd': + $this->addLibrary($libraries, $name, $this->runtime->getConstant('GD_VERSION')); + + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatchStrictGroups('/^libJPEG Version => (?.+?)(?: compatible)?$/im', $info, $libjpegMatches)) { + $this->addLibrary($libraries, $name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd'); + } + + if (Preg::isMatchStrictGroups('/^libPNG Version => (?.+)$/im', $info, $libpngMatches)) { + $this->addLibrary($libraries, $name.'-libpng', $libpngMatches['version'], 'libpng version for gd'); + } + + if (Preg::isMatchStrictGroups('/^FreeType Version => (?.+)$/im', $info, $freetypeMatches)) { + $this->addLibrary($libraries, $name.'-freetype', $freetypeMatches['version'], 'freetype version for gd'); + } + + if (Preg::isMatchStrictGroups('/^libXpm Version => (?\d+)$/im', $info, $libxpmMatches)) { + $this->addLibrary($libraries, $name.'-libxpm', Version::convertLibxpmVersionId((int) $libxpmMatches['versionId']), 'libxpm version for gd'); + } + + break; + + case 'gmp': + $this->addLibrary($libraries, $name, $this->runtime->getConstant('GMP_VERSION')); + break; + + case 'iconv': + $this->addLibrary($libraries, $name, $this->runtime->getConstant('ICONV_VERSION')); + break; + + case 'intl': + $info = $this->runtime->getExtensionInfo($name); + + $description = 'The ICU unicode and globalization support library'; + // Truthy check is for testing only so we can make the condition fail + if ($this->runtime->hasConstant('INTL_ICU_VERSION')) { + $this->addLibrary($libraries, 'icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description); + } elseif (Preg::isMatch('/^ICU version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, 'icu', $matches['version'], $description); + } + + // ICU TZData version => 2019c + if (Preg::isMatchStrictGroups('/^ICU TZData version => (?.*)$/im', $info, $zoneinfoMatches) && null !== ($version = Version::parseZoneinfoVersion($zoneinfoMatches['version']))) { + $this->addLibrary($libraries, 'icu-zoneinfo', $version, 'zoneinfo ("Olson") database for icu'); + } + + // Add a separate version for the CLDR library version + if ($this->runtime->hasClass('ResourceBundle')) { + $resourceBundle = $this->runtime->invoke(['ResourceBundle', 'create'], ['root', 'ICUDATA', false]); + if ($resourceBundle !== null) { + $this->addLibrary($libraries, 'icu-cldr', $resourceBundle->get('Version'), 'ICU CLDR project version'); + } + } + + if ($this->runtime->hasClass('IntlChar')) { + $this->addLibrary($libraries, 'icu-unicode', implode('.', array_slice($this->runtime->invoke(['IntlChar', 'getUnicodeVersion']), 0, 3)), 'ICU unicode version'); + } + break; + + case 'imagick': + // @phpstan-ignore staticMethod.dynamicCall (called like this for mockability) + $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion(); + // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org + // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org + if (Preg::isMatch('/^ImageMagick (?[\d.]+)(?:-(?\d+))?/', $imageMagickVersion['versionString'], $matches)) { + $version = $matches['version']; + if (isset($matches['patch'])) { + $version .= '.'.$matches['patch']; + } + + $this->addLibrary($libraries, $name.'-imagemagick', $version, null, ['imagick']); + } + break; + + case 'ldap': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatchStrictGroups('/^Vendor Version => (?\d+)$/im', $info, $matches) && Preg::isMatchStrictGroups('/^Vendor Name => (?.+)$/im', $info, $vendorMatches)) { + $this->addLibrary($libraries, $name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId((int) $matches['versionId']), $vendorMatches['vendor'].' version of ldap'); + } + break; + + case 'libxml': + // ext/dom, ext/simplexml, ext/xmlreader and ext/xmlwriter use the same libxml as the ext/libxml + $libxmlProvides = array_map(static function ($extension): string { + return $extension . '-libxml'; + }, array_intersect($loadedExtensions, ['dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter'])); + $this->addLibrary($libraries, $name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', [], $libxmlProvides); + + break; + + case 'mbstring': + $info = $this->runtime->getExtensionInfo($name); + + // libmbfl version => 1.3.2 + if (Preg::isMatch('/^libmbfl version => (?.+)$/im', $info, $libmbflMatches)) { + $this->addLibrary($libraries, $name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version'); + } + + if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) { + $this->addLibrary($libraries, $name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version'); + + // Multibyte regex (oniguruma) version => 5.9.5 + // oniguruma version => 6.9.0 + } elseif (Preg::isMatch('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?.+)$/im', $info, $onigurumaMatches)) { + $this->addLibrary($libraries, $name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version'); + } + + break; + + case 'memcached': + $info = $this->runtime->getExtensionInfo($name); + + // libmemcached version => 1.0.18 + if (Preg::isMatch('/^libmemcached version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libmemcached', $matches['version'], 'libmemcached version'); + } + break; + + case 'openssl': + // OpenSSL 1.1.1g 21 Apr 2020 + if (Preg::isMatchStrictGroups('{^(?:OpenSSL|LibreSSL)?\s*(?\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) { + $parsedVersion = Version::parseOpenssl($matches['version'], $isFips); + $this->addLibrary($libraries, $name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), [], $isFips ? [$name] : []); + } + break; + + case 'pcre': + $this->addLibrary($libraries, $name, Preg::replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION'))); + + $info = $this->runtime->getExtensionInfo($name); + + // PCRE Unicode Version => 12.1.0 + if (Preg::isMatchStrictGroups('/^PCRE Unicode Version => (?.+)$/im', $info, $pcreUnicodeMatches)) { + $this->addLibrary($libraries, $name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support'); + } + + break; + + case 'mysqlnd': + case 'pdo_mysql': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatchStrictGroups('/^(?:Client API version|Version) => mysqlnd (?.+?) /mi', $info, $matches)) { + $this->addLibrary($libraries, $name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name); + } + break; + + case 'mongodb': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatchStrictGroups('/^libmongoc bundled version => (?.+)$/im', $info, $libmongocMatches)) { + $this->addLibrary($libraries, $name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb'); + } + + if (Preg::isMatchStrictGroups('/^libbson bundled version => (?.+)$/im', $info, $libbsonMatches)) { + $this->addLibrary($libraries, $name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb'); + } + break; + + case 'pgsql': + if ($this->runtime->hasConstant('PGSQL_LIBPQ_VERSION')) { + $this->addLibrary($libraries, 'pgsql-libpq', $this->runtime->getConstant('PGSQL_LIBPQ_VERSION'), 'libpq for pgsql'); + break; + } + // intentional fall-through to next case... + + case 'pdo_pgsql': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatch('/^PostgreSQL\(libpq\) Version => (?.*)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libpq', $matches['version'], 'libpq for '.$name); + } + break; + + case 'pq': + $info = $this->runtime->getExtensionInfo($name); + + // Used Library => Compiled => Linked + // libpq => 14.3 (Ubuntu 14.3-1.pgdg22.04+1) => 15.0.2 + if (Preg::isMatch('/^libpq => (?.+) => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libpq', $matches['linked'], 'libpq for '.$name); + } + break; + + case 'rdkafka': + if ($this->runtime->hasConstant('RD_KAFKA_VERSION')) { + /** + * Interpreted as hex \c MM.mm.rr.xx: + * - MM = Major + * - mm = minor + * - rr = revision + * - xx = pre-release id (0xff is the final release) + * + * pre-release ID in practice is always 0xff even for RCs etc, so we ignore it + */ + $libRdKafkaVersionInt = $this->runtime->getConstant('RD_KAFKA_VERSION'); + $this->addLibrary($libraries, $name.'-librdkafka', sprintf('%d.%d.%d', ($libRdKafkaVersionInt & 0x7F000000) >> 24, ($libRdKafkaVersionInt & 0x00FF0000) >> 16, ($libRdKafkaVersionInt & 0x0000FF00) >> 8), 'librdkafka for '.$name); + } + break; + + case 'libsodium': + case 'sodium': + if ($this->runtime->hasConstant('SODIUM_LIBRARY_VERSION')) { + $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); + $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); + } + break; + + case 'sqlite3': + case 'pdo_sqlite': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatch('/^SQLite Library => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-sqlite', $matches['version']); + } + break; + + case 'ssh2': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatch('/^libssh2 version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libssh2', $matches['version']); + } + break; + + case 'xsl': + $this->addLibrary($libraries, 'libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, ['xsl']); + + $info = $this->runtime->getExtensionInfo('xsl'); + if (Preg::isMatch('/^libxslt compiled against libxml Version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, 'libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against'); + } + break; + + case 'yaml': + $info = $this->runtime->getExtensionInfo('yaml'); + + if (Preg::isMatch('/^LibYAML Version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libyaml', $matches['version'], 'libyaml version of yaml'); + } + break; + + case 'zip': + if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) { + $this->addLibrary($libraries, $name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION', 'ZipArchive'), null, ['zip']); + } + break; + + case 'zlib': + if ($this->runtime->hasConstant('ZLIB_VERSION')) { + $this->addLibrary($libraries, $name, $this->runtime->getConstant('ZLIB_VERSION')); + + // Linked Version => 1.2.8 + } elseif (Preg::isMatch('/^Linked Version => (?.+)$/im', $this->runtime->getExtensionInfo($name), $matches)) { + $this->addLibrary($libraries, $name, $matches['version']); + } + break; + + default: + break; + } + } + + $hhvmVersion = $this->hhvmDetector->getVersion(); + if ($hhvmVersion) { + try { + $prettyVersion = $hhvmVersion; + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $hhvmVersion); + $version = $this->versionParser->normalize($prettyVersion); + } + + $hhvm = new CompletePackage('hhvm', $version, $prettyVersion); + $hhvm->setDescription('The HHVM Runtime (64bit)'); + $this->addPackage($hhvm); + } + } + + /** + * @inheritDoc + */ + public function addPackage(PackageInterface $package): void + { + if (!$package instanceof CompletePackage) { + throw new \UnexpectedValueException('Expected CompletePackage but got '.get_class($package)); + } + + // Skip if overridden + if (isset($this->overrides[$package->getName()])) { + if ($this->overrides[$package->getName()]['version'] === false) { + $this->addDisabledPackage($package); + + return; + } + + $overrider = $this->findPackage($package->getName(), '*'); + if ($package->getVersion() === $overrider->getVersion()) { + $actualText = 'same as actual'; + } else { + $actualText = 'actual: '.$package->getPrettyVersion(); + } + if ($overrider instanceof CompletePackageInterface) { + $overrider->setDescription($overrider->getDescription().', '.$actualText); + } + + return; + } + + // Skip if PHP is overridden and we are adding a php-* package + if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) { + $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName()); + if ($package->getVersion() === $overrider->getVersion()) { + $actualText = 'same as actual'; + } else { + $actualText = 'actual: '.$package->getPrettyVersion(); + } + $overrider->setDescription($overrider->getDescription().', '.$actualText); + + return; + } + + parent::addPackage($package); + } + + /** + * @param array{version: string, name: string} $override + */ + private function addOverriddenPackage(array $override, ?string $name = null): CompletePackage + { + $version = $this->versionParser->normalize($override['version']); + $package = new CompletePackage($name ?: $override['name'], $version, $override['version']); + $package->setDescription('Package overridden via config.platform'); + $package->setExtra(['config.platform' => true]); + parent::addPackage($package); + + if ($package->getName() === 'php') { + self::$lastSeenPlatformPhp = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3)); + } + + return $package; + } + + private function addDisabledPackage(CompletePackage $package): void + { + $package->setDescription($package->getDescription().'. Package disabled via config.platform'); + $package->setExtra(['config.platform' => true]); + + $this->disabledPackages[$package->getName()] = $package; + } + + /** + * Parses the version and adds a new package to the repository + */ + private function addExtension(string $name, string $prettyVersion): void + { + $extraDescription = null; + + try { + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + $extraDescription = ' (actual version: '.$prettyVersion.')'; + if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) { + $prettyVersion = $match[1]; + } else { + $prettyVersion = '0'; + } + $version = $this->versionParser->normalize($prettyVersion); + } + + $packageName = $this->buildPackageName($name); + $ext = new CompletePackage($packageName, $version, $prettyVersion); + $ext->setDescription('The '.$name.' PHP extension'.$extraDescription); + $ext->setType('php-ext'); + + if ($name === 'uuid') { + $ext->setReplaces([ + 'lib-uuid' => new Link('ext-uuid', 'lib-uuid', new Constraint('=', $version), Link::TYPE_REPLACE, $ext->getPrettyVersion()), + ]); + } + + $this->addPackage($ext); + } + + private function buildPackageName(string $name): string + { + return 'ext-' . str_replace(' ', '-', strtolower($name)); + } + + /** + * @param array $libraries + * @param array $replaces + * @param array $provides + */ + private function addLibrary(array &$libraries, string $name, ?string $prettyVersion, ?string $description = null, array $replaces = [], array $provides = []): void + { + if (null === $prettyVersion) { + return; + } + try { + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + return; + } + + // avoid adding the same lib twice even if two conflicting extensions provide the same lib + // see https://github.com/composer/composer/issues/12082 + if (isset($libraries['lib-'.$name])) { + return; + } + $libraries['lib-'.$name] = true; + + if ($description === null) { + $description = 'The '.$name.' library'; + } + + $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); + $lib->setDescription($description); + + $replaceLinks = []; + foreach ($replaces as $replace) { + $replace = strtolower($replace); + $replaceLinks[$replace] = new Link('lib-'.$name, 'lib-'.$replace, new Constraint('=', $version), Link::TYPE_REPLACE, $lib->getPrettyVersion()); + } + $provideLinks = []; + foreach ($provides as $provide) { + $provide = strtolower($provide); + $provideLinks[$provide] = new Link('lib-'.$name, 'lib-'.$provide, new Constraint('=', $version), Link::TYPE_PROVIDE, $lib->getPrettyVersion()); + } + $lib->setReplaces($replaceLinks); + $lib->setProvides($provideLinks); + + $this->addPackage($lib); + } + + /** + * Check if a package name is a platform package. + */ + public static function isPlatformPackage(string $name): bool + { + static $cache = []; + + if (isset($cache[$name])) { + return $cache[$name]; + } + + return $cache[$name] = Preg::isMatch(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name); + } + + /** + * Returns the last seen config.platform.php version if defined + * + * This is a best effort attempt for internal purposes, retrieve the real + * packages from a PlatformRepository instance if you need a version guaranteed to + * be correct. + * + * @internal + */ + public static function getPlatformPhpVersion(): ?string + { + return self::$lastSeenPlatformPhp; + } + + public function search(string $query, int $mode = 0, ?string $type = null): array + { + // suppress vendor search as there are no vendors to match in platform packages + if ($mode === self::SEARCH_VENDOR) { + return []; + } + + return parent::search($query, $mode, $type); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositoryFactory.php b/vendor/composer/composer/src/Composer/Repository/RepositoryFactory.php new file mode 100644 index 000000000..4d2742d35 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositoryFactory.php @@ -0,0 +1,188 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Pcre\Preg; +use Composer\Util\HttpDownloader; +use Composer\Util\ProcessExecutor; +use Composer\Json\JsonFile; + +/** + * @author Jordi Boggiano + */ +class RepositoryFactory +{ + /** + * @return array|mixed + */ + public static function configFromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = false) + { + if (0 === strpos($repository, 'http')) { + $repoConfig = ['type' => 'composer', 'url' => $repository]; + } elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) { + $json = new JsonFile($repository, Factory::createHttpDownloader($io, $config)); + $data = $json->read(); + if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) { + $repoConfig = ['type' => 'composer', 'url' => 'file://' . strtr(realpath($repository), '\\', '/')]; + } elseif ($allowFilesystem) { + $repoConfig = ['type' => 'filesystem', 'json' => $json]; + } else { + throw new \InvalidArgumentException("Invalid repository URL ($repository) given. This file does not contain a valid composer repository."); + } + } elseif (strpos($repository, '{') === 0) { + // assume it is a json object that makes a repo config + $repoConfig = JsonFile::parseJson($repository); + } else { + throw new \InvalidArgumentException("Invalid repository url ($repository) given. Has to be a .json file, an http url or a JSON object."); + } + + return $repoConfig; + } + + public static function fromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = false, ?RepositoryManager $rm = null): RepositoryInterface + { + $repoConfig = static::configFromString($io, $config, $repository, $allowFilesystem); + + return static::createRepo($io, $config, $repoConfig, $rm); + } + + /** + * @param array $repoConfig + */ + public static function createRepo(IOInterface $io, Config $config, array $repoConfig, ?RepositoryManager $rm = null): RepositoryInterface + { + if (!$rm) { + @trigger_error('Not passing a repository manager when calling createRepo is deprecated since Composer 2.3.6', E_USER_DEPRECATED); + $rm = static::manager($io, $config); + } + $repos = self::createRepos($rm, [$repoConfig]); + + return reset($repos); + } + + /** + * @return RepositoryInterface[] + */ + public static function defaultRepos(?IOInterface $io = null, ?Config $config = null, ?RepositoryManager $rm = null): array + { + if (null === $rm) { + @trigger_error('Not passing a repository manager when calling defaultRepos is deprecated since Composer 2.3.6, use defaultReposWithDefaultManager() instead if you cannot get a manager.', E_USER_DEPRECATED); + } + + if (null === $config) { + $config = Factory::createConfig($io); + } + if (null !== $io) { + $io->loadConfiguration($config); + } + if (null === $rm) { + if (null === $io) { + throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager'); + } + $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config)); + } + + return self::createRepos($rm, $config->getRepositories()); + } + + public static function manager(IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null): RepositoryManager + { + if ($httpDownloader === null) { + $httpDownloader = Factory::createHttpDownloader($io, $config); + } + if ($process === null) { + $process = new ProcessExecutor($io); + $process->enableAsync(); + } + + $rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher, $process); + $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); + $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); + $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); + $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('bitbucket', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('git-bitbucket', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('github', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('gitlab', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('fossil', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository'); + $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository'); + + return $rm; + } + + /** + * @return RepositoryInterface[] + */ + public static function defaultReposWithDefaultManager(IOInterface $io): array + { + $manager = RepositoryFactory::manager($io, $config = Factory::createConfig($io)); + $io->loadConfiguration($config); + + return RepositoryFactory::defaultRepos($io, $config, $manager); + } + + /** + * @param array $repoConfigs + * + * @return RepositoryInterface[] + */ + private static function createRepos(RepositoryManager $rm, array $repoConfigs): array + { + $repos = []; + + foreach ($repoConfigs as $index => $repo) { + if (is_string($repo)) { + throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given'); + } + if (!is_array($repo)) { + throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.get_debug_type($repo).' given'); + } + if (!isset($repo['type'])) { + throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined'); + } + + $name = self::generateRepositoryName($index, $repo, $repos); + if ($repo['type'] === 'filesystem') { + $repos[$name] = new FilesystemRepository($repo['json']); + } else { + $repos[$name] = $rm->createRepository($repo['type'], $repo, (string) $index); + } + } + + return $repos; + } + + /** + * @param int|string $index + * @param array{url?: string} $repo + * @param array $existingRepos + */ + public static function generateRepositoryName($index, array $repo, array $existingRepos): string + { + $name = is_int($index) && isset($repo['url']) ? Preg::replace('{^https?://}i', '', $repo['url']) : (string) $index; + while (isset($existingRepos[$name])) { + $name .= '2'; + } + + return $name; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositoryInterface.php b/vendor/composer/composer/src/Composer/Repository/RepositoryInterface.php new file mode 100644 index 000000000..f90c96d50 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositoryInterface.php @@ -0,0 +1,119 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\PackageInterface; +use Composer\Package\BasePackage; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Repository interface. + * + * @author Nils Adermann + * @author Konstantin Kudryashov + * @author Jordi Boggiano + */ +interface RepositoryInterface extends \Countable +{ + public const SEARCH_FULLTEXT = 0; + public const SEARCH_NAME = 1; + public const SEARCH_VENDOR = 2; + + /** + * Checks if specified package registered (installed). + * + * @param PackageInterface $package package instance + * + * @return bool + */ + public function hasPackage(PackageInterface $package); + + /** + * Searches for the first match of a package by name and version. + * + * @param string $name package name + * @param string|ConstraintInterface $constraint package version or version constraint to match against + * + * @return BasePackage|null + */ + public function findPackage(string $name, $constraint); + + /** + * Searches for all packages matching a name and optionally a version. + * + * @param string $name package name + * @param string|ConstraintInterface $constraint package version or version constraint to match against + * + * @return BasePackage[] + */ + public function findPackages(string $name, $constraint = null); + + /** + * Returns list of registered packages. + * + * @return BasePackage[] + */ + public function getPackages(); + + /** + * Returns list of registered packages with the supplied name + * + * - The packages returned are the packages found which match the constraints, acceptable stability and stability flags provided + * - The namesFound returned are names which should be considered as canonically found in this repository, that should not be looked up in any further lower priority repositories + * + * @param ConstraintInterface[] $packageNameMap package names pointing to constraints + * @param array $acceptableStabilities array of stability => BasePackage::STABILITY_* value + * @param array $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @param array> $alreadyLoaded an array of package name => package version => package + * + * @return array + * + * @phpstan-param array, BasePackage::STABILITY_*> $acceptableStabilities + * @phpstan-param array $packageNameMap + * @phpstan-return array{namesFound: array, packages: array} + */ + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []); + + /** + * Searches the repository for packages containing the query + * + * @param string $query search query, for SEARCH_NAME and SEARCH_VENDOR regular expressions metacharacters are supported by implementations, and user input should be escaped through preg_quote by callers + * @param int $mode a set of SEARCH_* constants to search on, implementations should do a best effort only, default is SEARCH_FULLTEXT + * @param ?string $type The type of package to search for. Defaults to all types of packages + * + * @return array[] an array of array('name' => '...', 'description' => '...'|null, 'abandoned' => 'string'|true|unset) For SEARCH_VENDOR the name will be in "vendor" form + * @phpstan-return list + */ + public function search(string $query, int $mode = 0, ?string $type = null); + + /** + * Returns a list of packages providing a given package name + * + * Packages which have the same name as $packageName should not be returned, only those that have a "provide" on it. + * + * @param string $packageName package name which must be provided + * + * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...') + * @phpstan-return array + */ + public function getProviders(string $packageName); + + /** + * Returns a name representing this repository to the user + * + * This is best effort and definitely can not always be very precise + * + * @return string + */ + public function getRepoName(); +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositoryManager.php b/vendor/composer/composer/src/Composer/Repository/RepositoryManager.php new file mode 100644 index 000000000..65dad875e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositoryManager.php @@ -0,0 +1,188 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Package\PackageInterface; +use Composer\Util\HttpDownloader; +use Composer\Util\ProcessExecutor; + +/** + * Repositories manager. + * + * @author Jordi Boggiano + * @author Konstantin Kudryashov + * @author François Pluchino + */ +class RepositoryManager +{ + /** @var InstalledRepositoryInterface */ + private $localRepository; + /** @var list */ + private $repositories = []; + /** @var array> */ + private $repositoryClasses = []; + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var HttpDownloader */ + private $httpDownloader; + /** @var ?EventDispatcher */ + private $eventDispatcher; + /** @var ProcessExecutor */ + private $process; + + public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null) + { + $this->io = $io; + $this->config = $config; + $this->httpDownloader = $httpDownloader; + $this->eventDispatcher = $eventDispatcher; + $this->process = $process ?? new ProcessExecutor($io); + } + + /** + * Searches for a package by its name and version in managed repositories. + * + * @param string $name package name + * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against + */ + public function findPackage(string $name, $constraint): ?PackageInterface + { + foreach ($this->repositories as $repository) { + /** @var RepositoryInterface $repository */ + if ($package = $repository->findPackage($name, $constraint)) { + return $package; + } + } + + return null; + } + + /** + * Searches for all packages matching a name and optionally a version in managed repositories. + * + * @param string $name package name + * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against + * + * @return PackageInterface[] + */ + public function findPackages(string $name, $constraint): array + { + $packages = []; + + foreach ($this->getRepositories() as $repository) { + $packages = array_merge($packages, $repository->findPackages($name, $constraint)); + } + + return $packages; + } + + /** + * Adds repository + * + * @param RepositoryInterface $repository repository instance + */ + public function addRepository(RepositoryInterface $repository): void + { + $this->repositories[] = $repository; + } + + /** + * Adds a repository to the beginning of the chain + * + * This is useful when injecting additional repositories that should trump Packagist, e.g. from a plugin. + * + * @param RepositoryInterface $repository repository instance + */ + public function prependRepository(RepositoryInterface $repository): void + { + array_unshift($this->repositories, $repository); + } + + /** + * Returns a new repository for a specific installation type. + * + * @param string $type repository type + * @param array $config repository configuration + * @param string $name repository name + * @throws \InvalidArgumentException if repository for provided type is not registered + */ + public function createRepository(string $type, array $config, ?string $name = null): RepositoryInterface + { + if (!isset($this->repositoryClasses[$type])) { + throw new \InvalidArgumentException('Repository type is not registered: '.$type); + } + + if (isset($config['packagist']) && false === $config['packagist']) { + $this->io->writeError('Repository "'.$name.'" ('.json_encode($config).') has a packagist key which should be in its own repository definition'); + } + + $class = $this->repositoryClasses[$type]; + + if (isset($config['only']) || isset($config['exclude']) || isset($config['canonical'])) { + $filterConfig = $config; + unset($config['only'], $config['exclude'], $config['canonical']); + } + + $repository = new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher, $this->process); + + if (isset($filterConfig)) { + $repository = new FilterRepository($repository, $filterConfig); + } + + return $repository; + } + + /** + * Stores repository class for a specific installation type. + * + * @param string $type installation type + * @param class-string $class class name of the repo implementation + */ + public function setRepositoryClass(string $type, $class): void + { + $this->repositoryClasses[$type] = $class; + } + + /** + * Returns all repositories, except local one. + * + * @return RepositoryInterface[] + */ + public function getRepositories(): array + { + return $this->repositories; + } + + /** + * Sets local repository for the project. + * + * @param InstalledRepositoryInterface $repository repository instance + */ + public function setLocalRepository(InstalledRepositoryInterface $repository): void + { + $this->localRepository = $repository; + } + + /** + * Returns local repository for the project. + */ + public function getLocalRepository(): InstalledRepositoryInterface + { + return $this->localRepository; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php b/vendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php new file mode 100644 index 000000000..c4323145c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Thrown when a security problem, like a broken or missing signature + * + * @author Eric Daspet + */ +class RepositorySecurityException extends \Exception +{ +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositorySet.php b/vendor/composer/composer/src/Composer/Repository/RepositorySet.php new file mode 100644 index 000000000..13045b56b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositorySet.php @@ -0,0 +1,436 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\DependencyResolver\PoolOptimizer; +use Composer\DependencyResolver\Pool; +use Composer\DependencyResolver\PoolBuilder; +use Composer\DependencyResolver\Request; +use Composer\DependencyResolver\SecurityAdvisoryPoolFilter; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Advisory\SecurityAdvisory; +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\IO\IOInterface; +use Composer\IO\NullIO; +use Composer\Package\BasePackage; +use Composer\Package\AliasPackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\PackageInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Package\Version\StabilityFilter; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; + +/** + * @author Nils Adermann + * + * @see RepositoryUtils for ways to work with single repos + */ +class RepositorySet +{ + /** + * Packages are returned even though their stability does not match the required stability + */ + public const ALLOW_UNACCEPTABLE_STABILITIES = 1; + /** + * Packages will be looked up in all repositories, even after they have been found in a higher prio one + */ + public const ALLOW_SHADOWED_REPOSITORIES = 2; + + /** + * @var array[] + * @phpstan-var array> + */ + private $rootAliases; + + /** + * @var string[] + * @phpstan-var array + */ + private $rootReferences; + + /** @var RepositoryInterface[] */ + private $repositories = []; + + /** + * @var int[] array of stability => BasePackage::STABILITY_* value + * @phpstan-var array, BasePackage::STABILITY_*> + */ + private $acceptableStabilities; + + /** + * @var int[] array of package name => BasePackage::STABILITY_* value + * @phpstan-var array + */ + private $stabilityFlags; + + /** + * @var ConstraintInterface[] + * @phpstan-var array + */ + private $rootRequires; + + /** + * @var array + */ + private $temporaryConstraints; + + /** @var bool */ + private $locked = false; + /** @var bool */ + private $allowInstalledRepositories = false; + + /** + * In most cases if you are looking to use this class as a way to find packages from repositories + * passing minimumStability is all you need to worry about. The rest is for advanced pool creation including + * aliases, pinned references and other special cases. + * + * @param key-of $minimumStability + * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array $stabilityFlags + * @param array[] $rootAliases + * @phpstan-param list $rootAliases + * @param string[] $rootReferences an array of package name => source reference + * @phpstan-param array $rootReferences + * @param ConstraintInterface[] $rootRequires an array of package name => constraint from the root package + * @phpstan-param array $rootRequires + * @param array $temporaryConstraints Runtime temporary constraints that will be used to filter packages + */ + public function __construct(string $minimumStability = 'stable', array $stabilityFlags = [], array $rootAliases = [], array $rootReferences = [], array $rootRequires = [], array $temporaryConstraints = []) + { + $this->rootAliases = self::getRootAliasesPerPackage($rootAliases); + $this->rootReferences = $rootReferences; + + $this->acceptableStabilities = []; + foreach (BasePackage::STABILITIES as $stability => $value) { + if ($value <= BasePackage::STABILITIES[$minimumStability]) { + $this->acceptableStabilities[$stability] = $value; + } + } + $this->stabilityFlags = $stabilityFlags; + $this->rootRequires = $rootRequires; + foreach ($rootRequires as $name => $constraint) { + if (PlatformRepository::isPlatformPackage($name)) { + unset($this->rootRequires[$name]); + } + } + + $this->temporaryConstraints = $temporaryConstraints; + } + + public function allowInstalledRepositories(bool $allow = true): void + { + $this->allowInstalledRepositories = $allow; + } + + /** + * @return ConstraintInterface[] an array of package name => constraint from the root package, platform requirements excluded + * @phpstan-return array + */ + public function getRootRequires(): array + { + return $this->rootRequires; + } + + /** + * @return array Runtime temporary constraints that will be used to filter packages + */ + public function getTemporaryConstraints(): array + { + return $this->temporaryConstraints; + } + + /** + * Adds a repository to this repository set + * + * The first repos added have a higher priority. As soon as a package is found in any + * repository the search for that package ends, and following repos will not be consulted. + * + * @param RepositoryInterface $repo A package repository + */ + public function addRepository(RepositoryInterface $repo): void + { + if ($this->locked) { + throw new \RuntimeException("Pool has already been created from this repository set, it cannot be modified anymore."); + } + + if ($repo instanceof CompositeRepository) { + $repos = $repo->getRepositories(); + } else { + $repos = [$repo]; + } + + foreach ($repos as $repo) { + $this->repositories[] = $repo; + } + } + + /** + * Find packages providing or matching a name and optionally meeting a constraint in all repositories + * + * Returned in the order of repositories, matching priority + * + * @param int $flags any of the ALLOW_* constants from this class to tweak what is returned + * @return BasePackage[] + */ + public function findPackages(string $name, ?ConstraintInterface $constraint = null, int $flags = 0): array + { + $ignoreStability = ($flags & self::ALLOW_UNACCEPTABLE_STABILITIES) !== 0; + $loadFromAllRepos = ($flags & self::ALLOW_SHADOWED_REPOSITORIES) !== 0; + + $packages = []; + if ($loadFromAllRepos) { + foreach ($this->repositories as $repository) { + $packages[] = $repository->findPackages($name, $constraint) ?: []; + } + } else { + foreach ($this->repositories as $repository) { + $result = $repository->loadPackages([$name => $constraint], $ignoreStability ? BasePackage::STABILITIES : $this->acceptableStabilities, $ignoreStability ? [] : $this->stabilityFlags); + + $packages[] = $result['packages']; + foreach ($result['namesFound'] as $nameFound) { + // avoid loading the same package again from other repositories once it has been found + if ($name === $nameFound) { + break 2; + } + } + } + } + + $candidates = $packages ? array_merge(...$packages) : []; + + // when using loadPackages above (!$loadFromAllRepos) the repos already filter for stability so no need to do it again + if ($ignoreStability || !$loadFromAllRepos) { + return $candidates; + } + + $result = []; + foreach ($candidates as $candidate) { + if ($this->isPackageAcceptable($candidate->getNames(), $candidate->getStability())) { + $result[] = $candidate; + } + } + + return $result; + } + + /** + * @param string[] $packageNames + * @return ($allowPartialAdvisories is true ? array{advisories: array>, unreachableRepos: array} : array{advisories: array>, unreachableRepos: array}) + */ + public function getSecurityAdvisories(array $packageNames, bool $allowPartialAdvisories, bool $ignoreUnreachable = false): array + { + $map = []; + foreach ($packageNames as $name) { + $map[$name] = new MatchAllConstraint(); + } + + $unreachableRepos = []; + $advisories = $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories, $ignoreUnreachable, $unreachableRepos); + + return ['advisories' => $advisories, 'unreachableRepos' => $unreachableRepos]; + } + + /** + * @param PackageInterface[] $packages + * @return ($allowPartialAdvisories is true ? array{advisories: array>, unreachableRepos: array} : array{advisories: array>, unreachableRepos: array}) + */ + public function getMatchingSecurityAdvisories(array $packages, bool $allowPartialAdvisories = false, bool $ignoreUnreachable = false): array + { + $map = []; + foreach ($packages as $package) { + // ignore root alias versions as they are not actual package versions and should not matter when it comes to vulnerabilities + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + continue; + } + if (isset($map[$package->getName()])) { + $map[$package->getName()] = new MultiConstraint([new Constraint('=', $package->getVersion()), $map[$package->getName()]], false); + } else { + $map[$package->getName()] = new Constraint('=', $package->getVersion()); + } + } + + $unreachableRepos = []; + $advisories = $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories, $ignoreUnreachable, $unreachableRepos); + + return ['advisories' => $advisories, 'unreachableRepos' => $unreachableRepos]; + } + + /** + * @param array $packageConstraintMap + * @param array &$unreachableRepos Array to store messages about unreachable repositories + * @return ($allowPartialAdvisories is true ? array> : array>) + */ + private function getSecurityAdvisoriesForConstraints(array $packageConstraintMap, bool $allowPartialAdvisories, bool $ignoreUnreachable = false, array &$unreachableRepos = []): array + { + $repoAdvisories = []; + foreach ($this->repositories as $repository) { + try { + if (!$repository instanceof AdvisoryProviderInterface || !$repository->hasSecurityAdvisories()) { + continue; + } + + $repoAdvisories[] = $repository->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories)['advisories']; + } catch (\Composer\Downloader\TransportException $e) { + if (!$ignoreUnreachable) { + throw $e; + } + $unreachableRepos[] = $e->getMessage(); + } + } + + $advisories = count($repoAdvisories) > 0 ? array_merge_recursive([], ...$repoAdvisories) : []; + ksort($advisories); + + return $advisories; + } + + /** + * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...') + * @phpstan-return array + */ + public function getProviders(string $packageName): array + { + $providers = []; + foreach ($this->repositories as $repository) { + if ($repoProviders = $repository->getProviders($packageName)) { + $providers = array_merge($providers, $repoProviders); + } + } + + return $providers; + } + + /** + * Check for each given package name whether it would be accepted by this RepositorySet in the given $stability + * + * @param string[] $names + * @param key-of $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' + */ + public function isPackageAcceptable(array $names, string $stability): bool + { + return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability); + } + + /** + * Create a pool for dependency resolution from the packages in this repository set. + * + * @param list $ignoredTypes Packages of those types are ignored + * @param list|null $allowedTypes Only packages of those types are allowed if set to non-null + */ + public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $ignoredTypes = [], ?array $allowedTypes = null, ?SecurityAdvisoryPoolFilter $securityAdvisoryPoolFilter = null): Pool + { + $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints, $securityAdvisoryPoolFilter); + $poolBuilder->setIgnoredTypes($ignoredTypes); + $poolBuilder->setAllowedTypes($allowedTypes); + + foreach ($this->repositories as $repo) { + if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) { + throw new \LogicException('The pool can not accept packages from an installed repository'); + } + } + + $this->locked = true; + + return $poolBuilder->buildPool($this->repositories, $request); + } + + /** + * Create a pool for dependency resolution from the packages in this repository set. + */ + public function createPoolWithAllPackages(): Pool + { + foreach ($this->repositories as $repo) { + if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) { + throw new \LogicException('The pool can not accept packages from an installed repository'); + } + } + + $this->locked = true; + + $packages = []; + foreach ($this->repositories as $repository) { + foreach ($repository->getPackages() as $package) { + $packages[] = $package; + + if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) { + $alias = $this->rootAliases[$package->getName()][$package->getVersion()]; + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + if ($package instanceof CompletePackage) { + $aliasPackage = new CompleteAliasPackage($package, $alias['alias_normalized'], $alias['alias']); + } else { + $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); + } + $aliasPackage->setRootPackageAlias(true); + $packages[] = $aliasPackage; + } + } + } + + return new Pool($packages); + } + + public function createPoolForPackage(string $packageName, ?LockArrayRepository $lockedRepo = null): Pool + { + // TODO unify this with above in some simpler version without "request"? + return $this->createPoolForPackages([$packageName], $lockedRepo); + } + + /** + * @param string[] $packageNames + */ + public function createPoolForPackages(array $packageNames, ?LockArrayRepository $lockedRepo = null): Pool + { + $request = new Request($lockedRepo); + + $allowedPackages = []; + foreach ($packageNames as $packageName) { + if (PlatformRepository::isPlatformPackage($packageName)) { + throw new \LogicException('createPoolForPackage(s) can not be used for platform packages, as they are never loaded by the PoolBuilder which expects them to be fixed. Use createPoolWithAllPackages or pass in a proper request with the platform packages you need fixed in it.'); + } + + $request->requireName($packageName); + $allowedPackages[] = strtolower($packageName); + } + + if (count($allowedPackages) > 0) { + $request->restrictPackages($allowedPackages); + } + + return $this->createPool($request, new NullIO()); + } + + /** + * @param array[] $aliases + * @phpstan-param list $aliases + * + * @return array> + */ + private static function getRootAliasesPerPackage(array $aliases): array + { + $normalizedAliases = []; + + foreach ($aliases as $alias) { + $normalizedAliases[$alias['package']][$alias['version']] = [ + 'alias' => $alias['alias'], + 'alias_normalized' => $alias['alias_normalized'], + ]; + } + + return $normalizedAliases; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositoryUtils.php b/vendor/composer/composer/src/Composer/Repository/RepositoryUtils.php new file mode 100644 index 000000000..e6960c63d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositoryUtils.php @@ -0,0 +1,83 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\PackageInterface; + +/** + * @author Jordi Boggiano + * + * @see RepositorySet for ways to work with sets of repos + */ +class RepositoryUtils +{ + /** + * Find all of $packages which are required by $requirer, either directly or transitively + * + * Require-dev is ignored by default, you can enable the require-dev of the initial $requirer + * packages by passing $includeRequireDev=true, but require-dev of transitive dependencies + * are always ignored. + * + * @template T of PackageInterface + * @param array $packages + * @param list $bucket Do not pass this in, only used to avoid recursion with circular deps + * @return list + */ + public static function filterRequiredPackages(array $packages, PackageInterface $requirer, bool $includeRequireDev = false, array $bucket = []): array + { + $requires = $requirer->getRequires(); + if ($includeRequireDev) { + $requires = array_merge($requires, $requirer->getDevRequires()); + } + + foreach ($packages as $candidate) { + foreach ($candidate->getNames() as $name) { + if (isset($requires[$name])) { + if (!in_array($candidate, $bucket, true)) { + $bucket[] = $candidate; + $bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket); + } + break; + } + } + } + + return $bucket; + } + + /** + * Unwraps CompositeRepository, InstalledRepository and optionally FilterRepository to get a flat array of pure repository instances + * + * @return RepositoryInterface[] + */ + public static function flattenRepositories(RepositoryInterface $repo, bool $unwrapFilterRepos = true): array + { + // unwrap filter repos + if ($unwrapFilterRepos && $repo instanceof FilterRepository) { + $repo = $repo->getRepository(); + } + + if (!$repo instanceof CompositeRepository) { + return [$repo]; + } + + $repos = []; + foreach ($repo->getRepositories() as $r) { + foreach (self::flattenRepositories($r, $unwrapFilterRepos) as $r2) { + $repos[] = $r2; + } + } + + return $repos; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RootPackageRepository.php b/vendor/composer/composer/src/Composer/Repository/RootPackageRepository.php new file mode 100644 index 000000000..2e60e3a69 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RootPackageRepository.php @@ -0,0 +1,35 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\RootPackageInterface; + +/** + * Root package repository. + * + * This is used for serving the RootPackage inside an in-memory InstalledRepository + * + * @author Jordi Boggiano + */ +class RootPackageRepository extends ArrayRepository +{ + public function __construct(RootPackageInterface $package) + { + parent::__construct([$package]); + } + + public function getRepoName(): string + { + return 'root package repo'; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/ForgejoDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/ForgejoDriver.php new file mode 100644 index 000000000..18966ebf9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/ForgejoDriver.php @@ -0,0 +1,338 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Cache; +use Composer\Config; +use Composer\Downloader\TransportException; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Util\Forgejo; +use Composer\Util\ForgejoRepositoryData; +use Composer\Util\ForgejoUrl; +use Composer\Util\Http\Response; + +class ForgejoDriver extends VcsDriver +{ + /** @var ForgejoUrl */ + private $forgejoUrl; + /** @var ForgejoRepositoryData */ + private $repositoryData; + + /** @var ?GitDriver */ + protected $gitDriver = null; + /** @var array Map of tag name to identifier */ + private $tags; + /** @var array Map of branch name to identifier */ + private $branches; + + public function initialize(): void + { + $this->forgejoUrl = ForgejoUrl::create($this->url); + $this->originUrl = $this->forgejoUrl->originUrl; + + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->forgejoUrl->owner.'/'.$this->forgejoUrl->repository); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + + $this->fetchRepositoryData(); + } + + public function getFileContent(string $file, string $identifier): ?string + { + if ($this->gitDriver !== null) { + return $this->gitDriver->getFileContent($file, $identifier); + } + + $resource = $this->forgejoUrl->apiUrl.'/contents/' . $file . '?ref='.urlencode($identifier); + $resource = $this->getContents($resource)->decodeJson(); + + // The Forgejo contents API only returns files up to 1MB as base64 encoded files + // larger files either need be fetched with a raw accept header or by using the git blob endpoint + if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) { + $resource = $this->getContents($resource['git_url'])->decodeJson(); + } + + if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || false === ($content = base64_decode($resource['content'], true))) { + throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); + } + + return $content; + } + + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + if ($this->gitDriver !== null) { + return $this->gitDriver->getChangeDate($identifier); + } + + $resource = $this->forgejoUrl->apiUrl.'/git/commits/'.urlencode($identifier).'?verification=false&files=false'; + $commit = $this->getContents($resource)->decodeJson(); + + return new \DateTimeImmutable($commit['commit']['committer']['date']); + } + + public function getRootIdentifier(): string + { + if ($this->gitDriver !== null) { + return $this->gitDriver->getRootIdentifier(); + } + + return $this->repositoryData->defaultBranch; + } + + public function getBranches(): array + { + if ($this->gitDriver !== null) { + return $this->gitDriver->getBranches(); + } + + if (null === $this->branches) { + $branches = []; + $resource = $this->forgejoUrl->apiUrl.'/branches?per_page=100'; + + do { + $response = $this->getContents($resource); + $branchData = $response->decodeJson(); + foreach ($branchData as $branch) { + $branches[$branch['name']] = $branch['commit']['id']; + } + + $resource = $this->getNextPage($response); + } while ($resource); + + $this->branches = $branches; + } + + return $this->branches; + } + + public function getTags(): array + { + if ($this->gitDriver !== null) { + return $this->gitDriver->getTags(); + } + if (null === $this->tags) { + $tags = []; + $resource = $this->forgejoUrl->apiUrl.'/tags?per_page=100'; + + do { + $response = $this->getContents($resource); + $tagsData = $response->decodeJson(); + foreach ($tagsData as $tag) { + $tags[$tag['name']] = $tag['commit']['sha']; + } + + $resource = $this->getNextPage($response); + } while ($resource); + + $this->tags = $tags; + } + + return $this->tags; + } + + public function getDist(string $identifier): ?array + { + $url = $this->forgejoUrl->apiUrl.'/archive/'.$identifier.'.zip'; + + return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; + } + + public function getComposerInformation(string $identifier): ?array + { + if ($this->gitDriver !== null) { + return $this->gitDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && false !== ($res = $this->cache->read($identifier))) { + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); + } + } + + if ($composer !== null) { + // specials for forgejo + if (isset($composer['support']) && !is_array($composer['support'])) { + $composer['support'] = []; + } + if (!isset($composer['support']['source'])) { + if (false !== ($label = array_search($identifier, $this->getTags(), true))) { + $composer['support']['source'] = $this->repositoryData->htmlUrl.'/tag/' . $label; + } elseif (false !== ($label = array_search($identifier, $this->getBranches(), true))) { + $composer['support']['source'] = $this->repositoryData->htmlUrl.'/branch/'.$label; + } else { + $composer['support']['source'] = $this->repositoryData->htmlUrl.'/commit/'.$identifier; + } + } + if (!isset($composer['support']['issues']) && $this->repositoryData->hasIssues) { + $composer['support']['issues'] = $this->repositoryData->htmlUrl.'/issues'; + } + if (!isset($composer['abandoned']) && $this->repositoryData->isArchived) { + $composer['abandoned'] = true; + } + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + public function getSource(string $identifier): array + { + if ($this->gitDriver !== null) { + return $this->gitDriver->getSource($identifier); + } + + return ['type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier]; + } + + public function getUrl(): string + { + if ($this->gitDriver !== null) { + return $this->gitDriver->getUrl(); + } + + return $this->repositoryData->isPrivate ? $this->repositoryData->sshUrl : $this->repositoryData->httpCloneUrl; + } + + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + $forgejoUrl = ForgejoUrl::tryFrom($url); + if ($forgejoUrl === null) { + return false; + } + + if (!in_array(strtolower($forgejoUrl->originUrl), $config->get('forgejo-domains'), true)) { + return false; + } + + if (!extension_loaded('openssl')) { + $io->writeError('Skipping Forgejo driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); + + return false; + } + + return true; + } + + protected function setupGitDriver(string $url): void + { + $this->gitDriver = new GitDriver( + ['url' => $url], + $this->io, + $this->config, + $this->httpDownloader, + $this->process + ); + $this->gitDriver->initialize(); + } + + private function fetchRepositoryData(): void + { + if ($this->repositoryData !== null) { + return; + } + + $data = $this->getContents($this->forgejoUrl->apiUrl, true)->decodeJson(); + + if (null === $data && null !== $this->gitDriver) { + return; + } + + $this->repositoryData = ForgejoRepositoryData::fromRemoteData($data); + } + + protected function getNextPage(Response $response): ?string + { + $header = $response->getHeader('link'); + if ($header === null) { + return null; + } + + $links = explode(',', $header); + foreach ($links as $link) { + if (Preg::isMatch('{<(.+?)>; *rel="next"}', $link, $match)) { + return $match[1]; + } + } + + return null; + } + + protected function getContents(string $url, bool $fetchingRepoData = false): Response + { + $forgejo = new Forgejo($this->io, $this->config, $this->httpDownloader); + + try { + return parent::getContents($url); + } catch (TransportException $e) { + switch ($e->getCode()) { + case 401: + case 403: + case 404: + case 429: + if (!$fetchingRepoData) { + throw $e; + } + + if (!$this->io->isInteractive()) { + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + + if ( + !$this->io->hasAuthentication($this->originUrl) && + $forgejo->authorizeOAuthInteractively($this->forgejoUrl->originUrl, $e->getCode() === 429 ? 'API limit exhausted. Enter your Forgejo credentials to get a larger API limit ('.$this->url.')' : null) + ) { + return parent::getContents($url); + } + + throw $e; + default: + throw $e; + } + } + } + + /** + * @phpstan-impure + * + * @return true + * @throws \RuntimeException + */ + protected function attemptCloneFallback(): bool + { + try { + // If this repository may be private (hard to say for sure, + // Forgejo returns 404 for private repositories) and we + // cannot ask for authentication credentials (because we + // are not interactive) then we fallback to GitDriver. + $this->setupGitDriver($this->forgejoUrl->generateSshUrl()); + + return true; + } catch (\RuntimeException $e) { + $this->gitDriver = null; + + $this->io->writeError('Failed to clone the '.$this->forgejoUrl->generateSshUrl().' repository, try running in interactive mode so that you can enter your Forgejo credentials'); + throw $e; + } + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php new file mode 100644 index 000000000..8a305216c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php @@ -0,0 +1,248 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Cache; +use Composer\Config; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\IO\IOInterface; + +/** + * @author BohwaZ + */ +class FossilDriver extends VcsDriver +{ + /** @var array Map of tag name to identifier */ + protected $tags; + /** @var array Map of branch name to identifier */ + protected $branches; + /** @var ?string */ + protected $rootIdentifier = null; + /** @var ?string */ + protected $repoFile = null; + /** @var string */ + protected $checkoutDir; + + /** + * @inheritDoc + */ + public function initialize(): void + { + // Make sure fossil is installed and reachable. + $this->checkFossil(); + + // Ensure we are allowed to use this URL by config. + $this->config->prohibitUrlByConfig($this->url, $this->io); + + // Only if url points to a locally accessible directory, assume it's the checkout directory. + // Otherwise, it should be something fossil can clone from. + if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) { + $this->checkoutDir = $this->url; + } else { + if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + + $localName = Preg::replace('{[^a-z0-9]}i', '-', $this->url); + $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil'; + $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/'; + + $this->updateLocalRepo(); + } + + $this->getTags(); + $this->getBranches(); + } + + /** + * Check that fossil can be invoked via command line. + */ + protected function checkFossil(): void + { + if (0 !== $this->process->execute(['fossil', 'version'], $ignoredOutput)) { + throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput()); + } + } + + /** + * Clone or update existing local fossil repository. + */ + protected function updateLocalRepo(): void + { + assert($this->repoFile !== null); + + $fs = new Filesystem(); + $fs->ensureDirectoryExists($this->checkoutDir); + + if (!is_writable(dirname($this->checkoutDir))) { + throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$this->checkoutDir.'" directory is not writable by the current user.'); + } + + // update the repo if it is a valid fossil repository + if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute(['fossil', 'info'], $output, $this->checkoutDir)) { + if (0 !== $this->process->execute(['fossil', 'pull'], $output, $this->checkoutDir)) { + $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); + } + } else { + // clean up directory and do a fresh clone into it + $fs->removeDirectory($this->checkoutDir); + $fs->remove($this->repoFile); + + $fs->ensureDirectoryExists($this->checkoutDir); + + if (0 !== $this->process->execute(['fossil', 'clone', '--', $this->url, $this->repoFile], $output)) { + $output = $this->process->getErrorOutput(); + + throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output); + } + + if (0 !== $this->process->execute(['fossil', 'open', '--nested', '--', $this->repoFile], $output, $this->checkoutDir)) { + $output = $this->process->getErrorOutput(); + + throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output); + } + } + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if (null === $this->rootIdentifier) { + $this->rootIdentifier = 'trunk'; + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return ['type' => 'fossil', 'url' => $this->getUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + $this->process->execute(['fossil', 'cat', '-r', $identifier, '--', $file], $content, $this->checkoutDir); + + if ('' === trim($content)) { + return null; + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + $this->process->execute(['fossil', 'finfo', '-b', '-n', '1', 'composer.json'], $output, $this->checkoutDir); + [, $date] = explode(' ', trim($output), 3); + + return new \DateTimeImmutable($date, new \DateTimeZone('UTC')); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if (null === $this->tags) { + $tags = []; + + $this->process->execute(['fossil', 'tag', 'list'], $output, $this->checkoutDir); + foreach ($this->process->splitLines($output) as $tag) { + $tags[$tag] = $tag; + } + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if (null === $this->branches) { + $branches = []; + + $this->process->execute(['fossil', 'branch', 'list'], $output, $this->checkoutDir); + foreach ($this->process->splitLines($output) as $branch) { + $branch = trim(Preg::replace('/^\*/', '', trim($branch))); + $branches[$branch] = $branch; + } + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\.com|fossil\.))#i', $url)) { + return true; + } + + if (Preg::isMatch('!/fossil/|\.fossil!', $url)) { + return true; + } + + // local filesystem + if (Filesystem::isLocalPath($url)) { + $url = Filesystem::getPlatformPath($url); + if (!is_dir($url)) { + return false; + } + + $process = new ProcessExecutor($io); + // check whether there is a fossil repo in that path + if ($process->execute(['fossil', 'info'], $output, $url) === 0) { + return true; + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php new file mode 100644 index 000000000..f62056dab --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -0,0 +1,522 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Cache; +use Composer\Downloader\TransportException; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Util\Bitbucket; +use Composer\Util\Http\Response; + +/** + * @author Per Bernhardt + */ +class GitBitbucketDriver extends VcsDriver +{ + /** @var string */ + protected $owner; + /** @var string */ + protected $repository; + /** @var bool */ + private $hasIssues = false; + /** @var ?string */ + private $rootIdentifier; + /** @var array Map of tag name to identifier */ + private $tags; + /** @var array Map of branch name to identifier */ + private $branches; + /** @var string */ + private $branchesUrl = ''; + /** @var string */ + private $tagsUrl = ''; + /** @var string */ + private $homeUrl = ''; + /** @var string */ + private $website = ''; + /** @var string */ + private $cloneHttpsUrl = ''; + /** @var array */ + private $repoData; + + /** + * @var ?VcsDriver + */ + protected $fallbackDriver = null; + /** @var string|null if set either git or hg */ + private $vcsType; + + /** + * @inheritDoc + */ + public function initialize(): void + { + if (!Preg::isMatchStrictGroups('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(?:\.git|/?)?$#i', $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.', $this->url)); + } + + $this->owner = $match[1]; + $this->repository = $match[2]; + $this->originUrl = 'bitbucket.org'; + $this->cache = new Cache( + $this->io, + implode('/', [ + $this->config->get('cache-repo-dir'), + $this->originUrl, + $this->owner, + $this->repository, + ]) + ); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getUrl(); + } + + return $this->cloneHttpsUrl; + } + + /** + * Attempts to fetch the repository data via the BitBucket API and + * sets some parameters which are used in other methods + * + * @phpstan-impure + */ + protected function getRepoData(): bool + { + $resource = sprintf( + 'https://api.bitbucket.org/2.0/repositories/%s/%s?%s', + $this->owner, + $this->repository, + http_build_query( + ['fields' => '-project,-owner'], + '', + '&' + ) + ); + + $repoData = $this->fetchWithOAuthCredentials($resource, true)->decodeJson(); + if ($this->fallbackDriver) { + return false; + } + $this->parseCloneUrls($repoData['links']['clone']); + + $this->hasIssues = !empty($repoData['has_issues']); + $this->branchesUrl = $repoData['links']['branches']['href']; + $this->tagsUrl = $repoData['links']['tags']['href']; + $this->homeUrl = $repoData['links']['html']['href']; + $this->website = $repoData['website']; + $this->vcsType = $repoData['scm']; + + $this->repoData = $repoData; + + return true; + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); + } + } + + if ($composer !== null) { + // specials for bitbucket + if (isset($composer['support']) && !is_array($composer['support'])) { + $composer['support'] = []; + } + if (!isset($composer['support']['source'])) { + $label = array_search( + $identifier, + $this->getTags() + ) ?: array_search( + $identifier, + $this->getBranches() + ) ?: $identifier; + + if (array_key_exists($label, $tags = $this->getTags())) { + $hash = $tags[$label]; + } elseif (array_key_exists($label, $branches = $this->getBranches())) { + $hash = $branches[$label]; + } + + if (!isset($hash)) { + $composer['support']['source'] = sprintf( + 'https://%s/%s/%s/src', + $this->originUrl, + $this->owner, + $this->repository + ); + } else { + $composer['support']['source'] = sprintf( + 'https://%s/%s/%s/src/%s/?at=%s', + $this->originUrl, + $this->owner, + $this->repository, + $hash, + $label + ); + } + } + if (!isset($composer['support']['issues']) && $this->hasIssues) { + $composer['support']['issues'] = sprintf( + 'https://%s/%s/%s/issues', + $this->originUrl, + $this->owner, + $this->repository + ); + } + if (!isset($composer['homepage'])) { + $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website; + } + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getFileContent($file, $identifier); + } + + if (strpos($identifier, '/') !== false) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + + $resource = sprintf( + 'https://api.bitbucket.org/2.0/repositories/%s/%s/src/%s/%s', + $this->owner, + $this->repository, + $identifier, + $file + ); + + return $this->fetchWithOAuthCredentials($resource)->getBody(); + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getChangeDate($identifier); + } + + if (strpos($identifier, '/') !== false) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + + $resource = sprintf( + 'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date', + $this->owner, + $this->repository, + $identifier + ); + $commit = $this->fetchWithOAuthCredentials($resource)->decodeJson(); + + return new \DateTimeImmutable($commit['date']); + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getSource($identifier); + } + + return ['type' => $this->vcsType, 'url' => $this->getUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getDist($identifier); + } + + $url = sprintf( + 'https://bitbucket.org/%s/%s/get/%s.zip', + $this->owner, + $this->repository, + $identifier + ); + + return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getTags(); + } + + if (null === $this->tags) { + $tags = []; + $resource = sprintf( + '%s?%s', + $this->tagsUrl, + http_build_query( + [ + 'pagelen' => 100, + 'fields' => 'values.name,values.target.hash,next', + 'sort' => '-target.date', + ], + '', + '&' + ) + ); + $hasNext = true; + while ($hasNext) { + $tagsData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); + foreach ($tagsData['values'] as $data) { + $tags[$data['name']] = $data['target']['hash']; + } + if (empty($tagsData['next'])) { + $hasNext = false; + } else { + $resource = $tagsData['next']; + } + } + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getBranches(); + } + + if (null === $this->branches) { + $branches = []; + $resource = sprintf( + '%s?%s', + $this->branchesUrl, + http_build_query( + [ + 'pagelen' => 100, + 'fields' => 'values.name,values.target.hash,values.heads,next', + 'sort' => '-target.date', + ], + '', + '&' + ) + ); + $hasNext = true; + while ($hasNext) { + $branchData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); + foreach ($branchData['values'] as $data) { + $branches[$data['name']] = $data['target']['hash']; + } + if (empty($branchData['next'])) { + $hasNext = false; + } else { + $resource = $branchData['next']; + } + } + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * Get the remote content. + * + * @param string $url The URL of content + * + * @return Response The result + * + * @phpstan-impure + */ + protected function fetchWithOAuthCredentials(string $url, bool $fetchingRepoData = false): Response + { + try { + return parent::getContents($url); + } catch (TransportException $e) { + $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader); + + if (in_array($e->getCode(), [403, 404], true) || (401 === $e->getCode() && strpos($e->getMessage(), 'Could not authenticate against') === 0)) { + if (!$this->io->hasAuthentication($this->originUrl) + && $bitbucketUtil->authorizeOAuth($this->originUrl) + ) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive() && $fetchingRepoData) { + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + } + + throw $e; + } + } + + /** + * Generate an SSH URL + */ + protected function generateSshUrl(): string + { + return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; + } + + /** + * @phpstan-impure + * + * @return true + * @throws \RuntimeException + */ + protected function attemptCloneFallback(): bool + { + try { + $this->setupFallbackDriver($this->generateSshUrl()); + + return true; + } catch (\RuntimeException $e) { + $this->fallbackDriver = null; + + $this->io->writeError( + 'Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode' + . ' so that you can enter your Bitbucket OAuth consumer credentials' + ); + throw $e; + } + } + + protected function setupFallbackDriver(string $url): void + { + $this->fallbackDriver = new GitDriver( + ['url' => $url], + $this->io, + $this->config, + $this->httpDownloader, + $this->process + ); + $this->fallbackDriver->initialize(); + } + + /** + * @param array $cloneLinks + */ + protected function parseCloneUrls(array $cloneLinks): void + { + foreach ($cloneLinks as $cloneLink) { + if ($cloneLink['name'] === 'https') { + // Format: https://(user@)bitbucket.org/{user}/{repo} + // Strip username from URL (only present in clone URL's for private repositories) + $this->cloneHttpsUrl = Preg::replace('/https:\/\/([^@]+@)?/', 'https://', $cloneLink['href']); + } + } + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getRootIdentifier(); + } + + if (null === $this->rootIdentifier) { + if (!$this->getRepoData()) { + if (!$this->fallbackDriver) { + throw new \LogicException('A fallback driver should be setup if getRepoData returns false'); + } + + return $this->fallbackDriver->getRootIdentifier(); + } + + if ($this->vcsType !== 'git') { + throw new \RuntimeException( + $this->url.' does not appear to be a git repository, use '. + $this->cloneHttpsUrl.' but remember that Bitbucket no longer supports the mercurial repositories. '. + 'https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket' + ); + } + + $this->rootIdentifier = $this->repoData['mainbranch']['name'] ?? 'master'; + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (!Preg::isMatch('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i', $url)) { + return false; + } + + if (!extension_loaded('openssl')) { + $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); + + return false; + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php new file mode 100644 index 000000000..3cd3b178b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php @@ -0,0 +1,255 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\Util\Url; +use Composer\Util\Git as GitUtil; +use Composer\IO\IOInterface; +use Composer\Cache; +use Composer\Config; + +/** + * @author Jordi Boggiano + */ +class GitDriver extends VcsDriver +{ + /** @var array Map of tag name (can be turned to an int by php if it is a numeric name) to identifier */ + protected $tags; + /** @var array Map of branch name (can be turned to an int by php if it is a numeric name) to identifier */ + protected $branches; + /** @var string */ + protected $rootIdentifier; + /** @var string */ + protected $repoDir; + + /** + * @inheritDoc + */ + public function initialize(): void + { + if (Filesystem::isLocalPath($this->url)) { + $this->url = Preg::replace('{[\\/]\.git/?$}', '', $this->url); + if (!is_dir($this->url)) { + throw new \RuntimeException('Failed to read package information from '.$this->url.' as the path does not exist'); + } + $this->repoDir = $this->url; + $cacheUrl = realpath($this->url); + } else { + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + + $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)) . '/'; + + GitUtil::cleanEnv($this->process); + + $fs = new Filesystem(); + $fs->ensureDirectoryExists(dirname($this->repoDir)); + + if (!is_writable(dirname($this->repoDir))) { + throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.dirname($this->repoDir).'" directory is not writable by the current user.'); + } + + if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) { + throw new \InvalidArgumentException('The source URL '.$this->url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); + } + + $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs); + if (!$gitUtil->syncMirror($this->url, $this->repoDir)) { + if (!is_dir($this->repoDir)) { + throw new \RuntimeException('Failed to clone '.$this->url.' to read package information from it'); + } + $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated'); + } + + $cacheUrl = $this->url; + } + + $this->getTags(); + $this->getBranches(); + + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($cacheUrl))); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if (null === $this->rootIdentifier) { + $this->rootIdentifier = 'master'; + + $gitUtil = new GitUtil($this->io, $this->config, $this->process, new Filesystem()); + if (!Filesystem::isLocalPath($this->url)) { + $defaultBranch = $gitUtil->getMirrorDefaultBranch($this->url, $this->repoDir, false); + if ($defaultBranch !== null) { + return $this->rootIdentifier = $defaultBranch; + } + } + + // select currently checked out branch as default branch + $this->process->execute(['git', 'branch', '--no-color'], $output, $this->repoDir); + $branches = $this->process->splitLines($output); + if (!in_array('* master', $branches)) { + foreach ($branches as $branch) { + if ($branch && Preg::isMatchStrictGroups('{^\* +(\S+)}', $branch, $match)) { + $this->rootIdentifier = $match[1]; + break; + } + } + } + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return ['type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if (isset($identifier[0]) && $identifier[0] === '-') { + throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier); + } + + $this->process->execute(['git', 'show', $identifier.':'.$file], $content, $this->repoDir); + + if (trim($content) === '') { + return null; + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + $command = GitUtil::buildRevListCommand($this->process, ['-n1', '--format=%at', $identifier]); + $this->process->execute($command, $output, $this->repoDir); + + return new \DateTimeImmutable('@'.trim(GitUtil::parseRevListOutput($output, $this->process)), new \DateTimeZone('UTC')); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if (null === $this->tags) { + $this->tags = []; + + $this->process->execute(['git', 'show-ref', '--tags', '--dereference'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $tag) { + if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) { + $this->tags[$match[2]] = $match[1]; + } + } + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if (null === $this->branches) { + $branches = []; + + $this->process->execute(['git', 'branch', '--no-color', '--no-abbrev', '-v'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $branch) { + if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { + if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') { + $branches[$match[1]] = $match[2]; + } + } + } + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (Preg::isMatch('#(^git://|\.git/?$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) { + return true; + } + + // local filesystem + if (Filesystem::isLocalPath($url)) { + $url = Filesystem::getPlatformPath($url); + if (!is_dir($url)) { + return false; + } + + $process = new ProcessExecutor($io); + // check whether there is a git repo in that path + if ($process->execute(['git', 'tag'], $output, $url) === 0) { + return true; + } + GitUtil::checkForRepoOwnershipError($process->getErrorOutput(), $url); + } + + if (!$deep) { + return false; + } + + $process = new ProcessExecutor($io); + $gitUtil = new GitUtil($io, $config, $process, new Filesystem()); + GitUtil::cleanEnv($process); + + try { + $gitUtil->runCommands([['git', 'ls-remote', '--heads', '--', '%url%']], $url, sys_get_temp_dir()); + } catch (\RuntimeException $e) { + return false; + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php new file mode 100644 index 000000000..1b857d5b7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php @@ -0,0 +1,662 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Downloader\TransportException; +use Composer\Json\JsonFile; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\GitHub; +use Composer\Util\Http\Response; + +/** + * @author Jordi Boggiano + */ +class GitHubDriver extends VcsDriver +{ + /** @var string */ + protected $owner; + /** @var string */ + protected $repository; + /** @var array Map of tag name to identifier */ + protected $tags; + /** @var array Map of branch name to identifier */ + protected $branches; + /** @var string */ + protected $rootIdentifier; + /** @var mixed[] */ + protected $repoData; + /** @var bool */ + protected $hasIssues = false; + /** @var bool */ + protected $isPrivate = false; + /** @var bool */ + private $isArchived = false; + /** @var array|false|null */ + private $fundingInfo; + /** @var bool */ + private $allowGitFallback = true; + + /** + * Git Driver + * + * @var ?GitDriver + */ + protected $gitDriver = null; + + /** + * @inheritDoc + */ + public function initialize(): void + { + if (!Preg::isMatch('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#', $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The GitHub repository URL %s is invalid.', $this->url)); + } + + $this->owner = $match[3]; + $this->repository = $match[4]; + $this->originUrl = strtolower($match[1] ?? (string) $match[2]); + if ($this->originUrl === 'www.github.com') { + $this->originUrl = 'github.com'; + } + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + + if (isset($this->repoConfig['allow-git-fallback']) && $this->repoConfig['allow-git-fallback'] === false) { + $this->allowGitFallback = false; + } + + if ($this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'])) { + $this->setupGitDriver($this->url); + + return; + } + + $this->fetchRootIdentifier(); + } + + public function getRepositoryUrl(): string + { + return 'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository; + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if ($this->gitDriver) { + return $this->gitDriver->getRootIdentifier(); + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + if ($this->gitDriver) { + return $this->gitDriver->getUrl(); + } + + return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + } + + protected function getApiUrl(): string + { + if ('github.com' === $this->originUrl) { + $apiUrl = 'api.github.com'; + } else { + $apiUrl = $this->originUrl . '/api/v3'; + } + + return 'https://' . $apiUrl; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + if ($this->gitDriver) { + return $this->gitDriver->getSource($identifier); + } + if ($this->isPrivate) { + // Private GitHub repositories should be accessed using the + // SSH version of the URL. + $url = $this->generateSshUrl(); + } else { + $url = $this->getUrl(); + } + + return ['type' => 'git', 'url' => $url, 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier; + + return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if ($this->gitDriver) { + return $this->gitDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); + } + } + + if ($composer !== null) { + // specials for github + if (isset($composer['support']) && !is_array($composer['support'])) { + $composer['support'] = []; + } + if (!isset($composer['support']['source'])) { + $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier; + $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label); + } + if (!isset($composer['support']['issues']) && $this->hasIssues) { + $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); + } + if (!isset($composer['abandoned']) && $this->isArchived) { + $composer['abandoned'] = true; + } + if (!isset($composer['funding']) && $funding = $this->getFundingInfo()) { + $composer['funding'] = $funding; + } + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + /** + * @return array|false + */ + private function getFundingInfo() + { + if (null !== $this->fundingInfo) { + return $this->fundingInfo; + } + + if ($this->originUrl !== 'github.com') { + return $this->fundingInfo = false; + } + + foreach ([$this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/.github/FUNDING.yml', $this->getApiUrl() . '/repos/'.$this->owner.'/.github/contents/FUNDING.yml'] as $file) { + try { + $response = $this->httpDownloader->get($file, [ + 'retry-auth-failure' => false, + ])->decodeJson(); + } catch (TransportException $e) { + continue; + } + if (empty($response['content']) || $response['encoding'] !== 'base64' || !($funding = base64_decode($response['content']))) { + continue; + } + break; + } + if (empty($funding)) { + return $this->fundingInfo = false; + } + + $result = []; + $key = null; + foreach (Preg::split('{\r?\n}', $funding) as $line) { + $line = trim($line); + if (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*(.+)$}', $line, $match)) { + if ($match[2] === '[') { + $key = $match[1]; + continue; + } + if (Preg::isMatchStrictGroups('{^\[(.*?)\](?:\s*#.*)?$}', $match[2], $match2)) { + foreach (array_map('trim', Preg::split('{[\'"]?\s*,\s*[\'"]?}', $match2[1])) as $item) { + $result[] = ['type' => $match[1], 'url' => trim($item, '"\' ')]; + } + } elseif (Preg::isMatchStrictGroups('{^([^#].*?)(?:\s+#.*)?$}', $match[2], $match2)) { + $result[] = ['type' => $match[1], 'url' => trim($match2[1], '"\' ')]; + } + $key = null; + } elseif (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*#\s*$}', $line, $match)) { + $key = $match[1]; + } elseif ($key !== null && ( + Preg::isMatchStrictGroups('{^-\s*(.+)(?:\s+#.*)?$}', $line, $match) + || Preg::isMatchStrictGroups('{^(.+),(?:\s*#.*)?$}', $line, $match) + )) { + $result[] = ['type' => $key, 'url' => trim($match[1], '"\' ')]; + } elseif ($key !== null && $line === ']') { + $key = null; + } + } + + foreach ($result as $key => $item) { + switch ($item['type']) { + case 'community_bridge': + $result[$key]['url'] = 'https://funding.communitybridge.org/projects/' . basename($item['url']); + break; + case 'github': + $result[$key]['url'] = 'https://github.com/' . basename($item['url']); + break; + case 'issuehunt': + $result[$key]['url'] = 'https://issuehunt.io/r/' . $item['url']; + break; + case 'ko_fi': + $result[$key]['url'] = 'https://ko-fi.com/' . basename($item['url']); + break; + case 'liberapay': + $result[$key]['url'] = 'https://liberapay.com/' . basename($item['url']); + break; + case 'open_collective': + $result[$key]['url'] = 'https://opencollective.com/' . basename($item['url']); + break; + case 'patreon': + $result[$key]['url'] = 'https://www.patreon.com/' . basename($item['url']); + break; + case 'tidelift': + $result[$key]['url'] = 'https://tidelift.com/funding/github/' . $item['url']; + break; + case 'polar': + $result[$key]['url'] = 'https://polar.sh/' . basename($item['url']); + break; + case 'buy_me_a_coffee': + $result[$key]['url'] = 'https://www.buymeacoffee.com/' . basename($item['url']); + break; + case 'thanks_dev': + $result[$key]['url'] = 'https://thanks.dev/' . $item['url']; + break; + case 'otechie': + $result[$key]['url'] = 'https://otechie.com/' . basename($item['url']); + break; + case 'custom': + $bits = parse_url($item['url']); + if ($bits === false) { + unset($result[$key]); + break; + } + + if (!array_key_exists('scheme', $bits) && !array_key_exists('host', $bits)) { + if (Preg::isMatch('{^[a-z0-9-]++\.[a-z]{2,3}$}', $item['url'])) { + $result[$key]['url'] = 'https://'.$item['url']; + break; + } + + $this->io->writeError('Funding URL '.$item['url'].' not in a supported format.'); + unset($result[$key]); + break; + } + break; + } + } + + return $this->fundingInfo = $result; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if ($this->gitDriver) { + return $this->gitDriver->getFileContent($file, $identifier); + } + + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); + $resource = $this->getContents($resource)->decodeJson(); + + // The GitHub contents API only returns files up to 1MB as base64 encoded files + // larger files either need be fetched with a raw accept header or by using the git blob endpoint + if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) { + $resource = $this->getContents($resource['git_url'])->decodeJson(); + } + + if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || false === ($content = base64_decode($resource['content']))) { + throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + if ($this->gitDriver) { + return $this->gitDriver->getChangeDate($identifier); + } + + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier); + $commit = $this->getContents($resource)->decodeJson(); + + return new \DateTimeImmutable($commit['commit']['committer']['date']); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if ($this->gitDriver) { + return $this->gitDriver->getTags(); + } + if (null === $this->tags) { + $tags = []; + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100'; + + do { + $response = $this->getContents($resource); + $tagsData = $response->decodeJson(); + foreach ($tagsData as $tag) { + $tags[$tag['name']] = $tag['commit']['sha']; + } + + $resource = $this->getNextPage($response); + } while ($resource); + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if ($this->gitDriver) { + return $this->gitDriver->getBranches(); + } + if (null === $this->branches) { + $branches = []; + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100'; + + do { + $response = $this->getContents($resource); + $branchData = $response->decodeJson(); + foreach ($branchData as $branch) { + $name = substr($branch['ref'], 11); + if ($name !== 'gh-pages') { + $branches[$name] = $branch['object']['sha']; + } + } + + $resource = $this->getNextPage($response); + } while ($resource); + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (!Preg::isMatch('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#', $url, $matches)) { + return false; + } + + $originUrl = $matches[2] ?? (string) $matches[3]; + if (!in_array(strtolower(Preg::replace('{^www\.}i', '', $originUrl)), $config->get('github-domains'))) { + return false; + } + + if (!extension_loaded('openssl')) { + $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); + + return false; + } + + return true; + } + + /** + * Gives back the loaded /repos// result + * + * @return mixed[]|null + */ + public function getRepoData(): ?array + { + $this->fetchRootIdentifier(); + + return $this->repoData; + } + + /** + * Generate an SSH URL + */ + protected function generateSshUrl(): string + { + if (false !== strpos($this->originUrl, ':')) { + return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + } + + return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; + } + + /** + * @inheritDoc + */ + protected function getContents(string $url, bool $fetchingRepoData = false): Response + { + try { + return parent::getContents($url); + } catch (TransportException $e) { + $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->httpDownloader); + + switch ($e->getCode()) { + case 401: + case 404: + // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 + if (!$fetchingRepoData) { + throw $e; + } + + if ($gitHubUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive()) { + $this->attemptCloneFallback($e); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + + $scopesIssued = []; + $scopesNeeded = []; + if ($headers = $e->getHeaders()) { + if ($scopes = Response::findHeaderValue($headers, 'X-OAuth-Scopes')) { + $scopesIssued = explode(' ', $scopes); + } + if ($scopes = Response::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { + $scopesNeeded = explode(' ', $scopes); + } + } + $scopesFailed = array_diff($scopesNeeded, $scopesIssued); + // non-authenticated requests get no scopesNeeded, so ask for credentials + // authenticated requests which failed some scopes should ask for new credentials too + if (!$headers || !count($scopesNeeded) || count($scopesFailed)) { + $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata ('.$this->url.')'); + } + + return parent::getContents($url); + + case 403: + if (!$this->io->hasAuthentication($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive() && $fetchingRepoData) { + $this->attemptCloneFallback($e); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + + $rateLimited = $gitHubUtil->isRateLimited((array) $e->getHeaders()); + + if (!$this->io->hasAuthentication($this->originUrl)) { + if (!$this->io->isInteractive()) { + $this->io->writeError('GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit'); + throw $e; + } + + $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit ('.$this->url.')'); + + return parent::getContents($url); + } + + if ($rateLimited) { + $rateLimit = $gitHubUtil->getRateLimit($e->getHeaders()); + $this->io->writeError(sprintf( + 'GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests', + $rateLimit['limit'], + $rateLimit['reset'] + )); + } + + throw $e; + + default: + throw $e; + } + } + } + + /** + * Fetch root identifier from GitHub + * + * @throws TransportException + */ + protected function fetchRootIdentifier(): void + { + if ($this->repoData) { + return; + } + + $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository; + + try { + $this->repoData = $this->getContents($repoDataUrl, true)->decodeJson(); + } catch (TransportException $e) { + if ($e->getCode() === 499) { + $this->attemptCloneFallback($e); + } else { + throw $e; + } + } + if (null === $this->repoData && null !== $this->gitDriver) { + return; + } + + $this->owner = $this->repoData['owner']['login']; + $this->repository = $this->repoData['name']; + + $this->isPrivate = !empty($this->repoData['private']); + if (isset($this->repoData['default_branch'])) { + $this->rootIdentifier = $this->repoData['default_branch']; + } elseif (isset($this->repoData['master_branch'])) { + $this->rootIdentifier = $this->repoData['master_branch']; + } else { + $this->rootIdentifier = 'master'; + } + $this->hasIssues = !empty($this->repoData['has_issues']); + $this->isArchived = !empty($this->repoData['archived']); + } + + /** + * @phpstan-impure + * + * @return true + * @throws \RuntimeException + */ + protected function attemptCloneFallback(?\Throwable $e = null): bool + { + if (!$this->allowGitFallback) { + throw new \RuntimeException('Fallback to git driver disabled', 0, $e); + } + + $this->isPrivate = true; + + try { + // If this repository may be private (hard to say for sure, + // GitHub returns 404 for private repositories) and we + // cannot ask for authentication credentials (because we + // are not interactive) then we fallback to GitDriver. + $this->setupGitDriver($this->generateSshUrl()); + + return true; + } catch (\RuntimeException $e) { + $this->gitDriver = null; + + $this->io->writeError('Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials'); + throw $e; + } + } + + protected function setupGitDriver(string $url): void + { + if (!$this->allowGitFallback) { + throw new \RuntimeException('Fallback to git driver disabled'); + } + $this->gitDriver = new GitDriver( + ['url' => $url], + $this->io, + $this->config, + $this->httpDownloader, + $this->process + ); + $this->gitDriver->initialize(); + } + + protected function getNextPage(Response $response): ?string + { + $header = $response->getHeader('link'); + if (!$header) { + return null; + } + + $links = explode(',', $header); + foreach ($links as $link) { + if (Preg::isMatch('{<(.+?)>; *rel="next"}', $link, $match)) { + return $match[1]; + } + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php new file mode 100644 index 000000000..0f6c55a31 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php @@ -0,0 +1,642 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Util\HttpDownloader; +use Composer\Util\GitLab; +use Composer\Util\Http\Response; + +/** + * Driver for GitLab API, use the Git driver for local checkouts. + * + * @author Henrik Bjørnskov + * @author Jérôme Tamarelle + */ +class GitLabDriver extends VcsDriver +{ + /** + * @var string + * @phpstan-var 'https'|'http' + */ + private $scheme; + /** @var string */ + private $namespace; + /** @var string */ + private $repository; + + /** + * @var mixed[] Project data returned by GitLab API + */ + private $project = null; + + /** + * @var array Keeps commits returned by GitLab API as commit id => info + */ + private $commits = []; + + /** @var array Map of tag name to identifier */ + private $tags; + + /** @var array Map of branch name to identifier */ + private $branches; + + /** + * Git Driver + * + * @var ?GitDriver + */ + protected $gitDriver = null; + + /** + * Protocol to force use of for repository URLs. + * + * @var string One of ssh, http + */ + protected $protocol; + + /** + * Defaults to true unless we can make sure it is public + * + * @var bool defines whether the repo is private or not + */ + private $isPrivate = true; + + /** + * @var bool true if the origin has a port number or a path component in it + */ + private $hasNonstandardOrigin = false; + + public const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)(?::(?P[0-9]+))?/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\.git|/)?$#'; + + /** + * Extracts information from the repository url. + * + * SSH urls use https by default. Set "secure-http": false on the repository config to use http instead. + * + * @inheritDoc + */ + public function initialize(): void + { + if (!Preg::isMatch(self::URL_REGEX, $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', $this->url)); + } + + $guessedDomain = $match['domain'] ?? (string) $match['domain2']; + $configuredDomains = $this->config->get('gitlab-domains'); + $urlParts = explode('/', $match['parts']); + + $this->scheme = in_array($match['scheme'], ['https', 'http'], true) + ? $match['scheme'] + : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https') + ; + $origin = self::determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']); + if (false === $origin) { + throw new \LogicException('It should not be possible to create a gitlab driver with an unparsable origin URL ('.$this->url.')'); + } + $this->originUrl = $origin; + + if (is_string($protocol = $this->config->get('gitlab-protocol'))) { + // https treated as a synonym for http. + if (!in_array($protocol, ['git', 'http', 'https'], true)) { + throw new \RuntimeException('gitlab-protocol must be one of git, http.'); + } + $this->protocol = $protocol === 'git' ? 'ssh' : 'http'; + } + + if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) { + $this->hasNonstandardOrigin = true; + } + + $this->namespace = implode('/', $urlParts); + $this->repository = Preg::replace('#(\.git)$#', '', $match['repo']); + + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + + $this->fetchProject(); + } + + /** + * Updates the HttpDownloader instance. + * Mainly useful for tests. + * + * @internal + */ + public function setHttpDownloader(HttpDownloader $httpDownloader): void + { + $this->httpDownloader = $httpDownloader; + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if ($this->gitDriver) { + return $this->gitDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); + } + } + + if (null !== $composer) { + // specials for gitlab (this data is only available if authentication is provided) + if (isset($composer['support']) && !is_array($composer['support'])) { + $composer['support'] = []; + } + if (!isset($composer['support']['source']) && isset($this->project['web_url'])) { + $label = array_search($identifier, $this->getTags(), true) ?: array_search($identifier, $this->getBranches(), true) ?: $identifier; + $composer['support']['source'] = sprintf('%s/-/tree/%s', $this->project['web_url'], $label); + } + if (!isset($composer['support']['issues']) && !empty($this->project['issues_enabled']) && isset($this->project['web_url'])) { + $composer['support']['issues'] = sprintf('%s/-/issues', $this->project['web_url']); + } + if (!isset($composer['abandoned']) && !empty($this->project['archived'])) { + $composer['abandoned'] = true; + } + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if ($this->gitDriver) { + return $this->gitDriver->getFileContent($file, $identifier); + } + + // Convert the root identifier to a cacheable commit id + if (!Preg::isMatch('{[a-f0-9]{40}}i', $identifier)) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + + $resource = $this->getApiUrl().'/repository/files/'.$this->urlEncodeAll($file).'/raw?ref='.$identifier; + + try { + $content = $this->getContents($resource)->getBody(); + } catch (TransportException $e) { + if ($e->getCode() !== 404) { + throw $e; + } + + return null; + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + if ($this->gitDriver) { + return $this->gitDriver->getChangeDate($identifier); + } + + if (isset($this->commits[$identifier])) { + return new \DateTimeImmutable($this->commits[$identifier]['committed_date']); + } + + return null; + } + + public function getRepositoryUrl(): string + { + if ($this->protocol) { + return $this->project["{$this->protocol}_url_to_repo"]; + } + + return $this->isPrivate ? $this->project['ssh_url_to_repo'] : $this->project['http_url_to_repo']; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + if ($this->gitDriver) { + return $this->gitDriver->getUrl(); + } + + return $this->project['web_url']; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + $url = $this->getApiUrl().'/repository/archive.zip?sha='.$identifier; + + return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + if ($this->gitDriver) { + return $this->gitDriver->getSource($identifier); + } + + return ['type' => 'git', 'url' => $this->getRepositoryUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if ($this->gitDriver) { + return $this->gitDriver->getRootIdentifier(); + } + + return $this->project['default_branch']; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if ($this->gitDriver) { + return $this->gitDriver->getBranches(); + } + + if (null === $this->branches) { + $this->branches = $this->getReferences('branches'); + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if ($this->gitDriver) { + return $this->gitDriver->getTags(); + } + + if (null === $this->tags) { + $this->tags = $this->getReferences('tags'); + } + + return $this->tags; + } + + /** + * @return string Base URL for GitLab API v3 + */ + public function getApiUrl(): string + { + return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); + } + + /** + * Urlencode all non alphanumeric characters. rawurlencode() can not be used as it does not encode `.` + */ + private function urlEncodeAll(string $string): string + { + $encoded = ''; + for ($i = 0; isset($string[$i]); $i++) { + $character = $string[$i]; + if (!ctype_alnum($character) && !in_array($character, ['-', '_'], true)) { + $character = '%' . sprintf('%02X', ord($character)); + } + $encoded .= $character; + } + + return $encoded; + } + + /** + * @return string[] where keys are named references like tags or branches and the value a sha + */ + protected function getReferences(string $type): array + { + $perPage = 100; + $resource = $this->getApiUrl().'/repository/'.$type.'?per_page='.$perPage; + + $references = []; + do { + $response = $this->getContents($resource); + $data = $response->decodeJson(); + + foreach ($data as $datum) { + $references[$datum['name']] = $datum['commit']['id']; + + // Keep the last commit date of a reference to avoid + // unnecessary API call when retrieving the composer file. + $this->commits[$datum['commit']['id']] = $datum['commit']; + } + + if (count($data) >= $perPage) { + $resource = $this->getNextPage($response); + } else { + $resource = false; + } + } while ($resource); + + return $references; + } + + protected function fetchProject(): void + { + if (!is_null($this->project)) { + return; + } + + // we need to fetch the default branch from the api + $resource = $this->getApiUrl(); + $this->project = $this->getContents($resource, true)->decodeJson(); + if (isset($this->project['visibility'])) { + $this->isPrivate = $this->project['visibility'] !== 'public'; + } else { + // client is not authenticated, therefore repository has to be public + $this->isPrivate = false; + } + } + + /** + * @phpstan-impure + * + * @return true + * @throws \RuntimeException + */ + protected function attemptCloneFallback(): bool + { + if ($this->isPrivate === false) { + $url = $this->generatePublicUrl(); + } else { + $url = $this->generateSshUrl(); + } + + try { + // If this repository may be private and we + // cannot ask for authentication credentials (because we + // are not interactive) then we fallback to GitDriver. + $this->setupGitDriver($url); + + return true; + } catch (\RuntimeException $e) { + $this->gitDriver = null; + + $this->io->writeError('Failed to clone the '.$url.' repository, try running in interactive mode so that you can enter your credentials'); + throw $e; + } + } + + /** + * Generate an SSH URL + */ + protected function generateSshUrl(): string + { + if ($this->hasNonstandardOrigin) { + return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git'; + } + + return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git'; + } + + protected function generatePublicUrl(): string + { + return $this->scheme . '://' . $this->originUrl . '/'.$this->namespace.'/'.$this->repository.'.git'; + } + + protected function setupGitDriver(string $url): void + { + $this->gitDriver = new GitDriver( + ['url' => $url], + $this->io, + $this->config, + $this->httpDownloader, + $this->process + ); + $this->gitDriver->initialize(); + } + + /** + * @inheritDoc + */ + protected function getContents(string $url, bool $fetchingRepoData = false): Response + { + try { + $response = parent::getContents($url); + + if ($fetchingRepoData) { + $json = $response->decodeJson(); + + // Accessing the API with a token with Guest (10) or Planner (15) access will return + // more data than unauthenticated access but no default_branch data + // accessing files via the API will then also fail + if (!isset($json['default_branch']) && isset($json['permissions'])) { + $this->isPrivate = $json['visibility'] !== 'public'; + + $moreThanGuestAccess = false; + // Check both access levels (e.g. project, group) + // - value will be null if no access is set + // - value will be array with key access_level if set + foreach ($json['permissions'] as $permission) { + if ($permission && $permission['access_level'] >= 20) { + $moreThanGuestAccess = true; + } + } + + if (!$moreThanGuestAccess) { + $this->io->writeError('GitLab token with Guest or Planner only access detected'); + + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + } + + // force auth as the unauthenticated version of the API is broken + if (!isset($json['default_branch'])) { + // GitLab allows you to disable the repository inside a project to use a project only for issues and wiki + if (isset($json['repository_access_level']) && $json['repository_access_level'] === 'disabled') { + throw new TransportException('The GitLab repository is disabled in the project', 400); + } + + if (!empty($json['id'])) { + $this->isPrivate = false; + } + + throw new TransportException('GitLab API seems to not be authenticated as it did not return a default_branch', 401); + } + } + + return $response; + } catch (TransportException $e) { + $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->httpDownloader); + + switch ($e->getCode()) { + case 401: + case 404: + // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 + if (!$fetchingRepoData) { + throw $e; + } + + if ($gitLabUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if ($gitLabUtil->isOAuthExpired($this->originUrl) && $gitLabUtil->authorizeOAuthRefresh($this->scheme, $this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive()) { + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + $this->io->writeError('Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . ''); + $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata ('.$this->url.')'); + + return parent::getContents($url); + + case 403: + if (!$this->io->hasAuthentication($this->originUrl) && $gitLabUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive() && $fetchingRepoData) { + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + + throw $e; + + default: + throw $e; + } + } + } + + /** + * Uses the config `gitlab-domains` to see if the driver supports the url for the + * repository given. + * + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (!Preg::isMatch(self::URL_REGEX, $url, $match)) { + return false; + } + + $scheme = $match['scheme']; + $guessedDomain = $match['domain'] ?? (string) $match['domain2']; + $urlParts = explode('/', $match['parts']); + + if (false === self::determineOrigin($config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) { + return false; + } + + if ('https' === $scheme && !extension_loaded('openssl')) { + $io->writeError('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); + + return false; + } + + return true; + } + + /** + * Gives back the loaded /projects// result + * + * @return mixed[]|null + */ + public function getRepoData(): ?array + { + $this->fetchProject(); + + return $this->project; + } + + protected function getNextPage(Response $response): ?string + { + $header = $response->getHeader('link'); + + $links = explode(',', $header); + foreach ($links as $link) { + if (Preg::isMatchStrictGroups('{<(.+?)>; *rel="next"}', $link, $match)) { + return $match[1]; + } + } + + return null; + } + + /** + * @param array $configuredDomains + * @param array $urlParts + * + * @return string|false + */ + private static function determineOrigin(array $configuredDomains, string $guessedDomain, array &$urlParts, ?string $portNumber) + { + $guessedDomain = strtolower($guessedDomain); + + if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) { + if (null !== $portNumber) { + return $guessedDomain.':'.$portNumber; + } + + return $guessedDomain; + } + + if (null !== $portNumber) { + $guessedDomain .= ':'.$portNumber; + } + + while (null !== ($part = array_shift($urlParts))) { + $guessedDomain .= '/' . $part; + + if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array(Preg::replace('{:\d+}', '', $guessedDomain), $configuredDomains))) { + return $guessedDomain; + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php new file mode 100644 index 000000000..625a2a1eb --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php @@ -0,0 +1,242 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Cache; +use Composer\Pcre\Preg; +use Composer\Util\Hg as HgUtils; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\IO\IOInterface; +use Composer\Util\Url; + +/** + * @author Per Bernhardt + */ +class HgDriver extends VcsDriver +{ + /** @var array Map of tag name to identifier */ + protected $tags; + /** @var array Map of branch name to identifier */ + protected $branches; + /** @var string */ + protected $rootIdentifier; + /** @var string */ + protected $repoDir; + + /** + * @inheritDoc + */ + public function initialize(): void + { + if (Filesystem::isLocalPath($this->url)) { + $this->repoDir = $this->url; + } else { + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + + $cacheDir = $this->config->get('cache-vcs-dir'); + $this->repoDir = $cacheDir . '/' . Preg::replace('{[^a-z0-9]}i', '-', Url::sanitize($this->url)) . '/'; + + $fs = new Filesystem(); + $fs->ensureDirectoryExists($cacheDir); + + if (!is_writable(dirname($this->repoDir))) { + throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$cacheDir.'" directory is not writable by the current user.'); + } + + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($this->url, $this->io); + + $hgUtils = new HgUtils($this->io, $this->config, $this->process); + + // update the repo if it is a valid hg repository + if (is_dir($this->repoDir) && 0 === $this->process->execute(['hg', 'summary'], $output, $this->repoDir)) { + if (0 !== $this->process->execute(['hg', 'pull'], $output, $this->repoDir)) { + $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); + } + } else { + // clean up directory and do a fresh clone into it + $fs->removeDirectory($this->repoDir); + + $repoDir = $this->repoDir; + $command = static function ($url) use ($repoDir): array { + return ['hg', 'clone', '--noupdate', '--', $url, $repoDir]; + }; + + $hgUtils->runCommand($command, $this->url, null); + } + } + + $this->getTags(); + $this->getBranches(); + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if (null === $this->rootIdentifier) { + $this->process->execute(['hg', 'tip', '--template', '{node}'], $output, $this->repoDir); + $output = $this->process->splitLines($output); + $this->rootIdentifier = $output[0]; + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return ['type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if (isset($identifier[0]) && $identifier[0] === '-') { + throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier); + } + + $resource = ['hg', 'cat', '-r', $identifier, '--', $file]; + $this->process->execute($resource, $content, $this->repoDir); + + if (!trim($content)) { + return null; + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + $this->process->execute( + ['hg', 'log', '--template', '{date|rfc3339date}', '-r', $identifier], + $output, + $this->repoDir + ); + + return new \DateTimeImmutable(trim($output), new \DateTimeZone('UTC')); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if (null === $this->tags) { + $tags = []; + + $this->process->execute(['hg', 'tags'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $tag) { + if ($tag && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) { + $tags[$match[1]] = $match[2]; + } + } + unset($tags['tip']); + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if (null === $this->branches) { + $branches = []; + $bookmarks = []; + + $this->process->execute(['hg', 'branches'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') { + $branches[$match[1]] = $match[2]; + } + } + + $this->process->execute(['hg', 'bookmarks'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && Preg::isMatchStrictGroups('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') { + $bookmarks[$match[1]] = $match[2]; + } + } + + // Branches will have preference over bookmarks + $this->branches = array_merge($bookmarks, $branches); + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) { + return true; + } + + // local filesystem + if (Filesystem::isLocalPath($url)) { + $url = Filesystem::getPlatformPath($url); + if (!is_dir($url)) { + return false; + } + + $process = new ProcessExecutor($io); + // check whether there is a hg repo in that path + if ($process->execute(['hg', 'summary'], $output, $url) === 0) { + return true; + } + } + + if (!$deep) { + return false; + } + + $process = new ProcessExecutor($io); + $exit = $process->execute(['hg', 'identify', '--', $url], $ignored); + + return $exit === 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php new file mode 100644 index 000000000..a77c8c94c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php @@ -0,0 +1,188 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Perforce; +use Composer\Util\Http\Response; + +/** + * @author Matt Whittom + */ +class PerforceDriver extends VcsDriver +{ + /** @var string */ + protected $depot; + /** @var string */ + protected $branch; + /** @var ?Perforce */ + protected $perforce = null; + + /** + * @inheritDoc + */ + public function initialize(): void + { + $this->depot = $this->repoConfig['depot']; + $this->branch = ''; + if (!empty($this->repoConfig['branch'])) { + $this->branch = $this->repoConfig['branch']; + } + + $this->initPerforce($this->repoConfig); + $this->perforce->p4Login(); + $this->perforce->checkStream(); + + $this->perforce->writeP4ClientSpec(); + $this->perforce->connectClient(); + } + + /** + * @param array $repoConfig + */ + private function initPerforce(array $repoConfig): void + { + if (!empty($this->perforce)) { + return; + } + + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + + $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot; + $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io); + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + return $this->perforce->getFileContent($file, $identifier); + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + return null; + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + return $this->branch; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + return $this->perforce->getBranches(); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + return $this->perforce->getTags(); + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return [ + 'type' => 'perforce', + 'url' => $this->repoConfig['url'], + 'reference' => $identifier, + 'p4user' => $this->perforce->getUser(), + ]; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function hasComposerFile(string $identifier): bool + { + $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier); + + return !empty($composerInfo); + } + + /** + * @inheritDoc + */ + public function getContents(string $url): Response + { + throw new \BadMethodCallException('Not implemented/used in PerforceDriver'); + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if ($deep || Preg::isMatch('#\b(perforce|p4)\b#i', $url)) { + return Perforce::checkServerExists($url, new ProcessExecutor($io)); + } + + return false; + } + + /** + * @inheritDoc + */ + public function cleanup(): void + { + $this->perforce->cleanupClientSpec(); + $this->perforce = null; + } + + public function getDepot(): string + { + return $this->depot; + } + + public function getBranch(): string + { + return $this->branch; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php new file mode 100644 index 000000000..85227e8b7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php @@ -0,0 +1,409 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Cache; +use Composer\Config; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\Util\Url; +use Composer\Util\Svn as SvnUtil; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; + +/** + * @author Jordi Boggiano + * @author Till Klampaeckel + */ +class SvnDriver extends VcsDriver +{ + /** @var string */ + protected $baseUrl; + /** @var array Map of tag name to identifier */ + protected $tags; + /** @var array Map of branch name to identifier */ + protected $branches; + /** @var ?string */ + protected $rootIdentifier; + + /** @var string|false */ + protected $trunkPath = 'trunk'; + /** @var string */ + protected $branchesPath = 'branches'; + /** @var string */ + protected $tagsPath = 'tags'; + /** @var string */ + protected $packagePath = ''; + /** @var bool */ + protected $cacheCredentials = true; + + /** + * @var SvnUtil + */ + private $util; + + /** + * @inheritDoc + */ + public function initialize(): void + { + $this->url = $this->baseUrl = rtrim(self::normalizeUrl($this->url), '/'); + + SvnUtil::cleanEnv(); + + if (isset($this->repoConfig['trunk-path'])) { + $this->trunkPath = $this->repoConfig['trunk-path']; + } + if (isset($this->repoConfig['branches-path'])) { + $this->branchesPath = $this->repoConfig['branches-path']; + } + if (isset($this->repoConfig['tags-path'])) { + $this->tagsPath = $this->repoConfig['tags-path']; + } + if (array_key_exists('svn-cache-credentials', $this->repoConfig)) { + $this->cacheCredentials = (bool) $this->repoConfig['svn-cache-credentials']; + } + if (isset($this->repoConfig['package-path'])) { + $this->packagePath = '/' . trim($this->repoConfig['package-path'], '/'); + } + + if (false !== ($pos = strrpos($this->url, '/' . $this->trunkPath))) { + $this->baseUrl = substr($this->url, 0, $pos); + } + + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->baseUrl))); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + + $this->getBranches(); + $this->getTags(); + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + return $this->rootIdentifier ?: $this->trunkPath; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return ['type' => 'svn', 'url' => $this->baseUrl, 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + protected function shouldCache(string $identifier): bool + { + return $this->cache && Preg::isMatch('{@\d+$}', $identifier); + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier.'.json')) { + // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array + // and fix outdated invalid cache files + if ($res === '""') { + $res = 'null'; + $this->cache->write($identifier.'.json', $res); + } + + return $this->infoCache[$identifier] = JsonFile::parseJson($res); + } + + try { + $composer = $this->getBaseComposerInformation($identifier); + } catch (TransportException $e) { + $message = $e->getMessage(); + if (stripos($message, 'path not found') === false && stripos($message, 'svn: warning: W160013') === false) { + throw $e; + } + // remember a not-existent composer.json + $composer = null; + } + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier.'.json', JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); + } + + $this->infoCache[$identifier] = $composer; + } + + // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array + if (!is_array($this->infoCache[$identifier])) { + return null; + } + + return $this->infoCache[$identifier]; + } + + public function getFileContent(string $file, string $identifier): ?string + { + $identifier = '/' . trim($identifier, '/') . '/'; + + if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && $match[2] !== null) { + $path = $match[1]; + $rev = $match[2]; + } else { + $path = $identifier; + $rev = ''; + } + + try { + $resource = $path.$file; + $output = $this->execute(['svn', 'cat'], $this->baseUrl . $resource . $rev); + if ('' === trim($output)) { + return null; + } + } catch (\RuntimeException $e) { + throw new TransportException($e->getMessage()); + } + + return $output; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + $identifier = '/' . trim($identifier, '/') . '/'; + + if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && null !== $match[2]) { + $path = $match[1]; + $rev = $match[2]; + } else { + $path = $identifier; + $rev = ''; + } + + $output = $this->execute(['svn', 'info'], $this->baseUrl . $path . $rev); + foreach ($this->process->splitLines($output) as $line) { + if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) { + return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC')); + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if (null === $this->tags) { + $tags = []; + + if ($this->tagsPath !== false) { + $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->tagsPath); + if ($output !== '') { + $lastRev = 0; + foreach ($this->process->splitLines($output) as $line) { + $line = trim($line); + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $lastRev = (int) $match[1]; + } else { + $tags[rtrim($match[2], '/')] = $this->buildIdentifier( + '/' . $this->tagsPath . '/' . $match[2], + max($lastRev, (int) $match[1]) + ); + } + } + } + } + } + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if (null === $this->branches) { + $branches = []; + + if (false === $this->trunkPath) { + $trunkParent = $this->baseUrl . '/'; + } else { + $trunkParent = $this->baseUrl . '/' . $this->trunkPath; + } + + $output = $this->execute(['svn', 'ls', '--verbose'], $trunkParent); + if ($output !== '') { + foreach ($this->process->splitLines($output) as $line) { + $line = trim($line); + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $branches['trunk'] = $this->buildIdentifier( + '/' . $this->trunkPath, + (int) $match[1] + ); + $this->rootIdentifier = $branches['trunk']; + break; + } + } + } + } + unset($output); + + if ($this->branchesPath !== false) { + $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->branchesPath); + if ($output !== '') { + $lastRev = 0; + foreach ($this->process->splitLines(trim($output)) as $line) { + $line = trim($line); + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $lastRev = (int) $match[1]; + } else { + $branches[rtrim($match[2], '/')] = $this->buildIdentifier( + '/' . $this->branchesPath . '/' . $match[2], + max($lastRev, (int) $match[1]) + ); + } + } + } + } + } + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + $url = self::normalizeUrl($url); + if (Preg::isMatch('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) { + return true; + } + + // proceed with deep check for local urls since they are fast to process + if (!$deep && !Filesystem::isLocalPath($url)) { + return false; + } + + $process = new ProcessExecutor($io); + $exit = $process->execute(['svn', 'info', '--non-interactive', '--', $url], $ignoredOutput); + + if ($exit === 0) { + // This is definitely a Subversion repository. + return true; + } + + // Subversion client 1.7 and older + if (false !== stripos($process->getErrorOutput(), 'authorization failed:')) { + // This is likely a remote Subversion repository that requires + // authentication. We will handle actual authentication later. + return true; + } + + // Subversion client 1.8 and newer + if (false !== stripos($process->getErrorOutput(), 'Authentication failed')) { + // This is likely a remote Subversion or newer repository that requires + // authentication. We will handle actual authentication later. + return true; + } + + return false; + } + + /** + * An absolute path (leading '/') is converted to a file:// url. + */ + protected static function normalizeUrl(string $url): string + { + $fs = new Filesystem(); + if ($fs->isAbsolutePath($url)) { + return 'file://' . strtr($url, '\\', '/'); + } + + return $url; + } + + /** + * Execute an SVN command and try to fix up the process with credentials + * if necessary. + * + * @param non-empty-list $command The svn command to run. + * @param string $url The SVN URL. + * @throws \RuntimeException + */ + protected function execute(array $command, string $url): string + { + if (null === $this->util) { + $this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process); + $this->util->setCacheCredentials($this->cacheCredentials); + } + + try { + return $this->util->execute($command, $url); + } catch (\RuntimeException $e) { + if (null === $this->util->binaryVersion()) { + throw new \RuntimeException('Failed to load '.$this->url.', svn was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); + } + + throw new \RuntimeException( + 'Repository '.$this->url.' could not be processed, '.$e->getMessage() + ); + } + } + + /** + * Build the identifier respecting "package-path" config option + * + * @param string $baseDir The path to trunk/branch/tag + * @param int $revision The revision mark to add to identifier + */ + protected function buildIdentifier(string $baseDir, int $revision): string + { + return rtrim($baseDir, '/') . $this->packagePath . '/@' . $revision; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php new file mode 100644 index 000000000..c83933f3f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php @@ -0,0 +1,180 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Cache; +use Composer\Downloader\TransportException; +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\HttpDownloader; +use Composer\Util\Filesystem; +use Composer\Util\Http\Response; + +/** + * A driver implementation for driver with authentication interaction. + * + * @author François Pluchino + */ +abstract class VcsDriver implements VcsDriverInterface +{ + /** @var string */ + protected $url; + /** @var string */ + protected $originUrl; + /** @var array */ + protected $repoConfig; + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var HttpDownloader */ + protected $httpDownloader; + /** @var array */ + protected $infoCache = []; + /** @var ?Cache */ + protected $cache; + + /** + * Constructor. + * + * @param array{url: string}&array $repoConfig The repository configuration + * @param IOInterface $io The IO instance + * @param Config $config The composer configuration + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking + * @param ProcessExecutor $process Process instance, injectable for mocking + */ + final public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process) + { + if (Filesystem::isLocalPath($repoConfig['url'])) { + $repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']); + } + + $this->url = $repoConfig['url']; + $this->originUrl = $repoConfig['url']; + $this->repoConfig = $repoConfig; + $this->io = $io; + $this->config = $config; + $this->httpDownloader = $httpDownloader; + $this->process = $process; + } + + /** + * Returns whether or not the given $identifier should be cached or not. + * @phpstan-assert-if-true !null $this->cache + */ + protected function shouldCache(string $identifier): bool + { + return $this->cache && Preg::isMatch('{^[a-f0-9]{40}$}iD', $identifier); + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + return $this->infoCache[$identifier] = JsonFile::parseJson($res); + } + + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + /** + * @return array|null + */ + protected function getBaseComposerInformation(string $identifier): ?array + { + $composerFileContent = $this->getFileContent('composer.json', $identifier); + + if (!$composerFileContent) { + return null; + } + + $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json'); + + if ([] === $composer || !is_array($composer)) { + return null; + } + + if (empty($composer['time']) && null !== ($changeDate = $this->getChangeDate($identifier))) { + $composer['time'] = $changeDate->format(DATE_RFC3339); + } + + return $composer; + } + + /** + * @inheritDoc + */ + public function hasComposerFile(string $identifier): bool + { + try { + return null !== $this->getComposerInformation($identifier); + } catch (TransportException $e) { + } + + return false; + } + + /** + * Get the https or http protocol depending on SSL support. + * + * Call this only if you know that the server supports both. + * + * @return string The correct type of protocol + */ + protected function getScheme(): string + { + if (extension_loaded('openssl')) { + return 'https'; + } + + return 'http'; + } + + /** + * Get the remote content. + * + * @param string $url The URL of content + * + * @throws TransportException + */ + protected function getContents(string $url): Response + { + $options = $this->repoConfig['options'] ?? []; + + return $this->httpDownloader->get($url, $options); + } + + /** + * @inheritDoc + */ + public function cleanup(): void + { + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php b/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php new file mode 100644 index 000000000..96a4b8ad3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php @@ -0,0 +1,110 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\IO\IOInterface; + +/** + * @author Jordi Boggiano + * @internal + */ +interface VcsDriverInterface +{ + /** + * Initializes the driver (git clone, svn checkout, fetch info etc) + */ + public function initialize(): void; + + /** + * Return the composer.json file information + * + * @param string $identifier Any identifier to a specific branch/tag/commit + * @return mixed[]|null Array containing all infos from the composer.json file, or null to denote that no file was present + */ + public function getComposerInformation(string $identifier): ?array; + + /** + * Return the content of $file or null if the file does not exist. + */ + public function getFileContent(string $file, string $identifier): ?string; + + /** + * Get the changedate for $identifier. + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable; + + /** + * Return the root identifier (trunk, master, default/tip ..) + * + * @return string Identifier + */ + public function getRootIdentifier(): string; + + /** + * Return list of branches in the repository + * + * @return array Branch names as keys, identifiers as values + */ + public function getBranches(): array; + + /** + * Return list of tags in the repository + * + * @return array Tag names as keys, identifiers as values + */ + public function getTags(): array; + + /** + * @param string $identifier Any identifier to a specific branch/tag/commit + * + * @return array{type: string, url: string, reference: string, shasum: string}|null + */ + public function getDist(string $identifier): ?array; + + /** + * @param string $identifier Any identifier to a specific branch/tag/commit + * + * @return array{type: string, url: string, reference: string} + */ + public function getSource(string $identifier): array; + + /** + * Return the URL of the repository + */ + public function getUrl(): string; + + /** + * Return true if the repository has a composer file for a given identifier, + * false otherwise. + * + * @param string $identifier Any identifier to a specific branch/tag/commit + * @return bool Whether the repository has a composer file for a given identifier. + */ + public function hasComposerFile(string $identifier): bool; + + /** + * Performs any cleanup necessary as the driver is not longer needed + */ + public function cleanup(): void; + + /** + * Checks if this driver can handle a given url + * + * @param IOInterface $io IO instance + * @param Config $config current $config + * @param string $url URL to validate/check + * @param bool $deep unless true, only shallow checks (url matching typically) should be done + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool; +} diff --git a/vendor/composer/composer/src/Composer/Repository/VcsRepository.php b/vendor/composer/composer/src/Composer/Repository/VcsRepository.php new file mode 100644 index 000000000..0fc628e7b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/VcsRepository.php @@ -0,0 +1,536 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Repository\Vcs\VcsDriverInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Loader\ValidatingArrayLoader; +use Composer\Package\Loader\InvalidPackageException; +use Composer\Package\Loader\LoaderInterface; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\HttpDownloader; +use Composer\Util\Url; +use Composer\Semver\Constraint\Constraint; +use Composer\IO\IOInterface; +use Composer\Config; + +/** + * @author Jordi Boggiano + */ +class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInterface +{ + /** @var string */ + protected $url; + /** @var ?string */ + protected $packageName; + /** @var bool */ + protected $isVerbose; + /** @var bool */ + protected $isVeryVerbose; + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var VersionParser */ + protected $versionParser; + /** @var string */ + protected $type; + /** @var ?LoaderInterface */ + protected $loader; + /** @var array */ + protected $repoConfig; + /** @var HttpDownloader */ + protected $httpDownloader; + /** @var ProcessExecutor */ + protected $processExecutor; + /** @var bool */ + protected $branchErrorOccurred = false; + /** @var array> */ + private $drivers; + /** @var ?VcsDriverInterface */ + private $driver; + /** @var ?VersionCacheInterface */ + private $versionCache; + /** @var list */ + private $emptyReferences = []; + /** @var array<'tags'|'branches', array> */ + private $versionTransportExceptions = []; + + /** + * @param array{url: string, type?: string}&array $repoConfig + * @param array>|null $drivers + */ + public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null, ?array $drivers = null, ?VersionCacheInterface $versionCache = null) + { + parent::__construct(); + $this->drivers = $drivers ?: [ + 'github' => 'Composer\Repository\Vcs\GitHubDriver', + 'gitlab' => 'Composer\Repository\Vcs\GitLabDriver', + 'bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver', + 'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver', + 'forgejo' => 'Composer\Repository\Vcs\ForgejoDriver', + 'git' => 'Composer\Repository\Vcs\GitDriver', + 'hg' => 'Composer\Repository\Vcs\HgDriver', + 'perforce' => 'Composer\Repository\Vcs\PerforceDriver', + 'fossil' => 'Composer\Repository\Vcs\FossilDriver', + // svn must be last because identifying a subversion server for sure is practically impossible + 'svn' => 'Composer\Repository\Vcs\SvnDriver', + ]; + + $this->url = $repoConfig['url'] = Platform::expandPath($repoConfig['url']); + $this->io = $io; + $this->type = $repoConfig['type'] ?? 'vcs'; + $this->isVerbose = $io->isVerbose(); + $this->isVeryVerbose = $io->isVeryVerbose(); + $this->config = $config; + $this->repoConfig = $repoConfig; + $this->versionCache = $versionCache; + $this->httpDownloader = $httpDownloader; + $this->processExecutor = $process ?? new ProcessExecutor($io); + } + + public function getRepoName() + { + $driverClass = get_class($this->getDriver()); + $driverType = array_search($driverClass, $this->drivers); + if (!$driverType) { + $driverType = $driverClass; + } + + return 'vcs repo ('.$driverType.' '.Url::sanitize($this->url).')'; + } + + public function getRepoConfig() + { + return $this->repoConfig; + } + + public function setLoader(LoaderInterface $loader): void + { + $this->loader = $loader; + } + + public function getDriver(): ?VcsDriverInterface + { + if ($this->driver) { + return $this->driver; + } + + if (isset($this->drivers[$this->type])) { + $class = $this->drivers[$this->type]; + $this->driver = new $class($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); + $this->driver->initialize(); + + return $this->driver; + } + + foreach ($this->drivers as $driver) { + if ($driver::supports($this->io, $this->config, $this->url)) { + $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); + $this->driver->initialize(); + + return $this->driver; + } + } + + foreach ($this->drivers as $driver) { + if ($driver::supports($this->io, $this->config, $this->url, true)) { + $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); + $this->driver->initialize(); + + return $this->driver; + } + } + + return null; + } + + public function hadInvalidBranches(): bool + { + return $this->branchErrorOccurred; + } + + /** + * @return list + */ + public function getEmptyReferences(): array + { + return $this->emptyReferences; + } + + /** + * @return array<'tags'|'branches', array> + */ + public function getVersionTransportExceptions(): array + { + return $this->versionTransportExceptions; + } + + protected function initialize() + { + parent::initialize(); + + $isVerbose = $this->isVerbose; + $isVeryVerbose = $this->isVeryVerbose; + + $driver = $this->getDriver(); + if (!$driver) { + throw new \InvalidArgumentException('No driver found to handle VCS repository '.$this->url); + } + + $this->versionParser = new VersionParser; + if (!$this->loader) { + $this->loader = new ArrayLoader($this->versionParser); + } + + $hasRootIdentifierComposerJson = false; + try { + $hasRootIdentifierComposerJson = $driver->hasComposerFile($driver->getRootIdentifier()); + if ($hasRootIdentifierComposerJson) { + $data = $driver->getComposerInformation($driver->getRootIdentifier()); + $this->packageName = !empty($data['name']) ? $data['name'] : null; + } + } catch (\Exception $e) { + if ($e instanceof TransportException && $this->shouldRethrowTransportException($e)) { + throw $e; + } + + if ($isVeryVerbose) { + $this->io->writeError('Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().''); + } + } + + foreach ($driver->getTags() as $tag => $identifier) { + $tag = (string) $tag; + $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $tag . ')'; + + // strip the release- prefix from tags if present + $tag = str_replace('release-', '', $tag); + + $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose); + if ($cachedPackage) { + $this->addPackage($cachedPackage); + + continue; + } + if ($cachedPackage === false) { + $this->emptyReferences[] = $identifier; + + continue; + } + + if (!$parsedTag = $this->validateTag($tag)) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped tag '.$tag.', invalid tag name'); + } + continue; + } + + if ($isVeryVerbose) { + $this->io->writeError($msg); + } elseif ($isVerbose) { + $this->io->overwriteError($msg, false); + } + + try { + $data = $driver->getComposerInformation($identifier); + if (null === $data) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped tag '.$tag.', no composer file'); + } + $this->emptyReferences[] = $identifier; + continue; + } + + // manually versioned package + if (isset($data['version'])) { + $data['version_normalized'] = $this->versionParser->normalize($data['version']); + } else { + // auto-versioned package, read value from tag + $data['version'] = $tag; + $data['version_normalized'] = $parsedTag; + } + + // make sure tag packages have no -dev flag + $data['version'] = Preg::replace('{[.-]?dev$}i', '', $data['version']); + $data['version_normalized'] = Preg::replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']); + + // make sure tag do not contain the default-branch marker + unset($data['default-branch']); + + // broken package, version doesn't match tag + if ($data['version_normalized'] !== $parsedTag) { + if ($isVeryVerbose) { + if (Preg::isMatch('{(^dev-|[.-]?dev$)}i', $parsedTag)) { + $this->io->writeError('Skipped tag '.$tag.', invalid tag name, tags can not use dev prefixes or suffixes'); + } else { + $this->io->writeError('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); + } + } + continue; + } + + $tagPackageName = $this->packageName ?: ($data['name'] ?? ''); + if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally'); + } + continue; + } + + if ($isVeryVerbose) { + $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')'); + } + + $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier))); + } catch (\Exception $e) { + if ($e instanceof TransportException) { + $this->versionTransportExceptions['tags'][$tag] = $e; + if ($e->getCode() === 404) { + $this->emptyReferences[] = $identifier; + } + if ($this->shouldRethrowTransportException($e)) { + throw $e; + } + } + if ($isVeryVerbose) { + $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).''); + } + continue; + } + } + + if (!$isVeryVerbose) { + $this->io->overwriteError('', false); + } + + $branches = $driver->getBranches(); + // make sure the root identifier branch gets loaded first + if ($hasRootIdentifierComposerJson && isset($branches[$driver->getRootIdentifier()])) { + $branches = [$driver->getRootIdentifier() => $branches[$driver->getRootIdentifier()]] + $branches; + } + + foreach ($branches as $branch => $identifier) { + $branch = (string) $branch; + $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $branch . ')'; + if ($isVeryVerbose) { + $this->io->writeError($msg); + } elseif ($isVerbose) { + $this->io->overwriteError($msg, false); + } + + if (!$parsedBranch = $this->validateBranch($branch)) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped branch '.$branch.', invalid name'); + } + continue; + } + + // make sure branch packages have a dev flag + if (strpos($parsedBranch, 'dev-') === 0 || VersionParser::DEFAULT_BRANCH_ALIAS === $parsedBranch) { + $version = 'dev-' . str_replace('#', '+', $branch); + $parsedBranch = str_replace('#', '+', $parsedBranch); + } else { + $prefix = strpos($branch, 'v') === 0 ? 'v' : ''; + $version = $prefix . Preg::replace('{(\.9{7})+}', '.x', $parsedBranch); + } + + $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose, $driver->getRootIdentifier() === $branch); + if ($cachedPackage) { + $this->addPackage($cachedPackage); + + continue; + } + if ($cachedPackage === false) { + $this->emptyReferences[] = $identifier; + + continue; + } + + try { + $data = $driver->getComposerInformation($identifier); + if (null === $data) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped branch '.$branch.', no composer file'); + } + $this->emptyReferences[] = $identifier; + continue; + } + + // branches are always auto-versioned, read value from branch name + $data['version'] = $version; + $data['version_normalized'] = $parsedBranch; + + unset($data['default-branch']); + if ($driver->getRootIdentifier() === $branch) { + $data['default-branch'] = true; + } + + if ($isVeryVerbose) { + $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')'); + } + + $packageData = $this->preProcess($driver, $data, $identifier); + $package = $this->loader->load($packageData); + if ($this->loader instanceof ValidatingArrayLoader && \count($this->loader->getWarnings()) > 0) { + throw new InvalidPackageException($this->loader->getErrors(), $this->loader->getWarnings(), $packageData); + } + $this->addPackage($package); + } catch (TransportException $e) { + $this->versionTransportExceptions['branches'][$branch] = $e; + if ($e->getCode() === 404) { + $this->emptyReferences[] = $identifier; + } + if ($this->shouldRethrowTransportException($e)) { + throw $e; + } + if ($isVeryVerbose) { + $this->io->writeError('Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)'); + } + continue; + } catch (\Exception $e) { + if (!$isVeryVerbose) { + $this->io->writeError(''); + } + $this->branchErrorOccurred = true; + $this->io->writeError('Skipped branch '.$branch.', '.$e->getMessage().''); + $this->io->writeError(''); + continue; + } + } + $driver->cleanup(); + + if (!$isVeryVerbose) { + $this->io->overwriteError('', false); + } + + if (!$this->getPackages()) { + throw new InvalidRepositoryException('No valid composer.json was found in any branch or tag of '.$this->url.', could not load a package from it.'); + } + } + + /** + * @param array{name?: string, dist?: array{type: string, url: string, reference: string, shasum: string}, source?: array{type: string, url: string, reference: string}} $data + * + * @return array{name: string|null, dist: array{type: string, url: string, reference: string, shasum: string}|null, source: array{type: string, url: string, reference: string}} + */ + protected function preProcess(VcsDriverInterface $driver, array $data, string $identifier): array + { + // keep the name of the main identifier for all packages + // this ensures that a package can be renamed in one place and that all old tags + // will still be installable using that new name without requiring re-tagging + $dataPackageName = $data['name'] ?? null; + $data['name'] = $this->packageName ?: $dataPackageName; + + if (!isset($data['dist'])) { + $data['dist'] = $driver->getDist($identifier); + } + if (!isset($data['source'])) { + $data['source'] = $driver->getSource($identifier); + } + + // if custom dist info is provided but does not provide a reference, copy the source reference to it + if (is_array($data['dist']) && !isset($data['dist']['reference']) && isset($data['source']['reference'])) { + $data['dist']['reference'] = $data['source']['reference']; + } + + return $data; + } + + /** + * @return string|false + */ + private function validateBranch(string $branch) + { + try { + $normalizedBranch = $this->versionParser->normalizeBranch($branch); + + // validate that the branch name has no weird characters conflicting with constraints + $this->versionParser->parseConstraints($normalizedBranch); + + return $normalizedBranch; + } catch (\Exception $e) { + } + + return false; + } + + /** + * @return string|false + */ + private function validateTag(string $version) + { + try { + return $this->versionParser->normalize($version); + } catch (\Exception $e) { + } + + return false; + } + + /** + * @return \Composer\Package\CompletePackage|\Composer\Package\CompleteAliasPackage|null|false null if no cache present, false if the absence of a version was cached + */ + private function getCachedPackageVersion(string $version, string $identifier, bool $isVerbose, bool $isVeryVerbose, bool $isDefaultBranch = false) + { + if (!$this->versionCache) { + return null; + } + + $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier); + if ($cachedPackage === false) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped '.$version.', no composer file (cached from ref '.$identifier.')'); + } + + return false; + } + + if ($cachedPackage) { + $msg = 'Found cached composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $version . ')'; + if ($isVeryVerbose) { + $this->io->writeError($msg); + } elseif ($isVerbose) { + $this->io->overwriteError($msg, false); + } + + unset($cachedPackage['default-branch']); + if ($isDefaultBranch) { + $cachedPackage['default-branch'] = true; + } + + if ($existingPackage = $this->findPackage($cachedPackage['name'], new Constraint('=', $cachedPackage['version_normalized']))) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally'); + } + $cachedPackage = null; + } + } + + if ($cachedPackage) { + return $this->loader->load($cachedPackage); + } + + return null; + } + + private function shouldRethrowTransportException(TransportException $e): bool + { + return in_array($e->getCode(), [401, 403, 429], true) || $e->getCode() >= 500; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/VersionCacheInterface.php b/vendor/composer/composer/src/Composer/Repository/VersionCacheInterface.php new file mode 100644 index 000000000..ac0c41764 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/VersionCacheInterface.php @@ -0,0 +1,21 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +interface VersionCacheInterface +{ + /** + * @return mixed[]|null|false Package version data if found, false to indicate the identifier is known but has no package, null for an unknown identifier + */ + public function getVersionPackage(string $version, string $identifier); +} diff --git a/vendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php b/vendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php new file mode 100644 index 000000000..349c32472 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php @@ -0,0 +1,73 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Installer\InstallationManager; + +/** + * Writable array repository. + * + * @author Jordi Boggiano + */ +class WritableArrayRepository extends ArrayRepository implements WritableRepositoryInterface +{ + use CanonicalPackagesTrait; + + /** + * @var string[] + */ + protected $devPackageNames = []; + + /** @var bool|null */ + private $devMode = null; + + /** + * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown + */ + public function getDevMode() + { + return $this->devMode; + } + + /** + * @inheritDoc + */ + public function setDevPackageNames(array $devPackageNames) + { + $this->devPackageNames = $devPackageNames; + } + + /** + * @inheritDoc + */ + public function getDevPackageNames() + { + return $this->devPackageNames; + } + + /** + * @inheritDoc + */ + public function write(bool $devMode, InstallationManager $installationManager) + { + $this->devMode = $devMode; + } + + /** + * @inheritDoc + */ + public function reload() + { + $this->devMode = null; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php b/vendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php new file mode 100644 index 000000000..46a3c9ea1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php @@ -0,0 +1,73 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\PackageInterface; +use Composer\Installer\InstallationManager; + +/** + * Writable repository interface. + * + * @author Konstantin Kudryashov + */ +interface WritableRepositoryInterface extends RepositoryInterface +{ + /** + * Writes repository (f.e. to the disc). + * + * @param bool $devMode Whether dev requirements were included or not in this installation + * @return void + */ + public function write(bool $devMode, InstallationManager $installationManager); + + /** + * Adds package to the repository. + * + * @param PackageInterface $package package instance + * @return void + */ + public function addPackage(PackageInterface $package); + + /** + * Removes package from the repository. + * + * @param PackageInterface $package package instance + * @return void + */ + public function removePackage(PackageInterface $package); + + /** + * Get unique packages (at most one package of each name), with aliases resolved and removed. + * + * @return PackageInterface[] + */ + public function getCanonicalPackages(); + + /** + * Forces a reload of all packages. + * + * @return void + */ + public function reload(); + + /** + * @param string[] $devPackageNames + * @return void + */ + public function setDevPackageNames(array $devPackageNames); + + /** + * @return string[] Names of dependencies installed through require-dev + */ + public function getDevPackageNames(); +} diff --git a/vendor/composer/composer/src/Composer/Script/Event.php b/vendor/composer/composer/src/Composer/Script/Event.php new file mode 100644 index 000000000..949ac9ea8 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Script/Event.php @@ -0,0 +1,120 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Script; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\EventDispatcher\Event as BaseEvent; + +/** + * The script event class + * + * @author François Pluchino + * @author Nils Adermann + */ +class Event extends BaseEvent +{ + /** + * @var Composer The composer instance + */ + private $composer; + + /** + * @var IOInterface The IO instance + */ + private $io; + + /** + * @var bool Dev mode flag + */ + private $devMode; + + /** + * @var BaseEvent|null + */ + private $originatingEvent; + + /** + * Constructor. + * + * @param string $name The event name + * @param Composer $composer The composer object + * @param IOInterface $io The IOInterface object + * @param bool $devMode Whether or not we are in dev mode + * @param array $args Arguments passed by the user + * @param mixed[] $flags Optional flags to pass data not as argument + */ + public function __construct(string $name, Composer $composer, IOInterface $io, bool $devMode = false, array $args = [], array $flags = []) + { + parent::__construct($name, $args, $flags); + $this->composer = $composer; + $this->io = $io; + $this->devMode = $devMode; + } + + /** + * Returns the composer instance. + */ + public function getComposer(): Composer + { + return $this->composer; + } + + /** + * Returns the IO instance. + */ + public function getIO(): IOInterface + { + return $this->io; + } + + /** + * Return the dev mode flag + */ + public function isDevMode(): bool + { + return $this->devMode; + } + + /** + * Set the originating event. + */ + public function getOriginatingEvent(): ?BaseEvent + { + return $this->originatingEvent; + } + + /** + * Set the originating event. + * + * @return $this + */ + public function setOriginatingEvent(BaseEvent $event): self + { + $this->originatingEvent = $this->calculateOriginatingEvent($event); + + return $this; + } + + /** + * Returns the upper-most event in chain. + */ + private function calculateOriginatingEvent(BaseEvent $event): BaseEvent + { + if ($event instanceof Event && $event->getOriginatingEvent()) { + return $this->calculateOriginatingEvent($event->getOriginatingEvent()); + } + + return $event; + } +} diff --git a/vendor/composer/composer/src/Composer/Script/ScriptEvents.php b/vendor/composer/composer/src/Composer/Script/ScriptEvents.php new file mode 100644 index 000000000..1c4024885 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Script/ScriptEvents.php @@ -0,0 +1,131 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Script; + +/** + * The Script Events. + * + * @author François Pluchino + * @author Jordi Boggiano + */ +class ScriptEvents +{ + /** + * The PRE_INSTALL_CMD event occurs before the install command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_INSTALL_CMD = 'pre-install-cmd'; + + /** + * The POST_INSTALL_CMD event occurs after the install command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_INSTALL_CMD = 'post-install-cmd'; + + /** + * The PRE_UPDATE_CMD event occurs before the update command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_UPDATE_CMD = 'pre-update-cmd'; + + /** + * The POST_UPDATE_CMD event occurs after the update command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_UPDATE_CMD = 'post-update-cmd'; + + /** + * The PRE_STATUS_CMD event occurs before the status command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_STATUS_CMD = 'pre-status-cmd'; + + /** + * The POST_STATUS_CMD event occurs after the status command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_STATUS_CMD = 'post-status-cmd'; + + /** + * The PRE_AUTOLOAD_DUMP event occurs before the autoload file is generated. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_AUTOLOAD_DUMP = 'pre-autoload-dump'; + + /** + * The POST_AUTOLOAD_DUMP event occurs after the autoload file has been generated. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_AUTOLOAD_DUMP = 'post-autoload-dump'; + + /** + * The POST_ROOT_PACKAGE_INSTALL event occurs after the root package has been installed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_ROOT_PACKAGE_INSTALL = 'post-root-package-install'; + + /** + * The POST_CREATE_PROJECT event occurs after the create-project command has been executed. + * Note: Event occurs after POST_INSTALL_CMD + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_CREATE_PROJECT_CMD = 'post-create-project-cmd'; + + /** + * The PRE_ARCHIVE_CMD event occurs before the update command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_ARCHIVE_CMD = 'pre-archive-cmd'; + + /** + * The POST_ARCHIVE_CMD event occurs after the status command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_ARCHIVE_CMD = 'post-archive-cmd'; +} diff --git a/vendor/composer/composer/src/Composer/SelfUpdate/Keys.php b/vendor/composer/composer/src/Composer/SelfUpdate/Keys.php new file mode 100644 index 000000000..595ffa737 --- /dev/null +++ b/vendor/composer/composer/src/Composer/SelfUpdate/Keys.php @@ -0,0 +1,38 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\SelfUpdate; + +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class Keys +{ + public static function fingerprint(string $path): string + { + $hash = strtoupper(hash('sha256', Preg::replace('{\s}', '', file_get_contents($path)))); + + return implode(' ', [ + substr($hash, 0, 8), + substr($hash, 8, 8), + substr($hash, 16, 8), + substr($hash, 24, 8), + '', // Extra space + substr($hash, 32, 8), + substr($hash, 40, 8), + substr($hash, 48, 8), + substr($hash, 56, 8), + ]); + } +} diff --git a/vendor/composer/composer/src/Composer/SelfUpdate/Versions.php b/vendor/composer/composer/src/Composer/SelfUpdate/Versions.php new file mode 100644 index 000000000..8cc7d455c --- /dev/null +++ b/vendor/composer/composer/src/Composer/SelfUpdate/Versions.php @@ -0,0 +1,117 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\SelfUpdate; + +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\HttpDownloader; +use Composer\Config; + +/** + * @author Jordi Boggiano + */ +class Versions +{ + /** + * @var string[] + * @deprecated use Versions::CHANNELS + */ + public static $channels = self::CHANNELS; + + public const CHANNELS = ['stable', 'preview', 'snapshot', '1', '2', '2.2']; + + /** @var HttpDownloader */ + private $httpDownloader; + /** @var Config */ + private $config; + /** @var string */ + private $channel; + /** @var array>|null */ + private $versionsData = null; + + public function __construct(Config $config, HttpDownloader $httpDownloader) + { + $this->httpDownloader = $httpDownloader; + $this->config = $config; + } + + public function getChannel(): string + { + if ($this->channel) { + return $this->channel; + } + + $channelFile = $this->config->get('home').'/update-channel'; + if (file_exists($channelFile)) { + $channel = trim(file_get_contents($channelFile)); + if (in_array($channel, ['stable', 'preview', 'snapshot', '2.2'], true)) { + return $this->channel = $channel; + } + } + + return $this->channel = 'stable'; + } + + public function setChannel(string $channel, ?IOInterface $io = null): void + { + if (!in_array($channel, self::CHANNELS, true)) { + throw new \InvalidArgumentException('Invalid channel '.$channel.', must be one of: ' . implode(', ', self::CHANNELS)); + } + + $channelFile = $this->config->get('home').'/update-channel'; + $this->channel = $channel; + + // rewrite '2' and '1' channels to stable for future self-updates, but LTS ones like '2.2' remain pinned + $storedChannel = Preg::isMatch('{^\d+$}D', $channel) ? 'stable' : $channel; + $previouslyStored = file_exists($channelFile) ? trim((string) file_get_contents($channelFile)) : null; + file_put_contents($channelFile, $storedChannel.PHP_EOL); + + if ($io !== null && $previouslyStored !== $storedChannel) { + $io->writeError('Storing "'.$storedChannel.'" as default update channel for the next self-update run.'); + } + } + + /** + * @return array{path: string, version: string, min-php: int, eol?: true} + */ + public function getLatest(?string $channel = null): array + { + $versions = $this->getVersionsData(); + + foreach ($versions[$channel ?: $this->getChannel()] as $version) { + if ($version['min-php'] <= \PHP_VERSION_ID) { + return $version; + } + } + + throw new \UnexpectedValueException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')'); + } + + /** + * @return array> + */ + private function getVersionsData(): array + { + if (null === $this->versionsData) { + if ($this->config->get('disable-tls') === true) { + $protocol = 'http'; + } else { + $protocol = 'https'; + } + + $this->versionsData = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson(); + } + + return $this->versionsData; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/AuthHelper.php b/vendor/composer/composer/src/Composer/Util/AuthHelper.php new file mode 100644 index 000000000..f4bad07ef --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/AuthHelper.php @@ -0,0 +1,347 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class AuthHelper +{ + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var array Map of origins to message displayed */ + private $displayedOriginAuthentications = []; + /** @var array Map of URLs and whether they already retried with authentication from Bitbucket */ + private $bitbucketRetry = []; + + public function __construct(IOInterface $io, Config $config) + { + $this->io = $io; + $this->config = $config; + } + + /** + * @param 'prompt'|bool $storeAuth + */ + public function storeAuth(string $origin, $storeAuth): void + { + $store = false; + $configSource = $this->config->getAuthConfigSource(); + if ($storeAuth === true) { + $store = $configSource; + } elseif ($storeAuth === 'prompt') { + $answer = $this->io->askAndValidate( + 'Do you want to store credentials for '.$origin.' in '.$configSource->getName().' ? [Yn] ', + static function ($value): string { + $input = strtolower(substr(trim($value), 0, 1)); + if (in_array($input, ['y','n'])) { + return $input; + } + throw new \RuntimeException('Please answer (y)es or (n)o'); + }, + null, + 'y' + ); + + if ($answer === 'y') { + $store = $configSource; + } + } + if ($store) { + $store->addConfigSetting( + 'http-basic.'.$origin, + $this->io->getAuthentication($origin) + ); + } + } + + /** + * @param int $statusCode HTTP status code that triggered this call + * @param string|null $reason a message/description explaining why this was called + * @param string[] $headers + * @param int $retryCount the amount of retries already done on this URL + * @return array containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be + * retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json + * @phpstan-return array{retry: bool, storeAuth: 'prompt'|bool} + */ + public function promptAuthIfNeeded(string $url, string $origin, int $statusCode, ?string $reason = null, array $headers = [], int $retryCount = 0): array + { + $storeAuth = false; + + if (in_array($origin, $this->config->get('github-domains'), true)) { + $gitHubUtil = new GitHub($this->io, $this->config, null); + $message = "\n"; + + $rateLimited = $gitHubUtil->isRateLimited($headers); + $requiresSso = $gitHubUtil->requiresSso($headers); + + if ($requiresSso) { + $ssoUrl = $gitHubUtil->getSsoUrl($headers); + $message = 'GitHub API token requires SSO authorization. Authorize this token at ' . $ssoUrl . "\n"; + $this->io->writeError($message); + if (!$this->io->isInteractive()) { + throw new TransportException('Could not authenticate against ' . $origin, 403); + } + $this->io->ask('After authorizing your token, confirm that you would like to retry the request'); + + return ['retry' => true, 'storeAuth' => $storeAuth]; + } + + if ($rateLimited) { + $rateLimit = $gitHubUtil->getRateLimit($headers); + if ($this->io->hasAuthentication($origin)) { + $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.'; + } else { + $message = 'Create a GitHub OAuth token to go over the API rate limit.'; + } + + $message = sprintf( + 'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$url.'. '.$message.' You can also wait until %s for the rate limit to reset.', + $rateLimit['limit'], + $rateLimit['reset'] + )."\n"; + } else { + $message .= 'Could not fetch '.$url.', please '; + if ($this->io->hasAuthentication($origin)) { + $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos'; + } else { + $message .= 'create a GitHub OAuth token to access private repos'; + } + } + + if (!$gitHubUtil->authorizeOAuth($origin) + && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($origin, $message)) + ) { + throw new TransportException('Could not authenticate against '.$origin, 401); + } + } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) { + $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit'); + $gitLabUtil = new GitLab($this->io, $this->config, null); + + $auth = null; + if ($this->io->hasAuthentication($origin)) { + $auth = $this->io->getAuthentication($origin); + if (in_array($auth['password'], ['gitlab-ci-token', 'private-token', 'oauth2'], true)) { + throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); + } + } + + if (!$gitLabUtil->authorizeOAuth($origin) + && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively(parse_url($url, PHP_URL_SCHEME), $origin, $message)) + ) { + throw new TransportException('Could not authenticate against '.$origin, 401); + } + + if ($auth !== null && $this->io->hasAuthentication($origin)) { + if ($auth === $this->io->getAuthentication($origin)) { + throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); + } + } + } elseif ($origin === 'bitbucket.org' || $origin === 'api.bitbucket.org') { + $askForOAuthToken = true; + $origin = 'bitbucket.org'; + if ($this->io->hasAuthentication($origin)) { + $auth = $this->io->getAuthentication($origin); + if ($auth['username'] !== 'x-token-auth') { + $bitbucketUtil = new Bitbucket($this->io, $this->config); + $accessToken = $bitbucketUtil->requestToken($origin, $auth['username'], $auth['password']); + if (!empty($accessToken)) { + $this->io->setAuthentication($origin, 'x-token-auth', $accessToken); + $askForOAuthToken = false; + } + } elseif (!isset($this->bitbucketRetry[$url])) { + // when multiple requests fire at the same time, they will all fail and the first one resets the token to be correct above but then the others + // reach the code path and without this fallback they would end up throwing below + // see https://github.com/composer/composer/pull/11464 for more details + $askForOAuthToken = false; + $this->bitbucketRetry[$url] = true; + } else { + throw new TransportException('Could not authenticate against ' . $origin, 401); + } + } + + if ($askForOAuthToken) { + $message = "\n".'Could not fetch ' . $url . ', please create a bitbucket OAuth token to ' . (($statusCode === 401 || $statusCode === 403) ? 'access private repos' : 'go over the API rate limit'); + $bitBucketUtil = new Bitbucket($this->io, $this->config); + if (!$bitBucketUtil->authorizeOAuth($origin) + && (!$this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($origin, $message)) + ) { + throw new TransportException('Could not authenticate against ' . $origin, 401); + } + } + } else { + // 404s are only handled for github + if ($statusCode === 404) { + return ['retry' => false, 'storeAuth' => false]; + } + + // fail if the console is not interactive + if (!$this->io->isInteractive()) { + if ($statusCode === 401) { + $message = "The '" . $url . "' URL required authentication (HTTP 401).\nYou must be using the interactive console to authenticate"; + } elseif ($statusCode === 403) { + $message = "The '" . $url . "' URL could not be accessed (HTTP 403): " . $reason; + } else { + $message = "Unknown error code '" . $statusCode . "', reason: " . $reason; + } + + throw new TransportException($message, $statusCode); + } + + // fail if we already have auth + if ($this->io->hasAuthentication($origin)) { + // if two or more requests are started together for the same host, and the first + // received authentication already, we let the others retry before failing them + if ($retryCount === 0) { + return ['retry' => true, 'storeAuth' => false]; + } + + throw new TransportException("Invalid credentials (HTTP $statusCode) for '$url', aborting.", $statusCode); + } + + $this->io->writeError(' Authentication required ('.$origin.'):'); + $username = $this->io->ask(' Username: '); + $password = $this->io->askAndHideAnswer(' Password: '); + $this->io->setAuthentication($origin, $username, $password); + $storeAuth = $this->config->get('store-auths'); + } + + return ['retry' => true, 'storeAuth' => $storeAuth]; + } + + /** + * @deprecated use addAuthenticationOptions instead + * + * @param string[] $headers + * + * @return string[] updated headers array + */ + public function addAuthenticationHeader(array $headers, string $origin, string $url): array + { + trigger_error('AuthHelper::addAuthenticationHeader is deprecated since Composer 2.9 use addAuthenticationOptions instead.', E_USER_DEPRECATED); + + $options = ['http' => ['header' => &$headers]]; + $options = $this->addAuthenticationOptions($options, $origin, $url); + + return $options['http']['header']; + } + + /** + * @param array $options + * + * @return array updated options + */ + public function addAuthenticationOptions(array $options, string $origin, string $url): array + { + if (!isset($options['http'])) { + $options['http'] = []; + } + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + $headers = &$options['http']['header']; + if ($this->io->hasAuthentication($origin)) { + $authenticationDisplayMessage = null; + $auth = $this->io->getAuthentication($origin); + if ($auth['password'] === 'bearer') { + $headers[] = 'Authorization: Bearer '.$auth['username']; + } elseif ($auth['password'] === 'custom-headers') { + // Handle custom HTTP headers from auth.json + $customHeaders = null; + if (is_string($auth['username'])) { + $customHeaders = json_decode($auth['username'], true); + } + if (is_array($customHeaders)) { + foreach ($customHeaders as $header) { + $headers[] = $header; + } + $authenticationDisplayMessage = 'Using custom HTTP headers for authentication'; + } + } elseif ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) { + // only add the access_token if it is actually a github API URL + if (Preg::isMatch('{^https?://api\.github\.com/}', $url)) { + $headers[] = 'Authorization: token '.$auth['username']; + $authenticationDisplayMessage = 'Using GitHub token authentication'; + } + } elseif ( + in_array($auth['password'], ['oauth2', 'private-token', 'gitlab-ci-token'], true) + && in_array($origin, $this->config->get('gitlab-domains'), true) + ) { + if ($auth['password'] === 'oauth2') { + $headers[] = 'Authorization: Bearer '.$auth['username']; + $authenticationDisplayMessage = 'Using GitLab OAuth token authentication'; + } else { + $headers[] = 'PRIVATE-TOKEN: '.$auth['username']; + $authenticationDisplayMessage = 'Using GitLab private token authentication'; + } + } elseif ( + 'bitbucket.org' === $origin + && $url !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL + && 'x-token-auth' === $auth['username'] + ) { + if (!$this->isPublicBitBucketDownload($url)) { + $headers[] = 'Authorization: Bearer ' . $auth['password']; + $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication'; + } + } elseif ('client-certificate' === $auth['username']) { + $options['ssl'] = array_merge($options['ssl'] ?? [], json_decode((string) $auth['password'], true)); + $authenticationDisplayMessage = 'Using SSL client certificate'; + } else { + $authStr = base64_encode($auth['username'] . ':' . $auth['password']); + $headers[] = 'Authorization: Basic '.$authStr; + $authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"'; + } + + if ($authenticationDisplayMessage && (!isset($this->displayedOriginAuthentications[$origin]) || $this->displayedOriginAuthentications[$origin] !== $authenticationDisplayMessage)) { + $this->io->writeError($authenticationDisplayMessage, true, IOInterface::DEBUG); + $this->displayedOriginAuthentications[$origin] = $authenticationDisplayMessage; + } + } elseif (in_array($origin, ['api.bitbucket.org', 'api.github.com'], true)) { + return $this->addAuthenticationOptions($options, str_replace('api.', '', $origin), $url); + } + + return $options; + } + + /** + * @link https://github.com/composer/composer/issues/5584 + * + * @param string $urlToBitBucketFile URL to a file at bitbucket.org. + * + * @return bool Whether the given URL is a public BitBucket download which requires no authentication. + */ + public function isPublicBitBucketDownload(string $urlToBitBucketFile): bool + { + $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST); + if (strpos($domain, 'bitbucket.org') === false) { + // Bitbucket downloads are hosted on amazonaws. + // We do not need to authenticate there at all + return true; + } + + $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); + + // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} + // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} + $pathParts = explode('/', $path); + + return count($pathParts) >= 4 && $pathParts[3] === 'downloads'; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Bitbucket.php b/vendor/composer/composer/src/Composer/Util/Bitbucket.php new file mode 100644 index 000000000..15743a7e3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Bitbucket.php @@ -0,0 +1,257 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\Downloader\TransportException; + +/** + * @author Paul Wenke + */ +class Bitbucket +{ + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var ProcessExecutor */ + private $process; + /** @var HttpDownloader */ + private $httpDownloader; + /** @var array{access_token: string, expires_in?: int}|null */ + private $token = null; + /** @var int|null */ + private $time; + + public const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token'; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The composer configuration + * @param ProcessExecutor $process Process instance, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking + * @param int $time Timestamp, injectable for mocking + */ + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null, ?int $time = null) + { + $this->io = $io; + $this->config = $config; + $this->process = $process ?: new ProcessExecutor($io); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); + $this->time = $time; + } + + public function getToken(): string + { + if (!isset($this->token['access_token'])) { + return ''; + } + + return $this->token['access_token']; + } + + /** + * Attempts to authorize a Bitbucket domain via OAuth + * + * @param string $originUrl The host this Bitbucket instance is located at + * @return bool true on success + */ + public function authorizeOAuth(string $originUrl): bool + { + if ($originUrl !== 'bitbucket.org') { + return false; + } + + // if available use token from git config + if (0 === $this->process->execute(['git', 'config', 'bitbucket.accesstoken'], $output)) { + $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output)); + + return true; + } + + return false; + } + + private function requestAccessToken(): bool + { + try { + $response = $this->httpDownloader->get(self::OAUTH2_ACCESS_TOKEN_URL, [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'content' => 'grant_type=client_credentials', + ], + ]); + + $token = $response->decodeJson(); + if (!isset($token['expires_in']) || !isset($token['access_token'])) { + throw new \LogicException('Expected a token configured with expires_in and access_token present, got '.json_encode($token)); + } + + $this->token = $token; + } catch (TransportException $e) { + if ($e->getCode() === 400) { + $this->io->writeError('Invalid OAuth consumer provided.'); + $this->io->writeError('This can have three reasons:'); + $this->io->writeError('1. You are authenticating with a bitbucket username/password combination'); + $this->io->writeError('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url'); + $this->io->writeError('3. You are using an OAuth consumer, but didn\'t configure it as private consumer'); + + return false; + } + if (in_array($e->getCode(), [403, 401])) { + $this->io->writeError('Invalid OAuth consumer provided.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); + + return false; + } + + throw $e; + } + + return true; + } + + /** + * Authorizes a Bitbucket domain interactively via OAuth + * + * @param string $originUrl The host this Bitbucket instance is located at + * @param string $message The reason this authorization is required + * @throws \RuntimeException + * @throws TransportException|\Exception + * @return bool true on success + */ + public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool + { + if ($message) { + $this->io->writeError($message); + } + + $localAuthConfig = $this->config->getLocalAuthConfigSource(); + $url = 'https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/'; + $this->io->writeError('Follow the instructions here:'); + $this->io->writeError($url); + $this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); + $this->io->writeError('Ensure you enter a "Callback URL" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)'); + + $storeInLocalAuthConfig = false; + if ($localAuthConfig !== null) { + $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); + } + + $consumerKey = trim((string) $this->io->askAndHideAnswer('Consumer Key (hidden): ')); + + if (!$consumerKey) { + $this->io->writeError('No consumer key given, aborting.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); + + return false; + } + + $consumerSecret = trim((string) $this->io->askAndHideAnswer('Consumer Secret (hidden): ')); + + if (!$consumerSecret) { + $this->io->writeError('No consumer secret given, aborting.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); + + return false; + } + + $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); + + if (!$this->requestAccessToken()) { + return false; + } + + // store value in user config + $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); + $this->storeInAuthConfig($authConfigSource, $originUrl, $consumerKey, $consumerSecret); + + // Remove conflicting basic auth credentials (if available) + $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl); + + $this->io->writeError('Consumer stored successfully.'); + + return true; + } + + /** + * Retrieves an access token from Bitbucket. + */ + public function requestToken(string $originUrl, string $consumerKey, string $consumerSecret): string + { + if ($this->token !== null || $this->getTokenFromConfig($originUrl)) { + return $this->token['access_token']; + } + + $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); + if (!$this->requestAccessToken()) { + return ''; + } + + $this->storeInAuthConfig($this->config->getLocalAuthConfigSource() ?? $this->config->getAuthConfigSource(), $originUrl, $consumerKey, $consumerSecret); + + if (!isset($this->token['access_token'])) { + throw new \LogicException('Failed to initialize token above'); + } + + return $this->token['access_token']; + } + + /** + * Store the new/updated credentials to the configuration + */ + private function storeInAuthConfig(Config\ConfigSourceInterface $authConfigSource, string $originUrl, string $consumerKey, string $consumerSecret): void + { + $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl); + + if (null === $this->token || !isset($this->token['expires_in'])) { + throw new \LogicException('Expected a token configured with expires_in present, got '.json_encode($this->token)); + } + + $time = null === $this->time ? time() : $this->time; + $consumer = [ + "consumer-key" => $consumerKey, + "consumer-secret" => $consumerSecret, + "access-token" => $this->token['access_token'], + "access-token-expiration" => $time + $this->token['expires_in'], + ]; + + $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer); + } + + /** + * @phpstan-assert-if-true array{access_token: string} $this->token + */ + private function getTokenFromConfig(string $originUrl): bool + { + $authConfig = $this->config->get('bitbucket-oauth'); + + if ( + !isset($authConfig[$originUrl]['access-token'], $authConfig[$originUrl]['access-token-expiration']) + || time() > $authConfig[$originUrl]['access-token-expiration'] + ) { + return false; + } + + $this->token = [ + 'access_token' => $authConfig[$originUrl]['access-token'], + ]; + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ComposerMirror.php b/vendor/composer/composer/src/Composer/Util/ComposerMirror.php new file mode 100644 index 000000000..8f6a4f771 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ComposerMirror.php @@ -0,0 +1,75 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Pcre\Preg; + +/** + * Composer mirror utilities + * + * @author Jordi Boggiano + */ +class ComposerMirror +{ + /** + * @param non-empty-string $mirrorUrl + * @return non-empty-string + */ + public static function processUrl(string $mirrorUrl, string $packageName, string $version, ?string $reference, ?string $type, ?string $prettyVersion = null): string + { + if ($reference) { + $reference = Preg::isMatch('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : hash('md5', $reference); + } + $version = strpos($version, '/') === false ? $version : hash('md5', $version); + + $from = ['%package%', '%version%', '%reference%', '%type%']; + $to = [$packageName, $version, $reference, $type]; + if (null !== $prettyVersion) { + $from[] = '%prettyVersion%'; + $to[] = $prettyVersion; + } + + $url = str_replace($from, $to, $mirrorUrl); + assert($url !== ''); + + return $url; + } + + /** + * @param non-empty-string $mirrorUrl + */ + public static function processGitUrl(string $mirrorUrl, string $packageName, string $url, ?string $type): string + { + if (Preg::isMatch('#^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $url, $match)) { + $url = 'gh-'.$match[1].'/'.$match[2]; + } elseif (Preg::isMatch('#^https://bitbucket\.org/([^/]+)/(.+?)(?:\.git)?/?$#', $url, $match)) { + $url = 'bb-'.$match[1].'/'.$match[2]; + } else { + $url = Preg::replace('{[^a-z0-9_.-]}i', '-', trim($url, '/')); + } + + return str_replace( + ['%package%', '%normalizedUrl%', '%type%'], + [$packageName, $url, $type], + $mirrorUrl + ); + } + + /** + * @param non-empty-string $mirrorUrl + */ + public static function processHgUrl(string $mirrorUrl, string $packageName, string $url, string $type): string + { + return self::processGitUrl($mirrorUrl, $packageName, $url, $type); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ConfigValidator.php b/vendor/composer/composer/src/Composer/Util/ConfigValidator.php new file mode 100644 index 000000000..ac57199ee --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ConfigValidator.php @@ -0,0 +1,235 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Loader\ValidatingArrayLoader; +use Composer\Package\Loader\InvalidPackageException; +use Composer\Json\JsonValidationException; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Spdx\SpdxLicenses; +use Seld\JsonLint\DuplicateKeyException; +use Seld\JsonLint\JsonParser; + +/** + * Validates a composer configuration. + * + * @author Robert Schönthal + * @author Jordi Boggiano + */ +class ConfigValidator +{ + public const CHECK_VERSION = 1; + + /** @var IOInterface */ + private $io; + + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + /** + * Validates the config, and returns the result. + * + * @param string $file The path to the file + * @param int $arrayLoaderValidationFlags Flags for ArrayLoader validation + * @param int $flags Flags for validation + * + * @return array{list, list, list} a triple containing the errors, publishable errors, and warnings + */ + public function validate(string $file, int $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL, int $flags = self::CHECK_VERSION): array + { + $errors = []; + $publishErrors = []; + $warnings = []; + + // validate json schema + $laxValid = false; + $manifest = null; + try { + $json = new JsonFile($file, null, $this->io); + $manifest = $json->read(); + + $json->validateSchema(JsonFile::LAX_SCHEMA); + $laxValid = true; + $json->validateSchema(); + } catch (JsonValidationException $e) { + foreach ($e->getErrors() as $message) { + if ($laxValid) { + $publishErrors[] = $message; + } else { + $errors[] = $message; + } + } + } catch (\Exception $e) { + $errors[] = $e->getMessage(); + + return [$errors, $publishErrors, $warnings]; + } + + if (is_array($manifest)) { + $jsonParser = new JsonParser(); + try { + $jsonParser->parse((string) file_get_contents($file), JsonParser::DETECT_KEY_CONFLICTS); + } catch (DuplicateKeyException $e) { + $details = $e->getDetails(); + $warnings[] = 'Key '.$details['key'].' is a duplicate in '.$file.' at line '.$details['line']; + } + } + + // validate actual data + if (empty($manifest['license'])) { + $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.'; + } else { + $licenses = (array) $manifest['license']; + + // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer + foreach ($licenses as $key => $license) { + if ('proprietary' === $license) { + unset($licenses[$key]); + } + } + + $licenseValidator = new SpdxLicenses(); + foreach ($licenses as $license) { + $spdxLicense = $licenseValidator->getLicenseByIdentifier($license); + if ($spdxLicense && $spdxLicense[3]) { + if (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?\+$}i', $license)) { + $warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, use "'.str_replace('+', '', $license).'-or-later" instead', + $license + ); + } elseif (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?$}i', $license)) { + $warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead', + $license + ); + } else { + $warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/', + $license + ); + } + } + } + } + + if (($flags & self::CHECK_VERSION) && isset($manifest['version'])) { + $warnings[] = 'The version field is present, it is recommended to leave it out if the package is published on Packagist.'; + } + + if (!empty($manifest['name']) && Preg::isMatch('{[A-Z]}', $manifest['name'])) { + $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $manifest['name']); + $suggestName = strtolower($suggestName); + + $publishErrors[] = sprintf( + 'Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.', + $manifest['name'], + $suggestName + ); + } + + if (!empty($manifest['type']) && $manifest['type'] === 'composer-installer') { + $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See https://getcomposer.org/doc/articles/plugins.md for plugin documentation."; + } + + // check for require-dev overrides + if (isset($manifest['require'], $manifest['require-dev'])) { + $requireOverrides = array_intersect_key($manifest['require'], $manifest['require-dev']); + + if (!empty($requireOverrides)) { + $plural = (count($requireOverrides) > 1) ? 'are' : 'is'; + $warnings[] = implode(', ', array_keys($requireOverrides)). " {$plural} required both in require and require-dev, this can lead to unexpected behavior"; + } + } + + // check for meaningless provide/replace satisfying requirements + foreach (['provide', 'replace'] as $linkType) { + if (isset($manifest[$linkType])) { + foreach (['require', 'require-dev'] as $requireType) { + if (isset($manifest[$requireType])) { + foreach ($manifest[$linkType] as $provide => $constraint) { + if (isset($manifest[$requireType][$provide])) { + $warnings[] = 'The package ' . $provide . ' in '.$requireType.' is also listed in '.$linkType.' which satisfies the requirement. Remove it from '.$linkType.' if you wish to install it.'; + } + } + } + } + } + } + + // check for commit references + $require = $manifest['require'] ?? []; + $requireDev = $manifest['require-dev'] ?? []; + $packages = array_merge($require, $requireDev); + foreach ($packages as $package => $version) { + if (Preg::isMatch('/#/', $version)) { + $warnings[] = sprintf( + 'The package "%s" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.', + $package + ); + } + } + + // report scripts-descriptions for non-existent scripts + $scriptsDescriptions = $manifest['scripts-descriptions'] ?? []; + $scripts = $manifest['scripts'] ?? []; + foreach ($scriptsDescriptions as $scriptName => $scriptDescription) { + if (!array_key_exists($scriptName, $scripts)) { + $warnings[] = sprintf( + 'Description for non-existent script "%s" found in "scripts-descriptions"', + $scriptName + ); + } + } + + // report scripts-aliases for non-existent scripts + $scriptAliases = $manifest['scripts-aliases'] ?? []; + foreach ($scriptAliases as $scriptName => $scriptAlias) { + if (!array_key_exists($scriptName, $scripts)) { + $warnings[] = sprintf( + 'Aliases for non-existent script "%s" found in "scripts-aliases"', + $scriptName + ); + } + } + + // check for empty psr-0/psr-4 namespace prefixes + if (isset($manifest['autoload']['psr-0'][''])) { + $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance"; + } + if (isset($manifest['autoload']['psr-4'][''])) { + $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance"; + } + + $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags); + try { + if (!isset($manifest['version'])) { + $manifest['version'] = '1.0.0'; + } + if (!isset($manifest['name'])) { + $manifest['name'] = 'dummy/dummy'; + } + $loader->load($manifest); + } catch (InvalidPackageException $e) { + $errors = array_merge($errors, $e->getErrors()); + } + + $warnings = array_merge($warnings, $loader->getWarnings()); + + return [$errors, $publishErrors, $warnings]; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ErrorHandler.php b/vendor/composer/composer/src/Composer/Util/ErrorHandler.php new file mode 100644 index 000000000..8e383d3cc --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ErrorHandler.php @@ -0,0 +1,121 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; + +/** + * Convert PHP errors into exceptions + * + * @author Artem Lopata + */ +class ErrorHandler +{ + /** @var ?IOInterface */ + private static $io; + + /** @var int<0, 2> */ + private static $hasShownDeprecationNotice = 0; + + /** + * Error handler + * + * @param int $level Level of the error raised + * @param string $message Error message + * @param string $file Filename that the error was raised in + * @param int $line Line number the error was raised at + * + * @static + * @throws \ErrorException + */ + public static function handle(int $level, string $message, string $file, int $line): bool + { + $isDeprecationNotice = $level === E_DEPRECATED || $level === E_USER_DEPRECATED; + + // error code is not included in error_reporting + if (!$isDeprecationNotice && 0 === (error_reporting() & $level)) { + return true; + } + + if (filter_var(ini_get('xdebug.scream'), FILTER_VALIDATE_BOOLEAN)) { + $message .= "\n\nWarning: You have xdebug.scream enabled, the warning above may be". + "\na legitimately suppressed error that you were not supposed to see."; + } + + if (!$isDeprecationNotice) { + // ignore some newly introduced warnings in new php versions until dependencies + // can be fixed as we do not want to abort execution for those + if (in_array($level, [E_WARNING, E_USER_WARNING], true) && str_contains($message, 'should either be used or intentionally ignored by casting it as (void)')) { + self::outputWarning('Ignored new PHP warning but it should be reported and fixed: '.$message.' in '.$file.':'.$line, true); + + return true; + } + + throw new \ErrorException($message, 0, $level, $file, $line); + } + + if (self::$io !== null) { + if (self::$hasShownDeprecationNotice > 0 && !self::$io->isVerbose()) { + if (self::$hasShownDeprecationNotice === 1) { + self::$io->writeError('More deprecation notices were hidden, run again with `-v` to show them.'); + self::$hasShownDeprecationNotice = 2; + } + + return true; + } + self::$hasShownDeprecationNotice = 1; + self::outputWarning('Deprecation Notice: '.$message.' in '.$file.':'.$line); + } + + return true; + } + + /** + * Register error handler. + */ + public static function register(?IOInterface $io = null): void + { + set_error_handler([__CLASS__, 'handle']); + error_reporting(E_ALL); + self::$io = $io; + } + + private static function outputWarning(string $message, bool $outputEvenWithoutIO = false): void + { + if (self::$io !== null) { + self::$io->writeError(''.$message.''); + if (self::$io->isVerbose()) { + self::$io->writeError('Stack trace:'); + self::$io->writeError(array_filter(array_map(static function ($a): ?string { + if (isset($a['line'], $a['file'])) { + return ' '.$a['file'].':'.$a['line'].''; + } + + return null; + }, array_slice(debug_backtrace(), 2)), static function (?string $line) { + return $line !== null; + })); + } + + return; + } + + if ($outputEvenWithoutIO) { + if (defined('STDERR') && is_resource(STDERR)) { + fwrite(STDERR, 'Warning: '.$message.PHP_EOL); + } else { + echo 'Warning: '.$message.PHP_EOL; + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Filesystem.php b/vendor/composer/composer/src/Composer/Util/Filesystem.php new file mode 100644 index 000000000..0fbd2a1e4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Filesystem.php @@ -0,0 +1,970 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Pcre\Preg; +use ErrorException; +use React\Promise\PromiseInterface; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Finder\Finder; + +/** + * @author Jordi Boggiano + * @author Johannes M. Schmitt + */ +class Filesystem +{ + /** @var ?ProcessExecutor */ + private $processExecutor; + + public function __construct(?ProcessExecutor $executor = null) + { + $this->processExecutor = $executor; + } + + /** + * @return bool + */ + public function remove(string $file) + { + if (is_dir($file)) { + return $this->removeDirectory($file); + } + + if (file_exists($file)) { + return $this->unlink($file); + } + + return false; + } + + /** + * Checks if a directory is empty + * + * @return bool + */ + public function isDirEmpty(string $dir) + { + $finder = Finder::create() + ->ignoreVCS(false) + ->ignoreDotFiles(false) + ->depth(0) + ->in($dir); + + return \count($finder) === 0; + } + + /** + * @return void + */ + public function emptyDirectory(string $dir, bool $ensureDirectoryExists = true) + { + if (is_link($dir) && file_exists($dir)) { + $this->unlink($dir); + } + + if ($ensureDirectoryExists) { + $this->ensureDirectoryExists($dir); + } + + if (is_dir($dir)) { + $finder = Finder::create() + ->ignoreVCS(false) + ->ignoreDotFiles(false) + ->depth(0) + ->in($dir); + + foreach ($finder as $path) { + $this->remove((string) $path); + } + } + } + + /** + * Recursively remove a directory + * + * Uses the process component if proc_open is enabled on the PHP + * installation. + * + * @throws \RuntimeException + * @return bool + */ + public function removeDirectory(string $directory) + { + $edgeCaseResult = $this->removeEdgeCases($directory); + if ($edgeCaseResult !== null) { + return $edgeCaseResult; + } + + if (Platform::isWindows()) { + $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)]; + } else { + $cmd = ['rm', '-rf', $directory]; + } + + $result = $this->getProcess()->execute($cmd, $output) === 0; + + // clear stat cache because external processes aren't tracked by the php stat cache + clearstatcache(); + + if ($result && !is_dir($directory)) { + return true; + } + + return $this->removeDirectoryPhp($directory); + } + + /** + * Recursively remove a directory asynchronously + * + * Uses the process component if proc_open is enabled on the PHP + * installation. + * + * @throws \RuntimeException + * @return PromiseInterface + * @phpstan-return PromiseInterface + */ + public function removeDirectoryAsync(string $directory) + { + $edgeCaseResult = $this->removeEdgeCases($directory); + if ($edgeCaseResult !== null) { + return \React\Promise\resolve($edgeCaseResult); + } + + if (Platform::isWindows()) { + $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)]; + } else { + $cmd = ['rm', '-rf', $directory]; + } + + $promise = $this->getProcess()->executeAsync($cmd); + + return $promise->then(function ($process) use ($directory) { + // clear stat cache because external processes aren't tracked by the php stat cache + clearstatcache(); + + if ($process->isSuccessful()) { + if (!is_dir($directory)) { + return \React\Promise\resolve(true); + } + } + + return \React\Promise\resolve($this->removeDirectoryPhp($directory)); + }); + } + + /** + * @return bool|null Returns null, when no edge case was hit. Otherwise a bool whether removal was successful + */ + private function removeEdgeCases(string $directory, bool $fallbackToPhp = true): ?bool + { + if ($this->isSymlinkedDirectory($directory)) { + return $this->unlinkSymlinkedDirectory($directory); + } + + if ($this->isJunction($directory)) { + return $this->removeJunction($directory); + } + + if (is_link($directory)) { + return unlink($directory); + } + + if (!is_dir($directory) || !file_exists($directory)) { + return true; + } + + if (Preg::isMatch('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) { + throw new \RuntimeException('Aborting an attempted deletion of '.$directory.', this was probably not intended, if it is a real use case please report it.'); + } + + if (!\function_exists('proc_open') && $fallbackToPhp) { + return $this->removeDirectoryPhp($directory); + } + + return null; + } + + /** + * Recursively delete directory using PHP iterators. + * + * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files + * before directories, creating a single non-recursive loop + * to delete files/directories in the correct order. + * + * @return bool + */ + public function removeDirectoryPhp(string $directory) + { + $edgeCaseResult = $this->removeEdgeCases($directory, false); + if ($edgeCaseResult !== null) { + return $edgeCaseResult; + } + + try { + $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); + } catch (\UnexpectedValueException $e) { + // re-try once after clearing the stat cache if it failed as it + // sometimes fails without apparent reason, see https://github.com/composer/composer/issues/4009 + clearstatcache(); + usleep(100000); + if (!is_dir($directory)) { + return true; + } + $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); + } + $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($ri as $file) { + if ($file->isDir()) { + $this->rmdir($file->getPathname()); + } else { + $this->unlink($file->getPathname()); + } + } + + // release locks on the directory, see https://github.com/composer/composer/issues/9945 + unset($ri, $it, $file); + + return $this->rmdir($directory); + } + + /** + * @return void + */ + public function ensureDirectoryExists(string $directory) + { + if (!is_dir($directory)) { + if (file_exists($directory)) { + throw new \RuntimeException( + $directory.' exists and is not a directory.' + ); + } + + if (is_link($directory) && !@$this->unlinkImplementation($directory)) { + throw new \RuntimeException('Could not delete symbolic link '.$directory.': '.(error_get_last()['message'] ?? '')); + } + + if (!@mkdir($directory, 0777, true)) { + $e = new \RuntimeException($directory.' does not exist and could not be created: '.(error_get_last()['message'] ?? '')); + + // in pathological cases with paths like path/to/broken-symlink/../foo is_dir will fail to detect path/to/foo + // but normalizing the ../ away first makes it work so we attempt this just in case, and if it still fails we + // report the initial error we had with the original path, and ignore the normalized path exception + // see https://github.com/composer/composer/issues/11864 + $normalized = $this->normalizePath($directory); + if ($normalized !== $directory) { + try { + $this->ensureDirectoryExists($normalized); + + return; + } catch (\Throwable $ignoredEx) { + } + } + + throw $e; + } + } + } + + /** + * Attempts to unlink a file and in case of failure retries after 350ms on windows + * + * @throws \RuntimeException + * @return bool + */ + public function unlink(string $path) + { + $unlinked = @$this->unlinkImplementation($path); + if (!$unlinked) { + // retry after a bit on windows since it tends to be touchy with mass removals + if (Platform::isWindows()) { + usleep(350000); + $unlinked = @$this->unlinkImplementation($path); + } + + if (!$unlinked) { + $error = error_get_last(); + $message = 'Could not delete '.$path.': ' . ($error['message'] ?? ''); + if (Platform::isWindows()) { + $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; + } + + throw new \RuntimeException($message); + } + } + + return true; + } + + /** + * Attempts to rmdir a file and in case of failure retries after 350ms on windows + * + * @throws \RuntimeException + * @return bool + */ + public function rmdir(string $path) + { + $deleted = @rmdir($path); + if (!$deleted) { + // retry after a bit on windows since it tends to be touchy with mass removals + if (Platform::isWindows()) { + usleep(350000); + $deleted = @rmdir($path); + } + + if (!$deleted) { + $error = error_get_last(); + $message = 'Could not delete '.$path.': ' . ($error['message'] ?? ''); + if (Platform::isWindows()) { + $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; + } + + throw new \RuntimeException($message); + } + } + + return true; + } + + /** + * Copy then delete is a non-atomic version of {@link rename}. + * + * Some systems can't rename and also don't have proc_open, + * which requires this solution. + * + * @return void + */ + public function copyThenRemove(string $source, string $target) + { + $this->copy($source, $target); + if (!is_dir($source)) { + $this->unlink($source); + + return; + } + + $this->removeDirectoryPhp($source); + } + + /** + * Copies a file or directory from $source to $target. + * + * @return bool + */ + public function copy(string $source, string $target) + { + // refs https://github.com/composer/composer/issues/11864 + $target = $this->normalizePath($target); + + if (!is_dir($source)) { + try { + return copy($source, $target); + } catch (ErrorException $e) { + // if copy fails we attempt to copy it manually as this can help bypass issues with VirtualBox shared folders + // see https://github.com/composer/composer/issues/12057 + if (str_contains($e->getMessage(), 'Bad address')) { + $sourceHandle = fopen($source, 'r'); + $targetHandle = fopen($target, 'w'); + if (false === $sourceHandle || false === $targetHandle) { + throw $e; + } + while (!feof($sourceHandle)) { + if (false === fwrite($targetHandle, (string) fread($sourceHandle, 1024 * 1024))) { + throw $e; + } + } + fclose($sourceHandle); + fclose($targetHandle); + + return true; + } + throw $e; + } + } + + $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS); + $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST); + $this->ensureDirectoryExists($target); + + $result = true; + foreach ($ri as $file) { + $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathname(); + if ($file->isDir()) { + $this->ensureDirectoryExists($targetPath); + } else { + $result = $result && copy($file->getPathname(), $targetPath); + } + } + + return $result; + } + + /** + * @return void + */ + public function rename(string $source, string $target) + { + if (true === @rename($source, $target)) { + return; + } + + if (!\function_exists('proc_open')) { + $this->copyThenRemove($source, $target); + + return; + } + + if (Platform::isWindows()) { + // Try to copy & delete - this is a workaround for random "Access denied" errors. + $result = $this->getProcess()->execute(['xcopy', $source, $target, '/E', '/I', '/Q', '/Y'], $output); + + // clear stat cache because external processes aren't tracked by the php stat cache + clearstatcache(); + + if (0 === $result) { + $this->remove($source); + + return; + } + } else { + // We do not use PHP's "rename" function here since it does not support + // the case where $source, and $target are located on different partitions. + $result = $this->getProcess()->execute(['mv', $source, $target], $output); + + // clear stat cache because external processes aren't tracked by the php stat cache + clearstatcache(); + + if (0 === $result) { + return; + } + } + + $this->copyThenRemove($source, $target); + } + + /** + * Returns the shortest path from $from to $to + * + * @param bool $directories if true, the source/target are considered to be directories + * @param bool $preferRelative if true, relative paths will be preferred even if longer + * @throws \InvalidArgumentException + * @return string + */ + public function findShortestPath(string $from, string $to, bool $directories = false, bool $preferRelative = false) + { + if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { + throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); + } + + $from = $this->normalizePath($from); + $to = $this->normalizePath($to); + + if ($directories) { + $from = rtrim($from, '/') . '/dummy_file'; + } + + if (\dirname($from) === \dirname($to)) { + return './'.basename($to); + } + + $commonPath = $to; + while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath)) { + $commonPath = strtr(\dirname($commonPath), '\\', '/'); + } + + // no commonality at all + if (0 !== strpos($from, $commonPath)) { + return $to; + } + + $commonPath = rtrim($commonPath, '/') . '/'; + $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/'); + $commonPathCode = str_repeat('../', $sourcePathDepth); + + // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups + if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) { + return $to; + } + + $result = $commonPathCode . substr($to, \strlen($commonPath)); + if (\strlen($result) === 0) { + return './'; + } + + return $result; + } + + /** + * Returns PHP code that, when executed in $from, will return the path to $to + * + * @param bool $directories if true, the source/target are considered to be directories + * @param bool $preferRelative if true, relative paths will be preferred even if longer + * @throws \InvalidArgumentException + * @return string + */ + public function findShortestPathCode(string $from, string $to, bool $directories = false, bool $staticCode = false, bool $preferRelative = false) + { + if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { + throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); + } + + $from = $this->normalizePath($from); + $to = $this->normalizePath($to); + + if ($from === $to) { + return $directories ? '__DIR__' : '__FILE__'; + } + + $commonPath = $to; + while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath) && '.' !== $commonPath) { + $commonPath = strtr(\dirname($commonPath), '\\', '/'); + } + + // no commonality at all + if (0 !== strpos($from, $commonPath) || '.' === $commonPath) { + return var_export($to, true); + } + + $commonPath = rtrim($commonPath, '/') . '/'; + if (str_starts_with($to, $from.'/')) { + return '__DIR__ . '.var_export((string) substr($to, \strlen($from)), true); + } + $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/') + (int) $directories; + + // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups + if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) { + return var_export($to, true); + } + + if ($staticCode) { + $commonPathCode = "__DIR__ . '".str_repeat('/..', $sourcePathDepth)."'"; + } else { + $commonPathCode = str_repeat('dirname(', $sourcePathDepth).'__DIR__'.str_repeat(')', $sourcePathDepth); + } + $relTarget = (string) substr($to, \strlen($commonPath)); + + return $commonPathCode . (\strlen($relTarget) > 0 ? '.' . var_export('/' . $relTarget, true) : ''); + } + + /** + * Checks if the given path is absolute + * + * @return bool + */ + public function isAbsolutePath(string $path) + { + return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0; + } + + /** + * Returns size of a file or directory specified by path. If a directory is + * given, its size will be computed recursively. + * + * @param string $path Path to the file or directory + * @throws \RuntimeException + * @return int + */ + public function size(string $path) + { + if (!file_exists($path)) { + throw new \RuntimeException("$path does not exist."); + } + if (is_dir($path)) { + return $this->directorySize($path); + } + + return (int) filesize($path); + } + + /** + * Normalize a path. This replaces backslashes with slashes, removes ending + * slash and collapses redundant separators and up-level references. + * + * @param string $path Path to the file or directory + * @return string + */ + public function normalizePath(string $path) + { + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = ''; + + // extract windows UNC paths e.g. \\foo\bar + if (strpos($path, '//') === 0 && \strlen($path) > 2) { + $absolute = '//'; + $path = substr($path, 2); + } + + // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: + if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, \strlen($prefix)); + } + + if (strpos($path, '/') === 0) { + $absolute = '/'; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) { + array_pop($parts); + $up = !(\count($parts) === 0 || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + // ensure c: is normalized to C: + $prefix = Preg::replaceCallback('{(^|://)[a-z]:$}i', static function (array $m) { + return strtoupper($m[0]); + }, $prefix); + + return $prefix.$absolute.implode('/', $parts); + } + + /** + * Remove trailing slashes if present to avoid issues with symlinks + * + * And other possible unforeseen disasters, see https://github.com/composer/composer/pull/9422 + * + * @return string + */ + public static function trimTrailingSlash(string $path) + { + if (!Preg::isMatch('{^[/\\\\]+$}', $path)) { + $path = rtrim($path, '/\\'); + } + + return $path; + } + + /** + * Return if the given path is local + * + * @return bool + */ + public static function isLocalPath(string $path) + { + // on windows, \\foo indicates network paths so we exclude those from local paths, however it is unsafe + // on linux as file:////foo (which would be a network path \\foo on windows) will resolve to /foo which could be a local path + if (Platform::isWindows()) { + return Preg::isMatch('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); + } + + return Preg::isMatch('{^(file://|/|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); + } + + /** + * @return string + */ + public static function getPlatformPath(string $path) + { + if (Platform::isWindows()) { + $path = Preg::replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path); + } + + return Preg::replace('{^file://}i', '', $path); + } + + /** + * Cross-platform safe version of is_readable() + * + * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts + * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + * + * @return bool + */ + public static function isReadable(string $path) + { + if (is_readable($path)) { + return true; + } + + if (is_file($path)) { + return false !== Silencer::call('file_get_contents', $path, false, null, 0, 1); + } + + if (is_dir($path)) { + return false !== Silencer::call('opendir', $path); + } + + // assume false otherwise + return false; + } + + /** + * @return int + */ + protected function directorySize(string $directory) + { + $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); + $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); + + $size = 0; + foreach ($ri as $file) { + if ($file->isFile()) { + $size += $file->getSize(); + } + } + + return $size; + } + + /** + * @return ProcessExecutor + */ + protected function getProcess() + { + if (null === $this->processExecutor) { + $this->processExecutor = new ProcessExecutor(); + } + + return $this->processExecutor; + } + + /** + * delete symbolic link implementation (commonly known as "unlink()") + * + * symbolic links on windows which link to directories need rmdir instead of unlink + */ + private function unlinkImplementation(string $path): bool + { + if (Platform::isWindows() && is_dir($path) && is_link($path)) { + return rmdir($path); + } + + return unlink($path); + } + + /** + * Creates a relative symlink from $link to $target + * + * @param string $target The path of the binary file to be symlinked + * @param string $link The path where the symlink should be created + * @return bool + */ + public function relativeSymlink(string $target, string $link) + { + if (!function_exists('symlink')) { + return false; + } + + $cwd = Platform::getCwd(); + + $relativePath = $this->findShortestPath($link, $target); + chdir(\dirname($link)); + $result = @symlink($relativePath, $link); + + chdir($cwd); + + return $result; + } + + /** + * return true if that directory is a symlink. + * + * @return bool + */ + public function isSymlinkedDirectory(string $directory) + { + if (!is_dir($directory)) { + return false; + } + + $resolved = $this->resolveSymlinkedDirectorySymlink($directory); + + return is_link($resolved); + } + + private function unlinkSymlinkedDirectory(string $directory): bool + { + $resolved = $this->resolveSymlinkedDirectorySymlink($directory); + + return $this->unlink($resolved); + } + + /** + * resolve pathname to symbolic link of a directory + * + * @param string $pathname directory path to resolve + * + * @return string resolved path to symbolic link or original pathname (unresolved) + */ + private function resolveSymlinkedDirectorySymlink(string $pathname): string + { + if (!is_dir($pathname)) { + return $pathname; + } + + $resolved = rtrim($pathname, '/'); + + if (0 === \strlen($resolved)) { + return $pathname; + } + + return $resolved; + } + + /** + * Creates an NTFS junction. + * + * @return void + */ + public function junction(string $target, string $junction) + { + if (!Platform::isWindows()) { + throw new \LogicException(sprintf('Function %s is not available on non-Windows platform', __CLASS__)); + } + if (!is_dir($target)) { + throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target); + } + + // Removing any previously junction to ensure clean execution. + if (!is_dir($junction) || $this->isJunction($junction)) { + @rmdir($junction); + } + + $cmd = ['mklink', '/J', str_replace('/', DIRECTORY_SEPARATOR, $junction), Platform::realpath($target)]; + if ($this->getProcess()->execute($cmd, $output) !== 0) { + throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target); + } + clearstatcache(true, $junction); + } + + /** + * Returns whether the target directory is a Windows NTFS Junction. + * + * We test if the path is a directory and not an ordinary link, then check + * that the mode value returned from lstat (which gives the status of the + * link itself) is not a directory, by replicating the POSIX S_ISDIR test. + * + * This logic works because PHP does not set the mode value for a junction, + * since there is no universal file type flag for it. Unfortunately an + * uninitialized variable in PHP prior to 7.2.16 and 7.3.3 may cause a + * random value to be returned. See https://bugs.php.net/bug.php?id=77552 + * + * If this random value passes the S_ISDIR test, then a junction will not be + * detected and a recursive delete operation could lead to loss of data in + * the target directory. Note that Windows rmdir can handle this situation + * and will only delete the junction (from Windows 7 onwards). + * + * @param string $junction Path to check. + * @return bool + */ + public function isJunction(string $junction) + { + if (!Platform::isWindows()) { + return false; + } + + // Important to clear all caches first + clearstatcache(true, $junction); + + if (!is_dir($junction) || is_link($junction)) { + return false; + } + + $stat = lstat($junction); + + // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask) + return is_array($stat) ? 0x4000 !== ($stat['mode'] & 0xF000) : false; + } + + /** + * Removes a Windows NTFS junction. + * + * @return bool + */ + public function removeJunction(string $junction) + { + if (!Platform::isWindows()) { + return false; + } + $junction = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR); + if (!$this->isJunction($junction)) { + throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction)); + } + + return $this->rmdir($junction); + } + + /** + * @return int|false + */ + public function filePutContentsIfModified(string $path, string $content) + { + $currentContent = Silencer::call('file_get_contents', $path); + if (false === $currentContent || $currentContent !== $content) { + return file_put_contents($path, $content); + } + + return 0; + } + + /** + * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463 + */ + public function safeCopy(string $source, string $target): void + { + if (!file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source, $target)) { + $sourceHandle = fopen($source, 'r'); + assert($sourceHandle !== false, 'Could not open "'.$source.'" for reading.'); + $targetHandle = fopen($target, 'w+'); + assert($targetHandle !== false, 'Could not open "'.$target.'" for writing.'); + + stream_copy_to_stream($sourceHandle, $targetHandle); + fclose($sourceHandle); + fclose($targetHandle); + + touch($target, (int) filemtime($source), (int) fileatime($source)); + } + } + + /** + * compare 2 files + * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files + */ + private function filesAreEqual(string $a, string $b): bool + { + // Check if filesize is different + if (filesize($a) !== filesize($b)) { + return false; + } + + // Check if content is different + $aHandle = fopen($a, 'rb'); + assert($aHandle !== false, 'Could not open "'.$a.'" for reading.'); + $bHandle = fopen($b, 'rb'); + assert($bHandle !== false, 'Could not open "'.$b.'" for reading.'); + + $result = true; + while (!feof($aHandle)) { + if (fread($aHandle, 8192) !== fread($bHandle, 8192)) { + $result = false; + break; + } + } + + fclose($aHandle); + fclose($bHandle); + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Forgejo.php b/vendor/composer/composer/src/Composer/Util/Forgejo.php new file mode 100644 index 000000000..a8c3132c2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Forgejo.php @@ -0,0 +1,103 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\Downloader\TransportException; +use Composer\IO\IOInterface; + +/** + * @internal + * @readonly + */ +final class Forgejo +{ + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var HttpDownloader */ + private $httpDownloader; + + public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader) + { + $this->io = $io; + $this->config = $config; + $this->httpDownloader = $httpDownloader; + } + + /** + * Authorizes a Forgejo domain interactively + * + * @param string $originUrl The host this Forgejo instance is located at + * @param string $message The reason this authorization is required + * @throws \RuntimeException + * @throws TransportException|\Exception + * @return bool true on success + */ + public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool + { + if ($message !== null) { + $this->io->writeError($message); + } + + $url = 'https://'.$originUrl.'/user/settings/applications'; + $this->io->writeError('Setup a personal access token with repository:read permissions on:'); + $this->io->writeError($url); + $localAuthConfig = $this->config->getLocalAuthConfigSource(); + $this->io->writeError(sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); + $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#forgejo-token'); + + $storeInLocalAuthConfig = false; + if ($localAuthConfig !== null) { + $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); + } + + $username = trim((string) $this->io->ask('Username: ')); + $token = trim((string) $this->io->askAndHideAnswer('Token (hidden): ')); + + $addTokenManually = sprintf('You can also add it manually later by using "composer config --global --auth forgejo-token.%s "', $originUrl); + if ($token === '' || $username === '') { + $this->io->writeError('No username/token given, aborting.'); + $this->io->writeError($addTokenManually); + + return false; + } + + $this->io->setAuthentication($originUrl, $username, $token); + + try { + $this->httpDownloader->get('https://'. $originUrl . '/api/v1/version', [ + 'retry-auth-failure' => false, + ]); + } catch (TransportException $e) { + if (in_array($e->getCode(), [403, 401, 404], true)) { + $this->io->writeError('Invalid access token provided.'); + $this->io->writeError($addTokenManually); + + return false; + } + + throw $e; + } + + // store value in local/user config + $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); + $this->config->getConfigSource()->removeConfigSetting('forgejo-token.'.$originUrl); + $authConfigSource->addConfigSetting('forgejo-token.'.$originUrl, ['username' => $username, 'token' => $token]); + + $this->io->writeError('Token stored successfully.'); + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ForgejoRepositoryData.php b/vendor/composer/composer/src/Composer/Util/ForgejoRepositoryData.php new file mode 100644 index 000000000..6a442898f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ForgejoRepositoryData.php @@ -0,0 +1,69 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @internal + * @readonly + */ +final class ForgejoRepositoryData +{ + /** @var string */ + public $htmlUrl; + /** @var string */ + public $sshUrl; + /** @var string */ + public $httpCloneUrl; + /** @var bool */ + public $isPrivate; + /** @var string */ + public $defaultBranch; + /** @var bool */ + public $hasIssues; + /** @var bool */ + public $isArchived; + + public function __construct( + string $htmlUrl, + string $httpCloneUrl, + string $sshUrl, + bool $isPrivate, + string $defaultBranch, + bool $hasIssues, + bool $isArchived + ) { + $this->htmlUrl = $htmlUrl; + $this->httpCloneUrl = $httpCloneUrl; + $this->sshUrl = $sshUrl; + $this->isPrivate = $isPrivate; + $this->defaultBranch = $defaultBranch; + $this->hasIssues = $hasIssues; + $this->isArchived = $isArchived; + } + + /** + * @param array $data + */ + public static function fromRemoteData(array $data): self + { + return new self( + $data['html_url'], + $data['clone_url'], + $data['ssh_url'], + $data['private'], + $data['default_branch'], + $data['has_issues'], + $data['archived'] + ); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ForgejoUrl.php b/vendor/composer/composer/src/Composer/Util/ForgejoUrl.php new file mode 100644 index 000000000..d99d3008c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ForgejoUrl.php @@ -0,0 +1,77 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Pcre\Preg; + +/** + * @internal + * @readonly + */ +final class ForgejoUrl +{ + public const URL_REGEX = '{^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$}'; + + /** @var string */ + public $owner; + /** @var string */ + public $repository; + /** @var string */ + public $originUrl; + /** @var string */ + public $apiUrl; + + private function __construct( + string $owner, + string $repository, + string $originUrl, + string $apiUrl + ) { + $this->owner = $owner; + $this->repository = $repository; + $this->originUrl = $originUrl; + $this->apiUrl = $apiUrl; + } + + public static function create(string $repoUrl): self + { + $url = self::tryFrom($repoUrl); + if ($url !== null) { + return $url; + } + + throw new \InvalidArgumentException('This is not a valid Forgejo URL: ' . $repoUrl); + } + + public static function tryFrom(?string $repoUrl): ?self + { + if ($repoUrl === null || !Preg::isMatch(self::URL_REGEX, $repoUrl, $match)) { + return null; + } + + $originUrl = strtolower($match[1] ?? (string) $match[2]); + $apiBase = $originUrl . '/api/v1'; + + return new self( + $match[3], + $match[4], + $originUrl, + sprintf('https://%s/repos/%s/%s', $apiBase, $match[3], $match[4]) + ); + } + + public function generateSshUrl(): string + { + return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Git.php b/vendor/composer/composer/src/Composer/Util/Git.php new file mode 100644 index 000000000..8c64b4b24 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Git.php @@ -0,0 +1,693 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class Git +{ + /** @var string|false|null */ + private static $version = false; + + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var Filesystem */ + protected $filesystem; + /** @var HttpDownloader */ + protected $httpDownloader; + + public function __construct(IOInterface $io, Config $config, ProcessExecutor $process, Filesystem $fs) + { + $this->io = $io; + $this->config = $config; + $this->process = $process; + $this->filesystem = $fs; + } + + /** + * @param IOInterface|null $io If present, a warning is output there instead of throwing, so pass this in only for cases where this is a soft failure + */ + public static function checkForRepoOwnershipError(string $output, string $path, ?IOInterface $io = null): void + { + if (str_contains($output, 'fatal: detected dubious ownership')) { + $msg = 'The repository at "' . $path . '" does not have the correct ownership and git refuses to use it:' . PHP_EOL . PHP_EOL . $output; + if ($io === null) { + throw new \RuntimeException($msg); + } + $io->writeError(''.$msg.''); + } + } + + public function setHttpDownloader(HttpDownloader $httpDownloader): void + { + $this->httpDownloader = $httpDownloader; + } + + /** + * Runs a set of commands using the $url or a variation of it (with auth, ssh, ..) + * + * Commands should use %url% placeholders for the URL instead of inlining it to allow this function to do its job + * %sanitizedUrl% is also automatically replaced by the url without user/pass + * + * As soon as a single command fails it will halt, so assume the commands are run as && in bash + * + * @param non-empty-array> $commands + * @param mixed $commandOutput the output will be written into this var if passed by ref + * if a callable is passed it will be used as output handler + */ + public function runCommands(array $commands, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void + { + $callables = []; + foreach ($commands as $cmd) { + $callables[] = static function (string $url) use ($cmd): array { + $map = [ + '%url%' => $url, + '%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url), + ]; + + return array_map(static function ($value) use ($map): string { + return $map[$value] ?? $value; + }, $cmd); + }; + } + + // @phpstan-ignore method.deprecated + $this->runCommand($callables, $url, $cwd, $initialClone, $commandOutput); + } + + /** + * @param callable|array $commandCallable + * @param mixed $commandOutput the output will be written into this var if passed by ref + * if a callable is passed it will be used as output handler + * @deprecated Use runCommands with placeholders instead of callbacks for simplicity + */ + public function runCommand($commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void + { + $commandCallables = is_callable($commandCallable) ? [$commandCallable] : $commandCallable; + $lastCommand = ''; + + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($url, $this->io); + + if ($initialClone) { + $origCwd = $cwd; + } + + $runCommands = function ($url) use ($commandCallables, $cwd, &$commandOutput, &$lastCommand, $initialClone) { + $collectOutputs = !is_callable($commandOutput); + $outputs = []; + + $status = 0; + $counter = 0; + foreach ($commandCallables as $callable) { + $lastCommand = $callable($url); + if ($collectOutputs) { + $outputs[] = ''; + $output = &$outputs[count($outputs) - 1]; + } else { + $output = &$commandOutput; + } + $status = $this->process->execute($lastCommand, $output, $initialClone && $counter === 0 ? null : $cwd); + if ($status !== 0) { + break; + } + $counter++; + } + + if ($collectOutputs) { + $commandOutput = implode('', $outputs); + } + + return $status; + }; + + if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) { + throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); + } + + if (!$initialClone) { + // capture username/password from URL if there is one and we have no auth configured yet + $this->process->execute(['git', 'remote', '-v'], $output, $cwd); + if (Preg::isMatchStrictGroups('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) { + $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2])); + } + } + + $protocols = $this->config->get('github-protocols'); + // public github, autoswitch protocols + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + if (Preg::isMatchStrictGroups('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) { + $messages = []; + foreach ($protocols as $protocol) { + if ('ssh' === $protocol) { + $protoUrl = "git@" . $match[1] . ":" . $match[2]; + } else { + $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2]; + } + + if (0 === $runCommands($protoUrl)) { + return; + } + $messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput()); + + if ($initialClone && isset($origCwd)) { + $this->filesystem->removeDirectory($origCwd); + } + } + + // failed to checkout, first check git accessibility + if (!$this->io->hasAuthentication($match[1]) && !$this->io->isInteractive()) { + $this->throwException('Failed to clone ' . $url . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url); + } + } + + // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https + $bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true); + + $auth = null; + $credentials = []; + if ($bypassSshForGitHub || 0 !== $runCommands($url)) { + $errorMsg = $this->process->getErrorOutput(); + // private github repository without ssh key access, try https with auth + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + if (Preg::isMatchStrictGroups('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match) + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + || Preg::isMatchStrictGroups('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*?)(?:\.git)?$}i', $url, $match) + ) { + if (!$this->io->hasAuthentication($match[1])) { + $gitHubUtil = new GitHub($this->io, $this->config, $this->process); + $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; + + if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { + $gitHubUtil->authorizeOAuthInteractively($match[1], $message); + } + } + + if ($this->io->hasAuthentication($match[1])) { + $auth = $this->io->getAuthentication($match[1]); + $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; + if (0 === $runCommands($authUrl)) { + return; + } + + $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; + $errorMsg = $this->process->getErrorOutput(); + } + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + } elseif ( + Preg::isMatchStrictGroups('{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match) + || Preg::isMatchStrictGroups('{^(git)@(bitbucket\.org):(.+?\.git)$}i', $url, $match) + ) { //bitbucket either through oauth or app password, with fallback to ssh. + $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader); + + $domain = $match[2]; + $repo_with_git_part = $match[3]; + if (!str_ends_with($repo_with_git_part, '.git')) { + $repo_with_git_part .= '.git'; + } + if (!$this->io->hasAuthentication($domain)) { + $message = 'Enter your Bitbucket credentials to access private repos'; + + if (!$bitbucketUtil->authorizeOAuth($domain) && $this->io->isInteractive()) { + $bitbucketUtil->authorizeOAuthInteractively($domain, $message); + $accessToken = $bitbucketUtil->getToken(); + $this->io->setAuthentication($domain, 'x-token-auth', $accessToken); + } + } + + // First we try to authenticate with whatever we have stored. + // This will be successful if there is for example an app + // password in there. + if ($this->io->hasAuthentication($domain)) { + $auth = $this->io->getAuthentication($domain); + + // Bitbucket API tokens use the email address as the username for HTTP API calls and + // either the Bitbucket username or 'x-bitbucket-api-token-auth' as the username for git operations. + if (strpos((string) $auth['password'], 'ATAT') === 0) { + $auth['username'] = 'x-bitbucket-api-token-auth'; + } + + $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; + + if (0 === $runCommands($authUrl)) { + // Well if that succeeded on our first try, let's just + // take the win. + return; + } + + //We already have an access_token from a previous request. + if ($auth['username'] !== 'x-token-auth') { + $accessToken = $bitbucketUtil->requestToken($domain, $auth['username'], $auth['password']); + if (!empty($accessToken)) { + $this->io->setAuthentication($domain, 'x-token-auth', $accessToken); + } + } + } + + if ($this->io->hasAuthentication($domain)) { + $auth = $this->io->getAuthentication($domain); + $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; + if (0 === $runCommands($authUrl)) { + return; + } + + $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; + } + //Falling back to ssh + $sshUrl = 'git@bitbucket.org:' . $repo_with_git_part; + $this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.'); + if (0 === $runCommands($sshUrl)) { + return; + } + + $errorMsg = $this->process->getErrorOutput(); + } elseif ( + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + Preg::isMatchStrictGroups('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match) + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + || Preg::isMatchStrictGroups('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}i', $url, $match) + ) { + if ($match[1] === 'git') { + $match[1] = 'https'; + } + + if (!$this->io->hasAuthentication($match[2])) { + $gitLabUtil = new GitLab($this->io, $this->config, $this->process); + $message = 'Cloning failed, enter your GitLab credentials to access private repos'; + + if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) { + $gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message); + } + } + + if ($this->io->hasAuthentication($match[2])) { + $auth = $this->io->getAuthentication($match[2]); + if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') { + $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode((string) $auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password + } else { + $authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3]; + } + + if (0 === $runCommands($authUrl)) { + return; + } + + $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; + $errorMsg = $this->process->getErrorOutput(); + } + } elseif (null !== ($match = $this->getAuthenticationFailure($url))) { // private non-github/gitlab/bitbucket repo that failed to authenticate + if (str_contains($match[2], '@')) { + [$authParts, $match[2]] = explode('@', $match[2], 2); + } + + $storeAuth = false; + if ($this->io->hasAuthentication($match[2])) { + $auth = $this->io->getAuthentication($match[2]); + } elseif ($this->io->isInteractive()) { + $defaultUsername = null; + if (isset($authParts) && $authParts !== '') { + if (str_contains($authParts, ':')) { + [$defaultUsername] = explode(':', $authParts, 2); + } else { + $defaultUsername = $authParts; + } + } + + $this->io->writeError(' Authentication required (' . $match[2] . '):'); + $this->io->writeError('' . trim($errorMsg) . '', true, IOInterface::VERBOSE); + $auth = [ + 'username' => $this->io->ask(' Username: ', $defaultUsername), + 'password' => $this->io->askAndHideAnswer(' Password: '), + ]; + $storeAuth = $this->config->get('store-auths'); + } + + if (null !== $auth) { + $authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3]; + + if (0 === $runCommands($authUrl)) { + $this->io->setAuthentication($match[2], $auth['username'], $auth['password']); + $authHelper = new AuthHelper($this->io, $this->config); + $authHelper->storeAuth($match[2], $storeAuth); + + return; + } + + $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; + $errorMsg = $this->process->getErrorOutput(); + } + } + + if ($initialClone && isset($origCwd)) { + $this->filesystem->removeDirectory($origCwd); + } + + if (\is_array($lastCommand)) { + $lastCommand = implode(' ', $lastCommand); + } + if (count($credentials) > 0) { + $lastCommand = $this->maskCredentials($lastCommand, $credentials); + $errorMsg = $this->maskCredentials($errorMsg, $credentials); + } + $this->throwException('Failed to execute ' . $lastCommand . "\n\n" . $errorMsg, $url); + } + } + + public function syncMirror(string $url, string $dir): bool + { + if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK') && Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') { + $this->io->writeError('Aborting git mirror sync of '.$url.' as network is disabled'); + + return false; + } + + // update the repo if it is a valid git repository + if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') { + try { + $commands = [ + ['git', 'remote', 'set-url', 'origin', '--', '%url%'], + ['git', 'remote', 'update', '--prune', 'origin'], + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ['git', 'gc', '--auto'], + ]; + + $this->runCommands($commands, $url, $dir); + } catch (\Exception $e) { + $this->io->writeError('Sync mirror failed: ' . $e->getMessage() . '', true, IOInterface::DEBUG); + + return false; + } + + return true; + } + self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir); + + // clean up directory and do a fresh clone into it + $this->filesystem->removeDirectory($dir); + + $this->runCommands([['git', 'clone', '--mirror', '--', '%url%', $dir]], $url, $dir, true); + + return true; + } + + public function fetchRefOrSyncMirror(string $url, string $dir, string $ref, ?string $prettyVersion = null): bool + { + if ($this->checkRefIsInMirror($dir, $ref)) { + if (Preg::isMatch('{^[a-f0-9]{40}$}', $ref) && $prettyVersion !== null) { + $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion); + $branches = null; + $tags = null; + if (0 === $this->process->execute(['git', 'branch'], $output, $dir)) { + $branches = $output; + } + if (0 === $this->process->execute(['git', 'tag'], $output, $dir)) { + $tags = $output; + } + + // if the pretty version cannot be found as a branch (nor branch with 'v' in front of the branch as it may have been stripped when generating pretty name), + // nor as a tag, then we sync the mirror as otherwise it will likely fail during install. + // this can occur if a git tag gets created *after* the reference is already put into the cache, as the ref check above will then not sync the new tags + // see https://github.com/composer/composer/discussions/11002 + if (null !== $branches && !Preg::isMatch('{^[\s*]*v?'.preg_quote($branch).'$}m', $branches) + && null !== $tags && !Preg::isMatch('{^[\s*]*'.preg_quote($branch).'$}m', $tags) + ) { + $this->syncMirror($url, $dir); + } + } + + return true; + } + + if ($this->syncMirror($url, $dir)) { + return $this->checkRefIsInMirror($dir, $ref); + } + + return false; + } + + public static function getNoShowSignatureFlag(ProcessExecutor $process): string + { + $gitVersion = self::getVersion($process); + if ($gitVersion !== null && version_compare($gitVersion, '2.10.0-rc0', '>=')) { + return ' --no-show-signature'; + } + + return ''; + } + + /** + * @return list + */ + public static function getNoShowSignatureFlags(ProcessExecutor $process): array + { + $flags = static::getNoShowSignatureFlag($process); + if ('' === $flags) { + return []; + } + + return explode(' ', substr($flags, 1)); + } + + /** + * Checks if git version supports --no-commit-header flag (git 2.33+) + * + * @internal + */ + public static function supportsNoCommitHeaderFlag(ProcessExecutor $process): bool + { + $gitVersion = self::getVersion($process); + + return $gitVersion !== null && version_compare($gitVersion, '2.33.0-rc0', '>='); + } + + /** + * Builds a git rev-list command with --no-commit-header flag when supported (git 2.33+) + * + * @internal + * @param list $arguments Additional arguments for git rev-list + * @return non-empty-list + */ + public static function buildRevListCommand(ProcessExecutor $process, array $arguments): array + { + $command = ['git', 'rev-list']; + if (self::supportsNoCommitHeaderFlag($process)) { + $command[] = '--no-commit-header'; + } + + return array_merge($command, $arguments); + } + + /** + * Parses git rev-list output, removing 'commit ' header lines for git < 2.33. + * + * When --no-commit-header is not available (git < 2.33), git rev-list --format outputs + * "commit " before formatted output. This removes those lines. + * + * @internal + */ + public static function parseRevListOutput(string $output, ProcessExecutor $process): string + { + // If git supports --no-commit-header, output is already clean + if (self::supportsNoCommitHeaderFlag($process)) { + return $output; + } + + // Filter out "commit " lines for older git versions + return Preg::replace('{^commit [a-f0-9]{40}\n?}m', '', $output); + } + + private function checkRefIsInMirror(string $dir, string $ref): bool + { + if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') { + $exitCode = $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $ignoredOutput, $dir); + if ($exitCode === 0) { + return true; + } + } + self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir); + + return false; + } + + /** + * @return array|null + */ + private function getAuthenticationFailure(string $url): ?array + { + if (!Preg::isMatchStrictGroups('{^(https?://)([^/]+)(.*)$}i', $url, $match)) { + return null; + } + + $authFailures = [ + 'fatal: Authentication failed', + 'remote error: Invalid username or password.', + 'error: 401 Unauthorized', + 'fatal: unable to access', + 'fatal: could not read Username', + ]; + + $errorOutput = $this->process->getErrorOutput(); + foreach ($authFailures as $authFailure) { + if (strpos($errorOutput, $authFailure) !== false) { + return $match; + } + } + + return null; + } + + public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPathRepository): ?string + { + if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { + return null; + } + + try { + if ($isLocalPathRepository) { + $this->process->execute(['git', 'remote', 'show', 'origin'], $output, $dir); + } else { + $commands = [ + ['git', 'remote', 'set-url', 'origin', '--', '%url%'], + ['git', 'remote', 'show', 'origin'], + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ]; + + $this->runCommands($commands, $url, $dir, false, $output); + } + + $lines = $this->process->splitLines($output); + foreach ($lines as $line) { + if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches)) { + return $matches[1]; + } + } + } catch (\Exception $e) { + $this->io->writeError('Failed to fetch root identifier from remote: ' . $e->getMessage() . '', true, IOInterface::DEBUG); + } + + return null; + } + + public static function cleanEnv(?ProcessExecutor $process = null): void + { + $gitVersion = self::getVersion($process ?? new ProcessExecutor()); + if ($gitVersion !== null && version_compare($gitVersion, '2.3.0', '>=')) { + // added in git 2.3.0, prevents prompting the user for username/password + if (Platform::getEnv('GIT_TERMINAL_PROMPT') !== '0') { + Platform::putEnv('GIT_TERMINAL_PROMPT', '0'); + } + } else { + // added in git 1.7.1, prevents prompting the user for username/password + if (Platform::getEnv('GIT_ASKPASS') !== 'echo') { + Platform::putEnv('GIT_ASKPASS', 'echo'); + } + } + + // clean up rogue git env vars in case this is running in a git hook + if (Platform::getEnv('GIT_DIR')) { + Platform::clearEnv('GIT_DIR'); + } + if (Platform::getEnv('GIT_WORK_TREE')) { + Platform::clearEnv('GIT_WORK_TREE'); + } + + // Run processes with predictable LANGUAGE + if (Platform::getEnv('LANGUAGE') !== 'C') { + Platform::putEnv('LANGUAGE', 'C'); + } + + // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 + Platform::clearEnv('DYLD_LIBRARY_PATH'); + } + + /** + * @return non-empty-string + */ + public static function getGitHubDomainsRegex(Config $config): string + { + return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')'; + } + + /** + * @return non-empty-string + */ + public static function getGitLabDomainsRegex(Config $config): string + { + return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')'; + } + + /** + * @param non-empty-string $message + * + * @return never + */ + private function throwException($message, string $url): void + { + // git might delete a directory when it fails and php will not know + clearstatcache(); + + if (0 !== $this->process->execute(['git', '--version'], $ignoredOutput)) { + throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); + } + + throw new \RuntimeException(Url::sanitize($message)); + } + + /** + * Retrieves the current git version. + * + * @return string|null The git version number, if present. + */ + public static function getVersion(ProcessExecutor $process): ?string + { + if (false === self::$version) { + self::$version = null; + if (0 === $process->execute(['git', '--version'], $output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) { + self::$version = $matches[1]; + } + } + + return self::$version; + } + + /** + * @param string[] $credentials + */ + private function maskCredentials(string $error, array $credentials): string + { + $maskedCredentials = []; + + foreach ($credentials as $credential) { + if (in_array($credential, ['private-token', 'x-token-auth', 'oauth2', 'gitlab-ci-token', 'x-oauth-basic'])) { + $maskedCredentials[] = $credential; + } elseif (strlen($credential) > 6) { + $maskedCredentials[] = substr($credential, 0, 3) . '...' . substr($credential, -3); + } elseif (strlen($credential) > 3) { + $maskedCredentials[] = substr($credential, 0, 3) . '...'; + } else { + $maskedCredentials[] = 'XXX'; + } + } + + return str_replace($credentials, $maskedCredentials, $error); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/GitHub.php b/vendor/composer/composer/src/Composer/Util/GitHub.php new file mode 100644 index 000000000..eec5939be --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/GitHub.php @@ -0,0 +1,263 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class GitHub +{ + public const GITHUB_TOKEN_REGEX = '{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}'; + + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var HttpDownloader */ + protected $httpDownloader; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The composer configuration + * @param ProcessExecutor $process Process instance, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking + */ + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null) + { + $this->io = $io; + $this->config = $config; + $this->process = $process ?: new ProcessExecutor($io); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); + } + + /** + * Attempts to authorize a GitHub domain via OAuth + * + * @param string $originUrl The host this GitHub instance is located at + * @return bool true on success + */ + public function authorizeOAuth(string $originUrl): bool + { + if (!in_array($originUrl, $this->config->get('github-domains'))) { + return false; + } + + // if available use token from git config + if (0 === $this->process->execute(['git', 'config', 'github.accesstoken'], $output)) { + $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic'); + + return true; + } + + return false; + } + + /** + * Authorizes a GitHub domain interactively via OAuth + * + * @param string $originUrl The host this GitHub instance is located at + * @param string $message The reason this authorization is required + * @throws \RuntimeException + * @throws TransportException|\Exception + * @return bool true on success + */ + public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool + { + if ($message) { + $this->io->writeError($message); + } + + $note = 'Composer'; + if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute(['hostname'], $output)) { + $note .= ' on ' . trim($output); + } + $note .= ' ' . date('Y-m-d Hi'); + + $localAuthConfig = $this->config->getLocalAuthConfigSource(); + + $this->io->writeError([ + 'You need to provide a GitHub access token.', + sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName()), + 'Due to the security risk of tokens being exfiltrated, use tokens with short expiration times and only the minimum permissions necessary.', + '', + 'Carefully consider the following options in order:', + '', + ]); + + $this->io->writeError([ + '1. When you don\'t use \'vcs\' type \'repositories\' in composer.json and do not need to clone source or download dist files', + 'from private GitHub repositories over HTTPS, use a fine-grained token with read-only access to public information.', + 'Use the following URL to create such a token:', + 'https://'.$originUrl.'/settings/personal-access-tokens/new?name=' . str_replace('%20', '+', rawurlencode($note)), + '', + ]); + + $this->io->writeError([ + '2. When all relevant _private_ GitHub repositories belong to a single user or organisation, use a fine-grained token with', + 'repository "content" read-only permissions. You can start with the following URL, but you may need to change the resource owner', + 'to the right user or organisation. Additionally, you can scope permissions down to apply only to selected repositories.', + 'https://'.$originUrl.'/settings/personal-access-tokens/new?contents=read&name=' . str_replace('%20', '+', rawurlencode($note)), + '', + ]); + + $this->io->writeError([ + '3. A "classic" token grants broad permissions on your behalf to all repositories accessible by you.', + 'This may include write permissions, even though not needed by Composer. Use it only when you need to access', + 'private repositories across multiple organisations at the same time and using directory-specific authentication sources', + 'is not an option. You can generate a classic token here:', + 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note)), + '', + ]); + + $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth'); + + $storeInLocalAuthConfig = false; + if ($localAuthConfig !== null) { + $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); + } + + $token = trim((string) $this->io->askAndHideAnswer('Token (hidden): ')); + + if ($token === '') { + $this->io->writeError('No token given, aborting.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); + + return false; + } + + $this->io->setAuthentication($originUrl, $token, 'x-oauth-basic'); + + try { + $apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/'; + + $this->httpDownloader->get('https://'. $apiUrl, [ + 'retry-auth-failure' => false, + ]); + } catch (TransportException $e) { + if (in_array($e->getCode(), [403, 401])) { + $this->io->writeError('Invalid token provided.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); + + return false; + } + + throw $e; + } + + // store value in local/user config + $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); + $this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl); + $authConfigSource->addConfigSetting('github-oauth.'.$originUrl, $token); + + $this->io->writeError('Token stored successfully.'); + + return true; + } + + /** + * Extract rate limit from response. + * + * @param string[] $headers Headers from Composer\Downloader\TransportException. + * + * @return array{limit: int|'?', reset: string} + */ + public function getRateLimit(array $headers): array + { + $rateLimit = [ + 'limit' => '?', + 'reset' => '?', + ]; + + foreach ($headers as $header) { + $header = trim($header); + if (false === stripos($header, 'x-ratelimit-')) { + continue; + } + [$type, $value] = explode(':', $header, 2); + switch (strtolower($type)) { + case 'x-ratelimit-limit': + $rateLimit['limit'] = (int) trim($value); + break; + case 'x-ratelimit-reset': + $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value)); + break; + } + } + + return $rateLimit; + } + + /** + * Extract SSO URL from response. + * + * @param string[] $headers Headers from Composer\Downloader\TransportException. + */ + public function getSsoUrl(array $headers): ?string + { + foreach ($headers as $header) { + $header = trim($header); + if (false === stripos($header, 'x-github-sso: required')) { + continue; + } + if (Preg::isMatch('{\burl=(?P[^\s;]+)}', $header, $match)) { + return $match['url']; + } + } + + return null; + } + + /** + * Finds whether a request failed due to rate limiting + * + * @param string[] $headers Headers from Composer\Downloader\TransportException. + */ + public function isRateLimited(array $headers): bool + { + foreach ($headers as $header) { + if (Preg::isMatch('{^x-ratelimit-remaining: *0$}i', trim($header))) { + return true; + } + } + + return false; + } + + /** + * Finds whether a request failed due to lacking SSO authorization + * + * @see https://docs.github.com/en/rest/overview/other-authentication-methods#authenticating-for-saml-sso + * + * @param string[] $headers Headers from Composer\Downloader\TransportException. + */ + public function requiresSso(array $headers): bool + { + foreach ($headers as $header) { + if (Preg::isMatch('{^x-github-sso: required}i', trim($header))) { + return true; + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/GitLab.php b/vendor/composer/composer/src/Composer/Util/GitLab.php new file mode 100644 index 000000000..b727dd91b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/GitLab.php @@ -0,0 +1,319 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\Factory; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; + +/** + * @author Roshan Gautam + */ +class GitLab +{ + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var HttpDownloader */ + protected $httpDownloader; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The composer configuration + * @param ProcessExecutor $process Process instance, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking + */ + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null) + { + $this->io = $io; + $this->config = $config; + $this->process = $process ?: new ProcessExecutor($io); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); + } + + /** + * Attempts to authorize a GitLab domain via OAuth. + * + * @param string $originUrl The host this GitLab instance is located at + * + * @return bool true on success + */ + public function authorizeOAuth(string $originUrl): bool + { + // before composer 1.9, origin URLs had no port number in them + $bcOriginUrl = Preg::replace('{:\d+}', '', $originUrl); + + if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) { + return false; + } + + // if available use token from git config + if (0 === $this->process->execute(['git', 'config', 'gitlab.accesstoken'], $output)) { + $this->io->setAuthentication($originUrl, trim($output), 'oauth2'); + + return true; + } + + // if available use deploy token from git config + if (0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.user'], $tokenUser) && 0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.token'], $tokenPassword)) { + $this->io->setAuthentication($originUrl, trim($tokenUser), trim($tokenPassword)); + + return true; + } + + // if available use token from composer config + $authTokens = $this->config->get('gitlab-token'); + + if (isset($authTokens[$originUrl])) { + $token = $authTokens[$originUrl]; + } + + if (isset($authTokens[$bcOriginUrl])) { + $token = $authTokens[$bcOriginUrl]; + } + + if (isset($token)) { + $username = is_array($token) ? $token["username"] : $token; + $password = is_array($token) ? $token["token"] : 'private-token'; + + // Composer expects the GitLab token to be stored as username and 'private-token' or 'gitlab-ci-token' to be stored as password + // Detect cases where this is reversed and resolve automatically resolve it + if (in_array($username, ['private-token', 'gitlab-ci-token', 'oauth2'], true)) { + $this->io->setAuthentication($originUrl, $password, $username); + } else { + $this->io->setAuthentication($originUrl, $username, $password); + } + + return true; + } + + return false; + } + + /** + * Authorizes a GitLab domain interactively via OAuth. + * + * @param string $scheme Scheme used in the origin URL + * @param string $originUrl The host this GitLab instance is located at + * @param string $message The reason this authorization is required + * + * @throws \RuntimeException + * @throws TransportException|\Exception + * + * @return bool true on success + */ + public function authorizeOAuthInteractively(string $scheme, string $originUrl, ?string $message = null): bool + { + if ($message) { + $this->io->writeError($message); + } + + $localAuthConfig = $this->config->getLocalAuthConfigSource(); + $personalAccessTokenLink = $scheme.'://'.$originUrl.'/-/user_settings/personal_access_tokens'; + $revokeLink = $scheme.'://'.$originUrl.'/-/user_settings/applications'; + $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); + $this->io->writeError('To revoke access to this token you can visit:'); + $this->io->writeError($revokeLink); + $this->io->writeError('Alternatively you can setup an personal access token on:'); + $this->io->writeError($personalAccessTokenLink); + $this->io->writeError('and store it under "gitlab-token" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.'); + $this->io->writeError('https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token'); + $this->io->writeError('for more details.'); + + $storeInLocalAuthConfig = false; + if ($localAuthConfig !== null) { + $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); + } + + $attemptCounter = 0; + + while ($attemptCounter++ < 5) { + try { + $response = $this->createToken($scheme, $originUrl); + } catch (TransportException $e) { + // 401 is bad credentials, + // 403 is max login attempts exceeded + if (in_array($e->getCode(), [403, 401])) { + if (401 === $e->getCode()) { + $response = json_decode($e->getResponse(), true); + if (isset($response['error']) && $response['error'] === 'invalid_grant') { + $this->io->writeError('Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token'); + } else { + $this->io->writeError('Bad credentials.'); + } + } else { + $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.'); + } + + $this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at:'); + $this->io->writeError($personalAccessTokenLink); + $this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' "'); + + continue; + } + + throw $e; + } + + $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); + + $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); + // store value in user config in auth file + if (isset($response['expires_in'])) { + $authConfigSource->addConfigSetting( + 'gitlab-oauth.'.$originUrl, + [ + 'expires-at' => intval($response['created_at']) + intval($response['expires_in']), + 'refresh-token' => $response['refresh_token'], + 'token' => $response['access_token'], + ] + ); + } else { + $authConfigSource->addConfigSetting('gitlab-oauth.'.$originUrl, $response['access_token']); + } + + return true; + } + + throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.'); + } + + /** + * Authorizes a GitLab domain interactively via OAuth. + * + * @param string $scheme Scheme used in the origin URL + * @param string $originUrl The host this GitLab instance is located at + * + * @throws \RuntimeException + * @throws TransportException|\Exception + * + * @return bool true on success + */ + public function authorizeOAuthRefresh(string $scheme, string $originUrl): bool + { + try { + $response = $this->refreshToken($scheme, $originUrl); + } catch (TransportException $e) { + $this->io->writeError("Couldn't refresh access token: ".$e->getMessage()); + + return false; + } + + $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); + + // store value in user config in auth file + $this->config->getAuthConfigSource()->addConfigSetting( + 'gitlab-oauth.'.$originUrl, + [ + 'expires-at' => intval($response['created_at']) + intval($response['expires_in']), + 'refresh-token' => $response['refresh_token'], + 'token' => $response['access_token'], + ] + ); + + return true; + } + + /** + * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in?: positive-int, created_at: positive-int} + * + * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow + */ + private function createToken(string $scheme, string $originUrl): array + { + $username = $this->io->ask('Username: '); + $password = $this->io->askAndHideAnswer('Password: '); + + $headers = ['Content-Type: application/x-www-form-urlencoded']; + + $apiUrl = $originUrl; + $data = http_build_query([ + 'username' => $username, + 'password' => $password, + 'grant_type' => 'password', + ], '', '&'); + $options = [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'header' => $headers, + 'content' => $data, + ], + ]; + + $token = $this->httpDownloader->get($scheme.'://'.$apiUrl.'/oauth/token', $options)->decodeJson(); + + $this->io->writeError('Token successfully created'); + + return $token; + } + + /** + * Is the OAuth access token expired? + * + * @return bool true on expired token, false if token is fresh or expiration date is not set + */ + public function isOAuthExpired(string $originUrl): bool + { + $authTokens = $this->config->get('gitlab-oauth'); + if (isset($authTokens[$originUrl]['expires-at'])) { + if ($authTokens[$originUrl]['expires-at'] < time()) { + return true; + } + } + + return false; + } + + /** + * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in: positive-int, created_at: positive-int} + * + * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow + */ + private function refreshToken(string $scheme, string $originUrl): array + { + $authTokens = $this->config->get('gitlab-oauth'); + if (!isset($authTokens[$originUrl]['refresh-token'])) { + throw new \RuntimeException('No GitLab refresh token present for '.$originUrl.'.'); + } + + $refreshToken = $authTokens[$originUrl]['refresh-token']; + $headers = ['Content-Type: application/x-www-form-urlencoded']; + + $data = http_build_query([ + 'refresh_token' => $refreshToken, + 'grant_type' => 'refresh_token', + ], '', '&'); + $options = [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'header' => $headers, + 'content' => $data, + ], + ]; + + $token = $this->httpDownloader->get($scheme.'://'.$originUrl.'/oauth/token', $options)->decodeJson(); + $this->io->writeError('GitLab token successfully refreshed', true, IOInterface::VERY_VERBOSE); + $this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/-/user_settings/applications', true, IOInterface::VERY_VERBOSE); + + return $token; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Hg.php b/vendor/composer/composer/src/Composer/Util/Hg.php new file mode 100644 index 000000000..91d0258c9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Hg.php @@ -0,0 +1,121 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; + +/** + * @author Jonas Renaudot + */ +class Hg +{ + /** @var string|false|null */ + private static $version = false; + + /** + * @var IOInterface + */ + private $io; + + /** + * @var Config + */ + private $config; + + /** + * @var ProcessExecutor + */ + private $process; + + public function __construct(IOInterface $io, Config $config, ProcessExecutor $process) + { + $this->io = $io; + $this->config = $config; + $this->process = $process; + } + + public function runCommand(callable $commandCallable, string $url, ?string $cwd): void + { + $this->config->prohibitUrlByConfig($url, $this->io); + + // Try as is + $command = $commandCallable($url); + + if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { + return; + } + + // Try with the authentication information available + if ( + Preg::isMatch('{^(?Pssh|https?)://(?:(?P[^:@]+)(?::(?P[^:@]+))?@)?(?P[^/]+)(?P/.*)?}mi', $url, $matches) + && $this->io->hasAuthentication($matches['host']) + ) { + if ($matches['proto'] === 'ssh') { + $user = ''; + if ($matches['user'] !== null) { + $user = rawurlencode($matches['user']) . '@'; + } + $authenticatedUrl = $matches['proto'] . '://' . $user . $matches['host'] . $matches['path']; + } else { + $auth = $this->io->getAuthentication($matches['host']); + $authenticatedUrl = $matches['proto'] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $matches['host'] . $matches['path']; + } + $command = $commandCallable($authenticatedUrl); + + if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { + return; + } + + $error = $this->process->getErrorOutput(); + } else { + $error = 'The given URL (' .$url. ') does not match the required format (ssh|http(s)://(username:password@)example.com/path-to-repository)'; + } + + $this->throwException("Failed to clone $url, \n\n" . $error, $url); + } + + /** + * @param non-empty-string $message + * + * @return never + */ + private function throwException($message, string $url): void + { + if (null === self::getVersion($this->process)) { + throw new \RuntimeException(Url::sanitize( + 'Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput() + )); + } + + throw new \RuntimeException(Url::sanitize($message)); + } + + /** + * Retrieves the current hg version. + * + * @return string|null The hg version number, if present. + */ + public static function getVersion(ProcessExecutor $process): ?string + { + if (false === self::$version) { + self::$version = null; + if (0 === $process->execute(['hg', '--version'], $output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/', $output, $matches)) { + self::$version = $matches[1]; + } + } + + return self::$version; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/CurlDownloader.php b/vendor/composer/composer/src/Composer/Util/Http/CurlDownloader.php new file mode 100644 index 000000000..d3a59c403 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/CurlDownloader.php @@ -0,0 +1,701 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Config; +use Composer\Downloader\MaxFileSizeExceededException; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Util\Platform; +use Composer\Util\StreamContextFactory; +use Composer\Util\AuthHelper; +use Composer\Util\Url; +use Composer\Util\HttpDownloader; +use React\Promise\Promise; + +/** + * @internal + * @author Jordi Boggiano + * @author Nicolas Grekas + * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null} + * @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable, primaryIp: string} + */ +class CurlDownloader +{ + /** + * Known libcurl's broken versions when proxy is in use with HTTP/2 + * multiplexing. + * + * @var list + */ + private const BAD_MULTIPLEXING_CURL_VERSIONS = ['7.87.0', '7.88.0', '7.88.1']; + + /** @var \CurlMultiHandle */ + private $multiHandle; + /** @var \CurlShareHandle */ + private $shareHandle; + /** @var Job[] */ + private $jobs = []; + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var AuthHelper */ + private $authHelper; + /** @var float */ + private $selectTimeout = 5.0; + /** @var int */ + private $maxRedirects = 20; + /** @var int */ + private $maxRetries = 3; + /** @var array */ + protected $multiErrors = [ + CURLM_BAD_HANDLE => ['CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'], + CURLM_BAD_EASY_HANDLE => ['CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."], + CURLM_OUT_OF_MEMORY => ['CURLM_OUT_OF_MEMORY', 'You are doomed.'], + CURLM_INTERNAL_ERROR => ['CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!'], + ]; + + /** @var mixed[] */ + private static $options = [ + 'http' => [ + 'method' => CURLOPT_CUSTOMREQUEST, + 'content' => CURLOPT_POSTFIELDS, + 'header' => CURLOPT_HTTPHEADER, + 'timeout' => CURLOPT_TIMEOUT, + ], + 'ssl' => [ + 'cafile' => CURLOPT_CAINFO, + 'capath' => CURLOPT_CAPATH, + 'verify_peer' => CURLOPT_SSL_VERIFYPEER, + 'verify_peer_name' => CURLOPT_SSL_VERIFYHOST, + 'local_cert' => CURLOPT_SSLCERT, + 'local_pk' => CURLOPT_SSLKEY, + 'passphrase' => CURLOPT_SSLKEYPASSWD, + ], + ]; + + /** @var array */ + private static $timeInfo = [ + 'total_time' => true, + 'namelookup_time' => true, + 'connect_time' => true, + 'pretransfer_time' => true, + 'starttransfer_time' => true, + 'redirect_time' => true, + ]; + + /** + * @param mixed[] $options + */ + public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false) + { + $this->io = $io; + $this->config = $config; + + $this->multiHandle = $mh = curl_multi_init(); + if (function_exists('curl_multi_setopt')) { + if (ProxyManager::getInstance()->hasProxy() && ($version = curl_version()) !== false && in_array($version['version'], self::BAD_MULTIPLEXING_CURL_VERSIONS, true)) { + /** + * Disable HTTP/2 multiplexing for some broken versions of libcurl. + * + * In certain versions of libcurl when proxy is in use with HTTP/2 + * multiplexing, connections will continue stacking up. This was + * fixed in libcurl 8.0.0 in curl/curl@821f6e2a89de8aec1c7da3c0f381b92b2b801efc + */ + curl_multi_setopt($mh, CURLMOPT_PIPELINING, /* CURLPIPE_NOTHING */ 0); + } else { + curl_multi_setopt($mh, CURLMOPT_PIPELINING, \PHP_VERSION_ID >= 70400 ? /* CURLPIPE_MULTIPLEX */ 2 : /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3); + } + if (defined('CURLMOPT_MAX_HOST_CONNECTIONS') && !defined('HHVM_VERSION')) { + curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 8); + } + } + + if (function_exists('curl_share_init')) { + $this->shareHandle = $sh = curl_share_init(); + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); + } + + $this->authHelper = new AuthHelper($io, $config); + } + + /** + * @param mixed[] $options + * @param non-empty-string $url + * + * @return int internal job id + */ + public function download(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null): int + { + $attributes = []; + if (isset($options['retry-auth-failure'])) { + $attributes['retryAuthFailure'] = $options['retry-auth-failure']; + unset($options['retry-auth-failure']); + } + + return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo, $attributes); + } + + /** + * @param mixed[] $options + * + * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool, ipResolve?: 4|6|null} $attributes + * @param non-empty-string $url + * + * @return int internal job id + */ + private function initDownload(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null, array $attributes = []): int + { + $attributes = array_merge([ + 'retryAuthFailure' => true, + 'redirects' => 0, + 'retries' => 0, + 'storeAuth' => false, + 'ipResolve' => null, + ], $attributes); + + if ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '4') { + $attributes['ipResolve'] = 4; + } elseif ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '6') { + $attributes['ipResolve'] = 6; + } + + $originalOptions = $options; + + // check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 + if (!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $url) || (false === strpos($url, '$') && false === strpos($url, '%24'))) { + $this->config->prohibitUrlByConfig($url, $this->io, $options); + } + + $curlHandle = curl_init(); + $headerHandle = fopen('php://temp/maxmemory:32768', 'w+b'); + if (false === $headerHandle) { + throw new \RuntimeException('Failed to open a temp stream to store curl headers'); + } + + if ($copyTo !== null) { + $bodyTarget = $copyTo.'~'; + } else { + $bodyTarget = 'php://temp/maxmemory:524288'; + } + + $errorMessage = ''; + set_error_handler(static function (int $code, string $msg) use (&$errorMessage): bool { + if ($errorMessage) { + $errorMessage .= "\n"; + } + $errorMessage .= Preg::replace('{^fopen\(.*?\): }', '', $msg); + + return true; + }); + $bodyHandle = fopen($bodyTarget, 'w+b'); + restore_error_handler(); + if (false === $bodyHandle) { + throw new TransportException('The "'.$url.'" file could not be written to '.($copyTo ?? 'a temporary file').': '.$errorMessage); + } + + curl_setopt($curlHandle, CURLOPT_URL, $url); + curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false); + curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curlHandle, CURLOPT_TIMEOUT, max((int) ini_get("default_socket_timeout"), 300)); + curl_setopt($curlHandle, CURLOPT_WRITEHEADER, $headerHandle); + curl_setopt($curlHandle, CURLOPT_FILE, $bodyHandle); + curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports + curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + + if ($attributes['ipResolve'] === 4) { + curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + } elseif ($attributes['ipResolve'] === 6) { + curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); + } + + if (function_exists('curl_share_init')) { + curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle); + } + + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + + $options['http']['header'] = array_diff($options['http']['header'], ['Connection: close']); + $options['http']['header'][] = 'Connection: keep-alive'; + + $version = curl_version(); + $features = $version['features']; + + $proxy = ProxyManager::getInstance()->getProxyForRequest($url); + + if (0 === strpos($url, 'https://')) { + $willUseProxy = $proxy->getStatus() !== '' && !$proxy->isExcludedByNoProxy(); + + if (!$willUseProxy && \defined('CURL_VERSION_HTTP3') && \defined('CURL_HTTP_VERSION_3') && (CURL_VERSION_HTTP3 & $features) !== 0) { + curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3); + } elseif (\defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features) !== 0) { + curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + } + } + + // curl 8.7.0 - 8.7.1 has a bug whereas automatic accept-encoding header results in an error when reading the response + // https://github.com/composer/composer/issues/11913 + if (isset($version['version']) && in_array($version['version'], ['8.7.0', '8.7.1'], true) && \defined('CURL_VERSION_LIBZ') && (CURL_VERSION_LIBZ & $features) !== 0) { + curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip"); + } + + $options = $this->authHelper->addAuthenticationOptions($options, $origin, $url); + $options = StreamContextFactory::initOptions($url, $options, true); + + foreach (self::$options as $type => $curlOptions) { + foreach ($curlOptions as $name => $curlOption) { + if (isset($options[$type][$name])) { + if ($type === 'ssl' && $name === 'verify_peer_name') { + curl_setopt($curlHandle, $curlOption, $options[$type][$name] === true ? 2 : $options[$type][$name]); + } else { + curl_setopt($curlHandle, $curlOption, $options[$type][$name]); + } + } + } + } + + curl_setopt_array($curlHandle, $proxy->getCurlOptions($options['ssl'] ?? [])); + + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); + + $this->jobs[(int) $curlHandle] = [ + 'url' => $url, + 'origin' => $origin, + 'attributes' => $attributes, + 'options' => $originalOptions, + 'progress' => $progress, + 'curlHandle' => $curlHandle, + 'filename' => $copyTo, + 'headerHandle' => $headerHandle, + 'bodyHandle' => $bodyHandle, + 'resolve' => $resolve, + 'reject' => $reject, + 'primaryIp' => '', + ]; + + $usingProxy = $proxy->getStatus(' using proxy (%s)'); + $ifModified = false !== stripos(implode(',', $options['http']['header']), 'if-modified-since:') ? ' if modified' : ''; + if ($attributes['redirects'] === 0 && $attributes['retries'] === 0) { + $this->io->writeError('Downloading ' . Url::sanitize($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG); + } + + $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle)); + // TODO progress + + return (int) $curlHandle; + } + + public function abortRequest(int $id): void + { + if (isset($this->jobs[$id], $this->jobs[$id]['curlHandle'])) { + $job = $this->jobs[$id]; + curl_multi_remove_handle($this->multiHandle, $job['curlHandle']); + if (\PHP_VERSION_ID < 80000) { + curl_close($job['curlHandle']); + } + if (is_resource($job['headerHandle'])) { + fclose($job['headerHandle']); + } + if (is_resource($job['bodyHandle'])) { + fclose($job['bodyHandle']); + } + if (null !== $job['filename']) { + @unlink($job['filename'].'~'); + } + unset($this->jobs[$id]); + } + } + + public function tick(): void + { + static $timeoutWarning = false; + + if (count($this->jobs) === 0) { + return; + } + + $active = true; + $this->checkCurlResult(curl_multi_exec($this->multiHandle, $active)); + if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) { + // sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select + usleep(150); + } + + while ($progress = curl_multi_info_read($this->multiHandle)) { + $curlHandle = $progress['handle']; + $result = $progress['result']; + $i = (int) $curlHandle; + if (!isset($this->jobs[$i])) { + continue; + } + + $progress = curl_getinfo($curlHandle); + if (false === $progress) { + throw new \RuntimeException('Failed getting info from curl handle '.$i.' ('.$this->jobs[$i]['url'].')'); + } + $job = $this->jobs[$i]; + unset($this->jobs[$i]); + $error = curl_error($curlHandle); + $errno = curl_errno($curlHandle); + curl_multi_remove_handle($this->multiHandle, $curlHandle); + if (\PHP_VERSION_ID < 80000) { + curl_close($curlHandle); + } + + $headers = null; + $statusCode = null; + $response = null; + try { + // TODO progress + if (CURLE_OK !== $errno || $error || $result !== CURLE_OK) { + $errno = $errno ?: $result; + if (!$error && function_exists('curl_strerror')) { + $error = curl_strerror($errno); + } + $progress['error_code'] = $errno; + + if ($errno === 28 /* CURLE_OPERATION_TIMEDOUT */ && \PHP_VERSION_ID >= 70300 && $progress['namelookup_time'] === 0.0 && !$timeoutWarning) { + $timeoutWarning = true; + $this->io->writeError('A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.'); + } + + if ( + (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET') + && ( + in_array($errno, [7 /* CURLE_COULDNT_CONNECT */, 16 /* CURLE_HTTP2 */, 92 /* CURLE_HTTP2_STREAM */, 6 /* CURLE_COULDNT_RESOLVE_HOST */, 28 /* CURLE_OPERATION_TIMEDOUT */], true) + || (in_array($errno, [56 /* CURLE_RECV_ERROR */, 35 /* CURLE_SSL_CONNECT_ERROR */], true) && str_contains((string) $error, 'Connection reset by peer')) + ) && $job['attributes']['retries'] < $this->maxRetries + ) { + $attributes = ['retries' => $job['attributes']['retries'] + 1]; + if ($errno === 7 && !isset($job['attributes']['ipResolve'])) { // CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected + $attributes['ipResolve'] = 4; + } + $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG); + $this->restartJobWithDelay($job, $job['url'], $attributes); + continue; + } + + // TODO: Remove this as soon as https://github.com/curl/curl/issues/10591 is resolved + if ($errno === 55 /* CURLE_SEND_ERROR */) { + $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG); + $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]); + continue; + } + + throw new TransportException('curl error '.$errno.' while downloading '.Url::sanitize($progress['url']).': '.$error); + } + $statusCode = $progress['http_code']; + rewind($job['headerHandle']); + $headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle']))); + fclose($job['headerHandle']); + + if ($statusCode === 0) { + throw new \LogicException('Received unexpected http status code 0 without error for '.Url::sanitize($progress['url']).': headers '.var_export($headers, true).' curl info '.var_export($progress, true)); + } + + // prepare response object + if (null !== $job['filename']) { + $contents = $job['filename'].'~'; + if ($statusCode >= 300) { + rewind($job['bodyHandle']); + $contents = stream_get_contents($job['bodyHandle']); + } + $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress); + $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG); + } else { + $maxFileSize = $job['options']['max_file_size'] ?? null; + rewind($job['bodyHandle']); + if ($maxFileSize !== null) { + $contents = stream_get_contents($job['bodyHandle'], $maxFileSize); + // Gzipped responses with missing Content-Length header cannot be detected during the file download + // because $progress['size_download'] refers to the gzipped size downloaded, not the actual file size + if ($contents !== false && Platform::strlen($contents) >= $maxFileSize) { + throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($contents) . ' of allowed ' . $maxFileSize . ' bytes'); + } + } else { + $contents = stream_get_contents($job['bodyHandle']); + } + + $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress); + $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG); + } + fclose($job['bodyHandle']); + + if ($response->getStatusCode() >= 300 && $response->getHeader('content-type') === 'application/json') { + HttpDownloader::outputWarnings($this->io, $job['origin'], json_decode($response->getBody(), true)); + } + + $result = $this->isAuthenticatedRetryNeeded($job, $response); + if ($result['retry']) { + $this->restartJob($job, $job['url'], ['storeAuth' => $result['storeAuth'], 'retries' => $job['attributes']['retries'] + 1]); + continue; + } + + // handle 3xx redirects, 304 Not Modified is excluded + if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) { + $location = $this->handleRedirect($job, $response); + if ($location) { + $this->restartJob($job, $location, ['redirects' => $job['attributes']['redirects'] + 1]); + continue; + } + } + + // fail 4xx and 5xx responses and capture the response + if ($statusCode >= 400 && $statusCode <= 599) { + if ( + (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET') + && in_array($statusCode, [423, 425, 500, 502, 503, 504, 507, 510], true) + && $job['attributes']['retries'] < $this->maxRetries + ) { + $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to status code '. $statusCode, true, IOInterface::DEBUG); + $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]); + continue; + } + + throw $this->failResponse($job, $response, $response->getStatusMessage()); + } + + if ($job['attributes']['storeAuth'] !== false) { + $this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']); + } + + // resolve promise + if (null !== $job['filename']) { + rename($job['filename'].'~', $job['filename']); + $job['resolve']($response); + } else { + $job['resolve']($response); + } + } catch (\Exception $e) { + if ($e instanceof TransportException) { + if (null !== $headers) { + $e->setHeaders($headers); + $e->setStatusCode($statusCode); + } + if (null !== $response) { + $e->setResponse($response->getBody()); + } + $e->setResponseInfo($progress); + } + + $this->rejectJob($job, $e); + } + } + + foreach ($this->jobs as $i => $curlHandle) { + $curlHandle = $this->jobs[$i]['curlHandle']; + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); + + if ($this->jobs[$i]['progress'] !== $progress) { + $this->jobs[$i]['progress'] = $progress; + + if (isset($this->jobs[$i]['options']['max_file_size'])) { + // Compare max_file_size with the content-length header this value will be -1 until the header is parsed + if ($this->jobs[$i]['options']['max_file_size'] < $progress['download_content_length']) { + $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Content-length header indicates ' . $progress['download_content_length'] . ' bytes. Allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes')); + } + + // Compare max_file_size with the download size in bytes + if ($this->jobs[$i]['options']['max_file_size'] < $progress['size_download']) { + $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . $progress['size_download'] . ' of allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes')); + } + } + + if (isset($progress['primary_ip']) && $progress['primary_ip'] !== $this->jobs[$i]['primaryIp']) { + if ( + isset($this->jobs[$i]['options']['prevent_ip_access_callable']) && + is_callable($this->jobs[$i]['options']['prevent_ip_access_callable']) && + $this->jobs[$i]['options']['prevent_ip_access_callable']($progress['primary_ip']) + ) { + $this->rejectJob($this->jobs[$i], new TransportException(sprintf('IP "%s" is blocked for "%s".', $progress['primary_ip'], $progress['url']))); + } + + $this->jobs[$i]['primaryIp'] = (string) $progress['primary_ip']; + } + + // TODO progress + } + } + } + + /** + * @param Job $job + */ + private function handleRedirect(array $job, Response $response): string + { + if ($locationHeader = $response->getHeader('location')) { + if (parse_url($locationHeader, PHP_URL_SCHEME)) { + // Absolute URL; e.g. https://example.com/composer + $targetUrl = $locationHeader; + } elseif (parse_url($locationHeader, PHP_URL_HOST)) { + // Scheme relative; e.g. //example.com/foo + $targetUrl = parse_url($job['url'], PHP_URL_SCHEME).':'.$locationHeader; + } elseif ('/' === $locationHeader[0]) { + // Absolute path; e.g. /foo + $urlHost = parse_url($job['url'], PHP_URL_HOST); + + // Replace path using hostname as an anchor. + $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $job['url']); + } else { + // Relative path; e.g. foo + // This actually differs from PHP which seems to add duplicate slashes. + $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $job['url']); + } + } + + if (!empty($targetUrl)) { + $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, Url::sanitize($targetUrl)), true, IOInterface::DEBUG); + + return $targetUrl; + } + + throw new TransportException('The "'.$job['url'].'" file could not be downloaded, got redirect without Location ('.$response->getStatusMessage().')'); + } + + /** + * @param Job $job + * @return array{retry: bool, storeAuth: 'prompt'|bool} + */ + private function isAuthenticatedRetryNeeded(array $job, Response $response): array + { + if (in_array($response->getStatusCode(), [401, 403]) && $job['attributes']['retryAuthFailure']) { + $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders(), $job['attributes']['retries']); + + if ($result['retry']) { + return $result; + } + } + + $locationHeader = $response->getHeader('location'); + $needsAuthRetry = false; + + // check for bitbucket login page asking to authenticate + if ( + $job['origin'] === 'bitbucket.org' + && !$this->authHelper->isPublicBitBucketDownload($job['url']) + && substr($job['url'], -4) === '.zip' + && (!$locationHeader || substr($locationHeader, -4) !== '.zip') + && Preg::isMatch('{^text/html\b}i', $response->getHeader('content-type')) + ) { + $needsAuthRetry = 'Bitbucket requires authentication and it was not provided'; + } + + // check for gitlab 404 when downloading archives + if ( + $response->getStatusCode() === 404 + && in_array($job['origin'], $this->config->get('gitlab-domains'), true) + && false !== strpos($job['url'], 'archive.zip') + ) { + $needsAuthRetry = 'GitLab requires authentication and it was not provided'; + } + + if ($needsAuthRetry) { + if ($job['attributes']['retryAuthFailure']) { + $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401, null, [], $job['attributes']['retries']); + if ($result['retry']) { + return $result; + } + } + + throw $this->failResponse($job, $response, $needsAuthRetry); + } + + return ['retry' => false, 'storeAuth' => false]; + } + + /** + * @param Job $job + * @param non-empty-string $url + * + * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>, ipResolve?: 4|6} $attributes + */ + private function restartJob(array $job, string $url, array $attributes = []): void + { + if (null !== $job['filename']) { + @unlink($job['filename'].'~'); + } + + $attributes = array_merge($job['attributes'], $attributes); + $origin = Url::getOrigin($this->config, $url); + + $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['options'], $job['filename'], $attributes); + } + + /** + * @param Job $job + * @param non-empty-string $url + * + * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>, ipResolve?: 4|6} $attributes + */ + private function restartJobWithDelay(array $job, string $url, array $attributes): void + { + if ($attributes['retries'] >= 3) { + usleep(500000); // half a second delay for 3rd retry and beyond + } elseif ($attributes['retries'] >= 2) { + usleep(100000); // 100ms delay for 2nd retry + } // no sleep for the first retry + + $this->restartJob($job, $url, $attributes); + } + + /** + * @param Job $job + */ + private function failResponse(array $job, Response $response, string $errorMessage): TransportException + { + if (null !== $job['filename']) { + @unlink($job['filename'].'~'); + } + + $details = ''; + if (in_array(strtolower((string) $response->getHeader('content-type')), ['application/json', 'application/json; charset=utf-8'], true)) { + $details = ':'.PHP_EOL.substr($response->getBody(), 0, 200).(strlen($response->getBody()) > 200 ? '...' : ''); + } + + return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')' . $details, $response->getStatusCode()); + } + + /** + * @param Job $job + */ + private function rejectJob(array $job, \Exception $e): void + { + if (is_resource($job['headerHandle'])) { + fclose($job['headerHandle']); + } + if (is_resource($job['bodyHandle'])) { + fclose($job['bodyHandle']); + } + if (null !== $job['filename']) { + @unlink($job['filename'].'~'); + } + $job['reject']($e); + } + + private function checkCurlResult(int $code): void + { + if ($code !== CURLM_OK && $code !== CURLM_CALL_MULTI_PERFORM) { + throw new \RuntimeException( + isset($this->multiErrors[$code]) + ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}" + : 'Unexpected cURL error: ' . $code + ); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php b/vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php new file mode 100644 index 000000000..aca8f37ed --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +/** + * @phpstan-type CurlInfo array{url: mixed, content_type: mixed, http_code: mixed, header_size: mixed, request_size: mixed, filetime: mixed, ssl_verify_result: mixed, redirect_count: mixed, total_time: mixed, namelookup_time: mixed, connect_time: mixed, pretransfer_time: mixed, size_upload: mixed, size_download: mixed, speed_download: mixed, speed_upload: mixed, download_content_length: mixed, upload_content_length: mixed, starttransfer_time: mixed, redirect_time: mixed, certinfo: mixed, primary_ip: mixed, primary_port: mixed, local_ip: mixed, local_port: mixed, redirect_url: mixed} + */ +class CurlResponse extends Response +{ + /** + * @see https://www.php.net/curl_getinfo + * @var array + * @phpstan-var CurlInfo + */ + private $curlInfo; + + /** + * @phpstan-param CurlInfo $curlInfo + */ + public function __construct(array $request, ?int $code, array $headers, ?string $body, array $curlInfo) + { + parent::__construct($request, $code, $headers, $body); + $this->curlInfo = $curlInfo; + } + + /** + * @phpstan-return CurlInfo + */ + public function getCurlInfo(): array + { + return $this->curlInfo; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/ProxyItem.php b/vendor/composer/composer/src/Composer/Util/Http/ProxyItem.php new file mode 100644 index 000000000..2839be923 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/ProxyItem.php @@ -0,0 +1,119 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +/** + * @internal + * @author John Stevenson + */ +class ProxyItem +{ + /** @var non-empty-string */ + private $url; + /** @var non-empty-string */ + private $safeUrl; + /** @var ?non-empty-string */ + private $curlAuth; + /** @var string */ + private $optionsProxy; + /** @var ?non-empty-string */ + private $optionsAuth; + + /** + * @param string $proxyUrl The value from the environment + * @param string $envName The name of the environment variable + * @throws \RuntimeException If the proxy url is invalid + */ + public function __construct(string $proxyUrl, string $envName) + { + $syntaxError = sprintf('unsupported `%s` syntax', $envName); + + if (strpbrk($proxyUrl, "\r\n\t") !== false) { + throw new \RuntimeException($syntaxError); + } + if (false === ($proxy = parse_url($proxyUrl))) { + throw new \RuntimeException($syntaxError); + } + if (!isset($proxy['host'])) { + throw new \RuntimeException('unable to find proxy host in ' . $envName); + } + + $scheme = isset($proxy['scheme']) ? strtolower($proxy['scheme']) . '://' : 'http://'; + $safe = ''; + + if (isset($proxy['user'])) { + $safe = '***'; + $user = $proxy['user']; + $auth = rawurldecode($proxy['user']); + + if (isset($proxy['pass'])) { + $safe .= ':***'; + $user .= ':' . $proxy['pass']; + $auth .= ':' . rawurldecode($proxy['pass']); + } + + $safe .= '@'; + + if (strlen($user) > 0) { + $this->curlAuth = $user; + $this->optionsAuth = 'Proxy-Authorization: Basic ' . base64_encode($auth); + } + } + + $host = $proxy['host']; + $port = null; + + if (isset($proxy['port'])) { + $port = $proxy['port']; + } elseif ($scheme === 'http://') { + $port = 80; + } elseif ($scheme === 'https://') { + $port = 443; + } + + // We need a port because curl uses 1080 for http. Port 0 is reserved, + // but is considered valid depending on the PHP or Curl version. + if ($port === null) { + throw new \RuntimeException('unable to find proxy port in ' . $envName); + } + if ($port === 0) { + throw new \RuntimeException('port 0 is reserved in ' . $envName); + } + + $this->url = sprintf('%s%s:%d', $scheme, $host, $port); + $this->safeUrl = sprintf('%s%s%s:%d', $scheme, $safe, $host, $port); + + $scheme = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $scheme); + $this->optionsProxy = sprintf('%s%s:%d', $scheme, $host, $port); + } + + /** + * Returns a RequestProxy instance for the scheme of the request url + * + * @param string $scheme The scheme of the request url + */ + public function toRequestProxy(string $scheme): RequestProxy + { + $options = ['http' => ['proxy' => $this->optionsProxy]]; + + if ($this->optionsAuth !== null) { + $options['http']['header'] = $this->optionsAuth; + } + + if ($scheme === 'http') { + $options['http']['request_fulluri'] = true; + } + + return new RequestProxy($this->url, $this->curlAuth, $options, $this->safeUrl); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/ProxyManager.php b/vendor/composer/composer/src/Composer/Util/Http/ProxyManager.php new file mode 100644 index 000000000..3747cedaa --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/ProxyManager.php @@ -0,0 +1,173 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Downloader\TransportException; +use Composer\Util\NoProxyPattern; + +/** + * @internal + * @author John Stevenson + */ +class ProxyManager +{ + /** @var ?string */ + private $error = null; + /** @var ?ProxyItem */ + private $httpProxy = null; + /** @var ?ProxyItem */ + private $httpsProxy = null; + /** @var ?NoProxyPattern */ + private $noProxyHandler = null; + + /** @var ?self */ + private static $instance = null; + + private function __construct() + { + try { + $this->getProxyData(); + } catch (\RuntimeException $e) { + $this->error = $e->getMessage(); + } + } + + public static function getInstance(): ProxyManager + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Clears the persistent instance + */ + public static function reset(): void + { + self::$instance = null; + } + + public function hasProxy(): bool + { + return $this->httpProxy !== null || $this->httpsProxy !== null; + } + + /** + * Returns a RequestProxy instance for the request url + * + * @param non-empty-string $requestUrl + */ + public function getProxyForRequest(string $requestUrl): RequestProxy + { + if ($this->error !== null) { + throw new TransportException('Unable to use a proxy: '.$this->error); + } + + $scheme = (string) parse_url($requestUrl, PHP_URL_SCHEME); + $proxy = $this->getProxyForScheme($scheme); + + if ($proxy === null) { + return RequestProxy::none(); + } + + if ($this->noProxy($requestUrl)) { + return RequestProxy::noProxy(); + } + + return $proxy->toRequestProxy($scheme); + } + + /** + * Returns a ProxyItem if one is set for the scheme, otherwise null + */ + private function getProxyForScheme(string $scheme): ?ProxyItem + { + if ($scheme === 'http') { + return $this->httpProxy; + } + + if ($scheme === 'https') { + return $this->httpsProxy; + } + + return null; + } + + /** + * Finds proxy values from the environment and sets class properties + */ + private function getProxyData(): void + { + // Handle http_proxy/HTTP_PROXY on CLI only for security reasons + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + [$env, $name] = $this->getProxyEnv('http_proxy'); + if ($env !== null) { + $this->httpProxy = new ProxyItem($env, $name); + } + } + + // Handle cgi_http_proxy/CGI_HTTP_PROXY if needed + if ($this->httpProxy === null) { + [$env, $name] = $this->getProxyEnv('cgi_http_proxy'); + if ($env !== null) { + $this->httpProxy = new ProxyItem($env, $name); + } + } + + // Handle https_proxy/HTTPS_PROXY + [$env, $name] = $this->getProxyEnv('https_proxy'); + if ($env !== null) { + $this->httpsProxy = new ProxyItem($env, $name); + } + + // Handle no_proxy/NO_PROXY + [$env, $name] = $this->getProxyEnv('no_proxy'); + if ($env !== null) { + $this->noProxyHandler = new NoProxyPattern($env); + } + } + + /** + * Searches $_SERVER for case-sensitive values + * + * @return array{0: string|null, 1: string} value, name + */ + private function getProxyEnv(string $envName): array + { + $names = [strtolower($envName), strtoupper($envName)]; + + foreach ($names as $name) { + if (is_string($_SERVER[$name] ?? null)) { + if ($_SERVER[$name] !== '') { + return [$_SERVER[$name], $name]; + } + } + } + + return [null, '']; + } + + /** + * Returns true if a url matches no_proxy value + */ + private function noProxy(string $requestUrl): bool + { + if ($this->noProxyHandler === null) { + return false; + } + + return $this->noProxyHandler->test($requestUrl); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/RequestProxy.php b/vendor/composer/composer/src/Composer/Util/Http/RequestProxy.php new file mode 100644 index 000000000..d9df68861 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/RequestProxy.php @@ -0,0 +1,168 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Downloader\TransportException; + +/** + * @internal + * @author John Stevenson + * + * @phpstan-type contextOptions array{http: array{proxy: string, header?: string, request_fulluri?: bool}} + */ +class RequestProxy +{ + /** @var ?contextOptions */ + private $contextOptions; + /** @var ?non-empty-string */ + private $status; + /** @var ?non-empty-string */ + private $url; + /** @var ?non-empty-string */ + private $auth; + + /** + * @param ?non-empty-string $url The proxy url, without authorization + * @param ?non-empty-string $auth Authorization for curl + * @param ?contextOptions $contextOptions + * @param ?non-empty-string $status + */ + public function __construct(?string $url, ?string $auth, ?array $contextOptions, ?string $status) + { + $this->url = $url; + $this->auth = $auth; + $this->contextOptions = $contextOptions; + $this->status = $status; + } + + public static function none(): RequestProxy + { + return new self(null, null, null, null); + } + + public static function noProxy(): RequestProxy + { + return new self(null, null, null, 'excluded by no_proxy'); + } + + /** + * Returns the context options to use for this request, otherwise null + * + * @return ?contextOptions + */ + public function getContextOptions(): ?array + { + return $this->contextOptions; + } + + /** + * Returns an array of curl proxy options + * + * @param array $sslOptions + * @return array + */ + public function getCurlOptions(array $sslOptions): array + { + if ($this->isSecure() && !$this->supportsSecureProxy()) { + throw new TransportException('Cannot use an HTTPS proxy. PHP >= 7.3 and cUrl >= 7.52.0 are required.'); + } + + // Always set a proxy url, even an empty value, because it tells curl + // to ignore proxy environment variables + $options = [CURLOPT_PROXY => (string) $this->url]; + + // If using a proxy, tell curl to ignore no_proxy environment variables + if ($this->url !== null) { + $options[CURLOPT_NOPROXY] = ''; + } + + // Set any authorization + if ($this->auth !== null) { + $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC; + $options[CURLOPT_PROXYUSERPWD] = $this->auth; + } + + if ($this->isSecure()) { + if (isset($sslOptions['cafile'])) { + $options[CURLOPT_PROXY_CAINFO] = $sslOptions['cafile']; + } + if (isset($sslOptions['capath'])) { + $options[CURLOPT_PROXY_CAPATH] = $sslOptions['capath']; + } + } + + return $options; + } + + /** + * Returns proxy info associated with this request + * + * An empty return value means that the user has not set a proxy. + * A non-empty value will either be the sanitized proxy url if a proxy is + * required, or a message indicating that a no_proxy value has disabled the + * proxy. + * + * @param ?string $format Output format specifier + */ + public function getStatus(?string $format = null): string + { + if ($this->status === null) { + return ''; + } + + $format = $format ?? '%s'; + if (strpos($format, '%s') !== false) { + return sprintf($format, $this->status); + } + + throw new \InvalidArgumentException('String format specifier is missing'); + } + + /** + * Returns true if the request url has been excluded by a no_proxy value + * + * A false value can also mean that the user has not set a proxy. + */ + public function isExcludedByNoProxy(): bool + { + return $this->status !== null && $this->url === null; + } + + /** + * Returns true if this is a secure (HTTPS) proxy + * + * A false value means that this is either an HTTP proxy, or that a proxy + * is not required for this request, or that the user has not set a proxy. + */ + public function isSecure(): bool + { + return 0 === strpos((string) $this->url, 'https://'); + } + + /** + * Returns true if an HTTPS proxy can be used. + * + * This depends on PHP7.3+ for CURL_VERSION_HTTPS_PROXY + * and curl including the feature (from version 7.52.0) + */ + public function supportsSecureProxy(): bool + { + if (false === ($version = curl_version()) || !defined('CURL_VERSION_HTTPS_PROXY')) { + return false; + } + + $features = $version['features']; + + return (bool) ($features & CURL_VERSION_HTTPS_PROXY); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/Response.php b/vendor/composer/composer/src/Composer/Util/Http/Response.php new file mode 100644 index 000000000..a33f0779f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/Response.php @@ -0,0 +1,115 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; + +/** + * @phpstan-type Request array{url: non-empty-string, options?: mixed[], copyTo?: string|null} + */ +class Response +{ + /** @var Request */ + private $request; + /** @var int */ + private $code; + /** @var list */ + private $headers; + /** @var ?string */ + private $body; + + /** + * @param Request $request + * @param list $headers + */ + public function __construct(array $request, ?int $code, array $headers, ?string $body) + { + if (!isset($request['url'])) { + throw new \LogicException('url key missing from request array'); + } + $this->request = $request; + $this->code = (int) $code; + $this->headers = $headers; + $this->body = $body; + } + + public function getStatusCode(): int + { + return $this->code; + } + + public function getStatusMessage(): ?string + { + $value = null; + foreach ($this->headers as $header) { + if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) { + // In case of redirects, headers contain the headers of all responses + // so we can not return directly and need to keep iterating + $value = $header; + } + } + + return $value; + } + + /** + * @return string[] + */ + public function getHeaders(): array + { + return $this->headers; + } + + public function getHeader(string $name): ?string + { + return self::findHeaderValue($this->headers, $name); + } + + public function getBody(): ?string + { + return $this->body; + } + + /** + * @return mixed + */ + public function decodeJson() + { + return JsonFile::parseJson($this->body, $this->request['url']); + } + + /** + * @phpstan-impure + */ + public function collect(): void + { + unset($this->request, $this->code, $this->headers, $this->body); + } + + /** + * @param string[] $headers array of returned headers like from getLastHeaders() + * @param string $name header name (case insensitive) + */ + public static function findHeaderValue(array $headers, string $name): ?string + { + $value = null; + foreach ($headers as $header) { + if (Preg::isMatch('{^'.preg_quote($name).':\s*(.+?)\s*$}i', $header, $match)) { + $value = $match[1]; + } + } + + return $value; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/HttpDownloader.php b/vendor/composer/composer/src/Composer/Util/HttpDownloader.php new file mode 100644 index 000000000..1bef9a22c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/HttpDownloader.php @@ -0,0 +1,551 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Util\Http\Response; +use Composer\Util\Http\CurlDownloader; +use Composer\Composer; +use Composer\Package\Version\VersionParser; +use Composer\Semver\Constraint\Constraint; +use Composer\Exception\IrrecoverableDownloadException; +use React\Promise\Promise; +use React\Promise\PromiseInterface; + +/** + * @author Jordi Boggiano + * @phpstan-type Request array{url: non-empty-string, options: mixed[], copyTo: string|null} + * @phpstan-type Job array{id: int, status: int, request: Request, sync: bool, origin: string, resolve?: callable, reject?: callable, curl_id?: int, response?: Response, exception?: \Throwable} + */ +class HttpDownloader +{ + private const STATUS_QUEUED = 1; + private const STATUS_STARTED = 2; + private const STATUS_COMPLETED = 3; + private const STATUS_FAILED = 4; + private const STATUS_ABORTED = 5; + + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var array */ + private $jobs = []; + /** @var mixed[] */ + private $options = []; + /** @var int */ + private $runningJobs = 0; + /** @var int */ + private $maxJobs = 12; + /** @var ?CurlDownloader */ + private $curl; + /** @var ?RemoteFilesystem */ + private $rfs; + /** @var int */ + private $idGen = 0; + /** @var bool */ + private $disabled; + /** @var bool */ + private $allowAsync = false; + + /** + * @param IOInterface $io The IO instance + * @param Config $config The config + * @param mixed[] $options The options + */ + public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false) + { + $this->io = $io; + + $this->disabled = (bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK'); + + // Setup TLS options + // The cafile option can be set via config.json + if ($disableTls === false) { + $this->options = StreamContextFactory::getTlsDefaults($options, $io); + } + + // handle the other externally set options normally. + $this->options = array_replace_recursive($this->options, $options); + $this->config = $config; + + if (self::isCurlEnabled()) { + $this->curl = new CurlDownloader($io, $config, $options, $disableTls); + } + + $this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls); + + if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_HTTP'))) { + $this->maxJobs = max(1, min(50, (int) $maxJobs)); + } + } + + /** + * Download a file synchronously + * + * @param string $url URL to download + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * although not all options are supported when using the default curl downloader + * @throws TransportException + * @return Response + */ + public function get(string $url, array $options = []) + { + if ('' === $url) { + throw new \InvalidArgumentException('$url must not be an empty string'); + } + [$job, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null], true); + $promise->then(null, static function (\Throwable $e) { + // suppress error as it is rethrown to the caller by getResponse() a few lines below + }); + $this->wait($job['id']); + + $response = $this->getResponse($job['id']); + + return $response; + } + + /** + * Create an async download operation + * + * @param string $url URL to download + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * although not all options are supported when using the default curl downloader + * @throws TransportException + * @return PromiseInterface + * @phpstan-return PromiseInterface + */ + public function add(string $url, array $options = []) + { + if ('' === $url) { + throw new \InvalidArgumentException('$url must not be an empty string'); + } + [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null]); + + return $promise; + } + + /** + * Copy a file synchronously + * + * @param string $url URL to download + * @param string $to Path to copy to + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * although not all options are supported when using the default curl downloader + * @throws TransportException + * @return Response + */ + public function copy(string $url, string $to, array $options = []) + { + if ('' === $url) { + throw new \InvalidArgumentException('$url must not be an empty string'); + } + [$job] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to], true); + $this->wait($job['id']); + + return $this->getResponse($job['id']); + } + + /** + * Create an async copy operation + * + * @param string $url URL to download + * @param string $to Path to copy to + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * although not all options are supported when using the default curl downloader + * @throws TransportException + * @return PromiseInterface + * @phpstan-return PromiseInterface + */ + public function addCopy(string $url, string $to, array $options = []) + { + if ('' === $url) { + throw new \InvalidArgumentException('$url must not be an empty string'); + } + [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to]); + + return $promise; + } + + /** + * Retrieve the options set in the constructor + * + * @return mixed[] Options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Merges new options + * + * @param mixed[] $options + * @return void + */ + public function setOptions(array $options) + { + $this->options = array_replace_recursive($this->options, $options); + } + + /** + * @phpstan-param Request $request + * @return array{Job, PromiseInterface} + * @phpstan-return array{Job, PromiseInterface} + */ + private function addJob(array $request, bool $sync = false): array + { + $request['options'] = array_replace_recursive($this->options, $request['options']); + + /** @var Job */ + $job = [ + 'id' => $this->idGen++, + 'status' => self::STATUS_QUEUED, + 'request' => $request, + 'sync' => $sync, + 'origin' => Url::getOrigin($this->config, $request['url']), + ]; + + if (!$sync && !$this->allowAsync) { + throw new \LogicException('You must use the HttpDownloader instance which is part of a Composer\Loop instance to be able to run async http requests'); + } + + // capture username/password from URL if there is one + if (Preg::isMatchStrictGroups('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) { + $this->io->setAuthentication($job['origin'], rawurldecode($match[1]), rawurldecode($match[2])); + } + + $rfs = $this->rfs; + + if ($this->canUseCurl($job)) { + $resolver = static function ($resolve, $reject) use (&$job): void { + $job['status'] = HttpDownloader::STATUS_QUEUED; + $job['resolve'] = $resolve; + $job['reject'] = $reject; + }; + } else { + $resolver = static function ($resolve, $reject) use (&$job, $rfs): void { + // start job + $url = $job['request']['url']; + $options = $job['request']['options']; + + $job['status'] = HttpDownloader::STATUS_STARTED; + + if ($job['request']['copyTo']) { + $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false /* TODO progress */, $options); + + $headers = $rfs->getLastHeaders(); + $response = new Response($job['request'], $rfs->findStatusCode($headers), $headers, $job['request']['copyTo'].'~'); + + $resolve($response); + } else { + $body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options); + $headers = $rfs->getLastHeaders(); + $response = new Response($job['request'], $rfs->findStatusCode($headers), $headers, $body); + + $resolve($response); + } + }; + } + + $curl = $this->curl; + + $canceler = static function () use (&$job, $curl): void { + if ($job['status'] === HttpDownloader::STATUS_QUEUED) { + $job['status'] = HttpDownloader::STATUS_ABORTED; + } + if ($job['status'] !== HttpDownloader::STATUS_STARTED) { + return; + } + $job['status'] = HttpDownloader::STATUS_ABORTED; + if (isset($job['curl_id'])) { + $curl->abortRequest($job['curl_id']); + } + throw new IrrecoverableDownloadException('Download of ' . Url::sanitize($job['request']['url']) . ' canceled'); + }; + + $promise = new Promise($resolver, $canceler); + $promise = $promise->then(function ($response) use (&$job) { + $job['status'] = HttpDownloader::STATUS_COMPLETED; + $job['response'] = $response; + + $this->markJobDone(); + + return $response; + }, function ($e) use (&$job): void { + $job['status'] = HttpDownloader::STATUS_FAILED; + $job['exception'] = $e; + + $this->markJobDone(); + + throw $e; + }); + $this->jobs[$job['id']] = &$job; + + if ($this->runningJobs < $this->maxJobs) { + $this->startJob($job['id']); + } + + return [$job, $promise]; + } + + private function startJob(int $id): void + { + $job = &$this->jobs[$id]; + if ($job['status'] !== self::STATUS_QUEUED) { + return; + } + + // start job + $job['status'] = self::STATUS_STARTED; + $this->runningJobs++; + + assert(isset($job['resolve'])); + assert(isset($job['reject'])); + + $resolve = $job['resolve']; + $reject = $job['reject']; + $url = $job['request']['url']; + $options = $job['request']['options']; + $origin = $job['origin']; + + if ($this->disabled) { + if (isset($job['request']['options']['http']['header']) && false !== stripos(implode('', $job['request']['options']['http']['header']), 'if-modified-since')) { + $resolve(new Response(['url' => $url], 304, [], '')); + } else { + $e = new TransportException('Network disabled, request canceled: '.Url::sanitize($url), 499); + $e->setStatusCode(499); + $reject($e); + } + + return; + } + + try { + if ($job['request']['copyTo']) { + $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']); + } else { + $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options); + } + } catch (\Exception $exception) { + $reject($exception); + } + } + + private function markJobDone(): void + { + $this->runningJobs--; + } + + /** + * Wait for current async download jobs to complete + * + * @param int|null $index For internal use only, the job id + * + * @return void + */ + public function wait(?int $index = null) + { + do { + $jobCount = $this->countActiveJobs($index); + } while ($jobCount); + } + + /** + * @internal + */ + public function enableAsync(): void + { + $this->allowAsync = true; + } + + /** + * @internal + * + * @param int|null $index For internal use only, the job id + * @return int number of active (queued or started) jobs + */ + public function countActiveJobs(?int $index = null): int + { + if ($this->runningJobs < $this->maxJobs) { + foreach ($this->jobs as $job) { + if ($job['status'] === self::STATUS_QUEUED && $this->runningJobs < $this->maxJobs) { + $this->startJob($job['id']); + } + } + } + + if ($this->curl) { + $this->curl->tick(); + } + + if (null !== $index) { + return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0; + } + + $active = 0; + foreach ($this->jobs as $job) { + if ($job['status'] < self::STATUS_COMPLETED) { + $active++; + } elseif (!$job['sync']) { + unset($this->jobs[$job['id']]); + } + } + + return $active; + } + + /** + * @param int $index Job id + */ + private function getResponse(int $index): Response + { + if (!isset($this->jobs[$index])) { + throw new \LogicException('Invalid request id'); + } + + if ($this->jobs[$index]['status'] === self::STATUS_FAILED) { + assert(isset($this->jobs[$index]['exception'])); + throw $this->jobs[$index]['exception']; + } + + if (!isset($this->jobs[$index]['response'])) { + throw new \LogicException('Response not available yet, call wait() first'); + } + + $resp = $this->jobs[$index]['response']; + + unset($this->jobs[$index]); + + return $resp; + } + + /** + * @internal + * + * @param array{warning?: string, info?: string, warning-versions?: string, info-versions?: string, warnings?: array, infos?: array} $data + */ + public static function outputWarnings(IOInterface $io, string $url, $data): void + { + $cleanMessage = static function ($msg) use ($io) { + if (!$io->isDecorated()) { + $msg = Preg::replace('{'.chr(27).'\\[[;\d]*m}u', '', $msg); + } + + return $msg; + }; + + // legacy warning/info keys + foreach (['warning', 'info'] as $type) { + if (empty($data[$type])) { + continue; + } + + if (!empty($data[$type . '-versions'])) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($data[$type . '-versions']); + $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); + if (!$constraint->matches($composer)) { + continue; + } + } + + $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($data[$type]).''); + } + + // modern Composer 2.2+ format with support for multiple warning/info messages + foreach (['warnings', 'infos'] as $key) { + if (empty($data[$key])) { + continue; + } + + $versionParser = new VersionParser(); + foreach ($data[$key] as $spec) { + $type = substr($key, 0, -1); + $constraint = $versionParser->parseConstraints($spec['versions']); + $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); + if (!$constraint->matches($composer)) { + continue; + } + + $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($spec['message']).''); + } + } + } + + /** + * @internal + * + * @return ?string[] + */ + public static function getExceptionHints(\Throwable $e): ?array + { + if (!$e instanceof TransportException) { + return null; + } + + if ( + false !== strpos($e->getMessage(), 'Resolving timed out') + || false !== strpos($e->getMessage(), 'Could not resolve host') + ) { + Silencer::suppress(); + $testConnectivity = file_get_contents('https://8.8.8.8', false, stream_context_create([ + 'ssl' => ['verify_peer' => false], + 'http' => ['follow_location' => false, 'ignore_errors' => true], + ])); + Silencer::restore(); + if (false !== $testConnectivity) { + return [ + 'The following exception probably indicates you have misconfigured DNS resolver(s)', + ]; + } + + return [ + 'The following exception probably indicates you are offline or have misconfigured DNS resolver(s)', + ]; + } + + return null; + } + + /** + * @param Job $job + */ + private function canUseCurl(array $job): bool + { + if (!$this->curl) { + return false; + } + + if (!Preg::isMatch('{^https?://}i', $job['request']['url'])) { + return false; + } + + if (!empty($job['request']['options']['ssl']['allow_self_signed'])) { + return false; + } + + return true; + } + + /** + * @internal + */ + public static function isCurlEnabled(): bool + { + return \extension_loaded('curl') && \function_exists('curl_multi_exec') && \function_exists('curl_multi_init'); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/IniHelper.php b/vendor/composer/composer/src/Composer/Util/IniHelper.php new file mode 100644 index 000000000..c01a97dbb --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/IniHelper.php @@ -0,0 +1,62 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\XdebugHandler\XdebugHandler; + +/** + * Provides ini file location functions that work with and without a restart. + * When the process has restarted it uses a tmp ini and stores the original + * ini locations in an environment variable. + * + * @author John Stevenson + */ +class IniHelper +{ + /** + * Returns an array of php.ini locations with at least one entry + * + * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. + * The loaded ini location is the first entry and may be empty. + * + * @return string[] + */ + public static function getAll(): array + { + return XdebugHandler::getAllIniFiles(); + } + + /** + * Describes the location of the loaded php.ini file(s) + */ + public static function getMessage(): string + { + $paths = self::getAll(); + + if (empty($paths[0])) { + array_shift($paths); + } + + $ini = array_shift($paths); + + if (empty($ini)) { + return 'A php.ini file does not exist. You will have to create one.'; + } + + if (!empty($paths)) { + return 'Your command-line PHP is using multiple ini files. Run `php --ini` to show them.'; + } + + return 'The php.ini used by your command-line PHP is: '.$ini; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Loop.php b/vendor/composer/composer/src/Composer/Util/Loop.php new file mode 100644 index 000000000..7cee5c8e6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Loop.php @@ -0,0 +1,121 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Symfony\Component\Console\Helper\ProgressBar; +use React\Promise\PromiseInterface; + +/** + * @author Jordi Boggiano + */ +class Loop +{ + /** @var HttpDownloader */ + private $httpDownloader; + /** @var ProcessExecutor|null */ + private $processExecutor; + /** @var array>> */ + private $currentPromises = []; + /** @var int */ + private $waitIndex = 0; + + public function __construct(HttpDownloader $httpDownloader, ?ProcessExecutor $processExecutor = null) + { + $this->httpDownloader = $httpDownloader; + $this->httpDownloader->enableAsync(); + + $this->processExecutor = $processExecutor; + if ($this->processExecutor) { + $this->processExecutor->enableAsync(); + } + } + + public function getHttpDownloader(): HttpDownloader + { + return $this->httpDownloader; + } + + public function getProcessExecutor(): ?ProcessExecutor + { + return $this->processExecutor; + } + + /** + * @param array> $promises + */ + public function wait(array $promises, ?ProgressBar $progress = null): void + { + $uncaught = null; + + \React\Promise\all($promises)->then( + static function (): void { + }, + static function (\Throwable $e) use (&$uncaught): void { + $uncaught = $e; + } + ); + + // keep track of every group of promises that is waited on, so abortJobs can + // cancel them all, even if wait() was called within a wait() + $waitIndex = $this->waitIndex++; + $this->currentPromises[$waitIndex] = $promises; + + if ($progress) { + $totalJobs = 0; + $totalJobs += $this->httpDownloader->countActiveJobs(); + if ($this->processExecutor) { + $totalJobs += $this->processExecutor->countActiveJobs(); + } + $progress->start($totalJobs); + } + + $lastUpdate = 0; + while (true) { + $activeJobs = 0; + + $activeJobs += $this->httpDownloader->countActiveJobs(); + if ($this->processExecutor) { + $activeJobs += $this->processExecutor->countActiveJobs(); + } + + if ($progress && microtime(true) - $lastUpdate > 0.1) { + $lastUpdate = microtime(true); + $progress->setProgress($progress->getMaxSteps() - $activeJobs); + } + + if (!$activeJobs) { + break; + } + } + + // as we skip progress updates if they are too quick, make sure we do one last one here at 100% + if ($progress) { + $progress->finish(); + } + + unset($this->currentPromises[$waitIndex]); + if (null !== $uncaught) { + throw $uncaught; + } + } + + public function abortJobs(): void + { + foreach ($this->currentPromises as $promiseGroup) { + foreach ($promiseGroup as $promise) { + // to support react/promise 2.x we wrap the promise in a resolve() call for safety + \React\Promise\resolve($promise)->cancel(); + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/MetadataMinifier.php b/vendor/composer/composer/src/Composer/Util/MetadataMinifier.php new file mode 100644 index 000000000..ff93821dd --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/MetadataMinifier.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +@trigger_error('Composer\Util\MetadataMinifier is deprecated, use Composer\MetadataMinifier\MetadataMinifier from composer/metadata-minifier instead.', E_USER_DEPRECATED); + +/** + * @deprecated Use Composer\MetadataMinifier\MetadataMinifier instead + */ +class MetadataMinifier extends \Composer\MetadataMinifier\MetadataMinifier +{ +} diff --git a/vendor/composer/composer/src/Composer/Util/NoProxyPattern.php b/vendor/composer/composer/src/Composer/Util/NoProxyPattern.php new file mode 100644 index 000000000..45f47343e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/NoProxyPattern.php @@ -0,0 +1,412 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Pcre\Preg; +use stdClass; + +/** + * Tests URLs against NO_PROXY patterns + */ +class NoProxyPattern +{ + /** + * @var string[] + */ + protected $hostNames = []; + + /** + * @var (null|object)[] + */ + protected $rules = []; + + /** + * @var bool + */ + protected $noproxy; + + /** + * @param string $pattern NO_PROXY pattern + */ + public function __construct(string $pattern) + { + $this->hostNames = Preg::split('{[\s,]+}', $pattern, -1, PREG_SPLIT_NO_EMPTY); + $this->noproxy = empty($this->hostNames) || '*' === $this->hostNames[0]; + } + + /** + * Returns true if a URL matches the NO_PROXY pattern + */ + public function test(string $url): bool + { + if ($this->noproxy) { + return true; + } + + if (!$urlData = $this->getUrlData($url)) { + return false; + } + + foreach ($this->hostNames as $index => $hostName) { + if ($this->match($index, $hostName, $urlData)) { + return true; + } + } + + return false; + } + + /** + * Returns false is the url cannot be parsed, otherwise a data object + * + * @return bool|stdClass + */ + protected function getUrlData(string $url) + { + if (!$host = parse_url($url, PHP_URL_HOST)) { + return false; + } + + $port = parse_url($url, PHP_URL_PORT); + + if (empty($port)) { + switch (parse_url($url, PHP_URL_SCHEME)) { + case 'http': + $port = 80; + break; + case 'https': + $port = 443; + break; + } + } + + $hostName = $host . ($port ? ':' . $port : ''); + [$host, $port, $err] = $this->splitHostPort($hostName); + + if ($err || !$this->ipCheckData($host, $ipdata)) { + return false; + } + + return $this->makeData($host, $port, $ipdata); + } + + /** + * Returns true if the url is matched by a rule + */ + protected function match(int $index, string $hostName, stdClass $url): bool + { + if (!$rule = $this->getRule($index, $hostName)) { + // Data must have been misformatted + return false; + } + + if ($rule->ipdata) { + // Match ipdata first + if (!$url->ipdata) { + return false; + } + + if ($rule->ipdata->netmask) { + return $this->matchRange($rule->ipdata, $url->ipdata); + } + + $match = $rule->ipdata->ip === $url->ipdata->ip; + } else { + // Match host and port + $haystack = substr($url->name, -strlen($rule->name)); + $match = stripos($haystack, $rule->name) === 0; + } + + if ($match && $rule->port) { + $match = $rule->port === $url->port; + } + + return $match; + } + + /** + * Returns true if the target ip is in the network range + */ + protected function matchRange(stdClass $network, stdClass $target): bool + { + $net = unpack('C*', $network->ip); + $mask = unpack('C*', $network->netmask); + $ip = unpack('C*', $target->ip); + if (false === $net) { + throw new \RuntimeException('Could not parse network IP '.$network->ip); + } + if (false === $mask) { + throw new \RuntimeException('Could not parse netmask '.$network->netmask); + } + if (false === $ip) { + throw new \RuntimeException('Could not parse target IP '.$target->ip); + } + + for ($i = 1; $i < 17; ++$i) { + if (($net[$i] & $mask[$i]) !== ($ip[$i] & $mask[$i])) { + return false; + } + } + + return true; + } + + /** + * Finds or creates rule data for a hostname + * + * @return null|stdClass Null if the hostname is invalid + */ + private function getRule(int $index, string $hostName): ?stdClass + { + if (array_key_exists($index, $this->rules)) { + return $this->rules[$index]; + } + + $this->rules[$index] = null; + [$host, $port, $err] = $this->splitHostPort($hostName); + + if ($err || !$this->ipCheckData($host, $ipdata, true)) { + return null; + } + + $this->rules[$index] = $this->makeData($host, $port, $ipdata); + + return $this->rules[$index]; + } + + /** + * Creates an object containing IP data if the host is an IP address + * + * @param null|stdClass $ipdata Set by method if IP address found + * @param bool $allowPrefix Whether a CIDR prefix-length is expected + * + * @return bool False if the host contains invalid data + */ + private function ipCheckData(string $host, ?stdClass &$ipdata, bool $allowPrefix = false): bool + { + $ipdata = null; + $netmask = null; + $prefix = null; + $modified = false; + + // Check for a CIDR prefix-length + if (strpos($host, '/') !== false) { + [$host, $prefix] = explode('/', $host); + + if (!$allowPrefix || !$this->validateInt($prefix, 0, 128)) { + return false; + } + $prefix = (int) $prefix; + $modified = true; + } + + // See if this is an ip address + if (!filter_var($host, FILTER_VALIDATE_IP)) { + return !$modified; + } + + [$ip, $size] = $this->ipGetAddr($host); + + if ($prefix !== null) { + // Check for a valid prefix + if ($prefix > $size * 8) { + return false; + } + + [$ip, $netmask] = $this->ipGetNetwork($ip, $size, $prefix); + } + + $ipdata = $this->makeIpData($ip, $size, $netmask); + + return true; + } + + /** + * Returns an array of the IP in_addr and its byte size + * + * IPv4 addresses are always mapped to IPv6, which simplifies handling + * and comparison. + * + * @return mixed[] in_addr, size + */ + private function ipGetAddr(string $host): array + { + $ip = inet_pton($host); + $size = strlen($ip); + $mapped = $this->ipMapTo6($ip, $size); + + return [$mapped, $size]; + } + + /** + * Returns the binary network mask mapped to IPv6 + * + * @param int $prefix CIDR prefix-length + * @param int $size Byte size of in_addr + */ + private function ipGetMask(int $prefix, int $size): string + { + $mask = ''; + + if ($ones = floor($prefix / 8)) { + $mask = str_repeat(chr(255), (int) $ones); + } + + if ($remainder = $prefix % 8) { + $mask .= chr(0xff ^ (0xff >> $remainder)); + } + + $mask = str_pad($mask, $size, chr(0)); + + return $this->ipMapTo6($mask, $size); + } + + /** + * Calculates and returns the network and mask + * + * @param string $rangeIp IP in_addr + * @param int $size Byte size of in_addr + * @param int $prefix CIDR prefix-length + * + * @return string[] network in_addr, binary mask + */ + private function ipGetNetwork(string $rangeIp, int $size, int $prefix): array + { + $netmask = $this->ipGetMask($prefix, $size); + + // Get the network from the address and mask + $mask = unpack('C*', $netmask); + $ip = unpack('C*', $rangeIp); + $net = ''; + if (false === $mask) { + throw new \RuntimeException('Could not parse netmask '.$netmask); + } + if (false === $ip) { + throw new \RuntimeException('Could not parse range IP '.$rangeIp); + } + + for ($i = 1; $i < 17; ++$i) { + $net .= chr($ip[$i] & $mask[$i]); + } + + return [$net, $netmask]; + } + + /** + * Maps an IPv4 address to IPv6 + * + * @param string $binary in_addr + * @param int $size Byte size of in_addr + * + * @return string Mapped or existing in_addr + */ + private function ipMapTo6(string $binary, int $size): string + { + if ($size === 4) { + $prefix = str_repeat(chr(0), 10) . str_repeat(chr(255), 2); + $binary = $prefix . $binary; + } + + return $binary; + } + + /** + * Creates a rule data object + */ + private function makeData(string $host, int $port, ?stdClass $ipdata): stdClass + { + return (object) [ + 'host' => $host, + 'name' => '.' . ltrim($host, '.'), + 'port' => $port, + 'ipdata' => $ipdata, + ]; + } + + /** + * Creates an ip data object + * + * @param string $ip in_addr + * @param int $size Byte size of in_addr + * @param null|string $netmask Network mask + */ + private function makeIpData(string $ip, int $size, ?string $netmask): stdClass + { + return (object) [ + 'ip' => $ip, + 'size' => $size, + 'netmask' => $netmask, + ]; + } + + /** + * Splits the hostname into host and port components + * + * @return mixed[] host, port, if there was error + */ + private function splitHostPort(string $hostName): array + { + // host, port, err + $error = ['', '', true]; + $port = 0; + $ip6 = ''; + + // Check for square-bracket notation + if ($hostName[0] === '[') { + $index = strpos($hostName, ']'); + + // The smallest ip6 address is :: + if (false === $index || $index < 3) { + return $error; + } + + $ip6 = substr($hostName, 1, $index - 1); + $hostName = substr($hostName, $index + 1); + + if (strpbrk($hostName, '[]') !== false || substr_count($hostName, ':') > 1) { + return $error; + } + } + + if (substr_count($hostName, ':') === 1) { + $index = strpos($hostName, ':'); + $port = substr($hostName, $index + 1); + $hostName = substr($hostName, 0, $index); + + if (!$this->validateInt($port, 1, 65535)) { + return $error; + } + + $port = (int) $port; + } + + $host = $ip6 . $hostName; + + return [$host, $port, false]; + } + + /** + * Wrapper around filter_var FILTER_VALIDATE_INT + */ + private function validateInt(string $int, int $min, int $max): bool + { + $options = [ + 'options' => [ + 'min_range' => $min, + 'max_range' => $max, + ], + ]; + + return false !== filter_var($int, FILTER_VALIDATE_INT, $options); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/PackageInfo.php b/vendor/composer/composer/src/Composer/Util/PackageInfo.php new file mode 100644 index 000000000..e93c58447 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/PackageInfo.php @@ -0,0 +1,39 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Package\CompletePackageInterface; +use Composer\Package\PackageInterface; + +class PackageInfo +{ + public static function getViewSourceUrl(PackageInterface $package): ?string + { + if ($package instanceof CompletePackageInterface && isset($package->getSupport()['source']) && '' !== $package->getSupport()['source']) { + return $package->getSupport()['source']; + } + + return $package->getSourceUrl(); + } + + public static function getViewSourceOrHomepageUrl(PackageInterface $package): ?string + { + $url = self::getViewSourceUrl($package) ?? ($package instanceof CompletePackageInterface ? $package->getHomepage() : null); + + if ($url === '') { + return null; + } + + return $url; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/PackageSorter.php b/vendor/composer/composer/src/Composer/Util/PackageSorter.php new file mode 100644 index 000000000..80ea2cc9c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/PackageSorter.php @@ -0,0 +1,140 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Package\PackageInterface; +use Composer\Package\RootPackageInterface; + +class PackageSorter +{ + /** + * Returns the most recent version of a set of packages + * + * This is ideally the default branch version, or failing that it will return the package with the highest version + * + * @template T of PackageInterface + * @param array $packages + * @return ($packages is non-empty-array ? T : T|null) + */ + public static function getMostCurrentVersion(array $packages): ?PackageInterface + { + if (count($packages) === 0) { + return null; + } + + $highest = reset($packages); + foreach ($packages as $candidate) { + if ($candidate->isDefaultBranch()) { + return $candidate; + } + + if (version_compare($highest->getVersion(), $candidate->getVersion(), '<')) { + $highest = $candidate; + } + } + + return $highest; + } + + /** + * Sorts packages by name + * + * @template T of PackageInterface + * @param array $packages + * @return array + */ + public static function sortPackagesAlphabetically(array $packages): array + { + usort($packages, static function (PackageInterface $a, PackageInterface $b) { + return $a->getName() <=> $b->getName(); + }); + + return $packages; + } + + /** + * Sorts packages by dependency weight + * + * Packages of equal weight are sorted alphabetically + * + * @param PackageInterface[] $packages + * @param array $weights Pre-set weights for some packages to give them more (negative number) or less (positive) weight offsets + * @return PackageInterface[] sorted array + */ + public static function sortPackages(array $packages, array $weights = []): array + { + $usageList = []; + + foreach ($packages as $package) { + $links = $package->getRequires(); + if ($package instanceof RootPackageInterface) { + $links = array_merge($links, $package->getDevRequires()); + } + foreach ($links as $link) { + $target = $link->getTarget(); + $usageList[$target][] = $package->getName(); + } + } + $computing = []; + $computed = []; + $computeImportance = static function ($name) use (&$computeImportance, &$computing, &$computed, $usageList, $weights) { + // reusing computed importance + if (isset($computed[$name])) { + return $computed[$name]; + } + + // canceling circular dependency + if (isset($computing[$name])) { + return 0; + } + + $computing[$name] = true; + $weight = $weights[$name] ?? 0; + + if (isset($usageList[$name])) { + foreach ($usageList[$name] as $user) { + $weight -= 1 - $computeImportance($user); + } + } + + unset($computing[$name]); + $computed[$name] = $weight; + + return $weight; + }; + + $weightedPackages = []; + + foreach ($packages as $index => $package) { + $name = $package->getName(); + $weight = $computeImportance($name); + $weightedPackages[] = ['name' => $name, 'weight' => $weight, 'index' => $index]; + } + + usort($weightedPackages, static function (array $a, array $b): int { + if ($a['weight'] !== $b['weight']) { + return $a['weight'] - $b['weight']; + } + + return strnatcasecmp($a['name'], $b['name']); + }); + + $sortedPackages = []; + + foreach ($weightedPackages as $pkg) { + $sortedPackages[] = $packages[$pkg['index']]; + } + + return $sortedPackages; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Perforce.php b/vendor/composer/composer/src/Composer/Util/Perforce.php new file mode 100644 index 000000000..4922ab35c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Perforce.php @@ -0,0 +1,634 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Symfony\Component\Process\ExecutableFinder; +use Symfony\Component\Process\Process; + +/** + * @author Matt Whittom + * + * @phpstan-type RepoConfig array{unique_perforce_client_name?: string, depot?: string, branch?: string, p4user?: string, p4password?: string} + */ +class Perforce +{ + /** @var string */ + protected $path; + /** @var ?string */ + protected $p4Depot; + /** @var ?string */ + protected $p4Client; + /** @var ?string */ + protected $p4User; + /** @var ?string */ + protected $p4Password; + /** @var string */ + protected $p4Port; + /** @var ?string */ + protected $p4Stream; + /** @var string */ + protected $p4ClientSpec; + /** @var ?string */ + protected $p4DepotType; + /** @var ?string */ + protected $p4Branch; + /** @var ProcessExecutor */ + protected $process; + /** @var string */ + protected $uniquePerforceClientName; + /** @var bool */ + protected $windowsFlag; + /** @var string */ + protected $commandResult; + + /** @var IOInterface */ + protected $io; + + /** @var ?Filesystem */ + protected $filesystem; + + /** + * @phpstan-param RepoConfig $repoConfig + */ + public function __construct($repoConfig, string $port, string $path, ProcessExecutor $process, bool $isWindows, IOInterface $io) + { + $this->windowsFlag = $isWindows; + $this->p4Port = $port; + $this->initializePath($path); + $this->process = $process; + $this->initialize($repoConfig); + $this->io = $io; + } + + /** + * @phpstan-param RepoConfig $repoConfig + */ + public static function create($repoConfig, string $port, string $path, ProcessExecutor $process, IOInterface $io): self + { + return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io); + } + + public static function checkServerExists(string $url, ProcessExecutor $processExecutor): bool + { + return 0 === $processExecutor->execute(['p4', '-p', $url, 'info', '-s'], $ignoredOutput); + } + + /** + * @phpstan-param RepoConfig $repoConfig + */ + public function initialize($repoConfig): void + { + $this->uniquePerforceClientName = $this->generateUniquePerforceClientName(); + if (!$repoConfig) { + return; + } + if (isset($repoConfig['unique_perforce_client_name'])) { + $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name']; + } + + if (isset($repoConfig['depot'])) { + $this->p4Depot = $repoConfig['depot']; + } + if (isset($repoConfig['branch'])) { + $this->p4Branch = $repoConfig['branch']; + } + if (isset($repoConfig['p4user'])) { + $this->p4User = $repoConfig['p4user']; + } else { + $this->p4User = $this->getP4variable('P4USER'); + } + if (isset($repoConfig['p4password'])) { + $this->p4Password = $repoConfig['p4password']; + } + } + + public function initializeDepotAndBranch(?string $depot, ?string $branch): void + { + if (isset($depot)) { + $this->p4Depot = $depot; + } + if (isset($branch)) { + $this->p4Branch = $branch; + } + } + + /** + * @return non-empty-string + */ + public function generateUniquePerforceClientName(): string + { + return gethostname() . "_" . time(); + } + + public function cleanupClientSpec(): void + { + $client = $this->getClient(); + $task = 'client -d ' . ProcessExecutor::escape($client); + $useP4Client = false; + $command = $this->generateP4Command($task, $useP4Client); + $this->executeCommand($command); + $clientSpec = $this->getP4ClientSpec(); + $fileSystem = $this->getFilesystem(); + $fileSystem->remove($clientSpec); + } + + /** + * @param non-empty-string $command + */ + protected function executeCommand($command): int + { + $this->commandResult = ''; + + return $this->process->execute($command, $this->commandResult); + } + + public function getClient(): string + { + if (!isset($this->p4Client)) { + $cleanStreamName = str_replace(['//', '/', '@'], ['', '_', ''], $this->getStream()); + $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName; + } + + return $this->p4Client; + } + + protected function getPath(): string + { + return $this->path; + } + + public function initializePath(string $path): void + { + $this->path = $path; + $fs = $this->getFilesystem(); + $fs->ensureDirectoryExists($path); + } + + protected function getPort(): string + { + return $this->p4Port; + } + + public function setStream(string $stream): void + { + $this->p4Stream = $stream; + $index = strrpos($stream, '/'); + //Stream format is //depot/stream, while non-streaming depot is //depot + if ($index > 2) { + $this->p4DepotType = 'stream'; + } + } + + public function isStream(): bool + { + return is_string($this->p4DepotType) && (strcmp($this->p4DepotType, 'stream') === 0); + } + + public function getStream(): string + { + if (!isset($this->p4Stream)) { + if ($this->isStream()) { + $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch; + } else { + $this->p4Stream = '//' . $this->p4Depot; + } + } + + return $this->p4Stream; + } + + public function getStreamWithoutLabel(string $stream): string + { + $index = strpos($stream, '@'); + if ($index === false) { + return $stream; + } + + return substr($stream, 0, $index); + } + + /** + * @return non-empty-string + */ + public function getP4ClientSpec(): string + { + return $this->path . '/' . $this->getClient() . '.p4.spec'; + } + + public function getUser(): ?string + { + return $this->p4User; + } + + public function setUser(?string $user): void + { + $this->p4User = $user; + } + + public function queryP4User(): void + { + $this->getUser(); + if (strlen((string) $this->p4User) > 0) { + return; + } + $this->p4User = $this->getP4variable('P4USER'); + if (strlen((string) $this->p4User) > 0) { + return; + } + $this->p4User = $this->io->ask('Enter P4 User:'); + if ($this->windowsFlag) { + $command = $this->getP4Executable().' set P4USER=' . $this->p4User; + } else { + $command = 'export P4USER=' . $this->p4User; + } + $this->executeCommand($command); + } + + protected function getP4variable(string $name): ?string + { + if ($this->windowsFlag) { + $command = $this->getP4Executable().' set'; + $this->executeCommand($command); + $result = trim($this->commandResult); + $resArray = explode(PHP_EOL, $result); + foreach ($resArray as $line) { + $fields = explode('=', $line); + if (strcmp($name, $fields[0]) === 0) { + $index = strpos($fields[1], ' '); + if ($index === false) { + $value = $fields[1]; + } else { + $value = substr($fields[1], 0, $index); + } + $value = trim($value); + + return $value; + } + } + + return null; + } + + $command = 'echo $' . $name; + $this->executeCommand($command); + $result = trim($this->commandResult); + + return $result; + } + + public function queryP4Password(): ?string + { + if (isset($this->p4Password)) { + return $this->p4Password; + } + $password = $this->getP4variable('P4PASSWD'); + if (strlen((string) $password) <= 0) { + $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': '); + } + $this->p4Password = $password; + + return $password; + } + + /** + * @return non-empty-string + */ + public function generateP4Command(string $command, bool $useClient = true): string + { + $p4Command = $this->getP4Executable().' '; + $p4Command .= '-u ' . $this->getUser() . ' '; + if ($useClient) { + $p4Command .= '-c ' . $this->getClient() . ' '; + } + $p4Command .= '-p ' . $this->getPort() . ' ' . $command; + + return $p4Command; + } + + public function isLoggedIn(): bool + { + $command = $this->generateP4Command('login -s', false); + $exitCode = $this->executeCommand($command); + if ($exitCode) { + $errorOutput = $this->process->getErrorOutput(); + $index = strpos($errorOutput, $this->getUser()); + if ($index === false) { + $index = strpos($errorOutput, 'p4'); + if ($index === false) { + return false; + } + throw new \Exception('p4 command not found in path: ' . $errorOutput); + } + throw new \Exception('Invalid user name: ' . $this->getUser()); + } + + return true; + } + + public function connectClient(): void + { + $p4CreateClientCommand = $this->generateP4Command( + 'client -i < ' . ProcessExecutor::escape($this->getP4ClientSpec()) + ); + $this->executeCommand($p4CreateClientCommand); + } + + public function syncCodeBase(?string $sourceReference): void + { + $prevDir = Platform::getCwd(); + chdir($this->path); + $p4SyncCommand = $this->generateP4Command('sync -f '); + if (null !== $sourceReference) { + $p4SyncCommand .= '@' . $sourceReference; + } + $this->executeCommand($p4SyncCommand); + chdir($prevDir); + } + + /** + * @param resource|false $spec + */ + public function writeClientSpecToFile($spec): void + { + fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL); + fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL); + fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL); + fwrite($spec, 'Owner: ' . $this->getUser() . PHP_EOL . PHP_EOL); + fwrite($spec, 'Description:' . PHP_EOL); + fwrite($spec, ' Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL); + fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL); + fwrite($spec, 'Options: noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL); + fwrite($spec, 'SubmitOptions: revertunchanged' . PHP_EOL . PHP_EOL); + fwrite($spec, 'LineEnd: local' . PHP_EOL . PHP_EOL); + if ($this->isStream()) { + fwrite($spec, 'Stream:' . PHP_EOL); + fwrite($spec, ' ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL); + } else { + fwrite( + $spec, + 'View: ' . $this->getStream() . '/... //' . $this->getClient() . '/... ' . PHP_EOL + ); + } + } + + public function writeP4ClientSpec(): void + { + $clientSpec = $this->getP4ClientSpec(); + $spec = fopen($clientSpec, 'w'); + try { + $this->writeClientSpecToFile($spec); + } catch (\Exception $e) { + fclose($spec); + throw $e; + } + fclose($spec); + } + + /** + * @param resource $pipe + * @param mixed $name + */ + protected function read($pipe, $name): void + { + if (feof($pipe)) { + return; + } + $line = fgets($pipe); + while ($line !== false) { + $line = fgets($pipe); + } + } + + public function windowsLogin(?string $password): int + { + $command = $this->generateP4Command(' login -a'); + + $process = Process::fromShellCommandline($command, null, null, $password); + + return $process->run(); + } + + public function p4Login(): void + { + $this->queryP4User(); + if (!$this->isLoggedIn()) { + $password = $this->queryP4Password(); + if ($this->windowsFlag) { + $this->windowsLogin($password); + } else { + $command = 'echo ' . ProcessExecutor::escape($password) . ' | ' . $this->generateP4Command(' login -a', false); + $exitCode = $this->executeCommand($command); + if ($exitCode) { + throw new \Exception("Error logging in:" . $this->process->getErrorOutput()); + } + } + } + } + + /** + * @return mixed[]|null + */ + public function getComposerInformation(string $identifier): ?array + { + $composerFileContent = $this->getFileContent('composer.json', $identifier); + + if (!$composerFileContent) { + return null; + } + + return json_decode($composerFileContent, true); + } + + public function getFileContent(string $file, string $identifier): ?string + { + $path = $this->getFilePath($file, $identifier); + + $command = $this->generateP4Command(' print ' . ProcessExecutor::escape($path)); + $this->executeCommand($command); + $result = $this->commandResult; + + if (!trim($result)) { + return null; + } + + return $result; + } + + public function getFilePath(string $file, string $identifier): ?string + { + $index = strpos($identifier, '@'); + if ($index === false) { + return $identifier. '/' . $file; + } + + $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index); + $command = $this->generateP4Command(' files ' . ProcessExecutor::escape($path), false); + $this->executeCommand($command); + $result = $this->commandResult; + $index2 = strpos($result, 'no such file(s).'); + if ($index2 === false) { + $index3 = strpos($result, 'change'); + if ($index3 !== false) { + $phrase = trim(substr($result, $index3)); + $fields = explode(' ', $phrase); + + return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1]; + } + } + + return null; + } + + /** + * @return array{master: string} + */ + public function getBranches(): array + { + $possibleBranches = []; + if (!$this->isStream()) { + $possibleBranches[$this->p4Branch] = $this->getStream(); + } else { + $command = $this->generateP4Command('streams '.ProcessExecutor::escape('//' . $this->p4Depot . '/...')); + $this->executeCommand($command); + $result = $this->commandResult; + $resArray = explode(PHP_EOL, $result); + foreach ($resArray as $line) { + $resBits = explode(' ', $line); + if (count($resBits) > 4) { + $branch = Preg::replace('/[^A-Za-z0-9 ]/', '', $resBits[4]); + $possibleBranches[$branch] = $resBits[1]; + } + } + } + $command = $this->generateP4Command('changes '. ProcessExecutor::escape($this->getStream() . '/...'), false); + $this->executeCommand($command); + $result = $this->commandResult; + $resArray = explode(PHP_EOL, $result); + $lastCommit = $resArray[0]; + $lastCommitArr = explode(' ', $lastCommit); + $lastCommitNum = $lastCommitArr[1]; + + return ['master' => $possibleBranches[$this->p4Branch] . '@'. $lastCommitNum]; + } + + /** + * @return array + */ + public function getTags(): array + { + $command = $this->generateP4Command('labels'); + $this->executeCommand($command); + $result = $this->commandResult; + $resArray = explode(PHP_EOL, $result); + $tags = []; + foreach ($resArray as $line) { + if (strpos($line, 'Label') !== false) { + $fields = explode(' ', $line); + $tags[$fields[1]] = $this->getStream() . '@' . $fields[1]; + } + } + + return $tags; + } + + public function checkStream(): bool + { + $command = $this->generateP4Command('depots', false); + $this->executeCommand($command); + $result = $this->commandResult; + $resArray = explode(PHP_EOL, $result); + foreach ($resArray as $line) { + if (strpos($line, 'Depot') !== false) { + $fields = explode(' ', $line); + if (strcmp($this->p4Depot, $fields[1]) === 0) { + $this->p4DepotType = $fields[3]; + + return $this->isStream(); + } + } + } + + return false; + } + + /** + * @return mixed|null + */ + protected function getChangeList(string $reference): mixed + { + $index = strpos($reference, '@'); + if ($index === false) { + return null; + } + $label = substr($reference, $index); + $command = $this->generateP4Command(' changes -m1 ' . ProcessExecutor::escape($label)); + $this->executeCommand($command); + $changes = $this->commandResult; + if (strpos($changes, 'Change') !== 0) { + return null; + } + $fields = explode(' ', $changes); + + return $fields[1]; + } + + /** + * @return mixed|null + */ + public function getCommitLogs(string $fromReference, string $toReference): mixed + { + $fromChangeList = $this->getChangeList($fromReference); + if ($fromChangeList === null) { + return null; + } + $toChangeList = $this->getChangeList($toReference); + if ($toChangeList === null) { + return null; + } + $index = strpos($fromReference, '@'); + $main = substr($fromReference, 0, $index) . '/...'; + $command = $this->generateP4Command('filelog ' . ProcessExecutor::escape($main . '@' . $fromChangeList. ',' . $toChangeList)); + $this->executeCommand($command); + + return $this->commandResult; + } + + public function getFilesystem(): Filesystem + { + if (null === $this->filesystem) { + $this->filesystem = new Filesystem($this->process); + } + + return $this->filesystem; + } + + public function setFilesystem(Filesystem $fs): void + { + $this->filesystem = $fs; + } + + private function getP4Executable(): string + { + static $p4Executable; + + if ($p4Executable) { + return $p4Executable; + } + + $finder = new ExecutableFinder(); + + return $p4Executable = $finder->find('p4') ?? 'p4'; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Platform.php b/vendor/composer/composer/src/Composer/Util/Platform.php new file mode 100644 index 000000000..0cd14fed8 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Platform.php @@ -0,0 +1,353 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Pcre\Preg; + +/** + * Platform helper for uniform platform-specific tests. + * + * @author Niels Keurentjes + */ +class Platform +{ + /** @var ?bool */ + private static $isVirtualBoxGuest = null; + /** @var ?bool */ + private static $isWindowsSubsystemForLinux = null; + /** @var ?bool */ + private static $isDocker = null; + + /** + * getcwd() equivalent which always returns a string + * + * @throws \RuntimeException + */ + public static function getCwd(bool $allowEmpty = false): string + { + $cwd = getcwd(); + + // fallback to realpath('') just in case this works but odds are it would break as well if we are in a case where getcwd fails + if (false === $cwd) { + $cwd = realpath(''); + } + + // crappy state, assume '' and hopefully relative paths allow things to continue + if (false === $cwd) { + if ($allowEmpty) { + return ''; + } + + throw new \RuntimeException('Could not determine the current working directory'); + } + + return $cwd; + } + + /** + * Infallible realpath version that falls back on the given $path if realpath is not working + */ + public static function realpath(string $path): string + { + $realPath = realpath($path); + if ($realPath === false) { + return $path; + } + + return $realPath; + } + + /** + * getenv() equivalent but reads from the runtime global variables first + * + * @param non-empty-string $name + * + * @return string|false + */ + public static function getEnv(string $name) + { + if (array_key_exists($name, $_SERVER)) { + return (string) $_SERVER[$name]; + } + if (array_key_exists($name, $_ENV)) { + return (string) $_ENV[$name]; + } + + return getenv($name); + } + + /** + * putenv() equivalent but updates the runtime global variables too + */ + public static function putEnv(string $name, string $value): void + { + putenv($name . '=' . $value); + $_SERVER[$name] = $_ENV[$name] = $value; + } + + /** + * putenv('X') equivalent but updates the runtime global variables too + */ + public static function clearEnv(string $name): void + { + putenv($name); + unset($_SERVER[$name], $_ENV[$name]); + } + + /** + * Parses tildes and environment variables in paths. + */ + public static function expandPath(string $path): string + { + if (Preg::isMatch('#^~[\\/]#', $path)) { + return self::getUserDirectory() . substr($path, 1); + } + + return Preg::replaceCallback('#^(\$|(?P%))(?P\w++)(?(percent)%)(?P.*)#', static function ($matches): string { + // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons + if (Platform::isWindows() && $matches['var'] === 'HOME') { + if ((bool) Platform::getEnv('HOME')) { + return Platform::getEnv('HOME') . $matches['path']; + } + + return Platform::getEnv('USERPROFILE') . $matches['path']; + } + + return Platform::getEnv($matches['var']) . $matches['path']; + }, $path); + } + + /** + * @throws \RuntimeException If the user home could not reliably be determined + * @return string The formal user home as detected from environment parameters + */ + public static function getUserDirectory(): string + { + if (false !== ($home = self::getEnv('HOME'))) { + return $home; + } + + if (self::isWindows() && false !== ($home = self::getEnv('USERPROFILE'))) { + return $home; + } + + if (\function_exists('posix_getuid') && \function_exists('posix_getpwuid')) { + $info = posix_getpwuid(posix_getuid()); + + if (is_array($info)) { + return $info['dir']; + } + } + + throw new \RuntimeException('Could not determine user directory'); + } + + /** + * @return bool Whether the host machine is running on the Windows Subsystem for Linux (WSL) + */ + public static function isWindowsSubsystemForLinux(): bool + { + if (null === self::$isWindowsSubsystemForLinux) { + self::$isWindowsSubsystemForLinux = false; + + // while WSL will be hosted within windows, WSL itself cannot be windows based itself. + if (self::isWindows()) { + return self::$isWindowsSubsystemForLinux = false; + } + + if ( + !(bool) ini_get('open_basedir') + && is_readable('/proc/version') + && false !== stripos((string) Silencer::call('file_get_contents', '/proc/version'), 'microsoft') + && !self::isDocker() // Docker and Podman running inside WSL should not be seen as WSL + ) { + return self::$isWindowsSubsystemForLinux = true; + } + } + + return self::$isWindowsSubsystemForLinux; + } + + /** + * @return bool Whether the host machine is running a Windows OS + */ + public static function isWindows(): bool + { + return \defined('PHP_WINDOWS_VERSION_BUILD'); + } + + public static function isDocker(): bool + { + if (null !== self::$isDocker) { + return self::$isDocker; + } + + // cannot check so assume no + if ((bool) ini_get('open_basedir')) { + return self::$isDocker = false; + } + + // .dockerenv and .containerenv are present in some cases but not reliably + if (file_exists('/.dockerenv') || file_exists('/run/.containerenv') || file_exists('/var/run/.containerenv')) { + return self::$isDocker = true; + } + + // see https://www.baeldung.com/linux/is-process-running-inside-container + $cgroups = [ + '/proc/self/mountinfo', // cgroup v2 + '/proc/1/cgroup', // cgroup v1 + ]; + foreach ($cgroups as $cgroup) { + if (!is_readable($cgroup)) { + continue; + } + // suppress errors as some environments have these files as readable but system restrictions prevent the read from succeeding + // see https://github.com/composer/composer/issues/12095 + try { + $data = @file_get_contents($cgroup); + } catch (\Throwable $e) { + break; + } + if (!is_string($data)) { + continue; + } + // detect default mount points created by Docker/containerd + if (str_contains($data, '/var/lib/docker/') || str_contains($data, '/io.containerd.snapshotter')) { + return self::$isDocker = true; + } + } + + return self::$isDocker = false; + } + + /** + * @return int return a guaranteed binary length of the string, regardless of silly mbstring configs + */ + public static function strlen(string $str): int + { + static $useMbString = null; + if (null === $useMbString) { + $useMbString = \function_exists('mb_strlen') && (bool) ini_get('mbstring.func_overload'); + } + + if ($useMbString) { + return mb_strlen($str, '8bit'); + } + + return \strlen($str); + } + + /** + * @param ?resource $fd Open file descriptor or null to default to STDOUT + */ + public static function isTty($fd = null): bool + { + if ($fd === null) { + $fd = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w'); + if ($fd === false) { + return false; + } + } + + // detect msysgit/mingw and assume this is a tty because detection + // does not work correctly, see https://github.com/composer/composer/issues/9690 + if (in_array(strtoupper((string) self::getEnv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { + return true; + } + + // modern cross-platform function, includes the fstat + // fallback so if it is present we trust it + if (function_exists('stream_isatty')) { + return stream_isatty($fd); + } + + // only trusting this if it is positive, otherwise prefer fstat fallback + if (function_exists('posix_isatty') && posix_isatty($fd)) { + return true; + } + + $stat = @fstat($fd); + if ($stat === false) { + return false; + } + + // Check if formatted mode is S_IFCHR + return 0020000 === ($stat['mode'] & 0170000); + } + + /** + * @return bool Whether the current command is for bash completion + */ + public static function isInputCompletionProcess(): bool + { + return '_complete' === ($_SERVER['argv'][1] ?? null); + } + + public static function workaroundFilesystemIssues(): void + { + if (self::isVirtualBoxGuest()) { + usleep(200000); + } + } + + /** + * Attempts detection of VirtualBox guest VMs + * + * This works based on the process' user being "vagrant", the COMPOSER_RUNTIME_ENV env var being set to "virtualbox", or lsmod showing the virtualbox guest additions are loaded + */ + private static function isVirtualBoxGuest(): bool + { + if (null === self::$isVirtualBoxGuest) { + self::$isVirtualBoxGuest = false; + if (self::isWindows()) { + return self::$isVirtualBoxGuest; + } + + if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { + $processUser = posix_getpwuid(posix_geteuid()); + if (is_array($processUser) && $processUser['name'] === 'vagrant') { + return self::$isVirtualBoxGuest = true; + } + } + + if (self::getEnv('COMPOSER_RUNTIME_ENV') === 'virtualbox') { + return self::$isVirtualBoxGuest = true; + } + + if (defined('PHP_OS_FAMILY') && PHP_OS_FAMILY === 'Linux') { + $process = new ProcessExecutor(); + try { + if (0 === $process->execute(['lsmod'], $output) && str_contains($output, 'vboxguest')) { + return self::$isVirtualBoxGuest = true; + } + } catch (\Exception $e) { + // noop + } + } + } + + return self::$isVirtualBoxGuest; + } + + /** + * @return 'NUL'|'/dev/null' + */ + public static function getDevNull(): string + { + if (self::isWindows()) { + return 'NUL'; + } + + return '/dev/null'; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ProcessExecutor.php b/vendor/composer/composer/src/Composer/Util/ProcessExecutor.php new file mode 100644 index 000000000..8a4a9725d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ProcessExecutor.php @@ -0,0 +1,600 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Seld\Signal\SignalHandler; +use Symfony\Component\Process\Exception\ProcessSignaledException; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\Exception\RuntimeException; +use React\Promise\Promise; +use React\Promise\PromiseInterface; +use Symfony\Component\Process\ExecutableFinder; + +/** + * @author Robert Schönthal + * @author Jordi Boggiano + */ +class ProcessExecutor +{ + private const STATUS_QUEUED = 1; + private const STATUS_STARTED = 2; + private const STATUS_COMPLETED = 3; + private const STATUS_FAILED = 4; + private const STATUS_ABORTED = 5; + + private const BUILTIN_CMD_COMMANDS = [ + 'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date', + 'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto', + 'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause', + 'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set', + 'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol', + ]; + + private const GIT_CMDS_NEED_GIT_DIR = [ + ['show'], + ['log'], + ['branch'], + ['remote', 'set-url'], + ]; + + /** @var int */ + protected static $timeout = 300; + + /** @var bool */ + protected $captureOutput = false; + /** @var string */ + protected $errorOutput = ''; + /** @var ?IOInterface */ + protected $io; + + /** + * @phpstan-var array> + */ + private $jobs = []; + /** @var int */ + private $runningJobs = 0; + /** @var int */ + private $maxJobs = 10; + /** @var int */ + private $idGen = 0; + /** @var bool */ + private $allowAsync = false; + + /** @var array */ + private static $executables = []; + + public function __construct(?IOInterface $io = null) + { + $this->io = $io; + $this->resetMaxJobs(); + } + + /** + * runs a process on the commandline + * + * @param string|non-empty-list $command the command to execute + * @param mixed $output the output will be written into this var if passed by ref + * if a callable is passed it will be used as output handler + * @param null|string $cwd the working directory + * @return int statuscode + */ + public function execute($command, &$output = null, ?string $cwd = null): int + { + if (func_num_args() > 1) { + return $this->doExecute($command, $cwd, false, $output); + } + + return $this->doExecute($command, $cwd, false); + } + + /** + * runs a process on the commandline in TTY mode + * + * @param string|non-empty-list $command the command to execute + * @param null|string $cwd the working directory + * @return int statuscode + */ + public function executeTty($command, ?string $cwd = null): int + { + if (Platform::isTty()) { + return $this->doExecute($command, $cwd, true); + } + + return $this->doExecute($command, $cwd, false); + } + + /** + * @param string|non-empty-list $command + * @param array|null $env + * @param mixed $output + */ + private function runProcess($command, ?string $cwd, ?array $env, bool $tty, &$output = null): ?int + { + // On Windows, we don't rely on the OS to find the executable if possible to avoid lookups + // in the current directory which could be untrusted. Instead we use the ExecutableFinder. + + if (is_string($command)) { + if (Platform::isWindows() && Preg::isMatch('{^([^:/\\\\]++) }', $command, $match)) { + $command = substr_replace($command, self::escape(self::getExecutable($match[1])), 0, strlen($match[1])); + } + + $process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout()); + } else { + if (Platform::isWindows() && \strlen($command[0]) === strcspn($command[0], ':/\\')) { + $command[0] = self::getExecutable($command[0]); + } + + $process = new Process($command, $cwd, $env, null, static::getTimeout()); + } + + if (!Platform::isWindows() && $tty) { + try { + $process->setTty(true); + } catch (RuntimeException $e) { + // ignore TTY enabling errors + } + } + + $callback = is_callable($output) ? $output : function (string $type, string $buffer): void { + $this->outputHandler($type, $buffer); + }; + + $signalHandler = SignalHandler::create( + [SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], + function (string $signal) { + if ($this->io !== null) { + $this->io->writeError( + 'Received '.$signal.', aborting when child process is done', + true, + IOInterface::DEBUG + ); + } + } + ); + + try { + $process->run($callback); + + if ($this->captureOutput && !is_callable($output)) { + $output = $process->getOutput(); + } + + $this->errorOutput = $process->getErrorOutput(); + } catch (ProcessSignaledException $e) { + if ($signalHandler->isTriggered()) { + // exiting as we were signaled and the child process exited too due to the signal + $signalHandler->exitWithLastSignal(); + } + } finally { + $signalHandler->unregister(); + } + + return $process->getExitCode(); + } + + /** + * @param string|non-empty-list $command + * @param mixed $output + */ + private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int + { + $this->outputCommandRun($command, $cwd, false); + + $this->captureOutput = func_num_args() > 3; + $this->errorOutput = ''; + + $env = null; + + $requiresGitDirEnv = $this->requiresGitDirEnv($command); + if ($cwd !== null && $requiresGitDirEnv) { + $isBareRepository = !is_dir(sprintf('%s/.git', rtrim($cwd, '/'))); + if ($isBareRepository) { + $configValue = ''; + $this->runProcess(['git', 'config', 'safe.bareRepository'], $cwd, ['GIT_DIR' => $cwd], $tty, $configValue); + $configValue = trim($configValue); + if ($configValue === 'explicit') { + $env = ['GIT_DIR' => $cwd]; + } + } + } + + return $this->runProcess($command, $cwd, $env, $tty, $output); + } + + /** + * starts a process on the commandline in async mode + * + * @param string|list $command the command to execute + * @param string $cwd the working directory + * @phpstan-return PromiseInterface + */ + public function executeAsync($command, ?string $cwd = null): PromiseInterface + { + if (!$this->allowAsync) { + throw new \LogicException('You must use the ProcessExecutor instance which is part of a Composer\Loop instance to be able to run async processes'); + } + + $job = [ + 'id' => $this->idGen++, + 'status' => self::STATUS_QUEUED, + 'command' => $command, + 'cwd' => $cwd, + ]; + + $resolver = static function ($resolve, $reject) use (&$job): void { + $job['status'] = ProcessExecutor::STATUS_QUEUED; + $job['resolve'] = $resolve; + $job['reject'] = $reject; + }; + + $canceler = static function () use (&$job): void { + if ($job['status'] === ProcessExecutor::STATUS_QUEUED) { + $job['status'] = ProcessExecutor::STATUS_ABORTED; + } + if ($job['status'] !== ProcessExecutor::STATUS_STARTED) { + return; + } + $job['status'] = ProcessExecutor::STATUS_ABORTED; + try { + if (defined('SIGINT')) { + $job['process']->signal(SIGINT); + } + } catch (\Exception $e) { + // signal can throw in various conditions, but we don't care if it fails + } + $job['process']->stop(1); + + throw new \RuntimeException('Aborted process'); + }; + + $promise = new Promise($resolver, $canceler); + $promise = $promise->then(function () use (&$job) { + if ($job['process']->isSuccessful()) { + $job['status'] = ProcessExecutor::STATUS_COMPLETED; + } else { + $job['status'] = ProcessExecutor::STATUS_FAILED; + } + + $this->markJobDone(); + + return $job['process']; + }, function ($e) use (&$job): void { + $job['status'] = ProcessExecutor::STATUS_FAILED; + + $this->markJobDone(); + + throw $e; + }); + $this->jobs[$job['id']] = &$job; + + if ($this->runningJobs < $this->maxJobs) { + $this->startJob($job['id']); + } + + return $promise; + } + + protected function outputHandler(string $type, string $buffer): void + { + if ($this->captureOutput) { + return; + } + + if (null === $this->io) { + echo $buffer; + + return; + } + + if (Process::ERR === $type) { + $this->io->writeErrorRaw($buffer, false); + } else { + $this->io->writeRaw($buffer, false); + } + } + + private function startJob(int $id): void + { + $job = &$this->jobs[$id]; + if ($job['status'] !== self::STATUS_QUEUED) { + return; + } + + // start job + $job['status'] = self::STATUS_STARTED; + $this->runningJobs++; + + $command = $job['command']; + $cwd = $job['cwd']; + + $this->outputCommandRun($command, $cwd, true); + + try { + if (is_string($command)) { + $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout()); + } else { + $process = new Process($command, $cwd, null, null, static::getTimeout()); + } + } catch (\Throwable $e) { + $job['reject']($e); + + return; + } + + $job['process'] = $process; + + try { + $process->start(); + } catch (\Throwable $e) { + $job['reject']($e); + + return; + } + } + + public function setMaxJobs(int $maxJobs): void + { + $this->maxJobs = $maxJobs; + } + + public function resetMaxJobs(): void + { + if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_PROCESSES'))) { + $this->maxJobs = max(1, min(50, (int) $maxJobs)); + } else { + $this->maxJobs = 10; + } + } + + /** + * @param ?int $index job id + */ + public function wait($index = null): void + { + while (true) { + if (0 === $this->countActiveJobs($index)) { + return; + } + + usleep(1000); + } + } + + /** + * @internal + */ + public function enableAsync(): void + { + $this->allowAsync = true; + } + + /** + * @internal + * + * @param ?int $index job id + * @return int number of active (queued or started) jobs + */ + public function countActiveJobs($index = null): int + { + // tick + foreach ($this->jobs as $job) { + if ($job['status'] === self::STATUS_STARTED) { + if (!$job['process']->isRunning()) { + call_user_func($job['resolve'], $job['process']); + } + + $job['process']->checkTimeout(); + } + + if ($this->runningJobs < $this->maxJobs) { + if ($job['status'] === self::STATUS_QUEUED) { + $this->startJob($job['id']); + } + } + } + + if (null !== $index) { + return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0; + } + + $active = 0; + foreach ($this->jobs as $job) { + if ($job['status'] < self::STATUS_COMPLETED) { + $active++; + } else { + unset($this->jobs[$job['id']]); + } + } + + return $active; + } + + private function markJobDone(): void + { + $this->runningJobs--; + } + + /** + * @return string[] + */ + public function splitLines(?string $output): array + { + $output = trim((string) $output); + + return $output === '' ? [] : Preg::split('{\r?\n}', $output); + } + + /** + * Get any error output from the last command + */ + public function getErrorOutput(): string + { + return $this->errorOutput; + } + + /** + * @return int the timeout in seconds + */ + public static function getTimeout(): int + { + return static::$timeout; + } + + /** + * @param int $timeout the timeout in seconds + */ + public static function setTimeout(int $timeout): void + { + static::$timeout = $timeout; + } + + /** + * Escapes a string to be used as a shell argument. + * + * @param string|false|null $argument The argument that will be escaped + * + * @return string The escaped argument + */ + public static function escape($argument): string + { + return self::escapeArgument($argument); + } + + /** + * @param string|list $command + */ + private function outputCommandRun($command, ?string $cwd, bool $async): void + { + if (null === $this->io || !$this->io->isDebug()) { + return; + } + + $commandString = is_string($command) ? $command : implode(' ', array_map(self::class.'::escape', $command)); + $safeCommand = Preg::replaceCallback('{://(?P[^:/\s]+):(?P[^@\s/]+)@}i', static function ($m): string { + // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that + if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) { + return '://***:***@'; + } + if (Preg::isMatch('{^[a-f0-9]{12,}$}', $m['user'])) { + return '://***:***@'; + } + + return '://'.$m['user'].':***@'; + }, $commandString); + $safeCommand = Preg::replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand); + $this->io->writeError('Executing'.($async ? ' async' : '').' command ('.($cwd ?: 'CWD').'): '.$safeCommand); + } + + /** + * Escapes a string to be used as a shell argument for Symfony Process. + * + * This method expects cmd.exe to be started with the /V:ON option, which + * enables delayed environment variable expansion using ! as the delimiter. + * If this is not the case, any escaped ^^!var^^! will be transformed to + * ^!var^! and introduce two unintended carets. + * + * Modified from https://github.com/johnstevenson/winbox-args + * MIT Licensed (c) John Stevenson + * + * @param string|false|null $argument + */ + private static function escapeArgument($argument): string + { + if ('' === ($argument = (string) $argument)) { + return escapeshellarg($argument); + } + + if (!Platform::isWindows()) { + return "'".str_replace("'", "'\\''", $argument)."'"; + } + + // New lines break cmd.exe command parsing + // and special chars like the fullwidth quote can be used to break out + // of parameter encoding via "Best Fit" encoding conversion + $argument = strtr($argument, [ + "\n" => ' ', + "\u{ff02}" => '"', + "\u{02ba}" => '"', + "\u{301d}" => '"', + "\u{301e}" => '"', + "\u{030e}" => '"', + "\u{ff1a}" => ':', + "\u{0589}" => ':', + "\u{2236}" => ':', + "\u{ff0f}" => '/', + "\u{2044}" => '/', + "\u{2215}" => '/', + "\u{00b4}" => '/', + ]); + + // In addition to whitespace, commas need quoting to preserve paths + $quote = strpbrk($argument, " \t,") !== false; + $argument = Preg::replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes); + $meta = $dquotes > 0 || Preg::isMatch('/%[^%]+%|![^!]+!/', $argument); + + if (!$meta && !$quote) { + $quote = strpbrk($argument, '^&|<>()') !== false; + } + + if ($quote) { + $argument = '"'.Preg::replace('/(\\\\*)$/', '$1$1', $argument).'"'; + } + + if ($meta) { + $argument = Preg::replace('/(["^&|<>()%])/', '^$1', $argument); + $argument = Preg::replace('/(!)/', '^^$1', $argument); + } + + return $argument; + } + + /** + * @param string[]|string $command + */ + public function requiresGitDirEnv($command): bool + { + $cmd = !is_array($command) ? explode(' ', $command) : $command; + if ($cmd[0] !== 'git') { + return false; + } + + foreach (self::GIT_CMDS_NEED_GIT_DIR as $gitCmd) { + if (array_intersect($cmd, $gitCmd) === $gitCmd) { + return true; + } + } + + return false; + } + + /** + * Resolves executable paths on Windows + */ + private static function getExecutable(string $name): string + { + if (\in_array(strtolower($name), self::BUILTIN_CMD_COMMANDS, true)) { + return $name; + } + + if (!isset(self::$executables[$name])) { + $path = (new ExecutableFinder())->find($name, $name); + if ($path !== null) { + self::$executables[$name] = $path; + } + } + + return self::$executables[$name] ?? $name; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php b/vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php new file mode 100644 index 000000000..a2d2eacce --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php @@ -0,0 +1,738 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Closure; +use Composer\Config; +use Composer\Downloader\MaxFileSizeExceededException; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Util\Http\Response; +use Composer\Util\Http\ProxyManager; + +/** + * @internal + * @author François Pluchino + * @author Jordi Boggiano + * @author Nils Adermann + */ +class RemoteFilesystem +{ + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var string */ + private $scheme; + /** @var int */ + private $bytesMax; + /** @var string */ + private $originUrl; + /** @var non-empty-string */ + private $fileUrl; + /** @var ?string */ + private $fileName; + /** @var bool */ + private $retry = false; + /** @var bool */ + private $progress; + /** @var ?int */ + private $lastProgress; + /** @var mixed[] */ + private $options = []; + /** @var bool */ + private $disableTls = false; + /** @var list */ + private $lastHeaders; + /** @var bool */ + private $storeAuth = false; + /** @var AuthHelper */ + private $authHelper; + /** @var bool */ + private $degradedMode = false; + /** @var int */ + private $redirects; + /** @var int */ + private $maxRedirects = 20; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The config + * @param mixed[] $options The options + */ + public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false, ?AuthHelper $authHelper = null) + { + $this->io = $io; + + // Setup TLS options + // The cafile option can be set via config.json + if ($disableTls === false) { + $this->options = StreamContextFactory::getTlsDefaults($options, $io); + } else { + $this->disableTls = true; + } + + // handle the other externally set options normally. + $this->options = array_replace_recursive($this->options, $options); + $this->config = $config; + $this->authHelper = $authHelper ?? new AuthHelper($io, $config); + } + + /** + * Copy the remote file in local. + * + * @param string $originUrl The origin URL + * @param non-empty-string $fileUrl The file URL + * @param string $fileName the local filename + * @param bool $progress Display the progression + * @param mixed[] $options Additional context options + * + * @return bool true + */ + public function copy(string $originUrl, string $fileUrl, string $fileName, bool $progress = true, array $options = []) + { + return $this->get($originUrl, $fileUrl, $options, $fileName, $progress); + } + + /** + * Get the content. + * + * @param string $originUrl The origin URL + * @param non-empty-string $fileUrl The file URL + * @param bool $progress Display the progression + * @param mixed[] $options Additional context options + * + * @return bool|string The content + */ + public function getContents(string $originUrl, string $fileUrl, bool $progress = true, array $options = []) + { + return $this->get($originUrl, $fileUrl, $options, null, $progress); + } + + /** + * Retrieve the options set in the constructor + * + * @return mixed[] Options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Merges new options + * + * @param mixed[] $options + * @return void + */ + public function setOptions(array $options) + { + $this->options = array_replace_recursive($this->options, $options); + } + + /** + * Check is disable TLS. + * + * @return bool + */ + public function isTlsDisabled() + { + return $this->disableTls === true; + } + + /** + * Returns the headers of the last request + * + * @return list + */ + public function getLastHeaders() + { + return $this->lastHeaders; + } + + /** + * @param string[] $headers array of returned headers like from getLastHeaders() + * @return int|null + */ + public static function findStatusCode(array $headers) + { + $value = null; + foreach ($headers as $header) { + if (Preg::isMatch('{^HTTP/\S+ (\d+)}i', $header, $match)) { + // In case of redirects, http_response_headers contains the headers of all responses + // so we can not return directly and need to keep iterating + $value = (int) $match[1]; + } + } + + return $value; + } + + /** + * @param string[] $headers array of returned headers like from getLastHeaders() + * @return string|null + */ + public function findStatusMessage(array $headers) + { + $value = null; + foreach ($headers as $header) { + if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) { + // In case of redirects, http_response_headers contains the headers of all responses + // so we can not return directly and need to keep iterating + $value = $header; + } + } + + return $value; + } + + /** + * Get file content or copy action. + * + * @param string $originUrl The origin URL + * @param non-empty-string $fileUrl The file URL + * @param mixed[] $additionalOptions context options + * @param string $fileName the local filename + * @param bool $progress Display the progression + * + * @throws TransportException|\Exception + * @throws TransportException When the file could not be downloaded + * + * @return bool|string + */ + protected function get(string $originUrl, string $fileUrl, array $additionalOptions = [], ?string $fileName = null, bool $progress = true) + { + $this->scheme = parse_url(strtr($fileUrl, '\\', '/'), PHP_URL_SCHEME); + $this->bytesMax = 0; + $this->originUrl = $originUrl; + $this->fileUrl = $fileUrl; + $this->fileName = $fileName; + $this->progress = $progress; + $this->lastProgress = null; + $retryAuthFailure = true; + $this->lastHeaders = []; + $this->redirects = 1; // The first request counts. + + $tempAdditionalOptions = $additionalOptions; + if (isset($tempAdditionalOptions['retry-auth-failure'])) { + $retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure']; + + unset($tempAdditionalOptions['retry-auth-failure']); + } + + $isRedirect = false; + if (isset($tempAdditionalOptions['redirects'])) { + $this->redirects = $tempAdditionalOptions['redirects']; + $isRedirect = true; + + unset($tempAdditionalOptions['redirects']); + } + + $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions); + unset($tempAdditionalOptions); + + $origFileUrl = $fileUrl; + + if (isset($options['prevent_ip_access_callable'])) { + throw new \RuntimeException("RemoteFilesystem doesn't support the 'prevent_ip_access_callable' config."); + } + + if (isset($options['gitlab-token'])) { + $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token']; + unset($options['gitlab-token']); + } + + if (isset($options['http'])) { + $options['http']['ignore_errors'] = true; + } + + if ($this->degradedMode && strpos($fileUrl, 'http://repo.packagist.org/') === 0) { + // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol + $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20); + $degradedPackagist = true; + } + + $maxFileSize = null; + if (isset($options['max_file_size'])) { + $maxFileSize = $options['max_file_size']; + unset($options['max_file_size']); + } + + $ctx = StreamContextFactory::getContext($fileUrl, $options, ['notification' => Closure::fromCallable([$this, 'callbackGet'])]); + + $proxy = ProxyManager::getInstance()->getProxyForRequest($fileUrl); + $usingProxy = $proxy->getStatus(' using proxy (%s)'); + $this->io->writeError((strpos($origFileUrl, 'http') === 0 ? 'Downloading ' : 'Reading ') . Url::sanitize($origFileUrl) . $usingProxy, true, IOInterface::DEBUG); + unset($origFileUrl, $proxy, $usingProxy); + + // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 + if ((!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist)) { + $this->config->prohibitUrlByConfig($fileUrl, $this->io); + } + + if ($this->progress && !$isRedirect) { + $this->io->writeError("Downloading (connecting...)", false); + } + + $errorMessage = ''; + $errorCode = 0; + $result = false; + set_error_handler(static function ($code, $msg) use (&$errorMessage): bool { + if ($errorMessage) { + $errorMessage .= "\n"; + } + $errorMessage .= Preg::replace('{^file_get_contents\(.*?\): }', '', $msg); + + return true; + }); + $http_response_header = []; + try { + $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header, $maxFileSize); + + if (!empty($http_response_header[0])) { + $statusCode = self::findStatusCode($http_response_header); + if ($statusCode >= 300 && Response::findHeaderValue($http_response_header, 'content-type') === 'application/json') { + HttpDownloader::outputWarnings($this->io, $originUrl, json_decode($result, true)); + } + + if (in_array($statusCode, [401, 403]) && $retryAuthFailure) { + $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $http_response_header); + } + } + + $contentLength = !empty($http_response_header[0]) ? Response::findHeaderValue($http_response_header, 'content-length') : null; + if ($contentLength && Platform::strlen($result) < $contentLength) { + // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP + $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength); + $e->setHeaders($http_response_header); + $e->setStatusCode(self::findStatusCode($http_response_header)); + try { + $e->setResponse($this->decodeResult($result, $http_response_header)); + } catch (\Exception $discarded) { + $e->setResponse($this->normalizeResult($result)); + } + + $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::DEBUG); + + throw $e; + } + } catch (\Exception $e) { + if ($e instanceof TransportException && !empty($http_response_header[0])) { + $e->setHeaders($http_response_header); + $e->setStatusCode(self::findStatusCode($http_response_header)); + } + if ($e instanceof TransportException && $result !== false) { + $e->setResponse($this->decodeResult($result, $http_response_header)); + } + $result = false; + } + if ($errorMessage && !filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) { + $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')'; + } + restore_error_handler(); + if (isset($e) && !$this->retry) { + if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { + $this->degradedMode = true; + $this->io->writeError(''); + $this->io->writeError([ + ''.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', + ]); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + + throw $e; + } + + $statusCode = null; + $contentType = null; + $locationHeader = null; + if (!empty($http_response_header[0])) { + $statusCode = self::findStatusCode($http_response_header); + $contentType = Response::findHeaderValue($http_response_header, 'content-type'); + $locationHeader = Response::findHeaderValue($http_response_header, 'location'); + } + + // check for bitbucket login page asking to authenticate + if ($originUrl === 'bitbucket.org' + && !$this->authHelper->isPublicBitBucketDownload($fileUrl) + && substr($fileUrl, -4) === '.zip' + && (!$locationHeader || substr(parse_url($locationHeader, PHP_URL_PATH), -4) !== '.zip') + && $contentType && Preg::isMatch('{^text/html\b}i', $contentType) + ) { + $result = false; + if ($retryAuthFailure) { + $this->promptAuthAndRetry(401); + } + } + + // check for gitlab 404 when downloading archives + if ($statusCode === 404 + && in_array($originUrl, $this->config->get('gitlab-domains'), true) + && false !== strpos($fileUrl, 'archive.zip') + ) { + $result = false; + if ($retryAuthFailure) { + $this->promptAuthAndRetry(401); + } + } + + // handle 3xx redirects, 304 Not Modified is excluded + $hasFollowedRedirect = false; + if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) { + $hasFollowedRedirect = true; + $result = $this->handleRedirect($http_response_header, $additionalOptions, $result); + } + + // fail 4xx and 5xx responses and capture the response + if ($statusCode && $statusCode >= 400 && $statusCode <= 599) { + if (!$this->retry) { + if ($this->progress && !$isRedirect) { + $this->io->overwriteError("Downloading (failed)", false); + } + + $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.$http_response_header[0].')', $statusCode); + $e->setHeaders($http_response_header); + $e->setResponse($this->decodeResult($result, $http_response_header)); + $e->setStatusCode($statusCode); + throw $e; + } + $result = false; + } + + if ($this->progress && !$this->retry && !$isRedirect) { + $this->io->overwriteError("Downloading (".($result === false ? 'failed' : '100%').")", false); + } + + // decode gzip + if ($result && extension_loaded('zlib') && strpos($fileUrl, 'http') === 0 && !$hasFollowedRedirect) { + try { + $result = $this->decodeResult($result, $http_response_header); + } catch (\Exception $e) { + if ($this->degradedMode) { + throw $e; + } + + $this->degradedMode = true; + $this->io->writeError([ + '', + 'Failed to decode response: '.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', + ]); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + } + + // handle copy command if download was successful + if (false !== $result && null !== $fileName && !$isRedirect) { + if ('' === $result) { + throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response'); + } + + $errorMessage = ''; + set_error_handler(static function ($code, $msg) use (&$errorMessage): bool { + if ($errorMessage) { + $errorMessage .= "\n"; + } + $errorMessage .= Preg::replace('{^file_put_contents\(.*?\): }', '', $msg); + + return true; + }); + $result = (bool) file_put_contents($fileName, $result); + restore_error_handler(); + if (false === $result) { + throw new TransportException('The "'.$this->fileUrl.'" file could not be written to '.$fileName.': '.$errorMessage); + } + } + + if ($this->retry) { + $this->retry = false; + + $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + + if ($this->storeAuth) { + $this->authHelper->storeAuth($this->originUrl, $this->storeAuth); + $this->storeAuth = false; + } + + return $result; + } + + if (false === $result) { + $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode); + if (!empty($http_response_header[0])) { + $e->setHeaders($http_response_header); + } + + if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { + $this->degradedMode = true; + $this->io->writeError(''); + $this->io->writeError([ + ''.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', + ]); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + + throw $e; + } + + if (!empty($http_response_header[0])) { + $this->lastHeaders = $http_response_header; + } + + return $result; + } + + /** + * Get contents of remote URL. + * + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param resource $context The stream context + * @param string[] $responseHeaders + * @param int $maxFileSize The maximum allowed file size + * + * @return string|false The response contents or false on failure + * + * @param-out list $responseHeaders + */ + protected function getRemoteContents(string $originUrl, string $fileUrl, $context, ?array &$responseHeaders = null, ?int $maxFileSize = null) + { + $result = false; + + if (\PHP_VERSION_ID >= 80400) { + http_clear_last_response_headers(); + } + + try { + $e = null; + if ($maxFileSize !== null) { + $result = file_get_contents($fileUrl, false, $context, 0, $maxFileSize); + } else { + // passing `null` to file_get_contents will convert `null` to `0` and return 0 bytes + $result = file_get_contents($fileUrl, false, $context); + } + } catch (\Throwable $e) { + } + + if ($result !== false && $maxFileSize !== null && Platform::strlen($result) >= $maxFileSize) { + throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($result) . ' of allowed ' . $maxFileSize . ' bytes'); + } + + // https://www.php.net/manual/en/reserved.variables.httpresponseheader.php + if (\PHP_VERSION_ID >= 80400) { + $responseHeaders = http_get_last_response_headers() ?? []; + http_clear_last_response_headers(); + } else { + $responseHeaders = $http_response_header ?? []; + } + + if (null !== $e) { + throw $e; + } + + return $result; + } + + /** + * Get notification action. + * + * @param int $notificationCode The notification code + * @param int $severity The severity level + * @param string $message The message + * @param int $messageCode The message code + * @param int $bytesTransferred The loaded size + * @param int $bytesMax The total size + * + * @return void + * + * @throws TransportException + */ + protected function callbackGet(int $notificationCode, int $severity, ?string $message, int $messageCode, int $bytesTransferred, int $bytesMax) + { + switch ($notificationCode) { + case STREAM_NOTIFY_FAILURE: + if (400 === $messageCode) { + // This might happen if your host is secured by ssl client certificate authentication + // but you do not send an appropriate certificate + throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode); + } + break; + + case STREAM_NOTIFY_FILE_SIZE_IS: + $this->bytesMax = $bytesMax; + break; + + case STREAM_NOTIFY_PROGRESS: + if ($this->bytesMax > 0 && $this->progress) { + $progression = min(100, (int) round($bytesTransferred / $this->bytesMax * 100)); + + if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) { + $this->lastProgress = $progression; + $this->io->overwriteError("Downloading ($progression%)", false); + } + } + break; + + default: + break; + } + } + + /** + * @param positive-int $httpStatus + * @param string[] $headers + * + * @return void + */ + protected function promptAuthAndRetry($httpStatus, ?string $reason = null, array $headers = []) + { + $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $headers, 1 /** always pass 1 as RemoteFilesystem is single threaded there is no race condition possible */); + + $this->storeAuth = $result['storeAuth']; + $this->retry = $result['retry']; + + if ($this->retry) { + throw new TransportException('RETRY'); + } + } + + /** + * @param mixed[] $additionalOptions + * + * @return mixed[] + */ + protected function getOptionsForUrl(string $originUrl, array $additionalOptions) + { + $tlsOptions = []; + $headers = []; + + if (extension_loaded('zlib')) { + $headers[] = 'Accept-Encoding: gzip'; + } + + $options = array_replace_recursive($this->options, $tlsOptions, $additionalOptions); + if (!$this->degradedMode) { + // degraded mode disables HTTP/1.1 which causes issues with some bad + // proxies/software due to the use of chunked encoding + $options['http']['protocol_version'] = 1.1; + $headers[] = 'Connection: close'; + } + + if (isset($options['http']['header']) && !is_array($options['http']['header'])) { + $options['http']['header'] = explode("\r\n", trim($options['http']['header'], "\r\n")); + } + $options = $this->authHelper->addAuthenticationOptions($options, $originUrl, $this->fileUrl); + + $options['http']['follow_location'] = 0; + + foreach ($headers as $header) { + $options['http']['header'][] = $header; + } + + return $options; + } + + /** + * @param string[] $responseHeaders + * @param mixed[] $additionalOptions + * @param string|false $result + * + * @return bool|string + */ + private function handleRedirect(array $responseHeaders, array $additionalOptions, $result) + { + if ($locationHeader = Response::findHeaderValue($responseHeaders, 'location')) { + if (parse_url($locationHeader, PHP_URL_SCHEME)) { + // Absolute URL; e.g. https://example.com/composer + $targetUrl = $locationHeader; + } elseif (parse_url($locationHeader, PHP_URL_HOST)) { + // Scheme relative; e.g. //example.com/foo + $targetUrl = $this->scheme.':'.$locationHeader; + } elseif ('/' === $locationHeader[0]) { + // Absolute path; e.g. /foo + $urlHost = parse_url($this->fileUrl, PHP_URL_HOST); + + // Replace path using hostname as an anchor. + $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $this->fileUrl); + } else { + // Relative path; e.g. foo + // This actually differs from PHP which seems to add duplicate slashes. + $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $this->fileUrl); + } + } + + if (!empty($targetUrl)) { + $this->redirects++; + + $this->io->writeError('', true, IOInterface::DEBUG); + $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, Url::sanitize($targetUrl)), true, IOInterface::DEBUG); + + $additionalOptions['redirects'] = $this->redirects; + + return $this->get(parse_url($targetUrl, PHP_URL_HOST), $targetUrl, $additionalOptions, $this->fileName, $this->progress); + } + + if (!$this->retry) { + $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded, got redirect without Location ('.$responseHeaders[0].')'); + $e->setHeaders($responseHeaders); + $e->setResponse($this->decodeResult($result, $responseHeaders)); + + throw $e; + } + + return false; + } + + /** + * @param string|false $result + * @param string[] $responseHeaders + */ + private function decodeResult($result, array $responseHeaders): ?string + { + // decode gzip + if ($result && extension_loaded('zlib')) { + $contentEncoding = Response::findHeaderValue($responseHeaders, 'content-encoding'); + $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding); + + if ($decode) { + $result = zlib_decode($result); + + if ($result === false) { + throw new TransportException('Failed to decode zlib stream'); + } + } + } + + return $this->normalizeResult($result); + } + + /** + * @param string|false $result + */ + private function normalizeResult($result): ?string + { + if ($result === false) { + return null; + } + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Silencer.php b/vendor/composer/composer/src/Composer/Util/Silencer.php new file mode 100644 index 000000000..6dd0efb34 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Silencer.php @@ -0,0 +1,77 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * Temporarily suppress PHP error reporting, usually warnings and below. + * + * @author Niels Keurentjes + */ +class Silencer +{ + /** + * @var int[] Unpop stack + */ + private static $stack = []; + + /** + * Suppresses given mask or errors. + * + * @param int|null $mask Error levels to suppress, default value NULL indicates all warnings and below. + * @return int The old error reporting level. + */ + public static function suppress(?int $mask = null): int + { + if (!isset($mask)) { + $mask = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED; + } + $old = error_reporting(); + self::$stack[] = $old; + error_reporting($old & ~$mask); + + return $old; + } + + /** + * Restores a single state. + */ + public static function restore(): void + { + if (!empty(self::$stack)) { + error_reporting(array_pop(self::$stack)); + } + } + + /** + * Calls a specified function while silencing warnings and below. + * + * @param callable $callable Function to execute. + * @param mixed $parameters Function to execute. + * @throws \Exception Any exceptions from the callback are rethrown. + * @return mixed Return value of the callback. + */ + public static function call(callable $callable, ...$parameters) + { + try { + self::suppress(); + $result = $callable(...$parameters); + self::restore(); + + return $result; + } catch (\Exception $e) { + // Use a finally block for this when requirements are raised to PHP 5.5 + self::restore(); + throw $e; + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/StreamContextFactory.php b/vendor/composer/composer/src/Composer/Util/StreamContextFactory.php new file mode 100644 index 000000000..be4c976a9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/StreamContextFactory.php @@ -0,0 +1,255 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Composer; +use Composer\CaBundle\CaBundle; +use Composer\Downloader\TransportException; +use Composer\Repository\PlatformRepository; +use Composer\Util\Http\ProxyManager; +use Psr\Log\LoggerInterface; + +/** + * Allows the creation of a basic context supporting http proxy + * + * @author Jordan Alliot + * @author Markus Tacker + */ +final class StreamContextFactory +{ + /** + * Creates a context supporting HTTP proxies + * + * @param non-empty-string $url URL the context is to be used for + * @phpstan-param array{http?: array{follow_location?: int, max_redirects?: int, header?: string|array}} $defaultOptions + * @param mixed[] $defaultOptions Options to merge with the default + * @param mixed[] $defaultParams Parameters to specify on the context + * @throws \RuntimeException if https proxy required and OpenSSL uninstalled + * @return resource Default context + */ + public static function getContext(string $url, array $defaultOptions = [], array $defaultParams = []) + { + $options = ['http' => [ + // specify defaults again to try and work better with curlwrappers enabled + 'follow_location' => 1, + 'max_redirects' => 20, + ]]; + + $options = array_replace_recursive($options, self::initOptions($url, $defaultOptions)); + unset($defaultOptions['http']['header']); + $options = array_replace_recursive($options, $defaultOptions); + + if (isset($options['http']['header'])) { + $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']); + } + + return stream_context_create($options, $defaultParams); + } + + /** + * @param non-empty-string $url + * @param mixed[] $options + * @param bool $forCurl When true, will not add proxy values as these are handled separately + * @phpstan-return array{http: array{header: string[], proxy?: string, request_fulluri: bool}, ssl?: mixed[]} + * @return array formatted as a stream context array + */ + public static function initOptions(string $url, array $options, bool $forCurl = false): array + { + // Make sure the headers are in an array form + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + if (is_string($options['http']['header'])) { + $options['http']['header'] = explode("\r\n", $options['http']['header']); + } + + // Add stream proxy options if there is a proxy + if (!$forCurl) { + $proxy = ProxyManager::getInstance()->getProxyForRequest($url); + $proxyOptions = $proxy->getContextOptions(); + if ($proxyOptions !== null) { + $isHttpsRequest = 0 === strpos($url, 'https://'); + + if ($proxy->isSecure()) { + if (!extension_loaded('openssl')) { + throw new TransportException('You must enable the openssl extension to use a secure proxy.'); + } + if ($isHttpsRequest) { + throw new TransportException('You must enable the curl extension to make https requests through a secure proxy.'); + } + } elseif ($isHttpsRequest && !extension_loaded('openssl')) { + throw new TransportException('You must enable the openssl extension to make https requests through a proxy.'); + } + + // Header will be a Proxy-Authorization string or not set + if (isset($proxyOptions['http']['header'])) { + $options['http']['header'][] = $proxyOptions['http']['header']; + unset($proxyOptions['http']['header']); + } + $options = array_replace_recursive($options, $proxyOptions); + } + } + + if (defined('HHVM_VERSION')) { + $phpVersion = 'HHVM ' . HHVM_VERSION; + } else { + $phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; + } + + if ($forCurl) { + $curl = curl_version(); + $httpVersion = 'cURL '.$curl['version']; + } else { + $httpVersion = 'streams'; + } + + if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) { + $platformPhpVersion = PlatformRepository::getPlatformPhpVersion(); + $options['http']['header'][] = sprintf( + 'User-Agent: Composer/%s (%s; %s; %s; %s%s%s)', + Composer::getVersion(), + function_exists('php_uname') ? php_uname('s') : 'Unknown', + function_exists('php_uname') ? php_uname('r') : 'Unknown', + $phpVersion, + $httpVersion, + $platformPhpVersion ? '; Platform-PHP '.$platformPhpVersion : '', + Platform::getEnv('CI') ? '; CI' : '' + ); + } + + return $options; + } + + /** + * @param mixed[] $options + * + * @return mixed[] + */ + public static function getTlsDefaults(array $options, ?LoggerInterface $logger = null): array + { + $ciphers = implode(':', [ + 'ECDHE-RSA-AES128-GCM-SHA256', + 'ECDHE-ECDSA-AES128-GCM-SHA256', + 'ECDHE-RSA-AES256-GCM-SHA384', + 'ECDHE-ECDSA-AES256-GCM-SHA384', + 'DHE-RSA-AES128-GCM-SHA256', + 'DHE-DSS-AES128-GCM-SHA256', + 'kEDH+AESGCM', + 'ECDHE-RSA-AES128-SHA256', + 'ECDHE-ECDSA-AES128-SHA256', + 'ECDHE-RSA-AES128-SHA', + 'ECDHE-ECDSA-AES128-SHA', + 'ECDHE-RSA-AES256-SHA384', + 'ECDHE-ECDSA-AES256-SHA384', + 'ECDHE-RSA-AES256-SHA', + 'ECDHE-ECDSA-AES256-SHA', + 'DHE-RSA-AES128-SHA256', + 'DHE-RSA-AES128-SHA', + 'DHE-DSS-AES128-SHA256', + 'DHE-RSA-AES256-SHA256', + 'DHE-DSS-AES256-SHA', + 'DHE-RSA-AES256-SHA', + 'AES128-GCM-SHA256', + 'AES256-GCM-SHA384', + 'AES128-SHA256', + 'AES256-SHA256', + 'AES128-SHA', + 'AES256-SHA', + 'AES', + 'CAMELLIA', + 'DES-CBC3-SHA', + '!aNULL', + '!eNULL', + '!EXPORT', + '!DES', + '!RC4', + '!MD5', + '!PSK', + '!aECDH', + '!EDH-DSS-DES-CBC3-SHA', + '!EDH-RSA-DES-CBC3-SHA', + '!KRB5-DES-CBC3-SHA', + ]); + + /** + * CN_match and SNI_server_name are only known once a URL is passed. + * They will be set in the getOptionsForUrl() method which receives a URL. + * + * cafile or capath can be overridden by passing in those options to constructor. + */ + $defaults = [ + 'ssl' => [ + 'ciphers' => $ciphers, + 'verify_peer' => true, + 'verify_depth' => 7, + 'SNI_enabled' => true, + 'capture_peer_cert' => true, + ], + ]; + + if (isset($options['ssl'])) { + $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']); + } + + /** + * Attempt to find a local cafile or throw an exception if none pre-set + * The user may go download one if this occurs. + */ + if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) { + $result = CaBundle::getSystemCaRootBundlePath($logger); + + if (is_dir($result)) { + $defaults['ssl']['capath'] = $result; + } else { + $defaults['ssl']['cafile'] = $result; + } + } + + if (isset($defaults['ssl']['cafile']) && (!Filesystem::isReadable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $logger))) { + throw new TransportException('The configured cafile was not valid or could not be read.'); + } + + if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !Filesystem::isReadable($defaults['ssl']['capath']))) { + throw new TransportException('The configured capath was not valid or could not be read.'); + } + + /** + * Disable TLS compression to prevent CRIME attacks where supported. + */ + $defaults['ssl']['disable_compression'] = true; + + return $defaults; + } + + /** + * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and + * NOT at the end of the array + * + * This method fixes the array by moving the content-type header to the end + * + * @link https://bugs.php.net/bug.php?id=61548 + * @param string|string[] $header + * @return string[] + */ + private static function fixHttpHeaderField($header): array + { + if (!is_array($header)) { + $header = explode("\r\n", $header); + } + uasort($header, static function ($el): int { + return stripos($el, 'content-type') === 0 ? 1 : -1; + }); + + return $header; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Svn.php b/vendor/composer/composer/src/Composer/Util/Svn.php new file mode 100644 index 000000000..9ea057aac --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Svn.php @@ -0,0 +1,363 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; + +/** + * @author Till Klampaeckel + * @author Jordi Boggiano + */ +class Svn +{ + private const MAX_QTY_AUTH_TRIES = 5; + + /** + * @var ?array{username: string, password: string} + */ + protected $credentials; + + /** + * @var bool + */ + protected $hasAuth; + + /** + * @var IOInterface + */ + protected $io; + + /** + * @var string + */ + protected $url; + + /** + * @var bool + */ + protected $cacheCredentials = true; + + /** + * @var ProcessExecutor + */ + protected $process; + + /** + * @var int + */ + protected $qtyAuthTries = 0; + + /** + * @var Config + */ + protected $config; + + /** + * @var string|null + */ + private static $version; + + public function __construct(string $url, IOInterface $io, Config $config, ?ProcessExecutor $process = null) + { + $this->url = $url; + $this->io = $io; + $this->config = $config; + $this->process = $process ?: new ProcessExecutor($io); + } + + public static function cleanEnv(): void + { + // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 + Platform::clearEnv('DYLD_LIBRARY_PATH'); + } + + /** + * Execute an SVN remote command and try to fix up the process with credentials + * if necessary. + * + * @param non-empty-list $command SVN command to run + * @param string $url SVN url + * @param ?string $cwd Working directory + * @param ?string $path Target for a checkout + * @param bool $verbose Output all output to the user + * + * @throws \RuntimeException + */ + public function execute(array $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = false): string + { + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($url, $this->io); + + return $this->executeWithAuthRetry($command, $cwd, $url, $path, $verbose); + } + + /** + * Execute an SVN local command and try to fix up the process with credentials + * if necessary. + * + * @param non-empty-list $command SVN command to run + * @param string $path Path argument passed thru to the command + * @param string $cwd Working directory + * @param bool $verbose Output all output to the user + * + * @throws \RuntimeException + */ + public function executeLocal(array $command, string $path, ?string $cwd = null, bool $verbose = false): string + { + // A local command has no remote url + return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose); + } + + /** + * @param non-empty-list $svnCommand + */ + private function executeWithAuthRetry(array $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose): ?string + { + // Regenerate the command at each try, to use the newly user-provided credentials + $command = $this->getCommand($svnCommand, $url, $path); + + $output = null; + $io = $this->io; + $handler = static function ($type, $buffer) use (&$output, $io, $verbose) { + if ($type !== 'out') { + return null; + } + if (strpos($buffer, 'Redirecting to URL ') === 0) { + return null; + } + $output .= $buffer; + if ($verbose) { + $io->writeError($buffer, false); + } + }; + $status = $this->process->execute($command, $handler, $cwd); + if (0 === $status) { + return $output; + } + + $errorOutput = $this->process->getErrorOutput(); + $fullOutput = trim(implode("\n", [$output, $errorOutput])); + + // the error is not auth-related + if (false === stripos($fullOutput, 'Could not authenticate to server:') + && false === stripos($fullOutput, 'authorization failed') + && false === stripos($fullOutput, 'svn: E170001:') + && false === stripos($fullOutput, 'svn: E215004:')) { + throw new \RuntimeException($fullOutput); + } + + if (!$this->hasAuth()) { + $this->doAuthDance(); + } + + // try to authenticate if maximum quantity of tries not reached + if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) { + // restart the process + return $this->executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose); + } + + throw new \RuntimeException( + 'wrong credentials provided ('.$fullOutput.')' + ); + } + + public function setCacheCredentials(bool $cacheCredentials): void + { + $this->cacheCredentials = $cacheCredentials; + } + + /** + * Repositories requests credentials, let's put them in. + * + * @throws \RuntimeException + */ + protected function doAuthDance(): Svn + { + // cannot ask for credentials in non interactive mode + if (!$this->io->isInteractive()) { + throw new \RuntimeException( + 'can not ask for authentication in non interactive mode' + ); + } + + $this->io->writeError("The Subversion server ({$this->url}) requested credentials:"); + + $this->hasAuth = true; + $this->credentials = [ + 'username' => (string) $this->io->ask("Username: ", ''), + 'password' => (string) $this->io->askAndHideAnswer("Password: "), + ]; + + $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) "); + + return $this; + } + + /** + * A method to create the svn commands run. + * + * @param non-empty-list $cmd Usually 'svn ls' or something like that. + * @param string $url Repo URL. + * @param string $path Target for a checkout + * + * @return non-empty-list + */ + protected function getCommand(array $cmd, string $url, ?string $path = null): array + { + $cmd = array_merge( + $cmd, + ['--non-interactive'], + $this->getCredentialArgs(), + ['--', $url] + ); + + if ($path !== null) { + $cmd[] = $path; + } + + return $cmd; + } + + /** + * Return the credential string for the svn command. + * + * Adds --no-auth-cache when credentials are present. + * + * @return list + */ + protected function getCredentialArgs(): array + { + if (!$this->hasAuth()) { + return []; + } + + return array_merge( + $this->getAuthCacheArgs(), + ['--username', $this->getUsername(), '--password', $this->getPassword()] + ); + } + + /** + * Get the password for the svn command. Can be empty. + * + * @throws \LogicException + */ + protected function getPassword(): string + { + if ($this->credentials === null) { + throw new \LogicException("No svn auth detected."); + } + + return $this->credentials['password']; + } + + /** + * Get the username for the svn command. + * + * @throws \LogicException + */ + protected function getUsername(): string + { + if ($this->credentials === null) { + throw new \LogicException("No svn auth detected."); + } + + return $this->credentials['username']; + } + + /** + * Detect Svn Auth. + */ + protected function hasAuth(): bool + { + if (null !== $this->hasAuth) { + return $this->hasAuth; + } + + if (false === $this->createAuthFromConfig()) { + $this->createAuthFromUrl(); + } + + return (bool) $this->hasAuth; + } + + /** + * Return the no-auth-cache switch. + * + * @return list + */ + protected function getAuthCacheArgs(): array + { + return $this->cacheCredentials ? [] : ['--no-auth-cache']; + } + + /** + * Create the auth params from the configuration file. + */ + private function createAuthFromConfig(): bool + { + if (!$this->config->has('http-basic')) { + return $this->hasAuth = false; + } + + $authConfig = $this->config->get('http-basic'); + + $host = parse_url($this->url, PHP_URL_HOST); + if (isset($authConfig[$host])) { + $this->credentials = [ + 'username' => $authConfig[$host]['username'], + 'password' => $authConfig[$host]['password'], + ]; + + return $this->hasAuth = true; + } + + return $this->hasAuth = false; + } + + /** + * Create the auth params from the url + */ + private function createAuthFromUrl(): bool + { + $uri = parse_url($this->url); + if (empty($uri['user'])) { + return $this->hasAuth = false; + } + + $this->credentials = [ + 'username' => $uri['user'], + 'password' => !empty($uri['pass']) ? $uri['pass'] : '', + ]; + + return $this->hasAuth = true; + } + + /** + * Returns the version of the svn binary contained in PATH + */ + public function binaryVersion(): ?string + { + if (!self::$version) { + if (0 === $this->process->execute(['svn', '--version'], $output)) { + if (Preg::isMatch('{(\d+(?:\.\d+)+)}', $output, $match)) { + self::$version = $match[1]; + } + } + } + + return self::$version; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/SyncHelper.php b/vendor/composer/composer/src/Composer/Util/SyncHelper.php new file mode 100644 index 000000000..9a7398cc0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/SyncHelper.php @@ -0,0 +1,69 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Downloader\DownloaderInterface; +use Composer\Downloader\DownloadManager; +use Composer\Package\PackageInterface; +use React\Promise\PromiseInterface; + +class SyncHelper +{ + /** + * Helps you download + install a single package in a synchronous way + * + * This executes all the required steps and waits for promises to complete + * + * @param Loop $loop Loop instance which you can get from $composer->getLoop() + * @param DownloaderInterface|DownloadManager $downloader DownloadManager instance or Downloader instance you can get from $composer->getDownloadManager()->getDownloader('zip') for example + * @param string $path The installation path for the package + * @param PackageInterface $package The package to install + * @param PackageInterface|null $prevPackage The previous package if this is an update and not an initial installation + */ + public static function downloadAndInstallPackageSync(Loop $loop, $downloader, string $path, PackageInterface $package, ?PackageInterface $prevPackage = null): void + { + assert($downloader instanceof DownloaderInterface || $downloader instanceof DownloadManager); + + $type = $prevPackage !== null ? 'update' : 'install'; + + try { + self::await($loop, $downloader->download($package, $path, $prevPackage)); + + self::await($loop, $downloader->prepare($type, $package, $path, $prevPackage)); + + if ($type === 'update' && $prevPackage !== null) { + self::await($loop, $downloader->update($package, $prevPackage, $path)); + } else { + self::await($loop, $downloader->install($package, $path)); + } + } catch (\Exception $e) { + self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage)); + throw $e; + } + + self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage)); + } + + /** + * Waits for a promise to resolve + * + * @param Loop $loop Loop instance which you can get from $composer->getLoop() + * @phpstan-param PromiseInterface|null $promise + */ + public static function await(Loop $loop, ?PromiseInterface $promise = null): void + { + if ($promise !== null) { + $loop->wait([$promise]); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Tar.php b/vendor/composer/composer/src/Composer/Util/Tar.php new file mode 100644 index 000000000..1fb608f65 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Tar.php @@ -0,0 +1,59 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @author Wissem Riahi + */ +class Tar +{ + public static function getComposerJson(string $pathToArchive): ?string + { + $phar = new \PharData($pathToArchive); + + if (!$phar->valid()) { + return null; + } + + return self::extractComposerJsonFromFolder($phar); + } + + /** + * @throws \RuntimeException + */ + private static function extractComposerJsonFromFolder(\PharData $phar): string + { + if (isset($phar['composer.json'])) { + return $phar['composer.json']->getContent(); + } + + $topLevelPaths = []; + foreach ($phar as $folderFile) { + $name = $folderFile->getBasename(); + + if ($folderFile->isDir()) { + $topLevelPaths[$name] = true; + if (\count($topLevelPaths) > 1) { + throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); + } + } + } + + $composerJsonPath = key($topLevelPaths).'/composer.json'; + if (\count($topLevelPaths) > 0 && isset($phar[$composerJsonPath])) { + return $phar[$composerJsonPath]->getContent(); + } + + throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/TlsHelper.php b/vendor/composer/composer/src/Composer/Util/TlsHelper.php new file mode 100644 index 000000000..120715de5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/TlsHelper.php @@ -0,0 +1,209 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\CaBundle\CaBundle; +use Composer\Pcre\Preg; + +/** + * @author Chris Smith + * @deprecated Use composer/ca-bundle and composer/composer 2.2 if you still need PHP 5 compatibility, this class will be removed in Composer 3.0 + */ +final class TlsHelper +{ + /** + * Match hostname against a certificate. + * + * @param mixed $certificate X.509 certificate + * @param string $hostname Hostname in the URL + * @param string $cn Set to the common name of the certificate iff match found + */ + public static function checkCertificateHost($certificate, string $hostname, ?string &$cn = null): bool + { + $names = self::getCertificateNames($certificate); + + if (empty($names)) { + return false; + } + + $combinedNames = array_merge($names['san'], [$names['cn']]); + $hostname = strtolower($hostname); + + foreach ($combinedNames as $certName) { + $matcher = self::certNameMatcher($certName); + + if ($matcher && $matcher($hostname)) { + $cn = $names['cn']; + + return true; + } + } + + return false; + } + + /** + * Extract DNS names out of an X.509 certificate. + * + * @param mixed $certificate X.509 certificate + * + * @return array{cn: string, san: string[]}|null + */ + public static function getCertificateNames($certificate): ?array + { + if (is_array($certificate)) { + $info = $certificate; + } elseif (CaBundle::isOpensslParseSafe()) { + $info = openssl_x509_parse($certificate, false); + } + + if (!isset($info['subject']['commonName'])) { + return null; + } + + $commonName = strtolower($info['subject']['commonName']); + $subjectAltNames = []; + + if (isset($info['extensions']['subjectAltName'])) { + $subjectAltNames = Preg::split('{\s*,\s*}', $info['extensions']['subjectAltName']); + $subjectAltNames = array_filter( + array_map(static function ($name): ?string { + if (0 === strpos($name, 'DNS:')) { + return strtolower(ltrim(substr($name, 4))); + } + + return null; + }, $subjectAltNames), + static function (?string $san) { + return $san !== null; + } + ); + $subjectAltNames = array_values($subjectAltNames); + } + + return [ + 'cn' => $commonName, + 'san' => $subjectAltNames, + ]; + } + + /** + * Get the certificate pin. + * + * By Kevin McArthur of StormTide Digital Studios Inc. + * @KevinSMcArthur / https://github.com/StormTide + * + * See https://tools.ietf.org/html/draft-ietf-websec-key-pinning-02 + * + * This method was adapted from Sslurp. + * https://github.com/EvanDotPro/Sslurp + * + * (c) Evan Coury + * + * For the full copyright and license information, please see below: + * + * Copyright (c) 2013, Evan Coury + * 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. + * + * 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. + */ + public static function getCertificateFingerprint(string $certificate): string + { + $pubkey = openssl_get_publickey($certificate); + if ($pubkey === false) { + throw new \RuntimeException('Failed to retrieve the public key from certificate'); + } + $pubkeydetails = openssl_pkey_get_details($pubkey); + $pubkeypem = $pubkeydetails['key']; + //Convert PEM to DER before SHA1'ing + $start = '-----BEGIN PUBLIC KEY-----'; + $end = '-----END PUBLIC KEY-----'; + $pemtrim = substr($pubkeypem, strpos($pubkeypem, $start) + strlen($start), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1)); + $der = base64_decode($pemtrim); + + return hash('sha1', $der); + } + + /** + * Test if it is safe to use the PHP function openssl_x509_parse(). + * + * This checks if OpenSSL extensions is vulnerable to remote code execution + * via the exploit documented as CVE-2013-6420. + */ + public static function isOpensslParseSafe(): bool + { + return CaBundle::isOpensslParseSafe(); + } + + /** + * Convert certificate name into matching function. + * + * @param string $certName CN/SAN + */ + private static function certNameMatcher(string $certName): ?callable + { + $wildcards = substr_count($certName, '*'); + + if (0 === $wildcards) { + // Literal match. + return static function ($hostname) use ($certName): bool { + return $hostname === $certName; + }; + } + + if (1 === $wildcards) { + $components = explode('.', $certName); + + if (3 > count($components)) { + // Must have 3+ components + return null; + } + + $firstComponent = $components[0]; + + // Wildcard must be the last character. + if ('*' !== $firstComponent[strlen($firstComponent) - 1]) { + return null; + } + + $wildcardRegex = preg_quote($certName); + $wildcardRegex = str_replace('\\*', '[a-z0-9-]+', $wildcardRegex); + $wildcardRegex = "{^{$wildcardRegex}$}"; + + return static function ($hostname) use ($wildcardRegex): bool { + return Preg::isMatch($wildcardRegex, $hostname); + }; + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Url.php b/vendor/composer/composer/src/Composer/Util/Url.php new file mode 100644 index 000000000..7e615fe71 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Url.php @@ -0,0 +1,123 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class Url +{ + /** + * @param non-empty-string $url + * @return non-empty-string the updated URL + */ + public static function updateDistReference(Config $config, string $url, string $ref): string + { + $host = parse_url($url, PHP_URL_HOST); + + if ($host === 'api.github.com' || $host === 'github.com' || $host === 'www.github.com') { + if (Preg::isMatch('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) { + // update legacy github archives to API calls with the proper reference + $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref; + } elseif (Preg::isMatch('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$}i', $url, $match)) { + // update current github web archives to API calls with the proper reference + $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref; + } elseif (Preg::isMatch('{^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) { + // update api archives to the proper reference + $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref; + } + } elseif ($host === 'bitbucket.org' || $host === 'www.bitbucket.org') { + if (Preg::isMatch('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) { + // update Bitbucket archives to the proper reference + $url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $ref . '.' . $match[4]; + } + } elseif ($host === 'gitlab.com' || $host === 'www.gitlab.com') { + if (Preg::isMatch('{^https?://(?:www\.)?gitlab\.com/api/v[34]/projects/([^/]+)/repository/archive\.(zip|tar\.gz|tar\.bz2|tar)\?sha=.+$}i', $url, $match)) { + // update Gitlab archives to the proper reference + $url = 'https://gitlab.com/api/v4/projects/' . $match[1] . '/repository/archive.' . $match[2] . '?sha=' . $ref; + } + } elseif (in_array($host, $config->get('github-domains'), true)) { + $url = Preg::replace('{(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$}i', '$1/'.$ref, $url); + } elseif (in_array($host, $config->get('gitlab-domains'), true)) { + $url = Preg::replace('{(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$}i', '${1}'.$ref, $url); + } + + assert($url !== ''); + + return $url; + } + + /** + * @param non-empty-string $url + * @return non-empty-string + */ + public static function getOrigin(Config $config, string $url): string + { + if (0 === strpos($url, 'file://')) { + return $url; + } + + $origin = (string) parse_url($url, PHP_URL_HOST); + if ($port = parse_url($url, PHP_URL_PORT)) { + $origin .= ':'.$port; + } + + if (str_ends_with($origin, '.github.com') && $origin !== 'codeload.github.com') { + return 'github.com'; + } + + if ($origin === 'repo.packagist.org') { + return 'packagist.org'; + } + + if ($origin === '') { + $origin = $url; + } + + // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl + // is the host without the path, so we look for the registered gitlab-domains matching the host here + if ( + false === strpos($origin, '/') + && !in_array($origin, $config->get('gitlab-domains'), true) + ) { + foreach ($config->get('gitlab-domains') as $gitlabDomain) { + if ($gitlabDomain !== '' && str_starts_with($gitlabDomain, $origin)) { + return $gitlabDomain; + } + } + } + + return $origin; + } + + public static function sanitize(string $url): string + { + // GitHub repository rename result in redirect locations containing the access_token as GET parameter + // e.g. https://api.github.com/repositories/9999999999?access_token=github_token + $url = Preg::replace('{([&?]access_token=)[^&]+}', '$1***', $url); + + $url = Preg::replaceCallback('{^(?P[a-z0-9]+://)?(?P[^:/\s@]+):(?P[^@\s/]+)@}i', static function ($m): string { + // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that + if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) { + return $m['prefix'].'***:***@'; + } + + return $m['prefix'].$m['user'].':***@'; + }, $url); + + return $url; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Zip.php b/vendor/composer/composer/src/Composer/Util/Zip.php new file mode 100644 index 000000000..9fd8f0785 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Zip.php @@ -0,0 +1,101 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @author Andreas Schempp + */ +class Zip +{ + /** + * Gets content of the root composer.json inside a ZIP archive. + */ + public static function getComposerJson(string $pathToZip): ?string + { + if (!extension_loaded('zip')) { + throw new \RuntimeException('The Zip Util requires PHP\'s zip extension'); + } + + $zip = new \ZipArchive(); + if ($zip->open($pathToZip) !== true) { + return null; + } + + if (0 === $zip->numFiles) { + $zip->close(); + + return null; + } + + $foundFileIndex = self::locateFile($zip, 'composer.json'); + + $content = null; + $configurationFileName = $zip->getNameIndex($foundFileIndex); + $stream = $zip->getStream($configurationFileName); + + if (false !== $stream) { + $content = stream_get_contents($stream); + } + + $zip->close(); + + return $content; + } + + /** + * Find a file by name, returning the one that has the shortest path. + * + * @throws \RuntimeException + */ + private static function locateFile(\ZipArchive $zip, string $filename): int + { + // return root composer.json if it is there and is a file + if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) { + return $index; + } + + $topLevelPaths = []; + for ($i = 0; $i < $zip->numFiles; $i++) { + $name = $zip->getNameIndex($i); + $dirname = dirname($name); + + // ignore OSX specific resource fork folder + if (strpos($name, '__MACOSX') !== false) { + continue; + } + + // handle archives with proper TOC + if ($dirname === '.') { + $topLevelPaths[$name] = true; + if (\count($topLevelPaths) > 1) { + throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); + } + continue; + } + + // handle archives which do not have a TOC record for the directory itself + if (false === strpos($dirname, '\\') && false === strpos($dirname, '/')) { + $topLevelPaths[$dirname.'/'] = true; + if (\count($topLevelPaths) > 1) { + throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); + } + } + } + + if ($topLevelPaths && false !== ($index = $zip->locateName(key($topLevelPaths).$filename)) && $zip->getFromIndex($index) !== false) { + return $index; + } + + throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); + } +} diff --git a/vendor/composer/composer/src/bootstrap.php b/vendor/composer/composer/src/bootstrap.php new file mode 100644 index 000000000..886b4ff0c --- /dev/null +++ b/vendor/composer/composer/src/bootstrap.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Composer\Autoload\ClassLoader; + +function includeIfExists(string $file): ?ClassLoader +{ + return file_exists($file) ? include $file : null; +} + +if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) { + echo 'You must set up the project dependencies using `composer install`'.PHP_EOL. + 'See https://getcomposer.org/download/ for instructions on installing Composer'.PHP_EOL; + exit(1); +} + +return $loader; diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 1b86af2b6..8d98cc492 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -1,5 +1,142 @@ { "packages": [ + { + "name": "automattic/jetpack-autoloader", + "version": "v5.0.16", + "version_normalized": "5.0.16.0", + "source": { + "type": "git", + "url": "https://github.com/Automattic/jetpack-autoloader.git", + "reference": "d8ae822a35e7431137e860ee60eceedaa745e4d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/d8ae822a35e7431137e860ee60eceedaa745e4d1", + "reference": "d8ae822a35e7431137e860ee60eceedaa745e4d1", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.2", + "php": ">=7.2" + }, + "require-dev": { + "automattic/jetpack-changelogger": "^6.0.14", + "automattic/phpunit-select-config": "^1.0.3", + "composer/composer": "^2.2", + "yoast/phpunit-polyfills": "^4.0.0" + }, + "time": "2026-02-16T10:33:15+00:00", + "type": "composer-plugin", + "extra": { + "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin", + "autotagger": true, + "mirror-repo": "Automattic/jetpack-autoloader", + "branch-alias": { + "dev-trunk": "5.0.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}" + }, + "version-constants": { + "::VERSION": "src/AutoloadGenerator.php" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Automattic\\Jetpack\\Autoloader\\": "src" + }, + "classmap": [ + "src/AutoloadGenerator.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Creates a custom autoloader for a plugin or theme.", + "keywords": [ + "autoload", + "autoloader", + "composer", + "jetpack", + "plugin", + "wordpress" + ], + "support": { + "source": "https://github.com/Automattic/jetpack-autoloader/tree/v5.0.16" + }, + "install-path": "../automattic/jetpack-autoloader" + }, + { + "name": "brianhenryie/strauss", + "version": "0.19.5", + "version_normalized": "0.19.5.0", + "source": { + "type": "git", + "url": "https://github.com/BrianHenryIE/strauss.git", + "reference": "25c58001a786d4357a65aa9b045a8ca79b60bf06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/BrianHenryIE/strauss/zipball/25c58001a786d4357a65aa9b045a8ca79b60bf06", + "reference": "25c58001a786d4357a65aa9b045a8ca79b60bf06", + "shasum": "" + }, + "require": { + "composer/composer": "*", + "json-mapper/json-mapper": "^2.2", + "league/flysystem": "^2.1|^3.0", + "symfony/console": "^4|^5|^6|^7", + "symfony/finder": "^4|^5|^6|^7" + }, + "replace": { + "coenjacobs/mozart": "*" + }, + "require-dev": { + "brianhenryie/php-diff-test": "dev-master", + "clue/phar-composer": "^1.2", + "ext-json": "*", + "jaschilz/php-coverage-badger": "^2.0", + "mheap/phpunit-github-actions-printer": "^1.4", + "mockery/mockery": "^1.6", + "php": "^7.4|^8.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9|^10", + "squizlabs/php_codesniffer": "^3.5" + }, + "time": "2024-10-25T03:22:39+00:00", + "bin": [ + "bin/strauss" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "BrianHenryIE\\Strauss\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Henry", + "email": "BrianHenryIE@gmail.com" + }, + { + "name": "Coen Jacobs", + "email": "coenjacobs@gmail.com" + } + ], + "description": "Composes all dependencies as a package inside a WordPress plugin", + "support": { + "issues": "https://github.com/BrianHenryIE/strauss/issues", + "source": "https://github.com/BrianHenryIE/strauss/tree/0.19.5" + }, + "install-path": "../brianhenryie/strauss" + }, { "name": "ccampbell/chromephp", "version": "4.1.0", @@ -46,6 +183,269 @@ ], "install-path": "../ccampbell/chromephp" }, + { + "name": "composer/ca-bundle", + "version": "1.5.11", + "version_normalized": "1.5.11.0", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/68ff39175e8e94a4bb1d259407ce51a6a60f09e6", + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "time": "2026-03-30T09:16:10+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.11" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "install-path": "./ca-bundle" + }, + { + "name": "composer/class-map-generator", + "version": "1.7.2", + "version_normalized": "1.7.2.0", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "6a9c2f0970022ab00dc58c07d0685dd712f2231b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/6a9c2f0970022ab00dc58c07d0685dd712f2231b", + "reference": "6a9c2f0970022ab00dc58c07d0685dd712f2231b", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" + }, + "time": "2026-03-30T15:36:56+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.7.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "install-path": "./class-map-generator" + }, + { + "name": "composer/composer", + "version": "2.9.5", + "version_normalized": "2.9.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "72a8f8e653710e18d83e5dd531eb5a71fc3223e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/72a8f8e653710e18d83e5dd531eb5a71fc3223e6", + "reference": "72a8f8e653710e18d83e5dd531eb5a71fc3223e6", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^2.3 || ^3.3", + "composer/semver": "^3.3", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "ext-json": "*", + "justinrainbow/json-schema": "^6.5.1", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "react/promise": "^3.3", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "seld/signal-handler": "^2.0", + "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "symfony/polyfill-php84": "^1.30", + "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0", + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" + }, + "suggest": { + "ext-curl": "Provides HTTP support (will fallback to PHP streams if missing)", + "ext-openssl": "Enables access to repositories and packages over HTTPS", + "ext-zip": "Allows direct extraction of ZIP archives (unzip/7z binaries will be used instead if available)", + "ext-zlib": "Enables gzip for HTTP requests" + }, + "time": "2026-01-29T10:40:53+00:00", + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] + }, + "branch-alias": { + "dev-main": "2.9-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\": "src/Composer/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "security": "https://github.com/composer/composer/security/policy", + "source": "https://github.com/composer/composer/tree/2.9.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "install-path": "./composer" + }, { "name": "composer/installers", "version": "v2.3.0", @@ -196,70 +596,3439 @@ "install-path": "./installers" }, { - "name": "firebase/php-jwt", - "version": "v6.11.1", - "version_normalized": "6.11.1.0", + "name": "composer/metadata-minifier", + "version": "1.0.0", + "version_normalized": "1.0.0.0", "source": { "type": "git", - "url": "https://github.com/firebase/php-jwt.git", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", "shasum": "" }, "require": { - "php": "^8.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "guzzlehttp/guzzle": "^7.4", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "psr/cache": "^2.0||^3.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0" - }, - "suggest": { - "ext-sodium": "Support EdDSA (Ed25519) signatures", - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" }, - "time": "2025-04-09T20:32:01+00:00", + "time": "2021-04-07T13:37:33+00:00", "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, "installation-source": "dist", "autoload": { "psr-4": { - "Firebase\\JWT\\": "src" + "Composer\\MetadataMinifier\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Neuman Vong", - "email": "neuman+pear@twilio.com", - "role": "Developer" - }, - { - "name": "Anant Narayanan", - "email": "anant@php.net", - "role": "Developer" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "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", + "description": "Small utility library that handles metadata minification and expansion.", "keywords": [ - "jwt", - "php" + "composer", + "compression" ], "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" }, - "install-path": "../firebase/php-jwt" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./metadata-minifier" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "time": "2024-11-12T16:29:46+00:00", + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./pcre" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "version_normalized": "3.4.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "time": "2025-08-20T19:15:30+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "install-path": "./semver" + }, + { + "name": "composer/spdx-licenses", + "version": "1.6.0", + "version_normalized": "1.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/5ecd0cb4177696f9fd48f1605dda81db3dee7889", + "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" + }, + "time": "2026-04-08T20:18:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.6.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "install-path": "./spdx-licenses" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "version_normalized": "3.0.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "time": "2024-05-06T16:37:16+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./xdebug-handler" + }, + { + "name": "deliciousbrains/wp-background-processing", + "version": "1.3.1", + "version_normalized": "1.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/deliciousbrains/wp-background-processing.git", + "reference": "6d1e48165e461260075b9f161b3861c7278f71e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/deliciousbrains/wp-background-processing/zipball/6d1e48165e461260075b9f161b3861c7278f71e7", + "reference": "6d1e48165e461260075b9f161b3861c7278f71e7", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpcompatibility/phpcompatibility-wp": "*", + "phpunit/phpunit": "^8.0", + "spryker/code-sniffer": "^0.17.18", + "wp-coding-standards/wpcs": "^2.3", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "coenjacobs/mozart": "Easily wrap this library with your own prefix, to prevent collisions when multiple plugins use this library" + }, + "time": "2024-02-28T13:39:06+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "classes/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Delicious Brains", + "email": "nom@deliciousbrains.com" + } + ], + "description": "WP Background Processing can be used to fire off non-blocking asynchronous requests or as a background processing tool, allowing you to queue tasks.", + "support": { + "issues": "https://github.com/deliciousbrains/wp-background-processing/issues", + "source": "https://github.com/deliciousbrains/wp-background-processing/tree/1.3.1" + }, + "install-path": "../deliciousbrains/wp-background-processing" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "version_normalized": "1.1.6.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "time": "2026-02-07T07:09:04+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "install-path": "../doctrine/deprecations" + }, + { + "name": "firebase/php-jwt", + "version": "v7.0.5", + "version_normalized": "7.0.5.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpfastcache/phpfastcache": "^9.2", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "time": "2026-04-01T20:38:03+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/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5" + }, + "install-path": "../firebase/php-jwt" + }, + { + "name": "json-mapper/json-mapper", + "version": "2.25.1", + "version_normalized": "2.25.1.0", + "source": { + "type": "git", + "url": "https://github.com/JsonMapper/JsonMapper.git", + "reference": "4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JsonMapper/JsonMapper/zipball/4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61", + "reference": "4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61", + "shasum": "" + }, + "require": { + "ext-json": "*", + "myclabs/php-enum": "^1.7", + "nikic/php-parser": "^4.13 || ^5.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-docblock": "^5.6", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "psr/simple-cache": " ^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/polyfill-php73": "^1.18" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5 || ^7.0", + "php-coveralls/php-coveralls": "^2.4", + "phpstan/phpstan": "^0.12.14", + "phpstan/phpstan-phpunit": "^0.12.17", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0", + "vimeo/psalm": "^4.10 || ^5.0" + }, + "suggest": { + "json-mapper/laravel-package": "Use JsonMapper directly with Laravel", + "json-mapper/symfony-bundle": "Use JsonMapper directly with Symfony" + }, + "time": "2025-05-26T09:51:24+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "JsonMapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Map JSON structures to PHP classes", + "homepage": "https://jsonmapper.net", + "keywords": [ + "json", + "jsonmapper", + "mapper", + "middleware" + ], + "support": { + "docs": "https://jsonmapper.net", + "issues": "https://github.com/JsonMapper/JsonMapper/issues", + "source": "https://github.com/JsonMapper/JsonMapper" + }, + "funding": [ + { + "url": "https://github.com/DannyvdSluijs", + "type": "github" + } + ], + "install-path": "../json-mapper/json-mapper" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.8.0", + "version_normalized": "6.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "89ac92bcfe5d0a8a4433c7b89d394553ae7250cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/89ac92bcfe5d0a8a4433c7b89d394553ae7250cc", + "reference": "89ac92bcfe5d0a8a4433c7b89d394553ae7250cc", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "^23.2", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "time": "2026-04-02T12:43:11+00:00", + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.0" + }, + "install-path": "../justinrainbow/json-schema" + }, + { + "name": "league/flysystem", + "version": "3.33.0", + "version_normalized": "3.33.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "570b8871e0ce693764434b29154c54b434905350" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350", + "reference": "570b8871e0ce693764434b29154c54b434905350", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "time": "2026-03-25T07:59:30+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.33.0" + }, + "install-path": "../league/flysystem" + }, + { + "name": "league/flysystem-local", + "version": "3.31.0", + "version_normalized": "3.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "time": "2026-01-23T15:30:45+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + }, + "install-path": "../league/flysystem-local" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "version_normalized": "1.16.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "time": "2024-09-21T08:32:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "install-path": "../league/mime-type-detection" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.2", + "version_normalized": "4.7.2.0", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "time": "2025-09-14T11:18:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "install-path": "../marc-mabe/php-enum" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "version_normalized": "3.10.0.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "time": "2026-01-02T08:56:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "install-path": "../monolog/monolog" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.5", + "version_normalized": "1.8.5.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + }, + "time": "2025-01-14T11:49:03+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "https://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "install-path": "../myclabs/php-enum" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "version_normalized": "5.7.0.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "time": "2025-12-06T11:56:16+00:00", + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "install-path": "../nikic/php-parser" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "time": "2020-06-27T09:03:43+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "install-path": "../phpdocumentor/reflection-common" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.7", + "version_normalized": "5.6.7.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "time": "2026-03-18T20:47:46+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.7" + }, + "install-path": "../phpdocumentor/reflection-docblock" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.12.0", + "version_normalized": "1.12.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "time": "2025-11-21T15:09:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + }, + "install-path": "../phpdocumentor/type-resolver" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "version_normalized": "2.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "time": "2026-01-25T14:56:51+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "install-path": "../phpstan/phpdoc-parser" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-02-03T23:26:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "install-path": "../psr/cache" + }, + { + "name": "psr/container", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:47:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/log", + "version": "3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2024-09-11T13:17:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "install-path": "../psr/log" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-10-23T01:57:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "install-path": "../psr/simple-cache" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "version_normalized": "3.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "time": "2025-08-19T18:57:03+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "install-path": "../react/promise" + }, + { + "name": "seld/jsonlint", + "version": "1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "time": "2024-07-11T14:55:45+00:00", + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "install-path": "../seld/jsonlint" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "time": "2022-08-31T10:31:18+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "install-path": "../seld/phar-utils" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "time": "2023-09-03T09:24:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "install-path": "../seld/signal-handler" + }, + { + "name": "symfony/cache", + "version": "v7.4.8", + "version_normalized": "7.4.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "467464da294734b0fb17e853e5712abc8470f819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/467464da294734b0fb17e853e5712abc8470f819", + "reference": "467464da294734b0fb17e853e5712abc8470f819", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "ext-redis": "<6.1", + "ext-relay": "<0.12.1", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "time": "2026-03-30T15:15:47+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/cache" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.6.0", + "version_normalized": "3.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "time": "2025-03-13T15:25:07+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/cache-contracts" + }, + { + "name": "symfony/console", + "version": "v7.4.8", + "version_normalized": "7.4.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", + "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "time": "2026-03-30T13:54:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/console" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "version_normalized": "3.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2024-09-25T14:21:43+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/filesystem", + "version": "v8.0.8", + "version_normalized": "8.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "66b769ae743ce2d13e435528fbef4af03d623e5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/66b769ae743ce2d13e435528fbef4af03d623e5a", + "reference": "66b769ae743ce2d13e435528fbef4af03d623e5a", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^7.4|^8.0" + }, + "time": "2026-03-30T15:14:47+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/filesystem" + }, + { + "name": "symfony/finder", + "version": "v7.4.8", + "version_normalized": "7.4.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "e0be088d22278583a82da281886e8c3592fbf149" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/e0be088d22278583a82da281886e8c3592fbf149", + "reference": "e0be088d22278583a82da281886e8c3592fbf149", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" + }, + "time": "2026-03-24T13:12:05+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.34.0", + "version_normalized": "1.34.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2026-04-10T16:19:22+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.34.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.34.0", + "version_normalized": "1.34.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df", + "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2026-04-10T16:19:22+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.34.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-grapheme" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.34.0", + "version_normalized": "1.34.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.34.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-normalizer" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.34.0", + "version_normalized": "1.34.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2026-04-10T17:25:58+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.34.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.34.0", + "version_normalized": "1.34.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.34.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php73" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.34.0", + "version_normalized": "1.34.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2026-04-10T16:19:22+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.34.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.34.0", + "version_normalized": "1.34.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.34.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php81" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.34.0", + "version_normalized": "1.34.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2026-04-10T18:47:49+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.34.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php84" + }, + { + "name": "symfony/process", + "version": "v8.0.8", + "version_normalized": "8.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", + "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "time": "2026-03-30T15:14:47+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/process" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "version_normalized": "3.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "time": "2025-07-15T11:30:57+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + }, + { + "name": "symfony/string", + "version": "v8.0.8", + "version_normalized": "8.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "ae9488f874d7603f9d2dfbf120203882b645d963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963", + "reference": "ae9488f874d7603f9d2dfbf120203882b645d963", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "time": "2026-03-30T15:14:47+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/string" + }, + { + "name": "symfony/var-exporter", + "version": "v8.0.8", + "version_normalized": "8.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/15776bb07a91b089037da89f8832fa41d5fa6ec6", + "reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "time": "2026-03-30T15:14:47+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/var-exporter" }, { "name": "udx/lib-ud-api-client", @@ -309,16 +4078,16 @@ }, { "name": "udx/lib-wp-bootstrap", - "version": "1.3.4", - "version_normalized": "1.3.4.0", + "version": "1.3.3", + "version_normalized": "1.3.3.0", "source": { "type": "git", "url": "git@github.com:udx/lib-wp-bootstrap", - "reference": "1.3.4" + "reference": "1.3.3" }, "dist": { "type": "zip", - "url": "https://github.com/udx/lib-wp-bootstrap/archive/1.3.4.zip" + "url": "https://github.com/udx/lib-wp-bootstrap/archive/1.3.3.zip" }, "require": { "php": ">=5.3" @@ -343,18 +4112,83 @@ ], "install-path": "../udx/lib-wp-bootstrap" }, + { + "name": "webmozart/assert", + "version": "2.3.0", + "version_normalized": "2.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "time": "2026-04-11T10:33:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-feature/2-0": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.3.0" + }, + "install-path": "../webmozart/assert" + }, { "name": "wpackagist-plugin/meta-box", - "version": "5.10.19", - "version_normalized": "5.10.19.0", + "version": "5.11.4", + "version_normalized": "5.11.4.0", "source": { "type": "svn", "url": "https://plugins.svn.wordpress.org/meta-box/", - "reference": "tags/5.10.19" + "reference": "tags/5.11.4" }, "dist": { "type": "zip", - "url": "https://downloads.wordpress.org/plugin/meta-box.5.10.19.zip" + "url": "https://downloads.wordpress.org/plugin/meta-box.5.11.4.zip" }, "require": { "composer/installers": "^1.0 || ^2.0" @@ -365,6 +4199,55 @@ "install-path": "../wpmetabox/meta-box" } ], - "dev": false, - "dev-package-names": [] + "dev": true, + "dev-package-names": [ + "brianhenryie/strauss", + "composer/ca-bundle", + "composer/class-map-generator", + "composer/composer", + "composer/metadata-minifier", + "composer/pcre", + "composer/semver", + "composer/spdx-licenses", + "composer/xdebug-handler", + "deliciousbrains/wp-background-processing", + "doctrine/deprecations", + "json-mapper/json-mapper", + "justinrainbow/json-schema", + "league/flysystem", + "league/flysystem-local", + "league/mime-type-detection", + "marc-mabe/php-enum", + "myclabs/php-enum", + "nikic/php-parser", + "phpdocumentor/reflection-common", + "phpdocumentor/reflection-docblock", + "phpdocumentor/type-resolver", + "phpstan/phpdoc-parser", + "psr/cache", + "psr/container", + "react/promise", + "seld/jsonlint", + "seld/phar-utils", + "seld/signal-handler", + "symfony/cache", + "symfony/cache-contracts", + "symfony/console", + "symfony/deprecation-contracts", + "symfony/filesystem", + "symfony/finder", + "symfony/polyfill-ctype", + "symfony/polyfill-intl-grapheme", + "symfony/polyfill-intl-normalizer", + "symfony/polyfill-mbstring", + "symfony/polyfill-php73", + "symfony/polyfill-php80", + "symfony/polyfill-php81", + "symfony/polyfill-php84", + "symfony/process", + "symfony/service-contracts", + "symfony/string", + "symfony/var-exporter", + "webmozart/assert" + ] } diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index e418eea65..eddafa141 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,13 +3,31 @@ 'name' => 'wpcloud/wp-stateless', 'pretty_version' => 'dev-latest', 'version' => 'dev-latest', - 'reference' => 'b101c6a4d352f985df1f531078959f218d9b1f1a', + 'reference' => 'bf2175d64798e90c2189cf502c7a7387954bf355', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'dev' => false, + 'dev' => true, ), 'versions' => array( + 'automattic/jetpack-autoloader' => array( + 'pretty_version' => 'v5.0.16', + 'version' => '5.0.16.0', + 'reference' => 'd8ae822a35e7431137e860ee60eceedaa745e4d1', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../automattic/jetpack-autoloader', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'brianhenryie/strauss' => array( + 'pretty_version' => '0.19.5', + 'version' => '0.19.5.0', + 'reference' => '25c58001a786d4357a65aa9b045a8ca79b60bf06', + 'type' => 'library', + 'install_path' => __DIR__ . '/../brianhenryie/strauss', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'ccampbell/chromephp' => array( 'pretty_version' => '4.1.0', 'version' => '4.1.0.0', @@ -19,6 +37,39 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'coenjacobs/mozart' => array( + 'dev_requirement' => true, + 'replaced' => array( + 0 => '*', + ), + ), + 'composer/ca-bundle' => array( + 'pretty_version' => '1.5.11', + 'version' => '1.5.11.0', + 'reference' => '68ff39175e8e94a4bb1d259407ce51a6a60f09e6', + 'type' => 'library', + 'install_path' => __DIR__ . '/./ca-bundle', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/class-map-generator' => array( + 'pretty_version' => '1.7.2', + 'version' => '1.7.2.0', + 'reference' => '6a9c2f0970022ab00dc58c07d0685dd712f2231b', + 'type' => 'library', + 'install_path' => __DIR__ . '/./class-map-generator', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/composer' => array( + 'pretty_version' => '2.9.5', + 'version' => '2.9.5.0', + 'reference' => '72a8f8e653710e18d83e5dd531eb5a71fc3223e6', + 'type' => 'library', + 'install_path' => __DIR__ . '/./composer', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'composer/installers' => array( 'pretty_version' => 'v2.3.0', 'version' => '2.3.0.0', @@ -28,15 +79,454 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'composer/metadata-minifier' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'c549d23829536f0d0e984aaabbf02af91f443207', + 'type' => 'library', + 'install_path' => __DIR__ . '/./metadata-minifier', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/pcre' => array( + 'pretty_version' => '3.3.2', + 'version' => '3.3.2.0', + 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', + 'type' => 'library', + 'install_path' => __DIR__ . '/./pcre', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/semver' => array( + 'pretty_version' => '3.4.4', + 'version' => '3.4.4.0', + 'reference' => '198166618906cb2de69b95d7d47e5fa8aa1b2b95', + 'type' => 'library', + 'install_path' => __DIR__ . '/./semver', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/spdx-licenses' => array( + 'pretty_version' => '1.6.0', + 'version' => '1.6.0.0', + 'reference' => '5ecd0cb4177696f9fd48f1605dda81db3dee7889', + 'type' => 'library', + 'install_path' => __DIR__ . '/./spdx-licenses', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/xdebug-handler' => array( + 'pretty_version' => '3.0.5', + 'version' => '3.0.5.0', + 'reference' => '6c1925561632e83d60a44492e0b344cf48ab85ef', + 'type' => 'library', + 'install_path' => __DIR__ . '/./xdebug-handler', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'deliciousbrains/wp-background-processing' => array( + 'pretty_version' => '1.3.1', + 'version' => '1.3.1.0', + 'reference' => '6d1e48165e461260075b9f161b3861c7278f71e7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../deliciousbrains/wp-background-processing', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'doctrine/deprecations' => array( + 'pretty_version' => '1.1.6', + 'version' => '1.1.6.0', + 'reference' => 'd4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/deprecations', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'firebase/php-jwt' => array( - 'pretty_version' => 'v6.11.1', - 'version' => '6.11.1.0', - 'reference' => 'd1e91ecf8c598d073d0995afa8cd5c75c6e19e66', + 'pretty_version' => 'v7.0.5', + 'version' => '7.0.5.0', + 'reference' => '47ad26bab5e7c70ae8a6f08ed25ff83631121380', 'type' => 'library', 'install_path' => __DIR__ . '/../firebase/php-jwt', 'aliases' => array(), 'dev_requirement' => false, ), + 'json-mapper/json-mapper' => array( + 'pretty_version' => '2.25.1', + 'version' => '2.25.1.0', + 'reference' => '4fd6cb5ccfece349ed1aeb52c818bdf84ba75b61', + 'type' => 'library', + 'install_path' => __DIR__ . '/../json-mapper/json-mapper', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'justinrainbow/json-schema' => array( + 'pretty_version' => '6.8.0', + 'version' => '6.8.0.0', + 'reference' => '89ac92bcfe5d0a8a4433c7b89d394553ae7250cc', + 'type' => 'library', + 'install_path' => __DIR__ . '/../justinrainbow/json-schema', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'league/flysystem' => array( + 'pretty_version' => '3.33.0', + 'version' => '3.33.0.0', + 'reference' => '570b8871e0ce693764434b29154c54b434905350', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/flysystem', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'league/flysystem-local' => array( + 'pretty_version' => '3.31.0', + 'version' => '3.31.0.0', + 'reference' => '2f669db18a4c20c755c2bb7d3a7b0b2340488079', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/flysystem-local', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'league/mime-type-detection' => array( + 'pretty_version' => '1.16.0', + 'version' => '1.16.0.0', + 'reference' => '2d6702ff215bf922936ccc1ad31007edc76451b9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/mime-type-detection', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'marc-mabe/php-enum' => array( + 'pretty_version' => 'v4.7.2', + 'version' => '4.7.2.0', + 'reference' => 'bb426fcdd65c60fb3638ef741e8782508fda7eef', + 'type' => 'library', + 'install_path' => __DIR__ . '/../marc-mabe/php-enum', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'monolog/monolog' => array( + 'pretty_version' => '3.10.0', + 'version' => '3.10.0.0', + 'reference' => 'b321dd6749f0bf7189444158a3ce785cc16d69b0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../monolog/monolog', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'myclabs/php-enum' => array( + 'pretty_version' => '1.8.5', + 'version' => '1.8.5.0', + 'reference' => 'e7be26966b7398204a234f8673fdad5ac6277802', + 'type' => 'library', + 'install_path' => __DIR__ . '/../myclabs/php-enum', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'nikic/php-parser' => array( + 'pretty_version' => 'v5.7.0', + 'version' => '5.7.0.0', + 'reference' => 'dca41cd15c2ac9d055ad70dbfd011130757d1f82', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nikic/php-parser', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpdocumentor/reflection-common' => array( + 'pretty_version' => '2.2.0', + 'version' => '2.2.0.0', + 'reference' => '1d01c49d4ed62f25aa84a747ad35d5a16924662b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpdocumentor/reflection-common', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpdocumentor/reflection-docblock' => array( + 'pretty_version' => '5.6.7', + 'version' => '5.6.7.0', + 'reference' => '31a105931bc8ffa3a123383829772e832fd8d903', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpdocumentor/reflection-docblock', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpdocumentor/type-resolver' => array( + 'pretty_version' => '1.12.0', + 'version' => '1.12.0.0', + 'reference' => '92a98ada2b93d9b201a613cb5a33584dde25f195', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpdocumentor/type-resolver', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpstan/phpdoc-parser' => array( + 'pretty_version' => '2.3.2', + 'version' => '2.3.2.0', + 'reference' => 'a004701b11273a26cd7955a61d67a7f1e525a45a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpstan/phpdoc-parser', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'psr/cache' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/cache', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'psr/cache-implementation' => array( + 'dev_requirement' => true, + 'provided' => array( + 0 => '2.0|3.0', + ), + ), + 'psr/container' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'psr/log' => array( + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', + 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/log-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.0|3.0', + 1 => '3.0.0', + ), + ), + 'psr/simple-cache' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/simple-cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/simple-cache-implementation' => array( + 'dev_requirement' => true, + 'provided' => array( + 0 => '1.0|2.0|3.0', + ), + ), + 'react/promise' => array( + 'pretty_version' => 'v3.3.0', + 'version' => '3.3.0.0', + 'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../react/promise', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'seld/jsonlint' => array( + 'pretty_version' => '1.11.0', + 'version' => '1.11.0.0', + 'reference' => '1748aaf847fc731cfad7725aec413ee46f0cc3a2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../seld/jsonlint', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'seld/phar-utils' => array( + 'pretty_version' => '1.2.1', + 'version' => '1.2.1.0', + 'reference' => 'ea2f4014f163c1be4c601b9b7bd6af81ba8d701c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../seld/phar-utils', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'seld/signal-handler' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => '04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98', + 'type' => 'library', + 'install_path' => __DIR__ . '/../seld/signal-handler', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/cache' => array( + 'pretty_version' => 'v7.4.8', + 'version' => '7.4.8.0', + 'reference' => '467464da294734b0fb17e853e5712abc8470f819', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/cache', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/cache-contracts' => array( + 'pretty_version' => 'v3.6.0', + 'version' => '3.6.0.0', + 'reference' => '5d68a57d66910405e5c0b63d6f0af941e66fc868', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/cache-contracts', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/cache-implementation' => array( + 'dev_requirement' => true, + 'provided' => array( + 0 => '1.1|2.0|3.0', + ), + ), + 'symfony/console' => array( + 'pretty_version' => 'v7.4.8', + 'version' => '7.4.8.0', + 'reference' => '1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/console', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.6.0', + 'version' => '3.6.0.0', + 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/filesystem' => array( + 'pretty_version' => 'v8.0.8', + 'version' => '8.0.8.0', + 'reference' => '66b769ae743ce2d13e435528fbef4af03d623e5a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/filesystem', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/finder' => array( + 'pretty_version' => 'v7.4.8', + 'version' => '7.4.8.0', + 'reference' => 'e0be088d22278583a82da281886e8c3592fbf149', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.34.0', + 'version' => '1.34.0.0', + 'reference' => '141046a8f9477948ff284fa65be2095baafb94f2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-intl-grapheme' => array( + 'pretty_version' => 'v1.34.0', + 'version' => '1.34.0.0', + 'reference' => 'ad1b7b9092976d6c948b8a187cec9faaea9ec1df', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-intl-normalizer' => array( + 'pretty_version' => 'v1.34.0', + 'version' => '1.34.0.0', + 'reference' => '3833d7255cc303546435cb650316bff708a1c75c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.34.0', + 'version' => '1.34.0.0', + 'reference' => '6a21eb99c6973357967f6ce3708cd55a6bec6315', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-php73' => array( + 'pretty_version' => 'v1.34.0', + 'version' => '1.34.0.0', + 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php73', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.34.0', + 'version' => '1.34.0.0', + 'reference' => 'dfb55726c3a76ea3b6459fcfda1ec2d80a682411', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-php81' => array( + 'pretty_version' => 'v1.34.0', + 'version' => '1.34.0.0', + 'reference' => '4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php81', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-php84' => array( + 'pretty_version' => 'v1.34.0', + 'version' => '1.34.0.0', + 'reference' => '88486db2c389b290bf87ff1de7ebc1e13e42bb06', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php84', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/process' => array( + 'pretty_version' => 'v8.0.8', + 'version' => '8.0.8.0', + 'reference' => 'cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/process', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v3.6.1', + 'version' => '3.6.1.0', + 'reference' => '45112560a3ba2d715666a509a0bc9521d10b6c43', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/string' => array( + 'pretty_version' => 'v8.0.8', + 'version' => '8.0.8.0', + 'reference' => 'ae9488f874d7603f9d2dfbf120203882b645d963', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/string', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/var-exporter' => array( + 'pretty_version' => 'v8.0.8', + 'version' => '8.0.8.0', + 'reference' => '15776bb07a91b089037da89f8832fa41d5fa6ec6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/var-exporter', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'udx/lib-ud-api-client' => array( 'pretty_version' => '1.2.5', 'version' => '1.2.5.0', @@ -47,18 +537,27 @@ 'dev_requirement' => false, ), 'udx/lib-wp-bootstrap' => array( - 'pretty_version' => '1.3.4', - 'version' => '1.3.4.0', - 'reference' => '1.3.4', + 'pretty_version' => '1.3.3', + 'version' => '1.3.3.0', + 'reference' => '1.3.3', 'type' => 'library', 'install_path' => __DIR__ . '/../udx/lib-wp-bootstrap', 'aliases' => array(), 'dev_requirement' => false, ), + 'webmozart/assert' => array( + 'pretty_version' => '2.3.0', + 'version' => '2.3.0.0', + 'reference' => 'eb0d790f735ba6cff25c683a85a1da0eadeff9e4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../webmozart/assert', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'wpackagist-plugin/meta-box' => array( - 'pretty_version' => '5.10.19', - 'version' => '5.10.19.0', - 'reference' => 'tags/5.10.19', + 'pretty_version' => '5.11.4', + 'version' => '5.11.4.0', + 'reference' => 'tags/5.11.4', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../wpmetabox/meta-box', 'aliases' => array(), @@ -67,7 +566,7 @@ 'wpcloud/wp-stateless' => array( 'pretty_version' => 'dev-latest', 'version' => 'dev-latest', - 'reference' => 'b101c6a4d352f985df1f531078959f218d9b1f1a', + 'reference' => 'bf2175d64798e90c2189cf502c7a7387954bf355', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/vendor/composer/jetpack_autoload_classmap.php b/vendor/composer/jetpack_autoload_classmap.php new file mode 100644 index 000000000..d2ff0ee4f --- /dev/null +++ b/vendor/composer/jetpack_autoload_classmap.php @@ -0,0 +1,373 @@ + array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php' + ), + 'Automattic\\Jetpack\\Autoloader\\AutoloadGenerator' => array( + 'version' => '5.0.16', + 'path' => $vendorDir . '/automattic/jetpack-autoloader/src/AutoloadGenerator.php' + ), + 'CURLStringFile' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php' + ), + 'ChromePhp' => array( + 'version' => '4.1.0.0', + 'path' => $vendorDir . '/ccampbell/chromephp/ChromePhp.php' + ), + 'Deprecated' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Deprecated.php' + ), + 'JsonException' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php' + ), + 'Normalizer' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php' + ), + 'Pdo\\Dblib' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php' + ), + 'Pdo\\Firebird' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php' + ), + 'Pdo\\Mysql' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php' + ), + 'Pdo\\Odbc' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php' + ), + 'Pdo\\Pgsql' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php' + ), + 'Pdo\\Sqlite' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php' + ), + 'PhpToken' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php' + ), + 'ReflectionConstant' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php' + ), + 'ReturnTypeWillChange' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php' + ), + 'RoundingMode' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php' + ), + 'Stringable' => array( + 'version' => '4.7.2.0', + 'path' => $vendorDir . '/marc-mabe/php-enum/stubs/Stringable.php' + ), + 'UDX\\Settings' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/includes/class-settings.php' + ), + 'UDX\\Utility' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/includes/class-utility.php' + ), + 'UnhandledMatchError' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php' + ), + 'UsabilityDynamics\\UD_API\\API' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-api.php' + ), + 'UsabilityDynamics\\UD_API\\Admin' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-admin.php' + ), + 'UsabilityDynamics\\UD_API\\Bootstrap' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-bootstrap.php' + ), + 'UsabilityDynamics\\UD_API\\Licenses_Table' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-licenses-table.php' + ), + 'UsabilityDynamics\\UD_API\\Manager' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-manager.php' + ), + 'UsabilityDynamics\\UD_API\\More_Products_Table' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-more-products-table.php' + ), + 'UsabilityDynamics\\UD_API\\Scaffold' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-scaffold.php' + ), + 'UsabilityDynamics\\UD_API\\UI' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-ui.php' + ), + 'UsabilityDynamics\\UD_API\\Update_Checker' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-update-checker.php' + ), + 'UsabilityDynamics\\UD_API\\Utility' => array( + 'version' => '1.2.5.0', + 'path' => $vendorDir . '/udx/lib-ud-api-client/lib/classes/class-utility.php' + ), + 'UsabilityDynamics\\WP\\Bootstrap' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-bootstrap.php' + ), + 'UsabilityDynamics\\WP\\Bootstrap_Plugin' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-bootstrap-plugin.php' + ), + 'UsabilityDynamics\\WP\\Bootstrap_Theme' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-bootstrap-theme.php' + ), + 'UsabilityDynamics\\WP\\Dashboard' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-dashboard.php' + ), + 'UsabilityDynamics\\WP\\Errors' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-errors.php' + ), + 'UsabilityDynamics\\WP\\Scaffold' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-scaffold.php' + ), + 'UsabilityDynamics\\WP\\TGMPA_List_Table' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-tgm-list-table.php' + ), + 'UsabilityDynamics\\WP\\TGM_Bulk_Installer' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-tgm-bulk-installer.php' + ), + 'UsabilityDynamics\\WP\\TGM_Bulk_Installer_Skin' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-tgm-bulk-installer.php' + ), + 'UsabilityDynamics\\WP\\TGM_Plugin_Activation' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-tgm-plugin-activation.php' + ), + 'UsabilityDynamics\\WP\\Utility' => array( + 'version' => '1.3.3.0', + 'path' => $vendorDir . '/udx/lib-wp-bootstrap/lib/classes/class-utility.php' + ), + 'ValueError' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php' + ), + 'WP_Async_Request' => array( + 'version' => '1.3.1.0', + 'path' => $vendorDir . '/deliciousbrains/wp-background-processing/classes/wp-async-request.php' + ), + 'WP_Background_Process' => array( + 'version' => '1.3.1.0', + 'path' => $vendorDir . '/deliciousbrains/wp-background-processing/classes/wp-background-process.php' + ), + 'wpCloud\\StatelessMedia\\API' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-api.php' + ), + 'wpCloud\\StatelessMedia\\Addons' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-addons.php' + ), + 'wpCloud\\StatelessMedia\\Ajax' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-ajax.php' + ), + 'wpCloud\\StatelessMedia\\AppEngine' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-google-app-engine.php' + ), + 'wpCloud\\StatelessMedia\\Batch\\BatchTask' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/batch/class-batch-task.php' + ), + 'wpCloud\\StatelessMedia\\Batch\\BatchTaskManager' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/batch/class-batch-task-manager.php' + ), + 'wpCloud\\StatelessMedia\\Batch\\IBatchTask' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/batch/interface-batch.php' + ), + 'wpCloud\\StatelessMedia\\Batch\\Migration' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/batch/class-migration.php' + ), + 'wpCloud\\StatelessMedia\\Bootstrap' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-bootstrap.php' + ), + 'wpCloud\\StatelessMedia\\Compatibility' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-compatibility.php' + ), + 'wpCloud\\StatelessMedia\\DB' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-db.php' + ), + 'wpCloud\\StatelessMedia\\DynamicImageSupport' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-dynamic-image-support.php' + ), + 'wpCloud\\StatelessMedia\\EWWW' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/compatibility/ewww.php' + ), + 'wpCloud\\StatelessMedia\\Errors' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-errors.php' + ), + 'wpCloud\\StatelessMedia\\FatalException' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/exception-fatal.php' + ), + 'wpCloud\\StatelessMedia\\GS_Client' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-gs-client.php' + ), + 'wpCloud\\StatelessMedia\\Helper' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-helper.php' + ), + 'wpCloud\\StatelessMedia\\Imagify' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/compatibility/imagify.php' + ), + 'wpCloud\\StatelessMedia\\LearnDash' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/compatibility/learn-dash.php' + ), + 'wpCloud\\StatelessMedia\\Logger' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-logger.php' + ), + 'wpCloud\\StatelessMedia\\Migrator' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-migrator.php' + ), + 'wpCloud\\StatelessMedia\\Module' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-module.php' + ), + 'wpCloud\\StatelessMedia\\Settings' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-settings.php' + ), + 'wpCloud\\StatelessMedia\\ShortPixel' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/compatibility/shortpixel.php' + ), + 'wpCloud\\StatelessMedia\\Singleton' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/trait-singleton.php' + ), + 'wpCloud\\StatelessMedia\\Status' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-status.php' + ), + 'wpCloud\\StatelessMedia\\Status\\GoogleCloudInfo' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/status/class-info-google_cloud.php' + ), + 'wpCloud\\StatelessMedia\\Status\\Info' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/status/class-info.php' + ), + 'wpCloud\\StatelessMedia\\Status\\Migrations' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/status/class-migrations.php' + ), + 'wpCloud\\StatelessMedia\\Status\\StatelessInfo' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/status/class-info-stateless.php' + ), + 'wpCloud\\StatelessMedia\\StreamWrapper' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-gs-stream-wrapper.php' + ), + 'wpCloud\\StatelessMedia\\SyncNonMedia' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-sync-non-media.php' + ), + 'wpCloud\\StatelessMedia\\Sync\\BackgroundSync' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/sync/class-background-sync.php' + ), + 'wpCloud\\StatelessMedia\\Sync\\FileSync' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/sync/class-file-sync.php' + ), + 'wpCloud\\StatelessMedia\\Sync\\HelperWindow' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/sync/class-helper-window.php' + ), + 'wpCloud\\StatelessMedia\\Sync\\ISync' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/sync/interface-sync.php' + ), + 'wpCloud\\StatelessMedia\\Sync\\ImageSync' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/sync/class-image-sync.php' + ), + 'wpCloud\\StatelessMedia\\Sync\\LibrarySync' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/sync/class-library-sync.php' + ), + 'wpCloud\\StatelessMedia\\Sync\\NonLibrarySync' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/sync/class-non-library-sync.php' + ), + 'wpCloud\\StatelessMedia\\TheEventsCalendar' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/compatibility/the-events-calendar.php' + ), + 'wpCloud\\StatelessMedia\\UnprocessableException' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/exception-unprocessable.php' + ), + 'wpCloud\\StatelessMedia\\Upgrader' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-upgrader.php' + ), + 'wpCloud\\StatelessMedia\\Utility' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/class-utility.php' + ), + 'wpCloud\\StatelessMedia\\WPBakeryPageBuilder' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/compatibility/wpbakery-page-builder.php' + ), + 'wpCloud\\StatelessMedia\\WPSmush' => array( + 'version' => 'dev-latest', + 'path' => $baseDir . '/lib/classes/compatibility/wp-smush.php' + ), + '' => array( + 'version' => '7.4.8.0', + 'path' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php' + ), +); diff --git a/vendor/composer/jetpack_autoload_filemap.php b/vendor/composer/jetpack_autoload_filemap.php new file mode 100644 index 000000000..a23b2c5a0 --- /dev/null +++ b/vendor/composer/jetpack_autoload_filemap.php @@ -0,0 +1,53 @@ + array( + 'version' => '3.6.0.0', + 'path' => $vendorDir . '/symfony/deprecation-contracts/function.php' + ), + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php' + ), + '320cde22f66dd4f5d3fd621d3e88b98f' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php' + ), + '8825ede83f2f289127722d4e842cf7e8' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php' + ), + 'e69f7f6ee287b969198c3c9d6777bd38' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php' + ), + '0d59ee240a4cd96ddbb4ff164fccea4d' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php' + ), + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => array( + 'version' => '8.0.8.0', + 'path' => $vendorDir . '/symfony/string/Resources/functions.php' + ), + 'ad155f8f1cf0d418fe49e248db8c661b' => array( + 'version' => '3.3.0.0', + 'path' => $vendorDir . '/react/promise/src/functions_include.php' + ), + 'a4a119a56e50fbb293281d9a48007e0e' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php' + ), + '23c18046f52bef3eea034657bafda50f' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php' + ), + '9d2b9fc6db0f153a0a149fefb182415e' => array( + 'version' => '1.34.0.0', + 'path' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php' + ), +); diff --git a/vendor/composer/jetpack_autoload_psr4.php b/vendor/composer/jetpack_autoload_psr4.php new file mode 100644 index 000000000..b59edbb9a --- /dev/null +++ b/vendor/composer/jetpack_autoload_psr4.php @@ -0,0 +1,209 @@ + array( + 'version' => '1.12.0.0', + 'path' => array( $vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src' ) + ), + 'Webmozart\\Assert\\' => array( + 'version' => '2.3.0.0', + 'path' => array( $vendorDir . '/webmozart/assert/src' ) + ), + 'Symfony\\Polyfill\\Php84\\' => array( + 'version' => '1.34.0.0', + 'path' => array( $vendorDir . '/symfony/polyfill-php84' ) + ), + 'Symfony\\Polyfill\\Php81\\' => array( + 'version' => '1.34.0.0', + 'path' => array( $vendorDir . '/symfony/polyfill-php81' ) + ), + 'Symfony\\Polyfill\\Php80\\' => array( + 'version' => '1.34.0.0', + 'path' => array( $vendorDir . '/symfony/polyfill-php80' ) + ), + 'Symfony\\Polyfill\\Php73\\' => array( + 'version' => '1.34.0.0', + 'path' => array( $vendorDir . '/symfony/polyfill-php73' ) + ), + 'Symfony\\Polyfill\\Mbstring\\' => array( + 'version' => '1.34.0.0', + 'path' => array( $vendorDir . '/symfony/polyfill-mbstring' ) + ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array( + 'version' => '1.34.0.0', + 'path' => array( $vendorDir . '/symfony/polyfill-intl-normalizer' ) + ), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array( + 'version' => '1.34.0.0', + 'path' => array( $vendorDir . '/symfony/polyfill-intl-grapheme' ) + ), + 'Symfony\\Polyfill\\Ctype\\' => array( + 'version' => '1.34.0.0', + 'path' => array( $vendorDir . '/symfony/polyfill-ctype' ) + ), + 'Symfony\\Contracts\\Service\\' => array( + 'version' => '3.6.1.0', + 'path' => array( $vendorDir . '/symfony/service-contracts' ) + ), + 'Symfony\\Contracts\\Cache\\' => array( + 'version' => '3.6.0.0', + 'path' => array( $vendorDir . '/symfony/cache-contracts' ) + ), + 'Symfony\\Component\\VarExporter\\' => array( + 'version' => '8.0.8.0', + 'path' => array( $vendorDir . '/symfony/var-exporter' ) + ), + 'Symfony\\Component\\String\\' => array( + 'version' => '8.0.8.0', + 'path' => array( $vendorDir . '/symfony/string' ) + ), + 'Symfony\\Component\\Process\\' => array( + 'version' => '8.0.8.0', + 'path' => array( $vendorDir . '/symfony/process' ) + ), + 'Symfony\\Component\\Finder\\' => array( + 'version' => '7.4.8.0', + 'path' => array( $vendorDir . '/symfony/finder' ) + ), + 'Symfony\\Component\\Filesystem\\' => array( + 'version' => '8.0.8.0', + 'path' => array( $vendorDir . '/symfony/filesystem' ) + ), + 'Symfony\\Component\\Console\\' => array( + 'version' => '7.4.8.0', + 'path' => array( $vendorDir . '/symfony/console' ) + ), + 'Symfony\\Component\\Cache\\' => array( + 'version' => '7.4.8.0', + 'path' => array( $vendorDir . '/symfony/cache' ) + ), + 'Seld\\Signal\\' => array( + 'version' => '2.0.2.0', + 'path' => array( $vendorDir . '/seld/signal-handler/src' ) + ), + 'Seld\\PharUtils\\' => array( + 'version' => '1.2.1.0', + 'path' => array( $vendorDir . '/seld/phar-utils/src' ) + ), + 'Seld\\JsonLint\\' => array( + 'version' => '1.11.0.0', + 'path' => array( $vendorDir . '/seld/jsonlint/src/Seld/JsonLint' ) + ), + 'React\\Promise\\' => array( + 'version' => '3.3.0.0', + 'path' => array( $vendorDir . '/react/promise/src' ) + ), + 'Psr\\SimpleCache\\' => array( + 'version' => '1.0.1.0', + 'path' => array( $vendorDir . '/psr/simple-cache/src' ) + ), + 'Psr\\Log\\' => array( + 'version' => '3.0.2.0', + 'path' => array( $vendorDir . '/psr/log/src' ) + ), + 'Psr\\Container\\' => array( + 'version' => '2.0.2.0', + 'path' => array( $vendorDir . '/psr/container/src' ) + ), + 'Psr\\Cache\\' => array( + 'version' => '3.0.0.0', + 'path' => array( $vendorDir . '/psr/cache/src' ) + ), + 'PhpParser\\' => array( + 'version' => '5.7.0.0', + 'path' => array( $vendorDir . '/nikic/php-parser/lib/PhpParser' ) + ), + 'PHPStan\\PhpDocParser\\' => array( + 'version' => '2.3.2.0', + 'path' => array( $vendorDir . '/phpstan/phpdoc-parser/src' ) + ), + 'MyCLabs\\Enum\\' => array( + 'version' => '1.8.5.0', + 'path' => array( $vendorDir . '/myclabs/php-enum/src' ) + ), + 'Monolog\\' => array( + 'version' => '3.10.0.0', + 'path' => array( $vendorDir . '/monolog/monolog/src/Monolog' ) + ), + 'MabeEnum\\' => array( + 'version' => '4.7.2.0', + 'path' => array( $vendorDir . '/marc-mabe/php-enum/src' ) + ), + 'League\\MimeTypeDetection\\' => array( + 'version' => '1.16.0.0', + 'path' => array( $vendorDir . '/league/mime-type-detection/src' ) + ), + 'League\\Flysystem\\Local\\' => array( + 'version' => '3.31.0.0', + 'path' => array( $vendorDir . '/league/flysystem-local' ) + ), + 'League\\Flysystem\\' => array( + 'version' => '3.33.0.0', + 'path' => array( $vendorDir . '/league/flysystem/src' ) + ), + 'JsonSchema\\' => array( + 'version' => '6.8.0.0', + 'path' => array( $vendorDir . '/justinrainbow/json-schema/src/JsonSchema' ) + ), + 'JsonMapper\\' => array( + 'version' => '2.25.1.0', + 'path' => array( $vendorDir . '/json-mapper/json-mapper/src' ) + ), + 'Firebase\\JWT\\' => array( + 'version' => '7.0.5.0', + 'path' => array( $vendorDir . '/firebase/php-jwt/src' ) + ), + 'Doctrine\\Deprecations\\' => array( + 'version' => '1.1.6.0', + 'path' => array( $vendorDir . '/doctrine/deprecations/src' ) + ), + 'Composer\\XdebugHandler\\' => array( + 'version' => '3.0.5.0', + 'path' => array( $vendorDir . '/composer/xdebug-handler/src' ) + ), + 'Composer\\Spdx\\' => array( + 'version' => '1.6.0.0', + 'path' => array( $vendorDir . '/composer/spdx-licenses/src' ) + ), + 'Composer\\Semver\\' => array( + 'version' => '3.4.4.0', + 'path' => array( $vendorDir . '/composer/semver/src' ) + ), + 'Composer\\Pcre\\' => array( + 'version' => '3.3.2.0', + 'path' => array( $vendorDir . '/composer/pcre/src' ) + ), + 'Composer\\MetadataMinifier\\' => array( + 'version' => '1.0.0.0', + 'path' => array( $vendorDir . '/composer/metadata-minifier/src' ) + ), + 'Composer\\Installers\\' => array( + 'version' => '2.3.0.0', + 'path' => array( $vendorDir . '/composer/installers/src/Composer/Installers' ) + ), + 'Composer\\ClassMapGenerator\\' => array( + 'version' => '1.7.2.0', + 'path' => array( $vendorDir . '/composer/class-map-generator/src' ) + ), + 'Composer\\CaBundle\\' => array( + 'version' => '1.5.11.0', + 'path' => array( $vendorDir . '/composer/ca-bundle/src' ) + ), + 'Composer\\' => array( + 'version' => '2.9.5.0', + 'path' => array( $vendorDir . '/composer/composer/src/Composer' ) + ), + 'BrianHenryIE\\Strauss\\' => array( + 'version' => '0.19.5.0', + 'path' => array( $vendorDir . '/brianhenryie/strauss/src' ) + ), + 'Automattic\\Jetpack\\Autoloader\\' => array( + 'version' => '5.0.16', + 'path' => array( $vendorDir . '/automattic/jetpack-autoloader/src' ) + ), +); diff --git a/vendor/composer/metadata-minifier/LICENSE b/vendor/composer/metadata-minifier/LICENSE new file mode 100644 index 000000000..c5a282ff4 --- /dev/null +++ b/vendor/composer/metadata-minifier/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2021 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/metadata-minifier/README.md b/vendor/composer/metadata-minifier/README.md new file mode 100644 index 000000000..75accd454 --- /dev/null +++ b/vendor/composer/metadata-minifier/README.md @@ -0,0 +1,37 @@ +composer/metadata-minifier +========================== + +Small utility library that handles metadata minification and expansion. + +This is used by [Composer](https://github.com/composer/composer)'s 2.x repository metadata protocol. + + +Installation +------------ + +Install the latest version with: + +```bash +$ composer require composer/metadata-minifier +``` + + +Requirements +------------ + +* PHP 5.3.2 is required but using the latest version of PHP is highly recommended. + + +Basic usage +----------- + +### `Composer\MetadataMinifier\MetadataMinifier` + +- `MetadataMinifier::expand()`: Expands an array of minified versions back to their original format +- `MetadataMinifier::minify()`: Minifies an array of versions into a set of version diffs + + +License +------- + +composer/metadata-minifier is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/metadata-minifier/composer.json b/vendor/composer/metadata-minifier/composer.json new file mode 100644 index 000000000..d70a9bebc --- /dev/null +++ b/vendor/composer/metadata-minifier/composer.json @@ -0,0 +1,47 @@ +{ + "name": "composer/metadata-minifier", + "description": "Small utility library that handles metadata minification and expansion.", + "type": "library", + "license": "MIT", + "keywords": [ + "compression", + "composer" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.2 || ^5", + "phpstan/phpstan": "^0.12.55", + "composer/composer": "^2" + }, + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Test\\MetadataMinifier\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "scripts": { + "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit", + "phpstan": "vendor/bin/phpstan analyse" + } +} diff --git a/vendor/composer/metadata-minifier/phpstan.neon.dist b/vendor/composer/metadata-minifier/phpstan.neon.dist new file mode 100644 index 000000000..1cd333bd1 --- /dev/null +++ b/vendor/composer/metadata-minifier/phpstan.neon.dist @@ -0,0 +1,5 @@ +parameters: + level: 8 + paths: + - src + - tests diff --git a/vendor/composer/metadata-minifier/src/MetadataMinifier.php b/vendor/composer/metadata-minifier/src/MetadataMinifier.php new file mode 100644 index 000000000..312ddbaca --- /dev/null +++ b/vendor/composer/metadata-minifier/src/MetadataMinifier.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\MetadataMinifier; + +class MetadataMinifier +{ + /** + * Expands an array of minified versions back to their original format + * + * @param array[] $versions A list of minified version arrays + * @return array[] A list of version arrays + */ + public static function expand(array $versions) + { + $expanded = array(); + $expandedVersion = null; + foreach ($versions as $versionData) { + if (!$expandedVersion) { + $expandedVersion = $versionData; + $expanded[] = $expandedVersion; + continue; + } + + // add any changes from the previous version to the expanded one + foreach ($versionData as $key => $val) { + if ($val === '__unset') { + unset($expandedVersion[$key]); + } else { + $expandedVersion[$key] = $val; + } + } + + $expanded[] = $expandedVersion; + } + + return $expanded; + } + + /** + * Minifies an array of versions into a set of version diffs + * + * @param array[] $versions A list of version arrays + * @return array[] A list of versions minified with each array only containing the differences to the previous one + */ + public static function minify(array $versions) + { + $minifiedVersions = array(); + + $lastKnownVersionData = null; + foreach ($versions as $version) { + if (!$lastKnownVersionData) { + $lastKnownVersionData = $version; + $minifiedVersions[] = $version; + continue; + } + + $minifiedVersion = array(); + + // add any changes from the previous version + foreach ($version as $key => $val) { + if (!isset($lastKnownVersionData[$key]) || $lastKnownVersionData[$key] !== $val) { + $minifiedVersion[$key] = $val; + $lastKnownVersionData[$key] = $val; + } + } + + // store any deletions from the previous version for keys missing in current one + foreach ($lastKnownVersionData as $key => $val) { + if (!isset($version[$key])) { + $minifiedVersion[$key] = "__unset"; + unset($lastKnownVersionData[$key]); + } + } + + $minifiedVersions[] = $minifiedVersion; + } + + return $minifiedVersions; + } +} diff --git a/vendor/composer/pcre/LICENSE b/vendor/composer/pcre/LICENSE new file mode 100644 index 000000000..c5a282ff4 --- /dev/null +++ b/vendor/composer/pcre/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2021 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/pcre/README.md b/vendor/composer/pcre/README.md new file mode 100644 index 000000000..490651499 --- /dev/null +++ b/vendor/composer/pcre/README.md @@ -0,0 +1,189 @@ +composer/pcre +============= + +PCRE wrapping library that offers type-safe `preg_*` replacements. + +This library gives you a way to ensure `preg_*` functions do not fail silently, returning +unexpected `null`s that may not be handled. + +As of 3.0 this library enforces [`PREG_UNMATCHED_AS_NULL`](#preg_unmatched_as_null) usage +for all matching and replaceCallback functions, [read more below](#preg_unmatched_as_null) +to understand the implications. + +It thus makes it easier to work with static analysis tools like PHPStan or Psalm as it +simplifies and reduces the possible return values from all the `preg_*` functions which +are quite packed with edge cases. As of v2.2.0 / v3.2.0 the library also comes with a +[PHPStan extension](#phpstan-extension) for parsing regular expressions and giving you even better output types. + +This library is a thin wrapper around `preg_*` functions with [some limitations](#restrictions--limitations). +If you are looking for a richer API to handle regular expressions have a look at +[rawr/t-regx](https://packagist.org/packages/rawr/t-regx) instead. + +[![Continuous Integration](https://github.com/composer/pcre/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/pcre/actions) + + +Installation +------------ + +Install the latest version with: + +```bash +$ composer require composer/pcre +``` + + +Requirements +------------ + +* PHP 7.4.0 is required for 3.x versions +* PHP 7.2.0 is required for 2.x versions +* PHP 5.3.2 is required for 1.x versions + + +Basic usage +----------- + +Instead of: + +```php +if (preg_match('{fo+}', $string, $matches)) { ... } +if (preg_match('{fo+}', $string, $matches, PREG_OFFSET_CAPTURE)) { ... } +if (preg_match_all('{fo+}', $string, $matches)) { ... } +$newString = preg_replace('{fo+}', 'bar', $string); +$newString = preg_replace_callback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string); +$newString = preg_replace_callback_array(['{fo+}' => fn ($match) => strtoupper($match[0])], $string); +$filtered = preg_grep('{[a-z]}', $elements); +$array = preg_split('{[a-z]+}', $string); +``` + +You can now call these on the `Preg` class: + +```php +use Composer\Pcre\Preg; + +if (Preg::match('{fo+}', $string, $matches)) { ... } +if (Preg::matchWithOffsets('{fo+}', $string, $matches)) { ... } +if (Preg::matchAll('{fo+}', $string, $matches)) { ... } +$newString = Preg::replace('{fo+}', 'bar', $string); +$newString = Preg::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string); +$newString = Preg::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string); +$filtered = Preg::grep('{[a-z]}', $elements); +$array = Preg::split('{[a-z]+}', $string); +``` + +The main difference is if anything fails to match/replace/.., it will throw a `Composer\Pcre\PcreException` +instead of returning `null` (or false in some cases), so you can now use the return values safely relying on +the fact that they can only be strings (for replace), ints (for match) or arrays (for grep/split). + +Additionally the `Preg` class provides match methods that return `bool` rather than `int`, for stricter type safety +when the number of pattern matches is not useful: + +```php +use Composer\Pcre\Preg; + +if (Preg::isMatch('{fo+}', $string, $matches)) // bool +if (Preg::isMatchAll('{fo+}', $string, $matches)) // bool +``` + +Finally the `Preg` class provides a few `*StrictGroups` method variants that ensure match groups +are always present and thus non-nullable, making it easier to write type-safe code: + +```php +use Composer\Pcre\Preg; + +// $matches is guaranteed to be an array of strings, if a subpattern does not match and produces a null it will throw +if (Preg::matchStrictGroups('{fo+}', $string, $matches)) +if (Preg::matchAllStrictGroups('{fo+}', $string, $matches)) +``` + +**Note:** This is generally safe to use as long as you do not have optional subpatterns (i.e. `(something)?` +or `(something)*` or branches with a `|` that result in some groups not being matched at all). +A subpattern that can match an empty string like `(.*)` is **not** optional, it will be present as an +empty string in the matches. A non-matching subpattern, even if optional like `(?:foo)?` will anyway not be present in +matches so it is also not a problem to use these with `*StrictGroups` methods. + +If you would prefer a slightly more verbose usage, replacing by-ref arguments by result objects, you can use the `Regex` class: + +```php +use Composer\Pcre\Regex; + +// this is useful when you are just interested in knowing if something matched +// as it returns a bool instead of int(1/0) for match +$bool = Regex::isMatch('{fo+}', $string); + +$result = Regex::match('{fo+}', $string); +if ($result->matched) { something($result->matches); } + +$result = Regex::matchWithOffsets('{fo+}', $string); +if ($result->matched) { something($result->matches); } + +$result = Regex::matchAll('{fo+}', $string); +if ($result->matched && $result->count > 3) { something($result->matches); } + +$newString = Regex::replace('{fo+}', 'bar', $string)->result; +$newString = Regex::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string)->result; +$newString = Regex::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string)->result; +``` + +Note that `preg_grep` and `preg_split` are only callable via the `Preg` class as they do not have +complex return types warranting a specific result object. + +See the [MatchResult](src/MatchResult.php), [MatchWithOffsetsResult](src/MatchWithOffsetsResult.php), [MatchAllResult](src/MatchAllResult.php), +[MatchAllWithOffsetsResult](src/MatchAllWithOffsetsResult.php), and [ReplaceResult](src/ReplaceResult.php) class sources for more details. + +Restrictions / Limitations +-------------------------- + +Due to type safety requirements a few restrictions are in place. + +- matching using `PREG_OFFSET_CAPTURE` is made available via `matchWithOffsets` and `matchAllWithOffsets`. + You cannot pass the flag to `match`/`matchAll`. +- `Preg::split` will also reject `PREG_SPLIT_OFFSET_CAPTURE` and you should use `splitWithOffsets` + instead. +- `matchAll` rejects `PREG_SET_ORDER` as it also changes the shape of the returned matches. There + is no alternative provided as you can fairly easily code around it. +- `preg_filter` is not supported as it has a rather crazy API, most likely you should rather + use `Preg::grep` in combination with some loop and `Preg::replace`. +- `replace`, `replaceCallback` and `replaceCallbackArray` do not support an array `$subject`, + only simple strings. +- As of 2.0, the library always uses `PREG_UNMATCHED_AS_NULL` for matching, which offers [much + saner/more predictable results](#preg_unmatched_as_null). As of 3.0 the flag is also set for + `replaceCallback` and `replaceCallbackArray`. + +#### PREG_UNMATCHED_AS_NULL + +As of 2.0, this library always uses PREG_UNMATCHED_AS_NULL for all `match*` and `isMatch*` +functions. As of 3.0 it is also done for `replaceCallback` and `replaceCallbackArray`. + +This means your matches will always contain all matching groups, either as null if unmatched +or as string if it matched. + +The advantages in clarity and predictability are clearer if you compare the two outputs of +running this with and without PREG_UNMATCHED_AS_NULL in $flags: + +```php +preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, $flags); +``` + +| no flag | PREG_UNMATCHED_AS_NULL | +| --- | --- | +| array (size=4) | array (size=5) | +| 0 => string 'ac' (length=2) | 0 => string 'ac' (length=2) | +| 1 => string 'a' (length=1) | 1 => string 'a' (length=1) | +| 2 => string '' (length=0) | 2 => null | +| 3 => string 'c' (length=1) | 3 => string 'c' (length=1) | +| | 4 => null | +| group 2 (any unmatched group preceding one that matched) is set to `''`. You cannot tell if it matched an empty string or did not match at all | group 2 is `null` when unmatched and a string if it matched, easy to check for | +| group 4 (any optional group without a matching one following) is missing altogether. So you have to check with `isset()`, but really you want `isset($m[4]) && $m[4] !== ''` for safety unless you are very careful to check that a non-optional group follows it | group 4 is always set, and null in this case as there was no match, easy to check for with `$m[4] !== null` | + +PHPStan Extension +----------------- + +To use the PHPStan extension if you do not use `phpstan/extension-installer` you can include `vendor/composer/pcre/extension.neon` in your PHPStan config. + +The extension provides much better type information for $matches as well as regex validation where possible. + +License +------- + +composer/pcre is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/pcre/composer.json b/vendor/composer/pcre/composer.json new file mode 100644 index 000000000..d3a7e67cb --- /dev/null +++ b/vendor/composer/pcre/composer.json @@ -0,0 +1,54 @@ +{ + "name": "composer/pcre", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "type": "library", + "license": "MIT", + "keywords": [ + "pcre", + "regex", + "preg", + "regular expression" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8 || ^9", + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Pcre\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "scripts": { + "test": "@php vendor/bin/phpunit", + "phpstan": "@php phpstan analyse" + } +} diff --git a/vendor/composer/pcre/extension.neon b/vendor/composer/pcre/extension.neon new file mode 100644 index 000000000..b9cea113f --- /dev/null +++ b/vendor/composer/pcre/extension.neon @@ -0,0 +1,22 @@ +# composer/pcre PHPStan extensions +# +# These can be reused by third party packages by including 'vendor/composer/pcre/extension.neon' +# in your phpstan config + +services: + - + class: Composer\Pcre\PHPStan\PregMatchParameterOutTypeExtension + tags: + - phpstan.staticMethodParameterOutTypeExtension + - + class: Composer\Pcre\PHPStan\PregMatchTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension + - + class: Composer\Pcre\PHPStan\PregReplaceCallbackClosureTypeExtension + tags: + - phpstan.staticMethodParameterClosureTypeExtension + +rules: + - Composer\Pcre\PHPStan\UnsafeStrictGroupsCallRule + - Composer\Pcre\PHPStan\InvalidRegexPatternRule diff --git a/vendor/composer/pcre/src/MatchAllResult.php b/vendor/composer/pcre/src/MatchAllResult.php new file mode 100644 index 000000000..b22b52d6e --- /dev/null +++ b/vendor/composer/pcre/src/MatchAllResult.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchAllResult +{ + /** + * An array of match group => list of matched strings + * + * @readonly + * @var array> + */ + public $matches; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + $this->count = $count; + } +} diff --git a/vendor/composer/pcre/src/MatchAllStrictGroupsResult.php b/vendor/composer/pcre/src/MatchAllStrictGroupsResult.php new file mode 100644 index 000000000..b7ec39743 --- /dev/null +++ b/vendor/composer/pcre/src/MatchAllStrictGroupsResult.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchAllStrictGroupsResult +{ + /** + * An array of match group => list of matched strings + * + * @readonly + * @var array> + */ + public $matches; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + $this->count = $count; + } +} diff --git a/vendor/composer/pcre/src/MatchAllWithOffsetsResult.php b/vendor/composer/pcre/src/MatchAllWithOffsetsResult.php new file mode 100644 index 000000000..032a02cd3 --- /dev/null +++ b/vendor/composer/pcre/src/MatchAllWithOffsetsResult.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchAllWithOffsetsResult +{ + /** + * An array of match group => list of matches, every match being a pair of string matched + offset in bytes (or -1 if no match) + * + * @readonly + * @var array> + * @phpstan-var array}>> + */ + public $matches; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array> $matches + * @phpstan-param array}>> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + $this->count = $count; + } +} diff --git a/vendor/composer/pcre/src/MatchResult.php b/vendor/composer/pcre/src/MatchResult.php new file mode 100644 index 000000000..e951a5ee5 --- /dev/null +++ b/vendor/composer/pcre/src/MatchResult.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchResult +{ + /** + * An array of match group => string matched + * + * @readonly + * @var array + */ + public $matches; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + } +} diff --git a/vendor/composer/pcre/src/MatchStrictGroupsResult.php b/vendor/composer/pcre/src/MatchStrictGroupsResult.php new file mode 100644 index 000000000..126ee6293 --- /dev/null +++ b/vendor/composer/pcre/src/MatchStrictGroupsResult.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchStrictGroupsResult +{ + /** + * An array of match group => string matched + * + * @readonly + * @var array + */ + public $matches; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + } +} diff --git a/vendor/composer/pcre/src/MatchWithOffsetsResult.php b/vendor/composer/pcre/src/MatchWithOffsetsResult.php new file mode 100644 index 000000000..ba4d4bc4d --- /dev/null +++ b/vendor/composer/pcre/src/MatchWithOffsetsResult.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchWithOffsetsResult +{ + /** + * An array of match group => pair of string matched + offset in bytes (or -1 if no match) + * + * @readonly + * @var array + * @phpstan-var array}> + */ + public $matches; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array $matches + * @phpstan-param array}> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + } +} diff --git a/vendor/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php b/vendor/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php new file mode 100644 index 000000000..8a05fb24a --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php @@ -0,0 +1,142 @@ + + */ +class InvalidRegexPatternRule implements Rule +{ + public function getNodeType(): string + { + return StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $patterns = $this->extractPatterns($node, $scope); + + $errors = []; + foreach ($patterns as $pattern) { + $errorMessage = $this->validatePattern($pattern); + if ($errorMessage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->identifier('regexp.pattern')->build(); + } + + return $errors; + } + + /** + * @return string[] + */ + private function extractPatterns(StaticCall $node, Scope $scope): array + { + if (!$node->class instanceof FullyQualified) { + return []; + } + $isRegex = $node->class->toString() === Regex::class; + $isPreg = $node->class->toString() === Preg::class; + if (!$isRegex && !$isPreg) { + return []; + } + if (!$node->name instanceof Node\Identifier || !Preg::isMatch('{^(match|isMatch|grep|replace|split)}', $node->name->name)) { + return []; + } + + $functionName = $node->name->name; + if (!isset($node->getArgs()[0])) { + return []; + } + + $patternNode = $node->getArgs()[0]->value; + $patternType = $scope->getType($patternNode); + + $patternStrings = []; + + foreach ($patternType->getConstantStrings() as $constantStringType) { + if ($functionName === 'replaceCallbackArray') { + continue; + } + + $patternStrings[] = $constantStringType->getValue(); + } + + foreach ($patternType->getConstantArrays() as $constantArrayType) { + if ( + in_array($functionName, [ + 'replace', + 'replaceCallback', + ], true) + ) { + foreach ($constantArrayType->getValueTypes() as $arrayKeyType) { + foreach ($arrayKeyType->getConstantStrings() as $constantString) { + $patternStrings[] = $constantString->getValue(); + } + } + } + + if ($functionName !== 'replaceCallbackArray') { + continue; + } + + foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) { + foreach ($arrayKeyType->getConstantStrings() as $constantString) { + $patternStrings[] = $constantString->getValue(); + } + } + } + + return $patternStrings; + } + + private function validatePattern(string $pattern): ?string + { + try { + $msg = null; + $prev = set_error_handler(function (int $severity, string $message, string $file) use (&$msg): bool { + $msg = preg_replace("#^preg_match(_all)?\\(.*?\\): #", '', $message); + + return true; + }); + + if ($pattern === '') { + return 'Empty string is not a valid regular expression'; + } + + Preg::match($pattern, ''); + if ($msg !== null) { + return $msg; + } + } catch (PcreException $e) { + if ($e->getCode() === PREG_INTERNAL_ERROR && $msg !== null) { + return $msg; + } + + return preg_replace('{.*? failed executing ".*": }', '', $e->getMessage()); + } finally { + restore_error_handler(); + } + + return null; + } + +} diff --git a/vendor/composer/pcre/src/PHPStan/PregMatchFlags.php b/vendor/composer/pcre/src/PHPStan/PregMatchFlags.php new file mode 100644 index 000000000..aa30ab347 --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/PregMatchFlags.php @@ -0,0 +1,70 @@ +getType($flagsArg->value); + + $constantScalars = $flagsType->getConstantScalarValues(); + if ($constantScalars === []) { + return null; + } + + $internalFlagsTypes = []; + foreach ($flagsType->getConstantScalarValues() as $constantScalarValue) { + if (!is_int($constantScalarValue)) { + return null; + } + + $internalFlagsTypes[] = new ConstantIntegerType($constantScalarValue | PREG_UNMATCHED_AS_NULL); + } + return TypeCombinator::union(...$internalFlagsTypes); + } + + static public function removeNullFromMatches(Type $matchesType): Type + { + return TypeTraverser::map($matchesType, static function (Type $type, callable $traverse): Type { + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $traverse($type); + } + + if ($type instanceof ConstantArrayType) { + return new ConstantArrayType( + $type->getKeyTypes(), + array_map(static function (Type $valueType) use ($traverse): Type { + return $traverse($valueType); + }, $type->getValueTypes()), + $type->getNextAutoIndexes(), + [], + $type->isList() + ); + } + + if ($type instanceof ArrayType) { + return new ArrayType($type->getKeyType(), $traverse($type->getItemType())); + } + + return TypeCombinator::removeNull($type); + }); + } + +} diff --git a/vendor/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php b/vendor/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php new file mode 100644 index 000000000..e0d60208e --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php @@ -0,0 +1,65 @@ +regexShapeMatcher = $regexShapeMatcher; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return + $methodReflection->getDeclaringClass()->getName() === Preg::class + && in_array($methodReflection->getName(), [ + 'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups', + 'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups' + ], true) + && $parameter->getName() === 'matches'; + } + + public function getParameterOutTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type + { + $args = $methodCall->getArgs(); + $patternArg = $args[0] ?? null; + $matchesArg = $args[2] ?? null; + $flagsArg = $args[3] ?? null; + + if ( + $patternArg === null || $matchesArg === null + ) { + return null; + } + + $flagsType = PregMatchFlags::getType($flagsArg, $scope); + if ($flagsType === null) { + return null; + } + + if (stripos($methodReflection->getName(), 'matchAll') !== false) { + return $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); + } + + return $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); + } + +} diff --git a/vendor/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php b/vendor/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php new file mode 100644 index 000000000..3db0ce06a --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php @@ -0,0 +1,119 @@ +regexShapeMatcher = $regexShapeMatcher; + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function getClass(): string + { + return Preg::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection, StaticCall $node, TypeSpecifierContext $context): bool + { + return in_array($methodReflection->getName(), [ + 'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups', + 'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups' + ], true) + && !$context->null(); + } + + public function specifyTypes(MethodReflection $methodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $args = $node->getArgs(); + $patternArg = $args[0] ?? null; + $matchesArg = $args[2] ?? null; + $flagsArg = $args[3] ?? null; + + if ( + $patternArg === null || $matchesArg === null + ) { + return new SpecifiedTypes(); + } + + $flagsType = PregMatchFlags::getType($flagsArg, $scope); + if ($flagsType === null) { + return new SpecifiedTypes(); + } + + if (stripos($methodReflection->getName(), 'matchAll') !== false) { + $matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); + } else { + $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); + } + + if ($matchedType === null) { + return new SpecifiedTypes(); + } + + if ( + in_array($methodReflection->getName(), ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true) + ) { + $matchedType = PregMatchFlags::removeNullFromMatches($matchedType); + } + + $overwrite = false; + if ($context->false()) { + $overwrite = true; + $context = $context->negate(); + } + + // @phpstan-ignore function.alreadyNarrowedType + if (method_exists('PHPStan\Analyser\SpecifiedTypes', 'setRootExpr')) { + $typeSpecifier = $this->typeSpecifier->create( + $matchesArg->value, + $matchedType, + $context, + $scope + )->setRootExpr($node); + + return $overwrite ? $typeSpecifier->setAlwaysOverwriteTypes() : $typeSpecifier; + } + + // @phpstan-ignore arguments.count + return $this->typeSpecifier->create( + $matchesArg->value, + $matchedType, + $context, + // @phpstan-ignore argument.type + $overwrite, + $scope, + $node + ); + } +} diff --git a/vendor/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php b/vendor/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php new file mode 100644 index 000000000..7b9536725 --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php @@ -0,0 +1,91 @@ +regexShapeMatcher = $regexShapeMatcher; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return in_array($methodReflection->getDeclaringClass()->getName(), [Preg::class, Regex::class], true) + && in_array($methodReflection->getName(), ['replaceCallback', 'replaceCallbackStrictGroups'], true) + && $parameter->getName() === 'replacement'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type + { + $args = $methodCall->getArgs(); + $patternArg = $args[0] ?? null; + $flagsArg = $args[5] ?? null; + + if ( + $patternArg === null + ) { + return null; + } + + $flagsType = PregMatchFlags::getType($flagsArg, $scope); + + $matchesType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope); + if ($matchesType === null) { + return null; + } + + if ($methodReflection->getName() === 'replaceCallbackStrictGroups' && count($matchesType->getConstantArrays()) === 1) { + $matchesType = $matchesType->getConstantArrays()[0]; + $matchesType = new ConstantArrayType( + $matchesType->getKeyTypes(), + array_map(static function (Type $valueType): Type { + if (count($valueType->getConstantArrays()) === 1) { + $valueTypeArray = $valueType->getConstantArrays()[0]; + return new ConstantArrayType( + $valueTypeArray->getKeyTypes(), + array_map(static function (Type $valueType): Type { + return TypeCombinator::removeNull($valueType); + }, $valueTypeArray->getValueTypes()), + $valueTypeArray->getNextAutoIndexes(), + [], + $valueTypeArray->isList() + ); + } + return TypeCombinator::removeNull($valueType); + }, $matchesType->getValueTypes()), + $matchesType->getNextAutoIndexes(), + [], + $matchesType->isList() + ); + } + + return new ClosureType( + [ + new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $matchesType, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue()), + ], + new StringType() + ); + } +} diff --git a/vendor/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php b/vendor/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php new file mode 100644 index 000000000..5bced5070 --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php @@ -0,0 +1,112 @@ + + */ +final class UnsafeStrictGroupsCallRule implements Rule +{ + /** + * @var RegexArrayShapeMatcher + */ + private $regexShapeMatcher; + + public function __construct(RegexArrayShapeMatcher $regexShapeMatcher) + { + $this->regexShapeMatcher = $regexShapeMatcher; + } + + public function getNodeType(): string + { + return StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->class instanceof FullyQualified) { + return []; + } + $isRegex = $node->class->toString() === Regex::class; + $isPreg = $node->class->toString() === Preg::class; + if (!$isRegex && !$isPreg) { + return []; + } + if (!$node->name instanceof Node\Identifier || !in_array($node->name->name, ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)) { + return []; + } + + $args = $node->getArgs(); + if (!isset($args[0])) { + return []; + } + + $patternArg = $args[0] ?? null; + if ($isPreg) { + if (!isset($args[2])) { // no matches set, skip as the matches won't be used anyway + return []; + } + $flagsArg = $args[3] ?? null; + } else { + $flagsArg = $args[2] ?? null; + } + + if ($patternArg === null) { + return []; + } + + $flagsType = PregMatchFlags::getType($flagsArg, $scope); + if ($flagsType === null) { + return []; + } + + $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope); + if ($matchedType === null) { + return [ + RuleErrorBuilder::message(sprintf('The %s call is potentially unsafe as $matches\' type could not be inferred.', $node->name->name)) + ->identifier('composerPcre.maybeUnsafeStrictGroups') + ->build(), + ]; + } + + if (count($matchedType->getConstantArrays()) === 1) { + $matchedType = $matchedType->getConstantArrays()[0]; + $nullableGroups = []; + foreach ($matchedType->getValueTypes() as $index => $type) { + if (TypeCombinator::containsNull($type)) { + $nullableGroups[] = $matchedType->getKeyTypes()[$index]->getValue(); + } + } + + if (\count($nullableGroups) > 0) { + return [ + RuleErrorBuilder::message(sprintf( + 'The %s call is unsafe as match group%s "%s" %s optional and may be null.', + $node->name->name, + \count($nullableGroups) > 1 ? 's' : '', + implode('", "', $nullableGroups), + \count($nullableGroups) > 1 ? 'are' : 'is' + ))->identifier('composerPcre.unsafeStrictGroups')->build(), + ]; + } + } + + return []; + } +} diff --git a/vendor/composer/pcre/src/PcreException.php b/vendor/composer/pcre/src/PcreException.php new file mode 100644 index 000000000..23d93279d --- /dev/null +++ b/vendor/composer/pcre/src/PcreException.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class PcreException extends \RuntimeException +{ + /** + * @param string $function + * @param string|string[] $pattern + * @return self + */ + public static function fromFunction($function, $pattern) + { + $code = preg_last_error(); + + if (is_array($pattern)) { + $pattern = implode(', ', $pattern); + } + + return new PcreException($function.'(): failed executing "'.$pattern.'": '.self::pcreLastErrorMessage($code), $code); + } + + /** + * @param int $code + * @return string + */ + private static function pcreLastErrorMessage($code) + { + if (function_exists('preg_last_error_msg')) { + return preg_last_error_msg(); + } + + $constants = get_defined_constants(true); + if (!isset($constants['pcre']) || !is_array($constants['pcre'])) { + return 'UNDEFINED_ERROR'; + } + + foreach ($constants['pcre'] as $const => $val) { + if ($val === $code && substr($const, -6) === '_ERROR') { + return $const; + } + } + + return 'UNDEFINED_ERROR'; + } +} diff --git a/vendor/composer/pcre/src/Preg.php b/vendor/composer/pcre/src/Preg.php new file mode 100644 index 000000000..400abbfec --- /dev/null +++ b/vendor/composer/pcre/src/Preg.php @@ -0,0 +1,430 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class Preg +{ + /** @internal */ + public const ARRAY_MSG = '$subject as an array is not supported. You can use \'foreach\' instead.'; + /** @internal */ + public const INVALID_TYPE_MSG = '$subject must be a string, %s given.'; + + /** + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|1 + * + * @param-out array $matches + */ + public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + self::checkOffsetCapture($flags, 'matchWithOffsets'); + + $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset); + if ($result === false) { + throw PcreException::fromFunction('preg_match', $pattern); + } + + return $result; + } + + /** + * Variant of `match()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|1 + * @throws UnexpectedNullMatchException + * + * @param-out array $matches + */ + public static function matchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + $result = self::match($pattern, $subject, $matchesInternal, $flags, $offset); + $matches = self::enforceNonNullMatches($pattern, $matchesInternal, 'match'); + + return $result; + } + + /** + * Runs preg_match with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_OFFSET_CAPTURE are always set, no other flags are supported + * @return 0|1 + * + * @param-out array}> $matches + */ + public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int + { + $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset); + if ($result === false) { + throw PcreException::fromFunction('preg_match', $pattern); + } + + return $result; + } + + /** + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|positive-int + * + * @param-out array> $matches + */ + public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + self::checkOffsetCapture($flags, 'matchAllWithOffsets'); + self::checkSetOrder($flags); + + $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset); + if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false + throw PcreException::fromFunction('preg_match_all', $pattern); + } + + return $result; + } + + /** + * Variant of `match()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|positive-int + * @throws UnexpectedNullMatchException + * + * @param-out array> $matches + */ + public static function matchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + $result = self::matchAll($pattern, $subject, $matchesInternal, $flags, $offset); + $matches = self::enforceNonNullMatchAll($pattern, $matchesInternal, 'matchAll'); + + return $result; + } + + /** + * Runs preg_match_all with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported + * @return 0|positive-int + * + * @param-out array}>> $matches + */ + public static function matchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int + { + self::checkSetOrder($flags); + + $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset); + if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false + throw PcreException::fromFunction('preg_match_all', $pattern); + } + + return $result; + } + + /** + * @param string|string[] $pattern + * @param string|string[] $replacement + * @param string $subject + * @param int $count Set by method + * + * @param-out int<0, max> $count + */ + public static function replace($pattern, $replacement, $subject, int $limit = -1, ?int &$count = null): string + { + if (!is_scalar($subject)) { + if (is_array($subject)) { + throw new \InvalidArgumentException(static::ARRAY_MSG); + } + + throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); + } + + $result = preg_replace($pattern, $replacement, $subject, $limit, $count); + if ($result === null) { + throw PcreException::fromFunction('preg_replace', $pattern); + } + + return $result; + } + + /** + * @param string|string[] $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int $count Set by method + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + * + * @param-out int<0, max> $count + */ + public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string + { + if (!is_scalar($subject)) { + if (is_array($subject)) { + throw new \InvalidArgumentException(static::ARRAY_MSG); + } + + throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); + } + + $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL); + if ($result === null) { + throw PcreException::fromFunction('preg_replace_callback', $pattern); + } + + return $result; + } + + /** + * Variant of `replaceCallback()` which outputs non-null matches (or throws) + * + * @param string $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int $count Set by method + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + * + * @param-out int<0, max> $count + */ + public static function replaceCallbackStrictGroups(string $pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string + { + return self::replaceCallback($pattern, function (array $matches) use ($pattern, $replacement) { + return $replacement(self::enforceNonNullMatches($pattern, $matches, 'replaceCallback')); + }, $subject, $limit, $count, $flags); + } + + /** + * @param ($flags is PREG_OFFSET_CAPTURE ? (array}>): string>) : array): string>) $pattern + * @param string $subject + * @param int $count Set by method + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + * + * @param-out int<0, max> $count + */ + public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string + { + if (!is_scalar($subject)) { + if (is_array($subject)) { + throw new \InvalidArgumentException(static::ARRAY_MSG); + } + + throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); + } + + $result = preg_replace_callback_array($pattern, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL); + if ($result === null) { + $pattern = array_keys($pattern); + throw PcreException::fromFunction('preg_replace_callback_array', $pattern); + } + + return $result; + } + + /** + * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE + * @return list + */ + public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array + { + if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) { + throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead'); + } + + $result = preg_split($pattern, $subject, $limit, $flags); + if ($result === false) { + throw PcreException::fromFunction('preg_split', $pattern); + } + + return $result; + } + + /** + * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_OFFSET_CAPTURE is always set + * @return list + * @phpstan-return list}> + */ + public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0): array + { + $result = preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE); + if ($result === false) { + throw PcreException::fromFunction('preg_split', $pattern); + } + + return $result; + } + + /** + * @template T of string|\Stringable + * @param string $pattern + * @param array $array + * @param int-mask $flags PREG_GREP_INVERT + * @return array + */ + public static function grep(string $pattern, array $array, int $flags = 0): array + { + $result = preg_grep($pattern, $array, $flags); + if ($result === false) { + throw PcreException::fromFunction('preg_grep', $pattern); + } + + return $result; + } + + /** + * Variant of match() which returns a bool instead of int + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array $matches + */ + public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) static::match($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of `isMatch()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @throws UnexpectedNullMatchException + * + * @param-out array $matches + */ + public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of matchAll() which returns a bool instead of int + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array> $matches + */ + public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of `isMatchAll()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array> $matches + */ + public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of matchWithOffsets() which returns a bool instead of int + * + * Runs preg_match with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array}> $matches + */ + public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool + { + return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of matchAllWithOffsets() which returns a bool instead of int + * + * Runs preg_match_all with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array}>> $matches + */ + public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool + { + return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); + } + + private static function checkOffsetCapture(int $flags, string $useFunctionName): void + { + if (($flags & PREG_OFFSET_CAPTURE) !== 0) { + throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead'); + } + } + + private static function checkSetOrder(int $flags): void + { + if (($flags & PREG_SET_ORDER) !== 0) { + throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches'); + } + } + + /** + * @param array $matches + * @return array + * @throws UnexpectedNullMatchException + */ + private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod) + { + foreach ($matches as $group => $match) { + if (is_string($match) || (is_array($match) && is_string($match[0]))) { + continue; + } + + throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); + } + + /** @var array */ + return $matches; + } + + /** + * @param array> $matches + * @return array> + * @throws UnexpectedNullMatchException + */ + private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod) + { + foreach ($matches as $group => $groupMatches) { + foreach ($groupMatches as $match) { + if (null === $match) { + throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); + } + } + } + + /** @var array> */ + return $matches; + } +} diff --git a/vendor/composer/pcre/src/Regex.php b/vendor/composer/pcre/src/Regex.php new file mode 100644 index 000000000..038cf0696 --- /dev/null +++ b/vendor/composer/pcre/src/Regex.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class Regex +{ + /** + * @param non-empty-string $pattern + */ + public static function isMatch(string $pattern, string $subject, int $offset = 0): bool + { + return (bool) Preg::match($pattern, $subject, $matches, 0, $offset); + } + + /** + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + */ + public static function match(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchResult + { + self::checkOffsetCapture($flags, 'matchWithOffsets'); + + $count = Preg::match($pattern, $subject, $matches, $flags, $offset); + + return new MatchResult($count, $matches); + } + + /** + * Variant of `match()` which returns non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @throws UnexpectedNullMatchException + */ + public static function matchStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchStrictGroupsResult + { + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + $count = Preg::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); + + return new MatchStrictGroupsResult($count, $matches); + } + + /** + * Runs preg_match with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported + */ + public static function matchWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchWithOffsetsResult + { + $count = Preg::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); + + return new MatchWithOffsetsResult($count, $matches); + } + + /** + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + */ + public static function matchAll(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllResult + { + self::checkOffsetCapture($flags, 'matchAllWithOffsets'); + self::checkSetOrder($flags); + + $count = Preg::matchAll($pattern, $subject, $matches, $flags, $offset); + + return new MatchAllResult($count, $matches); + } + + /** + * Variant of `matchAll()` which returns non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @throws UnexpectedNullMatchException + */ + public static function matchAllStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllStrictGroupsResult + { + self::checkOffsetCapture($flags, 'matchAllWithOffsets'); + self::checkSetOrder($flags); + + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + $count = Preg::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); + + return new MatchAllStrictGroupsResult($count, $matches); + } + + /** + * Runs preg_match_all with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported + */ + public static function matchAllWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllWithOffsetsResult + { + self::checkSetOrder($flags); + + $count = Preg::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); + + return new MatchAllWithOffsetsResult($count, $matches); + } + /** + * @param string|string[] $pattern + * @param string|string[] $replacement + * @param string $subject + */ + public static function replace($pattern, $replacement, $subject, int $limit = -1): ReplaceResult + { + $result = Preg::replace($pattern, $replacement, $subject, $limit, $count); + + return new ReplaceResult($count, $result); + } + + /** + * @param string|string[] $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + */ + public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult + { + $result = Preg::replaceCallback($pattern, $replacement, $subject, $limit, $count, $flags); + + return new ReplaceResult($count, $result); + } + + /** + * Variant of `replaceCallback()` which outputs non-null matches (or throws) + * + * @param string $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + */ + public static function replaceCallbackStrictGroups($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult + { + $result = Preg::replaceCallbackStrictGroups($pattern, $replacement, $subject, $limit, $count, $flags); + + return new ReplaceResult($count, $result); + } + + /** + * @param ($flags is PREG_OFFSET_CAPTURE ? (array}>): string>) : array): string>) $pattern + * @param string $subject + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + */ + public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int $flags = 0): ReplaceResult + { + $result = Preg::replaceCallbackArray($pattern, $subject, $limit, $count, $flags); + + return new ReplaceResult($count, $result); + } + + private static function checkOffsetCapture(int $flags, string $useFunctionName): void + { + if (($flags & PREG_OFFSET_CAPTURE) !== 0) { + throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use '.$useFunctionName.'() instead'); + } + } + + private static function checkSetOrder(int $flags): void + { + if (($flags & PREG_SET_ORDER) !== 0) { + throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the return type'); + } + } +} diff --git a/vendor/composer/pcre/src/ReplaceResult.php b/vendor/composer/pcre/src/ReplaceResult.php new file mode 100644 index 000000000..33847712d --- /dev/null +++ b/vendor/composer/pcre/src/ReplaceResult.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class ReplaceResult +{ + /** + * @readonly + * @var string + */ + public $result; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + */ + public function __construct(int $count, string $result) + { + $this->count = $count; + $this->matched = (bool) $count; + $this->result = $result; + } +} diff --git a/vendor/composer/pcre/src/UnexpectedNullMatchException.php b/vendor/composer/pcre/src/UnexpectedNullMatchException.php new file mode 100644 index 000000000..f123828bf --- /dev/null +++ b/vendor/composer/pcre/src/UnexpectedNullMatchException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class UnexpectedNullMatchException extends PcreException +{ + public static function fromFunction($function, $pattern) + { + throw new \LogicException('fromFunction should not be called on '.self::class.', use '.PcreException::class); + } +} diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php index a70ba47c6..2beb14918 100644 --- a/vendor/composer/platform_check.php +++ b/vendor/composer/platform_check.php @@ -4,8 +4,8 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 80000)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; } if ($issues) { diff --git a/vendor/composer/semver/CHANGELOG.md b/vendor/composer/semver/CHANGELOG.md new file mode 100644 index 000000000..bad46cd1c --- /dev/null +++ b/vendor/composer/semver/CHANGELOG.md @@ -0,0 +1,229 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +### [3.4.3] 2024-09-19 + + * Fixed some type annotations + +### [3.4.2] 2024-07-12 + + * Fixed PHP 5.3 syntax error + +### [3.4.1] 2024-07-12 + + * Fixed normalizeStability's return type to enforce valid stabilities + +### [3.4.0] 2023-08-31 + + * Support larger major version numbers (#149) + +### [3.3.2] 2022-04-01 + + * Fixed handling of non-string values (#134) + +### [3.3.1] 2022-03-16 + + * Fixed possible cache key clash in the CompilingMatcher memoization (#132) + +### [3.3.0] 2022-03-15 + + * Improved performance of CompilingMatcher by memoizing more (#131) + * Added CompilingMatcher::clear to clear all memoization caches + +### [3.2.9] 2022-02-04 + + * Revert #129 (Fixed MultiConstraint with MatchAllConstraint) which caused regressions + +### [3.2.8] 2022-02-04 + + * Updates to latest phpstan / CI by @Seldaek in https://github.com/composer/semver/pull/130 + * Fixed MultiConstraint with MatchAllConstraint by @Toflar in https://github.com/composer/semver/pull/129 + +### [3.2.7] 2022-01-04 + + * Fixed: typo in type definition of Intervals class causing issues with Psalm scanning vendors + +### [3.2.6] 2021-10-25 + + * Fixed: type improvements to parseStability + +### [3.2.5] 2021-05-24 + + * Fixed: issue comparing disjunctive MultiConstraints to conjunctive ones (#127) + * Fixed: added complete type information using phpstan annotations + +### [3.2.4] 2020-11-13 + + * Fixed: code clean-up + +### [3.2.3] 2020-11-12 + + * Fixed: constraints in the form of `X || Y, >=Y.1` and other such complex constructs were in some cases being optimized into a more restrictive constraint + +### [3.2.2] 2020-10-14 + + * Fixed: internal code cleanups + +### [3.2.1] 2020-09-27 + + * Fixed: accidental validation of broken constraints combining ^/~ and wildcards, and -dev suffix allowing weird cases + * Fixed: normalization of beta0 and such which was dropping the 0 + +### [3.2.0] 2020-09-09 + + * Added: support for `x || @dev`, not very useful but seen in the wild and failed to validate with 1.5.2/1.6.0 + * Added: support for `foobar-dev` being equal to `dev-foobar`, dev-foobar is the official way to write it but we need to support the other for BC and convenience + +### [3.1.0] 2020-09-08 + + * Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 3.0.1 + * Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package + +### [3.0.1] 2020-09-08 + + * Fixed: handling of some invalid -dev versions which were seen as valid + +### [3.0.0] 2020-05-26 + + * Break: Renamed `EmptyConstraint`, replace it with `MatchAllConstraint` + * Break: Unlikely to affect anyone but strictly speaking a breaking change, `*.*` and such variants will not match all `dev-*` versions anymore, only `*` does + * Break: ConstraintInterface is now considered internal/private and not meant to be implemented by third parties anymore + * Added `Intervals` class to check if a constraint is a subsets of another one, and allow compacting complex MultiConstraints into simpler ones + * Added `CompilingMatcher` class to speed up constraint matching against simple Constraint instances + * Added `MatchAllConstraint` and `MatchNoneConstraint` which match everything and nothing + * Added more advanced optimization of contiguous constraints inside MultiConstraint + * Added tentative support for PHP 8 + * Fixed ConstraintInterface::matches to be commutative in all cases + +### [2.0.0] 2020-04-21 + + * Break: `dev-master`, `dev-trunk` and `dev-default` now normalize to `dev-master`, `dev-trunk` and `dev-default` instead of `9999999-dev` in 1.x + * Break: Removed the deprecated `AbstractConstraint` + * Added `getUpperBound` and `getLowerBound` to ConstraintInterface. They return `Composer\Semver\Constraint\Bound` instances + * Added `MultiConstraint::create` to create the most-optimal form of ConstraintInterface from an array of constraint strings + +### [1.7.2] 2020-12-03 + + * Fixed: Allow installing on php 8 + +### [1.7.1] 2020-09-27 + + * Fixed: accidental validation of broken constraints combining ^/~ and wildcards, and -dev suffix allowing weird cases + * Fixed: normalization of beta0 and such which was dropping the 0 + +### [1.7.0] 2020-09-09 + + * Added: support for `x || @dev`, not very useful but seen in the wild and failed to validate with 1.5.2/1.6.0 + * Added: support for `foobar-dev` being equal to `dev-foobar`, dev-foobar is the official way to write it but we need to support the other for BC and convenience + +### [1.6.0] 2020-09-08 + + * Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 1.5.2 + * Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package + +### [1.5.2] 2020-09-08 + + * Fixed: handling of some invalid -dev versions which were seen as valid + * Fixed: some doctypes + +### [1.5.1] 2020-01-13 + + * Fixed: Parsing of aliased version was not validating the alias to be a valid version + +### [1.5.0] 2019-03-19 + + * Added: some support for date versions (e.g. 201903) in `~` operator + * Fixed: support for stabilities in `~` operator was inconsistent + +### [1.4.2] 2016-08-30 + + * Fixed: collapsing of complex constraints lead to buggy constraints + +### [1.4.1] 2016-06-02 + + * Changed: branch-like requirements no longer strip build metadata - [composer/semver#38](https://github.com/composer/semver/pull/38). + +### [1.4.0] 2016-03-30 + + * Added: getters on MultiConstraint - [composer/semver#35](https://github.com/composer/semver/pull/35). + +### [1.3.0] 2016-02-25 + + * Fixed: stability parsing - [composer/composer#1234](https://github.com/composer/composer/issues/4889). + * Changed: collapse contiguous constraints when possible. + +### [1.2.0] 2015-11-10 + + * Changed: allow multiple numerical identifiers in 'pre-release' version part. + * Changed: add more 'v' prefix support. + +### [1.1.0] 2015-11-03 + + * Changed: dropped redundant `test` namespace. + * Changed: minor adjustment in datetime parsing normalization. + * Changed: `ConstraintInterface` relaxed, setPrettyString is not required anymore. + * Changed: `AbstractConstraint` marked deprecated, will be removed in 2.0. + * Changed: `Constraint` is now extensible. + +### [1.0.0] 2015-09-21 + + * Break: `VersionConstraint` renamed to `Constraint`. + * Break: `SpecificConstraint` renamed to `AbstractConstraint`. + * Break: `LinkConstraintInterface` renamed to `ConstraintInterface`. + * Break: `VersionParser::parseNameVersionPairs` was removed. + * Changed: `VersionParser::parseConstraints` allows (but ignores) build metadata now. + * Changed: `VersionParser::parseConstraints` allows (but ignores) prefixing numeric versions with a 'v' now. + * Changed: Fixed namespace(s) of test files. + * Changed: `Comparator::compare` no longer throws `InvalidArgumentException`. + * Changed: `Constraint` now throws `InvalidArgumentException`. + +### [0.1.0] 2015-07-23 + + * Added: `Composer\Semver\Comparator`, various methods to compare versions. + * Added: various documents such as README.md, LICENSE, etc. + * Added: configuration files for Git, Travis, php-cs-fixer, phpunit. + * Break: the following namespaces were renamed: + - Namespace: `Composer\Package\Version` -> `Composer\Semver` + - Namespace: `Composer\Package\LinkConstraint` -> `Composer\Semver\Constraint` + - Namespace: `Composer\Test\Package\Version` -> `Composer\Test\Semver` + - Namespace: `Composer\Test\Package\LinkConstraint` -> `Composer\Test\Semver\Constraint` + * Changed: code style using php-cs-fixer. + +[3.4.3]: https://github.com/composer/semver/compare/3.4.2...3.4.3 +[3.4.2]: https://github.com/composer/semver/compare/3.4.1...3.4.2 +[3.4.1]: https://github.com/composer/semver/compare/3.4.0...3.4.1 +[3.4.0]: https://github.com/composer/semver/compare/3.3.2...3.4.0 +[3.3.2]: https://github.com/composer/semver/compare/3.3.1...3.3.2 +[3.3.1]: https://github.com/composer/semver/compare/3.3.0...3.3.1 +[3.3.0]: https://github.com/composer/semver/compare/3.2.9...3.3.0 +[3.2.9]: https://github.com/composer/semver/compare/3.2.8...3.2.9 +[3.2.8]: https://github.com/composer/semver/compare/3.2.7...3.2.8 +[3.2.7]: https://github.com/composer/semver/compare/3.2.6...3.2.7 +[3.2.6]: https://github.com/composer/semver/compare/3.2.5...3.2.6 +[3.2.5]: https://github.com/composer/semver/compare/3.2.4...3.2.5 +[3.2.4]: https://github.com/composer/semver/compare/3.2.3...3.2.4 +[3.2.3]: https://github.com/composer/semver/compare/3.2.2...3.2.3 +[3.2.2]: https://github.com/composer/semver/compare/3.2.1...3.2.2 +[3.2.1]: https://github.com/composer/semver/compare/3.2.0...3.2.1 +[3.2.0]: https://github.com/composer/semver/compare/3.1.0...3.2.0 +[3.1.0]: https://github.com/composer/semver/compare/3.0.1...3.1.0 +[3.0.1]: https://github.com/composer/semver/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/composer/semver/compare/2.0.0...3.0.0 +[2.0.0]: https://github.com/composer/semver/compare/1.5.1...2.0.0 +[1.7.2]: https://github.com/composer/semver/compare/1.7.1...1.7.2 +[1.7.1]: https://github.com/composer/semver/compare/1.7.0...1.7.1 +[1.7.0]: https://github.com/composer/semver/compare/1.6.0...1.7.0 +[1.6.0]: https://github.com/composer/semver/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/composer/semver/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/composer/semver/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/composer/semver/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/composer/semver/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/composer/semver/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/composer/semver/compare/1.3.0...1.4.0 +[1.3.0]: https://github.com/composer/semver/compare/1.2.0...1.3.0 +[1.2.0]: https://github.com/composer/semver/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/composer/semver/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/composer/semver/compare/0.1.0...1.0.0 +[0.1.0]: https://github.com/composer/semver/compare/5e0b9a4da...0.1.0 diff --git a/vendor/composer/semver/LICENSE b/vendor/composer/semver/LICENSE new file mode 100644 index 000000000..466975862 --- /dev/null +++ b/vendor/composer/semver/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2015 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/semver/README.md b/vendor/composer/semver/README.md new file mode 100644 index 000000000..767784906 --- /dev/null +++ b/vendor/composer/semver/README.md @@ -0,0 +1,99 @@ +composer/semver +=============== + +Semver (Semantic Versioning) library that offers utilities, version constraint parsing and validation. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + +[![Continuous Integration](https://github.com/composer/semver/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/continuous-integration.yml) +[![PHP Lint](https://github.com/composer/semver/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/lint.yml) +[![PHPStan](https://github.com/composer/semver/actions/workflows/phpstan.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/phpstan.yml) + +Installation +------------ + +Install the latest version with: + +```bash +composer require composer/semver +``` + + +Requirements +------------ + +* PHP 5.3.2 is required but using the latest version of PHP is highly recommended. + + +Version Comparison +------------------ + +For details on how versions are compared, refer to the [Versions](https://getcomposer.org/doc/articles/versions.md) +article in the documentation section of the [getcomposer.org](https://getcomposer.org) website. + + +Basic usage +----------- + +### Comparator + +The [`Composer\Semver\Comparator`](https://github.com/composer/semver/blob/main/src/Comparator.php) class provides the following methods for comparing versions: + +* greaterThan($v1, $v2) +* greaterThanOrEqualTo($v1, $v2) +* lessThan($v1, $v2) +* lessThanOrEqualTo($v1, $v2) +* equalTo($v1, $v2) +* notEqualTo($v1, $v2) + +Each function takes two version strings as arguments and returns a boolean. For example: + +```php +use Composer\Semver\Comparator; + +Comparator::greaterThan('1.25.0', '1.24.0'); // 1.25.0 > 1.24.0 +``` + +### Semver + +The [`Composer\Semver\Semver`](https://github.com/composer/semver/blob/main/src/Semver.php) class provides the following methods: + +* satisfies($version, $constraints) +* satisfiedBy(array $versions, $constraint) +* sort($versions) +* rsort($versions) + +### Intervals + +The [`Composer\Semver\Intervals`](https://github.com/composer/semver/blob/main/src/Intervals.php) static class provides +a few utilities to work with complex constraints or read version intervals from a constraint: + +```php +use Composer\Semver\Intervals; + +// Checks whether $candidate is a subset of $constraint +Intervals::isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint); + +// Checks whether $a and $b have any intersection, equivalent to $a->matches($b) +Intervals::haveIntersections(ConstraintInterface $a, ConstraintInterface $b); + +// Optimizes a complex multi constraint by merging all intervals down to the smallest +// possible multi constraint. The drawbacks are this is not very fast, and the resulting +// multi constraint will have no human readable prettyConstraint configured on it +Intervals::compactConstraint(ConstraintInterface $constraint); + +// Creates an array of numeric intervals and branch constraints representing a given constraint +Intervals::get(ConstraintInterface $constraint); + +// Clears the memoization cache when you are done processing constraints +Intervals::clear() +``` + +See the class docblocks for more details. + + +License +------- + +composer/semver is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/semver/composer.json b/vendor/composer/semver/composer.json new file mode 100644 index 000000000..1fad9e548 --- /dev/null +++ b/vendor/composer/semver/composer.json @@ -0,0 +1,59 @@ +{ + "name": "composer/semver", + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "type": "library", + "license": "MIT", + "keywords": [ + "semver", + "semantic", + "versioning", + "validation" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^7", + "phpstan/phpstan": "^1.11" + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Semver\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "scripts": { + "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" + } +} diff --git a/vendor/composer/semver/src/Comparator.php b/vendor/composer/semver/src/Comparator.php new file mode 100644 index 000000000..38f483aa6 --- /dev/null +++ b/vendor/composer/semver/src/Comparator.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; + +class Comparator +{ + /** + * Evaluates the expression: $version1 > $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function greaterThan($version1, $version2) + { + return self::compare($version1, '>', $version2); + } + + /** + * Evaluates the expression: $version1 >= $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function greaterThanOrEqualTo($version1, $version2) + { + return self::compare($version1, '>=', $version2); + } + + /** + * Evaluates the expression: $version1 < $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function lessThan($version1, $version2) + { + return self::compare($version1, '<', $version2); + } + + /** + * Evaluates the expression: $version1 <= $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function lessThanOrEqualTo($version1, $version2) + { + return self::compare($version1, '<=', $version2); + } + + /** + * Evaluates the expression: $version1 == $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function equalTo($version1, $version2) + { + return self::compare($version1, '==', $version2); + } + + /** + * Evaluates the expression: $version1 != $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function notEqualTo($version1, $version2) + { + return self::compare($version1, '!=', $version2); + } + + /** + * Evaluates the expression: $version1 $operator $version2. + * + * @param string $version1 + * @param string $operator + * @param string $version2 + * + * @return bool + * + * @phpstan-param Constraint::STR_OP_* $operator + */ + public static function compare($version1, $operator, $version2) + { + $constraint = new Constraint($operator, $version2); + + return $constraint->matchSpecific(new Constraint('==', $version1), true); + } +} diff --git a/vendor/composer/semver/src/CompilingMatcher.php b/vendor/composer/semver/src/CompilingMatcher.php new file mode 100644 index 000000000..aea1d3b95 --- /dev/null +++ b/vendor/composer/semver/src/CompilingMatcher.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Helper class to evaluate constraint by compiling and reusing the code to evaluate + */ +class CompilingMatcher +{ + /** + * @var array + * @phpstan-var array + */ + private static $compiledCheckerCache = array(); + /** + * @var array + * @phpstan-var array + */ + private static $resultCache = array(); + + /** @var bool */ + private static $enabled; + + /** + * @phpstan-var array + */ + private static $transOpInt = array( + Constraint::OP_EQ => Constraint::STR_OP_EQ, + Constraint::OP_LT => Constraint::STR_OP_LT, + Constraint::OP_LE => Constraint::STR_OP_LE, + Constraint::OP_GT => Constraint::STR_OP_GT, + Constraint::OP_GE => Constraint::STR_OP_GE, + Constraint::OP_NE => Constraint::STR_OP_NE, + ); + + /** + * Clears the memoization cache once you are done + * + * @return void + */ + public static function clear() + { + self::$resultCache = array(); + self::$compiledCheckerCache = array(); + } + + /** + * Evaluates the expression: $constraint match $operator $version + * + * @param ConstraintInterface $constraint + * @param int $operator + * @phpstan-param Constraint::OP_* $operator + * @param string $version + * + * @return bool + */ + public static function match(ConstraintInterface $constraint, $operator, $version) + { + $resultCacheKey = $operator.$constraint.';'.$version; + + if (isset(self::$resultCache[$resultCacheKey])) { + return self::$resultCache[$resultCacheKey]; + } + + if (self::$enabled === null) { + self::$enabled = !\in_array('eval', explode(',', (string) ini_get('disable_functions')), true); + } + if (!self::$enabled) { + return self::$resultCache[$resultCacheKey] = $constraint->matches(new Constraint(self::$transOpInt[$operator], $version)); + } + + $cacheKey = $operator.$constraint; + if (!isset(self::$compiledCheckerCache[$cacheKey])) { + $code = $constraint->compile($operator); + self::$compiledCheckerCache[$cacheKey] = $function = eval('return function($v, $b){return '.$code.';};'); + } else { + $function = self::$compiledCheckerCache[$cacheKey]; + } + + return self::$resultCache[$resultCacheKey] = $function($version, strpos($version, 'dev-') === 0); + } +} diff --git a/vendor/composer/semver/src/Constraint/Bound.php b/vendor/composer/semver/src/Constraint/Bound.php new file mode 100644 index 000000000..7effb11a8 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/Bound.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +class Bound +{ + /** + * @var string + */ + private $version; + + /** + * @var bool + */ + private $isInclusive; + + /** + * @param string $version + * @param bool $isInclusive + */ + public function __construct($version, $isInclusive) + { + $this->version = $version; + $this->isInclusive = $isInclusive; + } + + /** + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * @return bool + */ + public function isInclusive() + { + return $this->isInclusive; + } + + /** + * @return bool + */ + public function isZero() + { + return $this->getVersion() === '0.0.0.0-dev' && $this->isInclusive(); + } + + /** + * @return bool + */ + public function isPositiveInfinity() + { + return $this->getVersion() === PHP_INT_MAX.'.0.0.0' && !$this->isInclusive(); + } + + /** + * Compares a bound to another with a given operator. + * + * @param Bound $other + * @param string $operator + * + * @return bool + */ + public function compareTo(Bound $other, $operator) + { + if (!\in_array($operator, array('<', '>'), true)) { + throw new \InvalidArgumentException('Does not support any other operator other than > or <.'); + } + + // If they are the same it doesn't matter + if ($this == $other) { + return false; + } + + $compareResult = version_compare($this->getVersion(), $other->getVersion()); + + // Not the same version means we don't need to check if the bounds are inclusive or not + if (0 !== $compareResult) { + return (('>' === $operator) ? 1 : -1) === $compareResult; + } + + // Question we're answering here is "am I higher than $other?" + return '>' === $operator ? $other->isInclusive() : !$other->isInclusive(); + } + + public function __toString() + { + return sprintf( + '%s [%s]', + $this->getVersion(), + $this->isInclusive() ? 'inclusive' : 'exclusive' + ); + } + + /** + * @return self + */ + public static function zero() + { + return new Bound('0.0.0.0-dev', true); + } + + /** + * @return self + */ + public static function positiveInfinity() + { + return new Bound(PHP_INT_MAX.'.0.0.0', false); + } +} diff --git a/vendor/composer/semver/src/Constraint/Constraint.php b/vendor/composer/semver/src/Constraint/Constraint.php new file mode 100644 index 000000000..dc394829e --- /dev/null +++ b/vendor/composer/semver/src/Constraint/Constraint.php @@ -0,0 +1,435 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Defines a constraint. + */ +class Constraint implements ConstraintInterface +{ + /* operator integer values */ + const OP_EQ = 0; + const OP_LT = 1; + const OP_LE = 2; + const OP_GT = 3; + const OP_GE = 4; + const OP_NE = 5; + + /* operator string values */ + const STR_OP_EQ = '=='; + const STR_OP_EQ_ALT = '='; + const STR_OP_LT = '<'; + const STR_OP_LE = '<='; + const STR_OP_GT = '>'; + const STR_OP_GE = '>='; + const STR_OP_NE = '!='; + const STR_OP_NE_ALT = '<>'; + + /** + * Operator to integer translation table. + * + * @var array + * @phpstan-var array + */ + private static $transOpStr = array( + '=' => self::OP_EQ, + '==' => self::OP_EQ, + '<' => self::OP_LT, + '<=' => self::OP_LE, + '>' => self::OP_GT, + '>=' => self::OP_GE, + '<>' => self::OP_NE, + '!=' => self::OP_NE, + ); + + /** + * Integer to operator translation table. + * + * @var array + * @phpstan-var array + */ + private static $transOpInt = array( + self::OP_EQ => '==', + self::OP_LT => '<', + self::OP_LE => '<=', + self::OP_GT => '>', + self::OP_GE => '>=', + self::OP_NE => '!=', + ); + + /** + * @var int + * @phpstan-var self::OP_* + */ + protected $operator; + + /** @var string */ + protected $version; + + /** @var string|null */ + protected $prettyString; + + /** @var Bound */ + protected $lowerBound; + + /** @var Bound */ + protected $upperBound; + + /** + * Sets operator and version to compare with. + * + * @param string $operator + * @param string $version + * + * @throws \InvalidArgumentException if invalid operator is given. + * + * @phpstan-param self::STR_OP_* $operator + */ + public function __construct($operator, $version) + { + if (!isset(self::$transOpStr[$operator])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid operator "%s" given, expected one of: %s', + $operator, + implode(', ', self::getSupportedOperators()) + )); + } + + $this->operator = self::$transOpStr[$operator]; + $this->version = $version; + } + + /** + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * @return string + * + * @phpstan-return self::STR_OP_* + */ + public function getOperator() + { + return self::$transOpInt[$this->operator]; + } + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + if ($provider instanceof self) { + return $this->matchSpecific($provider); + } + + // turn matching around to find a match + return $provider->matches($this); + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return $this->__toString(); + } + + /** + * Get all supported comparison operators. + * + * @return array + * + * @phpstan-return list + */ + public static function getSupportedOperators() + { + return array_keys(self::$transOpStr); + } + + /** + * @param string $operator + * @return int + * + * @phpstan-param self::STR_OP_* $operator + * @phpstan-return self::OP_* + */ + public static function getOperatorConstant($operator) + { + return self::$transOpStr[$operator]; + } + + /** + * @param string $a + * @param string $b + * @param string $operator + * @param bool $compareBranches + * + * @throws \InvalidArgumentException if invalid operator is given. + * + * @return bool + * + * @phpstan-param self::STR_OP_* $operator + */ + public function versionCompare($a, $b, $operator, $compareBranches = false) + { + if (!isset(self::$transOpStr[$operator])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid operator "%s" given, expected one of: %s', + $operator, + implode(', ', self::getSupportedOperators()) + )); + } + + $aIsBranch = strpos($a, 'dev-') === 0; + $bIsBranch = strpos($b, 'dev-') === 0; + + if ($operator === '!=' && ($aIsBranch || $bIsBranch)) { + return $a !== $b; + } + + if ($aIsBranch && $bIsBranch) { + return $operator === '==' && $a === $b; + } + + // when branches are not comparable, we make sure dev branches never match anything + if (!$compareBranches && ($aIsBranch || $bIsBranch)) { + return false; + } + + return \version_compare($a, $b, $operator); + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + if (strpos($this->version, 'dev-') === 0) { + if (self::OP_EQ === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('$b && $v === %s', \var_export($this->version, true)); + } + if (self::OP_NE === $otherOperator) { + return sprintf('!$b || $v !== %s', \var_export($this->version, true)); + } + return 'false'; + } + + if (self::OP_NE === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('!$b || $v !== %s', \var_export($this->version, true)); + } + if (self::OP_NE === $otherOperator) { + return 'true'; + } + return '!$b'; + } + + return 'false'; + } + + if (self::OP_EQ === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('\version_compare($v, %s, \'==\')', \var_export($this->version, true)); + } + if (self::OP_NE === $otherOperator) { + return sprintf('$b || \version_compare($v, %s, \'!=\')', \var_export($this->version, true)); + } + + return sprintf('!$b && \version_compare(%s, $v, \'%s\')', \var_export($this->version, true), self::$transOpInt[$otherOperator]); + } + + if (self::OP_NE === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('$b || (!$b && \version_compare($v, %s, \'!=\'))', \var_export($this->version, true)); + } + + if (self::OP_NE === $otherOperator) { + return 'true'; + } + return '!$b'; + } + + if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) { + if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) { + return '!$b'; + } + } else { // $this->operator must be self::OP_GT || self::OP_GE here + if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) { + return '!$b'; + } + } + + if (self::OP_NE === $otherOperator) { + return 'true'; + } + + $codeComparison = sprintf('\version_compare($v, %s, \'%s\')', \var_export($this->version, true), self::$transOpInt[$this->operator]); + if ($this->operator === self::OP_LE) { + if ($otherOperator === self::OP_GT) { + return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison; + } + } elseif ($this->operator === self::OP_GE) { + if ($otherOperator === self::OP_LT) { + return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison; + } + } + + return sprintf('!$b && %s', $codeComparison); + } + + /** + * @param Constraint $provider + * @param bool $compareBranches + * + * @return bool + */ + public function matchSpecific(Constraint $provider, $compareBranches = false) + { + $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]); + $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]); + + $isEqualOp = self::OP_EQ === $this->operator; + $isNonEqualOp = self::OP_NE === $this->operator; + $isProviderEqualOp = self::OP_EQ === $provider->operator; + $isProviderNonEqualOp = self::OP_NE === $provider->operator; + + // '!=' operator is match when other operator is not '==' operator or version is not match + // these kinds of comparisons always have a solution + if ($isNonEqualOp || $isProviderNonEqualOp) { + if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && strpos($provider->version, 'dev-') === 0) { + return false; + } + + if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && strpos($this->version, 'dev-') === 0) { + return false; + } + + if (!$isEqualOp && !$isProviderEqualOp) { + return true; + } + return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); + } + + // an example for the condition is <= 2.0 & < 1.0 + // these kinds of comparisons always have a solution + if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { + return !(strpos($this->version, 'dev-') === 0 || strpos($provider->version, 'dev-') === 0); + } + + $version1 = $isEqualOp ? $this->version : $provider->version; + $version2 = $isEqualOp ? $provider->version : $this->version; + $operator = $isEqualOp ? $provider->operator : $this->operator; + + if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) { + // special case, e.g. require >= 1.0 and provide < 1.0 + // 1.0 >= 1.0 but 1.0 is outside of the provided interval + + return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp + && self::$transOpInt[$this->operator] !== $noEqualOp + && \version_compare($provider->version, $this->version, '==')); + } + + return false; + } + + /** + * @return string + */ + public function __toString() + { + return self::$transOpInt[$this->operator] . ' ' . $this->version; + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + $this->extractBounds(); + + return $this->lowerBound; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + $this->extractBounds(); + + return $this->upperBound; + } + + /** + * @return void + */ + private function extractBounds() + { + if (null !== $this->lowerBound) { + return; + } + + // Branches + if (strpos($this->version, 'dev-') === 0) { + $this->lowerBound = Bound::zero(); + $this->upperBound = Bound::positiveInfinity(); + + return; + } + + switch ($this->operator) { + case self::OP_EQ: + $this->lowerBound = new Bound($this->version, true); + $this->upperBound = new Bound($this->version, true); + break; + case self::OP_LT: + $this->lowerBound = Bound::zero(); + $this->upperBound = new Bound($this->version, false); + break; + case self::OP_LE: + $this->lowerBound = Bound::zero(); + $this->upperBound = new Bound($this->version, true); + break; + case self::OP_GT: + $this->lowerBound = new Bound($this->version, false); + $this->upperBound = Bound::positiveInfinity(); + break; + case self::OP_GE: + $this->lowerBound = new Bound($this->version, true); + $this->upperBound = Bound::positiveInfinity(); + break; + case self::OP_NE: + $this->lowerBound = Bound::zero(); + $this->upperBound = Bound::positiveInfinity(); + break; + } + } +} diff --git a/vendor/composer/semver/src/Constraint/ConstraintInterface.php b/vendor/composer/semver/src/Constraint/ConstraintInterface.php new file mode 100644 index 000000000..389b935b5 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/ConstraintInterface.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * DO NOT IMPLEMENT this interface. It is only meant for usage as a type hint + * in libraries relying on composer/semver but creating your own constraint class + * that implements this interface is not a supported use case and will cause the + * composer/semver components to return unexpected results. + */ +interface ConstraintInterface +{ + /** + * Checks whether the given constraint intersects in any way with this constraint + * + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider); + + /** + * Provides a compiled version of the constraint for the given operator + * The compiled version must be a PHP expression. + * Executor of compile version must provide 2 variables: + * - $v = the string version to compare with + * - $b = whether or not the version is a non-comparable branch (starts with "dev-") + * + * @see Constraint::OP_* for the list of available operators. + * @example return '!$b && version_compare($v, '1.0', '>')'; + * + * @param int $otherOperator one Constraint::OP_* + * + * @return string + * + * @phpstan-param Constraint::OP_* $otherOperator + */ + public function compile($otherOperator); + + /** + * @return Bound + */ + public function getUpperBound(); + + /** + * @return Bound + */ + public function getLowerBound(); + + /** + * @return string + */ + public function getPrettyString(); + + /** + * @param string|null $prettyString + * + * @return void + */ + public function setPrettyString($prettyString); + + /** + * @return string + */ + public function __toString(); +} diff --git a/vendor/composer/semver/src/Constraint/MatchAllConstraint.php b/vendor/composer/semver/src/Constraint/MatchAllConstraint.php new file mode 100644 index 000000000..5e51af950 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/MatchAllConstraint.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Defines the absence of a constraint. + * + * This constraint matches everything. + */ +class MatchAllConstraint implements ConstraintInterface +{ + /** @var string|null */ + protected $prettyString; + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + return 'true'; + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + return '*'; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + return Bound::positiveInfinity(); + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + return Bound::zero(); + } +} diff --git a/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php b/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php new file mode 100644 index 000000000..dadcf6228 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Blackhole of constraints, nothing escapes it + */ +class MatchNoneConstraint implements ConstraintInterface +{ + /** @var string|null */ + protected $prettyString; + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + return false; + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + return 'false'; + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + return '[]'; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + return new Bound('0.0.0.0-dev', false); + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + return new Bound('0.0.0.0-dev', false); + } +} diff --git a/vendor/composer/semver/src/Constraint/MultiConstraint.php b/vendor/composer/semver/src/Constraint/MultiConstraint.php new file mode 100644 index 000000000..1f4c00616 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/MultiConstraint.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Defines a conjunctive or disjunctive set of constraints. + */ +class MultiConstraint implements ConstraintInterface +{ + /** + * @var ConstraintInterface[] + * @phpstan-var non-empty-array + */ + protected $constraints; + + /** @var string|null */ + protected $prettyString; + + /** @var string|null */ + protected $string; + + /** @var bool */ + protected $conjunctive; + + /** @var Bound|null */ + protected $lowerBound; + + /** @var Bound|null */ + protected $upperBound; + + /** + * @param ConstraintInterface[] $constraints A set of constraints + * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive + * + * @throws \InvalidArgumentException If less than 2 constraints are passed + */ + public function __construct(array $constraints, $conjunctive = true) + { + if (\count($constraints) < 2) { + throw new \InvalidArgumentException( + 'Must provide at least two constraints for a MultiConstraint. Use '. + 'the regular Constraint class for one constraint only or MatchAllConstraint for none. You may use '. + 'MultiConstraint::create() which optimizes and handles those cases automatically.' + ); + } + + $this->constraints = $constraints; + $this->conjunctive = $conjunctive; + } + + /** + * @return ConstraintInterface[] + */ + public function getConstraints() + { + return $this->constraints; + } + + /** + * @return bool + */ + public function isConjunctive() + { + return $this->conjunctive; + } + + /** + * @return bool + */ + public function isDisjunctive() + { + return !$this->conjunctive; + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + $parts = array(); + foreach ($this->constraints as $constraint) { + $code = $constraint->compile($otherOperator); + if ($code === 'true') { + if (!$this->conjunctive) { + return 'true'; + } + } elseif ($code === 'false') { + if ($this->conjunctive) { + return 'false'; + } + } else { + $parts[] = '('.$code.')'; + } + } + + if (!$parts) { + return $this->conjunctive ? 'true' : 'false'; + } + + return $this->conjunctive ? implode('&&', $parts) : implode('||', $parts); + } + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + if (false === $this->conjunctive) { + foreach ($this->constraints as $constraint) { + if ($provider->matches($constraint)) { + return true; + } + } + + return false; + } + + // when matching a conjunctive and a disjunctive multi constraint we have to iterate over the disjunctive one + // otherwise we'd return true if different parts of the disjunctive constraint match the conjunctive one + // which would lead to incorrect results, e.g. [>1 and <2] would match [<1 or >2] although they do not intersect + if ($provider instanceof MultiConstraint && $provider->isDisjunctive()) { + return $provider->matches($this); + } + + foreach ($this->constraints as $constraint) { + if (!$provider->matches($constraint)) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + if ($this->string !== null) { + return $this->string; + } + + $constraints = array(); + foreach ($this->constraints as $constraint) { + $constraints[] = (string) $constraint; + } + + return $this->string = '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + $this->extractBounds(); + + if (null === $this->lowerBound) { + throw new \LogicException('extractBounds should have populated the lowerBound property'); + } + + return $this->lowerBound; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + $this->extractBounds(); + + if (null === $this->upperBound) { + throw new \LogicException('extractBounds should have populated the upperBound property'); + } + + return $this->upperBound; + } + + /** + * Tries to optimize the constraints as much as possible, meaning + * reducing/collapsing congruent constraints etc. + * Does not necessarily return a MultiConstraint instance if + * things can be reduced to a simple constraint + * + * @param ConstraintInterface[] $constraints A set of constraints + * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive + * + * @return ConstraintInterface + */ + public static function create(array $constraints, $conjunctive = true) + { + if (0 === \count($constraints)) { + return new MatchAllConstraint(); + } + + if (1 === \count($constraints)) { + return $constraints[0]; + } + + $optimized = self::optimizeConstraints($constraints, $conjunctive); + if ($optimized !== null) { + list($constraints, $conjunctive) = $optimized; + if (\count($constraints) === 1) { + return $constraints[0]; + } + } + + return new self($constraints, $conjunctive); + } + + /** + * @param ConstraintInterface[] $constraints + * @param bool $conjunctive + * @return ?array + * + * @phpstan-return array{0: list, 1: bool}|null + */ + private static function optimizeConstraints(array $constraints, $conjunctive) + { + // parse the two OR groups and if they are contiguous we collapse + // them into one constraint + // [>= 1 < 2] || [>= 2 < 3] || [>= 3 < 4] => [>= 1 < 4] + if (!$conjunctive) { + $left = $constraints[0]; + $mergedConstraints = array(); + $optimized = false; + for ($i = 1, $l = \count($constraints); $i < $l; $i++) { + $right = $constraints[$i]; + if ( + $left instanceof self + && $left->conjunctive + && $right instanceof self + && $right->conjunctive + && \count($left->constraints) === 2 + && \count($right->constraints) === 2 + && ($left0 = (string) $left->constraints[0]) + && $left0[0] === '>' && $left0[1] === '=' + && ($left1 = (string) $left->constraints[1]) + && $left1[0] === '<' + && ($right0 = (string) $right->constraints[0]) + && $right0[0] === '>' && $right0[1] === '=' + && ($right1 = (string) $right->constraints[1]) + && $right1[0] === '<' + && substr($left1, 2) === substr($right0, 3) + ) { + $optimized = true; + $left = new MultiConstraint( + array( + $left->constraints[0], + $right->constraints[1], + ), + true); + } else { + $mergedConstraints[] = $left; + $left = $right; + } + } + if ($optimized) { + $mergedConstraints[] = $left; + return array($mergedConstraints, false); + } + } + + // TODO: Here's the place to put more optimizations + + return null; + } + + /** + * @return void + */ + private function extractBounds() + { + if (null !== $this->lowerBound) { + return; + } + + foreach ($this->constraints as $constraint) { + if (null === $this->lowerBound || null === $this->upperBound) { + $this->lowerBound = $constraint->getLowerBound(); + $this->upperBound = $constraint->getUpperBound(); + continue; + } + + if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) { + $this->lowerBound = $constraint->getLowerBound(); + } + + if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) { + $this->upperBound = $constraint->getUpperBound(); + } + } + } +} diff --git a/vendor/composer/semver/src/Interval.php b/vendor/composer/semver/src/Interval.php new file mode 100644 index 000000000..43d5a4f5c --- /dev/null +++ b/vendor/composer/semver/src/Interval.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; + +class Interval +{ + /** @var Constraint */ + private $start; + /** @var Constraint */ + private $end; + + public function __construct(Constraint $start, Constraint $end) + { + $this->start = $start; + $this->end = $end; + } + + /** + * @return Constraint + */ + public function getStart() + { + return $this->start; + } + + /** + * @return Constraint + */ + public function getEnd() + { + return $this->end; + } + + /** + * @return Constraint + */ + public static function fromZero() + { + static $zero; + + if (null === $zero) { + $zero = new Constraint('>=', '0.0.0.0-dev'); + } + + return $zero; + } + + /** + * @return Constraint + */ + public static function untilPositiveInfinity() + { + static $positiveInfinity; + + if (null === $positiveInfinity) { + $positiveInfinity = new Constraint('<', PHP_INT_MAX.'.0.0.0'); + } + + return $positiveInfinity; + } + + /** + * @return self + */ + public static function any() + { + return new self(self::fromZero(), self::untilPositiveInfinity()); + } + + /** + * @return array{'names': string[], 'exclude': bool} + */ + public static function anyDev() + { + // any == exclude nothing + return array('names' => array(), 'exclude' => true); + } + + /** + * @return array{'names': string[], 'exclude': bool} + */ + public static function noDev() + { + // nothing == no names included + return array('names' => array(), 'exclude' => false); + } +} diff --git a/vendor/composer/semver/src/Intervals.php b/vendor/composer/semver/src/Intervals.php new file mode 100644 index 000000000..d889d0ada --- /dev/null +++ b/vendor/composer/semver/src/Intervals.php @@ -0,0 +1,478 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MatchNoneConstraint; +use Composer\Semver\Constraint\MultiConstraint; + +/** + * Helper class generating intervals from constraints + * + * This contains utilities for: + * + * - compacting an existing constraint which can be used to combine several into one + * by creating a MultiConstraint out of the many constraints you have. + * + * - checking whether one subset is a subset of another. + * + * Note: You should call clear to free memoization memory usage when you are done using this class + */ +class Intervals +{ + /** + * @phpstan-var array + */ + private static $intervalsCache = array(); + + /** + * @phpstan-var array + */ + private static $opSortOrder = array( + '>=' => -3, + '<' => -2, + '>' => 2, + '<=' => 3, + ); + + /** + * Clears the memoization cache once you are done + * + * @return void + */ + public static function clear() + { + self::$intervalsCache = array(); + } + + /** + * Checks whether $candidate is a subset of $constraint + * + * @return bool + */ + public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint) + { + if ($constraint instanceof MatchAllConstraint) { + return true; + } + + if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) { + return false; + } + + $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), true)); + $candidateIntervals = self::get($candidate); + if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) { + return false; + } + + foreach ($intersectionIntervals['numeric'] as $index => $interval) { + if (!isset($candidateIntervals['numeric'][$index])) { + return false; + } + + if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) { + return false; + } + + if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) { + return false; + } + } + + if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) { + return false; + } + if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) { + return false; + } + foreach ($intersectionIntervals['branches']['names'] as $index => $name) { + if ($name !== $candidateIntervals['branches']['names'][$index]) { + return false; + } + } + + return true; + } + + /** + * Checks whether $a and $b have any intersection, equivalent to $a->matches($b) + * + * @return bool + */ + public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b) + { + if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) { + return true; + } + + if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) { + return false; + } + + $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), true), true); + + return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0; + } + + /** + * Attempts to optimize a MultiConstraint + * + * When merging MultiConstraints together they can get very large, this will + * compact it by looking at the real intervals covered by all the constraints + * and then creates a new constraint containing only the smallest amount of rules + * to match the same intervals. + * + * @return ConstraintInterface + */ + public static function compactConstraint(ConstraintInterface $constraint) + { + if (!$constraint instanceof MultiConstraint) { + return $constraint; + } + + $intervals = self::generateIntervals($constraint); + $constraints = array(); + $hasNumericMatchAll = false; + + if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) Interval::untilPositiveInfinity()) { + $constraints[] = $intervals['numeric'][0]->getStart(); + $hasNumericMatchAll = true; + } else { + $unEqualConstraints = array(); + for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) { + $interval = $intervals['numeric'][$i]; + + // if current interval ends with < N and next interval begins with > N we can swap this out for != N + // but this needs to happen as a conjunctive expression together with the start of the current interval + // and end of next interval, so [>=M, N, [>=M, !=N, getEnd()->getOperator() === '<' && $i+1 < $count) { + $nextInterval = $intervals['numeric'][$i+1]; + if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') { + // only add a start if we didn't already do so, can be skipped if we're looking at second + // interval in [>=M, N, P, =M, !=N] already and we only want to add !=P right now + if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) { + $unEqualConstraints[] = $interval->getStart(); + } + $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion()); + continue; + } + } + + if (\count($unEqualConstraints) > 0) { + // this is where the end of the following interval of a != constraint is added as explained above + if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) { + $unEqualConstraints[] = $interval->getEnd(); + } + + // count is 1 if entire constraint is just one != expression + if (\count($unEqualConstraints) > 1) { + $constraints[] = new MultiConstraint($unEqualConstraints, true); + } else { + $constraints[] = $unEqualConstraints[0]; + } + + $unEqualConstraints = array(); + continue; + } + + // convert back >= x - <= x intervals to == x + if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') { + $constraints[] = new Constraint('==', $interval->getStart()->getVersion()); + continue; + } + + if ((string) $interval->getStart() === (string) Interval::fromZero()) { + $constraints[] = $interval->getEnd(); + } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) { + $constraints[] = $interval->getStart(); + } else { + $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), true); + } + } + } + + $devConstraints = array(); + + if (0 === \count($intervals['branches']['names'])) { + if ($intervals['branches']['exclude']) { + if ($hasNumericMatchAll) { + return new MatchAllConstraint; + } + // otherwise constraint should contain a != operator and already cover this + } + } else { + foreach ($intervals['branches']['names'] as $branchName) { + if ($intervals['branches']['exclude']) { + $devConstraints[] = new Constraint('!=', $branchName); + } else { + $devConstraints[] = new Constraint('==', $branchName); + } + } + + // excluded branches, e.g. != dev-foo are conjunctive with the interval, so + // > 2.0 != dev-foo must return a conjunctive constraint + if ($intervals['branches']['exclude']) { + if (\count($constraints) > 1) { + return new MultiConstraint(array_merge( + array(new MultiConstraint($constraints, false)), + $devConstraints + ), true); + } + + if (\count($constraints) === 1 && (string)$constraints[0] === (string)Interval::fromZero()) { + if (\count($devConstraints) > 1) { + return new MultiConstraint($devConstraints, true); + } + return $devConstraints[0]; + } + + return new MultiConstraint(array_merge($constraints, $devConstraints), true); + } + + // otherwise devConstraints contains a list of == operators for branches which are disjunctive with the + // rest of the constraint + $constraints = array_merge($constraints, $devConstraints); + } + + if (\count($constraints) > 1) { + return new MultiConstraint($constraints, false); + } + + if (\count($constraints) === 1) { + return $constraints[0]; + } + + return new MatchNoneConstraint; + } + + /** + * Creates an array of numeric intervals and branch constraints representing a given constraint + * + * if the returned numeric array is empty it means the constraint matches nothing in the numeric range (0 - +inf) + * if the returned branches array is empty it means no dev-* versions are matched + * if a constraint matches all possible dev-* versions, branches will contain Interval::anyDev() + * + * @return array + * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} + */ + public static function get(ConstraintInterface $constraint) + { + $key = (string) $constraint; + + if (!isset(self::$intervalsCache[$key])) { + self::$intervalsCache[$key] = self::generateIntervals($constraint); + } + + return self::$intervalsCache[$key]; + } + + /** + * @param bool $stopOnFirstValidInterval + * + * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} + */ + private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = false) + { + if ($constraint instanceof MatchAllConstraint) { + return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev()); + } + + if ($constraint instanceof MatchNoneConstraint) { + return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => false)); + } + + if ($constraint instanceof Constraint) { + return self::generateSingleConstraintIntervals($constraint); + } + + if (!$constraint instanceof MultiConstraint) { + throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got '.\get_class($constraint).'.'); + } + + $constraints = $constraint->getConstraints(); + + $numericGroups = array(); + $constraintBranches = array(); + foreach ($constraints as $c) { + $res = self::get($c); + $numericGroups[] = $res['numeric']; + $constraintBranches[] = $res['branches']; + } + + if ($constraint->isDisjunctive()) { + $branches = Interval::noDev(); + foreach ($constraintBranches as $b) { + if ($b['exclude']) { + if ($branches['exclude']) { + // disjunctive constraint, so only exclude what's excluded in all constraints + // !=a,!=b || !=b,!=c => !=b + $branches['names'] = array_intersect($branches['names'], $b['names']); + } else { + // disjunctive constraint so exclude all names which are not explicitly included in the alternative + // (==b || ==c) || !=a,!=b => !=a + $branches['exclude'] = true; + $branches['names'] = array_diff($b['names'], $branches['names']); + } + } else { + if ($branches['exclude']) { + // disjunctive constraint so exclude all names which are not explicitly included in the alternative + // !=a,!=b || (==b || ==c) => !=a + $branches['names'] = array_diff($branches['names'], $b['names']); + } else { + // disjunctive constraint, so just add all the other branches + // (==a || ==b) || ==c => ==a || ==b || ==c + $branches['names'] = array_merge($branches['names'], $b['names']); + } + } + } + } else { + $branches = Interval::anyDev(); + foreach ($constraintBranches as $b) { + if ($b['exclude']) { + if ($branches['exclude']) { + // conjunctive, so just add all branch names to be excluded + // !=a && !=b => !=a,!=b + $branches['names'] = array_merge($branches['names'], $b['names']); + } else { + // conjunctive, so only keep included names which are not excluded + // (==a||==c) && !=a,!=b => ==c + $branches['names'] = array_diff($branches['names'], $b['names']); + } + } else { + if ($branches['exclude']) { + // conjunctive, so only keep included names which are not excluded + // !=a,!=b && (==a||==c) => ==c + $branches['names'] = array_diff($b['names'], $branches['names']); + $branches['exclude'] = false; + } else { + // conjunctive, so only keep names that are included in both + // (==a||==b) && (==a||==c) => ==a + $branches['names'] = array_intersect($branches['names'], $b['names']); + } + } + } + } + + $branches['names'] = array_unique($branches['names']); + + if (\count($numericGroups) === 1) { + return array('numeric' => $numericGroups[0], 'branches' => $branches); + } + + $borders = array(); + foreach ($numericGroups as $group) { + foreach ($group as $interval) { + $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start'); + $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end'); + } + } + + $opSortOrder = self::$opSortOrder; + usort($borders, function ($a, $b) use ($opSortOrder) { + $order = version_compare($a['version'], $b['version']); + if ($order === 0) { + return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']]; + } + + return $order; + }); + + $activeIntervals = 0; + $intervals = array(); + $index = 0; + $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1; + $start = null; + foreach ($borders as $border) { + if ($border['side'] === 'start') { + $activeIntervals++; + } else { + $activeIntervals--; + } + if (!$start && $activeIntervals >= $activationThreshold) { + $start = new Constraint($border['operator'], $border['version']); + } elseif ($start && $activeIntervals < $activationThreshold) { + // filter out invalid intervals like > x - <= x, or >= x - < x + if ( + version_compare($start->getVersion(), $border['version'], '=') + && ( + ($start->getOperator() === '>' && $border['operator'] === '<=') + || ($start->getOperator() === '>=' && $border['operator'] === '<') + ) + ) { + unset($intervals[$index]); + } else { + $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version'])); + $index++; + + if ($stopOnFirstValidInterval) { + break; + } + } + + $start = null; + } + } + + return array('numeric' => $intervals, 'branches' => $branches); + } + + /** + * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} + */ + private static function generateSingleConstraintIntervals(Constraint $constraint) + { + $op = $constraint->getOperator(); + + // handle branch constraints first + if (strpos($constraint->getVersion(), 'dev-') === 0) { + $intervals = array(); + $branches = array('names' => array(), 'exclude' => false); + + // != dev-foo means any numeric version may match, we treat >/< like != they are not really defined for branches + if ($op === '!=') { + $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity()); + $branches = array('names' => array($constraint->getVersion()), 'exclude' => true); + } elseif ($op === '==') { + $branches['names'][] = $constraint->getVersion(); + } + + return array( + 'numeric' => $intervals, + 'branches' => $branches, + ); + } + + if ($op[0] === '>') { // > & >= + return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev()); + } + if ($op[0] === '<') { // < & <= + return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev()); + } + if ($op === '!=') { + // convert !=x to intervals of 0 - x - +inf + dev* + return array('numeric' => array( + new Interval(Interval::fromZero(), new Constraint('<', $constraint->getVersion())), + new Interval(new Constraint('>', $constraint->getVersion()), Interval::untilPositiveInfinity()), + ), 'branches' => Interval::anyDev()); + } + + // convert ==x to an interval of >=x - <=x + return array('numeric' => array( + new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion())), + ), 'branches' => Interval::noDev()); + } +} diff --git a/vendor/composer/semver/src/Semver.php b/vendor/composer/semver/src/Semver.php new file mode 100644 index 000000000..4fe907532 --- /dev/null +++ b/vendor/composer/semver/src/Semver.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; + +class Semver +{ + const SORT_ASC = 1; + const SORT_DESC = -1; + + /** @var VersionParser */ + private static $versionParser; + + /** + * Determine if given version satisfies given constraints. + * + * @param string $version + * @param string $constraints + * + * @return bool + */ + public static function satisfies($version, $constraints) + { + if (null === self::$versionParser) { + self::$versionParser = new VersionParser(); + } + + $versionParser = self::$versionParser; + $provider = new Constraint('==', $versionParser->normalize($version)); + $parsedConstraints = $versionParser->parseConstraints($constraints); + + return $parsedConstraints->matches($provider); + } + + /** + * Return all versions that satisfy given constraints. + * + * @param string[] $versions + * @param string $constraints + * + * @return list + */ + public static function satisfiedBy(array $versions, $constraints) + { + $versions = array_filter($versions, function ($version) use ($constraints) { + return Semver::satisfies($version, $constraints); + }); + + return array_values($versions); + } + + /** + * Sort given array of versions. + * + * @param string[] $versions + * + * @return list + */ + public static function sort(array $versions) + { + return self::usort($versions, self::SORT_ASC); + } + + /** + * Sort given array of versions in reverse. + * + * @param string[] $versions + * + * @return list + */ + public static function rsort(array $versions) + { + return self::usort($versions, self::SORT_DESC); + } + + /** + * @param string[] $versions + * @param int $direction + * + * @return list + */ + private static function usort(array $versions, $direction) + { + if (null === self::$versionParser) { + self::$versionParser = new VersionParser(); + } + + $versionParser = self::$versionParser; + $normalized = array(); + + // Normalize outside of usort() scope for minor performance increase. + // Creates an array of arrays: [[normalized, key], ...] + foreach ($versions as $key => $version) { + $normalizedVersion = $versionParser->normalize($version); + $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion); + $normalized[] = array($normalizedVersion, $key); + } + + usort($normalized, function (array $left, array $right) use ($direction) { + if ($left[0] === $right[0]) { + return 0; + } + + if (Comparator::lessThan($left[0], $right[0])) { + return -$direction; + } + + return $direction; + }); + + // Recreate input array, using the original indexes which are now in sorted order. + $sorted = array(); + foreach ($normalized as $item) { + $sorted[] = $versions[$item[1]]; + } + + return $sorted; + } +} diff --git a/vendor/composer/semver/src/VersionParser.php b/vendor/composer/semver/src/VersionParser.php new file mode 100644 index 000000000..305a0faec --- /dev/null +++ b/vendor/composer/semver/src/VersionParser.php @@ -0,0 +1,591 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Constraint\Constraint; + +/** + * Version parser. + * + * @author Jordi Boggiano + */ +class VersionParser +{ + /** + * Regex to match pre-release data (sort of). + * + * Due to backwards compatibility: + * - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted. + * - Only stabilities as recognized by Composer are allowed to precede a numerical identifier. + * - Numerical-only pre-release identifiers are not supported, see tests. + * + * |--------------| + * [major].[minor].[patch] -[pre-release] +[build-metadata] + * + * @var string + */ + private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?'; + + /** @var string */ + private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev'; + + /** + * Returns the stability of a version. + * + * @param string $version + * + * @return string + * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + public static function parseStability($version) + { + $version = (string) preg_replace('{#.+$}', '', (string) $version); + + if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) { + return 'dev'; + } + + preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match); + + if (!empty($match[3])) { + return 'dev'; + } + + if (!empty($match[1])) { + if ('beta' === $match[1] || 'b' === $match[1]) { + return 'beta'; + } + if ('alpha' === $match[1] || 'a' === $match[1]) { + return 'alpha'; + } + if ('rc' === $match[1]) { + return 'RC'; + } + } + + return 'stable'; + } + + /** + * @param string $stability + * + * @return string + * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + public static function normalizeStability($stability) + { + $stability = strtolower((string) $stability); + + if (!in_array($stability, array('stable', 'rc', 'beta', 'alpha', 'dev'), true)) { + throw new \InvalidArgumentException('Invalid stability string "'.$stability.'", expected one of stable, RC, beta, alpha or dev'); + } + + return $stability === 'rc' ? 'RC' : $stability; + } + + /** + * Normalizes a version string to be able to perform comparisons on it. + * + * @param string $version + * @param ?string $fullVersion optional complete version string to give more context + * + * @throws \UnexpectedValueException + * + * @return string + */ + public function normalize($version, $fullVersion = null) + { + $version = trim((string) $version); + $origVersion = $version; + if (null === $fullVersion) { + $fullVersion = $version; + } + + // strip off aliasing + if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) { + $version = $match[1]; + } + + // strip off stability flag + if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) { + $version = substr($version, 0, strlen($version) - strlen($match[0])); + } + + // normalize master/trunk/default branches to dev-name for BC with 1.x as these used to be valid constraints + if (\in_array($version, array('master', 'trunk', 'default'), true)) { + $version = 'dev-' . $version; + } + + // if requirement is branch-like, use full name + if (stripos($version, 'dev-') === 0) { + return 'dev-' . substr($version, 4); + } + + // strip off build metadata + if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) { + $version = $match[1]; + } + + // match classical versioning + if (preg_match('{^v?(\d{1,5}+)(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = $matches[1] + . (!empty($matches[2]) ? $matches[2] : '.0') + . (!empty($matches[3]) ? $matches[3] : '.0') + . (!empty($matches[4]) ? $matches[4] : '.0'); + $index = 5; + // match date(time) based versioning + } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = (string) preg_replace('{\D}', '.', $matches[1]); + $index = 2; + } + + // add version modifiers if a version was matched + if (isset($index)) { + if (!empty($matches[$index])) { + if ('stable' === $matches[$index]) { + return $version; + } + $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : ''); + } + + if (!empty($matches[$index + 2])) { + $version .= '-dev'; + } + + return $version; + } + + // match dev branches + if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { + try { + $normalized = $this->normalizeBranch($match[1]); + // a branch ending with -dev is only valid if it is numeric + // if it gets prefixed with dev- it means the branch name should + // have had a dev- prefix already when passed to normalize + if (strpos($normalized, 'dev-') === false) { + return $normalized; + } + } catch (\Exception $e) { + } + } + + $extraMessage = ''; + if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) { + $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; + } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) { + $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; + } + + throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage); + } + + /** + * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison. + * + * @param string $branch Branch name (e.g. 2.1.x-dev) + * + * @return string|false Numeric prefix if present (e.g. 2.1.) or false + */ + public function parseNumericAliasPrefix($branch) + { + if (preg_match('{^(?P(\d++\\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) { + return $matches['version'] . '.'; + } + + return false; + } + + /** + * Normalizes a branch name to be able to perform comparisons on it. + * + * @param string $name + * + * @return string + */ + public function normalizeBranch($name) + { + $name = trim((string) $name); + + if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) { + $version = ''; + for ($i = 1; $i < 5; ++$i) { + $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; + } + + return str_replace('x', '9999999', $version) . '-dev'; + } + + return 'dev-' . $name; + } + + /** + * Normalizes a default branch name (i.e. master on git) to 9999999-dev. + * + * @param string $name + * + * @return string + * + * @deprecated No need to use this anymore in theory, Composer 2 does not normalize any branch names to 9999999-dev anymore + */ + public function normalizeDefaultBranch($name) + { + if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') { + return '9999999-dev'; + } + + return (string) $name; + } + + /** + * Parses a constraint string into MultiConstraint and/or Constraint objects. + * + * @param string $constraints + * + * @return ConstraintInterface + */ + public function parseConstraints($constraints) + { + $prettyConstraint = (string) $constraints; + + $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints)); + if (false === $orConstraints) { + throw new \RuntimeException('Failed to preg_split string: '.$constraints); + } + $orGroups = array(); + + foreach ($orConstraints as $orConstraint) { + $andConstraints = preg_split('{(?< ,]) *(? 1) { + $constraintObjects = array(); + foreach ($andConstraints as $andConstraint) { + foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) { + $constraintObjects[] = $parsedAndConstraint; + } + } + } else { + $constraintObjects = $this->parseConstraint($andConstraints[0]); + } + + if (1 === \count($constraintObjects)) { + $constraint = $constraintObjects[0]; + } else { + $constraint = new MultiConstraint($constraintObjects); + } + + $orGroups[] = $constraint; + } + + $parsedConstraint = MultiConstraint::create($orGroups, false); + + $parsedConstraint->setPrettyString($prettyConstraint); + + return $parsedConstraint; + } + + /** + * @param string $constraint + * + * @throws \UnexpectedValueException + * + * @return array + * + * @phpstan-return non-empty-array + */ + private function parseConstraint($constraint) + { + // strip off aliasing + if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) { + $constraint = $match[1]; + } + + // strip @stability flags, and keep it for later use + if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) { + $constraint = '' !== $match[1] ? $match[1] : '*'; + if ($match[2] !== 'stable') { + $stabilityModifier = $match[2]; + } + } + + // get rid of #refs as those are used by composer only + if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) { + $constraint = $match[1]; + } + + if (preg_match('{^(v)?[xX*](\.[xX*])*$}i', $constraint, $match)) { + if (!empty($match[1]) || !empty($match[2])) { + return array(new Constraint('>=', '0.0.0.0-dev')); + } + + return array(new MatchAllConstraint()); + } + + $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?'; + + // Tilde Range + // + // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous + // version, to ensure that unstable instances of the current version are allowed. However, if a stability + // suffix is added to the constraint, then a >= match on the current version is used instead. + if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { + if (strpos($constraint, '~>') === 0) { + throw new \UnexpectedValueException( + 'Could not parse version constraint ' . $constraint . ': ' . + 'Invalid operator "~>", you probably meant to use the "~" operator' + ); + } + + // Work out which position in the version we are operating at + if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) { + $position = 4; + } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { + $position = 3; + } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { + $position = 2; + } else { + $position = 1; + } + + // when matching 2.x-dev or 3.0.x-dev we have to shift the second or third number, despite no second/third number matching above + if (!empty($matches[8])) { + $position++; + } + + // Calculate the stability suffix + $stabilitySuffix = ''; + if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { + $stabilitySuffix .= '-dev'; + } + + $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); + $lowerBound = new Constraint('>=', $lowVersion); + + // For upper bound, we increment the position of one more significance, + // but highPosition = 0 would be illegal + $highPosition = max(1, $position - 1); + $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; + $upperBound = new Constraint('<', $highVersion); + + return array( + $lowerBound, + $upperBound, + ); + } + + // Caret Range + // + // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. + // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for + // versions 0.X >=0.1.0, and no updates for versions 0.0.X + if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) { + // Work out which position in the version we are operating at + if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) { + $position = 1; + } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) { + $position = 2; + } else { + $position = 3; + } + + // Calculate the stability suffix + $stabilitySuffix = ''; + if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { + $stabilitySuffix .= '-dev'; + } + + $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); + $lowerBound = new Constraint('>=', $lowVersion); + + // For upper bound, we increment the position of one more significance, + // but highPosition = 0 would be illegal + $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; + $upperBound = new Constraint('<', $highVersion); + + return array( + $lowerBound, + $upperBound, + ); + } + + // X Range + // + // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple. + // A partial version range is treated as an X-Range, so the special character is in fact optional. + if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) { + if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { + $position = 3; + } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { + $position = 2; + } else { + $position = 1; + } + + $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; + $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; + + if ($lowVersion === '0.0.0.0-dev') { + return array(new Constraint('<', $highVersion)); + } + + return array( + new Constraint('>=', $lowVersion), + new Constraint('<', $highVersion), + ); + } + + // Hyphen Range + // + // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range, + // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in + // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but + // nothing that would be greater than the provided tuple parts. + if (preg_match('{^(?P' . $versionRegex . ') +- +(?P' . $versionRegex . ')($)}i', $constraint, $matches)) { + // Calculate the stability suffix + $lowStabilitySuffix = ''; + if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) { + $lowStabilitySuffix = '-dev'; + } + + $lowVersion = $this->normalize($matches['from']); + $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); + + $empty = function ($x) { + return ($x === 0 || $x === '0') ? false : empty($x); + }; + + if ((!$empty($matches[12]) && !$empty($matches[13])) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) { + $highVersion = $this->normalize($matches['to']); + $upperBound = new Constraint('<=', $highVersion); + } else { + $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]); + + // validate to version + $this->normalize($matches['to']); + + $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev'; + $upperBound = new Constraint('<', $highVersion); + } + + return array( + $lowerBound, + $upperBound, + ); + } + + // Basic Comparators + if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) { + try { + try { + $version = $this->normalize($matches[2]); + } catch (\UnexpectedValueException $e) { + // recover from an invalid constraint like foobar-dev which should be dev-foobar + // except if the constraint uses a known operator, in which case it must be a parse error + if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) { + $version = $this->normalize('dev-'.substr($matches[2], 0, -4)); + } else { + throw $e; + } + } + + $op = $matches[1] ?: '='; + + if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') { + $version .= '-' . $stabilityModifier; + } elseif ('<' === $op || '>=' === $op) { + if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) { + if (strpos($matches[2], 'dev-') !== 0) { + $version .= '-dev'; + } + } + } + + return array(new Constraint($matches[1] ?: '=', $version)); + } catch (\Exception $e) { + } + } + + $message = 'Could not parse version constraint ' . $constraint; + if (isset($e)) { + $message .= ': ' . $e->getMessage(); + } + + throw new \UnexpectedValueException($message); + } + + /** + * Increment, decrement, or simply pad a version number. + * + * Support function for {@link parseConstraint()} + * + * @param array $matches Array with version parts in array indexes 1,2,3,4 + * @param int $position 1,2,3,4 - which segment of the version to increment/decrement + * @param int $increment + * @param string $pad The string to pad version parts after $position + * + * @return string|null The new version + * + * @phpstan-param string[] $matches + */ + private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0') + { + for ($i = 4; $i > 0; --$i) { + if ($i > $position) { + $matches[$i] = $pad; + } elseif ($i === $position && $increment) { + $matches[$i] += $increment; + // If $matches[$i] was 0, carry the decrement + if ($matches[$i] < 0) { + $matches[$i] = $pad; + --$position; + + // Return null on a carry overflow + if ($i === 1) { + return null; + } + } + } + } + + return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; + } + + /** + * Expand shorthand stability string to long version. + * + * @param string $stability + * + * @return string + */ + private function expandStability($stability) + { + $stability = strtolower($stability); + + switch ($stability) { + case 'a': + return 'alpha'; + case 'b': + return 'beta'; + case 'p': + case 'pl': + return 'patch'; + case 'rc': + return 'RC'; + default: + return $stability; + } + } +} diff --git a/vendor/composer/spdx-licenses/LICENSE b/vendor/composer/spdx-licenses/LICENSE new file mode 100644 index 000000000..466975862 --- /dev/null +++ b/vendor/composer/spdx-licenses/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2015 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/spdx-licenses/README.md b/vendor/composer/spdx-licenses/README.md new file mode 100644 index 000000000..c0995f987 --- /dev/null +++ b/vendor/composer/spdx-licenses/README.md @@ -0,0 +1,69 @@ +composer/spdx-licenses +====================== + +SPDX (Software Package Data Exchange) licenses list and validation library. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + +[![Continuous Integration](https://github.com/composer/spdx-licenses/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/spdx-licenses/actions) + +Installation +------------ + +Install the latest version with: + +```bash +$ composer require composer/spdx-licenses +``` + +Basic Usage +----------- + +```php +getLicenseByIdentifier('MIT'); + +// get a license exception by identifier +$licenses->getExceptionByIdentifier('Autoconf-exception-3.0'); + +// get a license identifier by name +$licenses->getIdentifierByName('MIT License'); + +// check if a license is OSI approved by identifier +$licenses->isOsiApprovedByIdentifier('MIT'); + +// check if a license identifier is deprecated +$licenses->isDeprecatedByIdentifier('MIT'); + +// check if input is a valid SPDX license expression +$licenses->validate($input); +``` + +> Read the [specifications](https://spdx.org/specifications) +> to find out more about valid license expressions. + +Requirements +------------ + +* PHP 5.3.2 is required but using the latest version of PHP is highly recommended. + +License +------- + +composer/spdx-licenses is licensed under the MIT License, see the LICENSE file for details. + +Source +------ + +License information is curated by [SPDX](https://spdx.org/). The data is pulled from the +[License List Data](https://github.com/spdx/license-list-data) repository. + +* [Licenses](https://spdx.org/licenses/index.html) +* [License Exceptions](https://spdx.org/licenses/exceptions-index.html) diff --git a/vendor/composer/spdx-licenses/composer.json b/vendor/composer/spdx-licenses/composer.json new file mode 100644 index 000000000..a3b4ad9d1 --- /dev/null +++ b/vendor/composer/spdx-licenses/composer.json @@ -0,0 +1,59 @@ +{ + "name": "composer/spdx-licenses", + "description": "SPDX licenses list and validation library.", + "type": "library", + "license": "MIT", + "keywords": [ + "spdx", + "license", + "validator" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0", + "phpstan/phpstan": "^1.11" + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Spdx\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "scripts": { + "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit", + "phpstan": "vendor/bin/phpstan analyse", + "sync-licenses": "bin/update-spdx-licenses" + } +} diff --git a/vendor/composer/spdx-licenses/res/spdx-exceptions.json b/vendor/composer/spdx-licenses/res/spdx-exceptions.json new file mode 100644 index 000000000..83e147bdd --- /dev/null +++ b/vendor/composer/spdx-licenses/res/spdx-exceptions.json @@ -0,0 +1,254 @@ +{ + "389-exception": [ + "389 Directory Server Exception" + ], + "Asterisk-exception": [ + "Asterisk exception" + ], + "Asterisk-linking-protocols-exception": [ + "Asterisk linking protocols exception" + ], + "Autoconf-exception-2.0": [ + "Autoconf exception 2.0" + ], + "Autoconf-exception-3.0": [ + "Autoconf exception 3.0" + ], + "Autoconf-exception-generic": [ + "Autoconf generic exception" + ], + "Autoconf-exception-generic-3.0": [ + "Autoconf generic exception for GPL-3.0" + ], + "Autoconf-exception-macro": [ + "Autoconf macro exception" + ], + "Bison-exception-1.24": [ + "Bison exception 1.24" + ], + "Bison-exception-2.2": [ + "Bison exception 2.2" + ], + "Bootloader-exception": [ + "Bootloader Distribution Exception" + ], + "CGAL-linking-exception": [ + "CGAL Linking Exception" + ], + "Classpath-exception-2.0": [ + "Classpath exception 2.0" + ], + "Classpath-exception-2.0-short": [ + "Classpath exception 2.0 - short" + ], + "CLISP-exception-2.0": [ + "CLISP exception 2.0" + ], + "cryptsetup-OpenSSL-exception": [ + "cryptsetup OpenSSL exception" + ], + "Digia-Qt-LGPL-exception-1.1": [ + "Digia Qt LGPL Exception version 1.1" + ], + "DigiRule-FOSS-exception": [ + "DigiRule FOSS License Exception" + ], + "eCos-exception-2.0": [ + "eCos exception 2.0" + ], + "erlang-otp-linking-exception": [ + "Erlang/OTP Linking Exception" + ], + "Fawkes-Runtime-exception": [ + "Fawkes Runtime Exception" + ], + "FLTK-exception": [ + "FLTK exception" + ], + "fmt-exception": [ + "fmt exception" + ], + "Font-exception-2.0": [ + "Font exception 2.0" + ], + "freertos-exception-2.0": [ + "FreeRTOS Exception 2.0" + ], + "GCC-exception-2.0": [ + "GCC Runtime Library exception 2.0" + ], + "GCC-exception-2.0-note": [ + "GCC Runtime Library exception 2.0 - note variant" + ], + "GCC-exception-3.1": [ + "GCC Runtime Library exception 3.1" + ], + "Gmsh-exception": [ + "Gmsh exception" + ], + "GNAT-exception": [ + "GNAT exception" + ], + "GNOME-examples-exception": [ + "GNOME examples exception" + ], + "GNU-compiler-exception": [ + "GNU Compiler Exception" + ], + "gnu-javamail-exception": [ + "GNU JavaMail exception" + ], + "GPL-3.0-389-ds-base-exception": [ + "GPL-3.0 389 DS Base Exception" + ], + "GPL-3.0-interface-exception": [ + "GPL-3.0 Interface Exception" + ], + "GPL-3.0-linking-exception": [ + "GPL-3.0 Linking Exception" + ], + "GPL-3.0-linking-source-exception": [ + "GPL-3.0 Linking Exception (with Corresponding Source)" + ], + "GPL-CC-1.0": [ + "GPL Cooperation Commitment 1.0" + ], + "GStreamer-exception-2005": [ + "GStreamer Exception (2005)" + ], + "GStreamer-exception-2008": [ + "GStreamer Exception (2008)" + ], + "harbour-exception": [ + "harbour exception" + ], + "i2p-gpl-java-exception": [ + "i2p GPL+Java Exception" + ], + "Independent-modules-exception": [ + "Independent Module Linking exception" + ], + "KiCad-libraries-exception": [ + "KiCad Libraries Exception" + ], + "kvirc-openssl-exception": [ + "kvirc OpenSSL Exception" + ], + "LGPL-3.0-linking-exception": [ + "LGPL-3.0 Linking Exception" + ], + "libpri-OpenH323-exception": [ + "libpri OpenH323 exception" + ], + "Libtool-exception": [ + "Libtool Exception" + ], + "Linux-syscall-note": [ + "Linux Syscall Note" + ], + "LLGPL": [ + "LLGPL Preamble" + ], + "LLVM-exception": [ + "LLVM Exception" + ], + "LZMA-exception": [ + "LZMA exception" + ], + "mif-exception": [ + "Macros and Inline Functions Exception" + ], + "mxml-exception": [ + "mxml Exception" + ], + "Nokia-Qt-exception-1.1": [ + "Nokia Qt LGPL exception 1.1" + ], + "OCaml-LGPL-linking-exception": [ + "OCaml LGPL Linking Exception" + ], + "OCCT-exception-1.0": [ + "Open CASCADE Exception 1.0" + ], + "OpenJDK-assembly-exception-1.0": [ + "OpenJDK Assembly exception 1.0" + ], + "openvpn-openssl-exception": [ + "OpenVPN OpenSSL Exception" + ], + "PCRE2-exception": [ + "PCRE2 exception" + ], + "polyparse-exception": [ + "Polyparse Exception" + ], + "PS-or-PDF-font-exception-20170817": [ + "PS/PDF font exception (2017-08-17)" + ], + "QPL-1.0-INRIA-2004-exception": [ + "INRIA QPL 1.0 2004 variant exception" + ], + "Qt-GPL-exception-1.0": [ + "Qt GPL exception 1.0" + ], + "Qt-LGPL-exception-1.1": [ + "Qt LGPL exception 1.1" + ], + "Qwt-exception-1.0": [ + "Qwt exception 1.0" + ], + "romic-exception": [ + "Romic Exception" + ], + "RRDtool-FLOSS-exception-2.0": [ + "RRDtool FLOSS exception 2.0" + ], + "rsync-linking-exception": [ + "rsync Linking Exception" + ], + "SANE-exception": [ + "SANE Exception" + ], + "SHL-2.0": [ + "Solderpad Hardware License v2.0" + ], + "SHL-2.1": [ + "Solderpad Hardware License v2.1" + ], + "Simple-Library-Usage-exception": [ + "Simple Library Usage Exception" + ], + "sqlitestudio-OpenSSL-exception": [ + "sqlitestudio OpenSSL exception" + ], + "stunnel-exception": [ + "stunnel Exception" + ], + "SWI-exception": [ + "SWI exception" + ], + "Swift-exception": [ + "Swift Exception" + ], + "Texinfo-exception": [ + "Texinfo exception" + ], + "u-boot-exception-2.0": [ + "U-Boot exception 2.0" + ], + "UBDL-exception": [ + "Unmodified Binary Distribution exception" + ], + "Universal-FOSS-exception-1.0": [ + "Universal FOSS Exception, Version 1.0" + ], + "vsftpd-openssl-exception": [ + "vsftpd OpenSSL exception" + ], + "WxWindows-exception-3.1": [ + "WxWindows Library Exception 3.1" + ], + "x11vnc-openssl-exception": [ + "x11vnc OpenSSL Exception" + ] +} \ No newline at end of file diff --git a/vendor/composer/spdx-licenses/res/spdx-licenses.json b/vendor/composer/spdx-licenses/res/spdx-licenses.json new file mode 100644 index 000000000..519f5a3f8 --- /dev/null +++ b/vendor/composer/spdx-licenses/res/spdx-licenses.json @@ -0,0 +1,3637 @@ +{ + "0BSD": [ + "BSD Zero Clause License", + true, + false + ], + "3D-Slicer-1.0": [ + "3D Slicer License v1.0", + false, + false + ], + "AAL": [ + "Attribution Assurance License", + true, + false + ], + "Abstyles": [ + "Abstyles License", + false, + false + ], + "AdaCore-doc": [ + "AdaCore Doc License", + false, + false + ], + "Adobe-2006": [ + "Adobe Systems Incorporated Source Code License Agreement", + false, + false + ], + "Adobe-Display-PostScript": [ + "Adobe Display PostScript License", + false, + false + ], + "Adobe-Glyph": [ + "Adobe Glyph List License", + false, + false + ], + "Adobe-Utopia": [ + "Adobe Utopia Font License", + false, + false + ], + "ADSL": [ + "Amazon Digital Services License", + false, + false + ], + "Advanced-Cryptics-Dictionary": [ + "Advanced Cryptics Dictionary License", + false, + false + ], + "AFL-1.1": [ + "Academic Free License v1.1", + true, + false + ], + "AFL-1.2": [ + "Academic Free License v1.2", + true, + false + ], + "AFL-2.0": [ + "Academic Free License v2.0", + true, + false + ], + "AFL-2.1": [ + "Academic Free License v2.1", + true, + false + ], + "AFL-3.0": [ + "Academic Free License v3.0", + true, + false + ], + "Afmparse": [ + "Afmparse License", + false, + false + ], + "AGPL-1.0": [ + "Affero General Public License v1.0", + false, + true + ], + "AGPL-1.0-only": [ + "Affero General Public License v1.0 only", + false, + false + ], + "AGPL-1.0-or-later": [ + "Affero General Public License v1.0 or later", + false, + false + ], + "AGPL-3.0": [ + "GNU Affero General Public License v3.0", + true, + true + ], + "AGPL-3.0-only": [ + "GNU Affero General Public License v3.0 only", + true, + false + ], + "AGPL-3.0-or-later": [ + "GNU Affero General Public License v3.0 or later", + true, + false + ], + "Aladdin": [ + "Aladdin Free Public License", + false, + false + ], + "ALGLIB-Documentation": [ + "ALGLIB Documentation License", + true, + false + ], + "AMD-newlib": [ + "AMD newlib License", + false, + false + ], + "AMDPLPA": [ + "AMD's plpa_map.c License", + false, + false + ], + "AML": [ + "Apple MIT License", + false, + false + ], + "AML-glslang": [ + "AML glslang variant License", + false, + false + ], + "AMPAS": [ + "Academy of Motion Picture Arts and Sciences BSD", + false, + false + ], + "ANTLR-PD": [ + "ANTLR Software Rights Notice", + false, + false + ], + "ANTLR-PD-fallback": [ + "ANTLR Software Rights Notice with license fallback", + false, + false + ], + "any-OSI": [ + "Any OSI License", + false, + false + ], + "any-OSI-perl-modules": [ + "Any OSI License - Perl Modules", + false, + false + ], + "Apache-1.0": [ + "Apache License 1.0", + false, + false + ], + "Apache-1.1": [ + "Apache License 1.1", + true, + false + ], + "Apache-2.0": [ + "Apache License 2.0", + true, + false + ], + "APAFML": [ + "Adobe Postscript AFM License", + false, + false + ], + "APL-1.0": [ + "Adaptive Public License 1.0", + true, + false + ], + "App-s2p": [ + "App::s2p License", + false, + false + ], + "APSL-1.0": [ + "Apple Public Source License 1.0", + true, + false + ], + "APSL-1.1": [ + "Apple Public Source License 1.1", + true, + false + ], + "APSL-1.2": [ + "Apple Public Source License 1.2", + true, + false + ], + "APSL-2.0": [ + "Apple Public Source License 2.0", + true, + false + ], + "Arphic-1999": [ + "Arphic Public License", + false, + false + ], + "Artistic-1.0": [ + "Artistic License 1.0", + true, + false + ], + "Artistic-1.0-cl8": [ + "Artistic License 1.0 w/clause 8", + true, + false + ], + "Artistic-1.0-Perl": [ + "Artistic License 1.0 (Perl)", + true, + false + ], + "Artistic-2.0": [ + "Artistic License 2.0", + true, + false + ], + "Artistic-dist": [ + "Artistic License 1.0 (dist)", + false, + false + ], + "Aspell-RU": [ + "Aspell Russian License", + false, + false + ], + "ASWF-Digital-Assets-1.0": [ + "ASWF Digital Assets License version 1.0", + false, + false + ], + "ASWF-Digital-Assets-1.1": [ + "ASWF Digital Assets License 1.1", + false, + false + ], + "Baekmuk": [ + "Baekmuk License", + false, + false + ], + "Bahyph": [ + "Bahyph License", + false, + false + ], + "Barr": [ + "Barr License", + false, + false + ], + "bcrypt-Solar-Designer": [ + "bcrypt Solar Designer License", + false, + false + ], + "Beerware": [ + "Beerware License", + false, + false + ], + "Bitstream-Charter": [ + "Bitstream Charter Font License", + false, + false + ], + "Bitstream-Vera": [ + "Bitstream Vera Font License", + false, + false + ], + "BitTorrent-1.0": [ + "BitTorrent Open Source License v1.0", + false, + false + ], + "BitTorrent-1.1": [ + "BitTorrent Open Source License v1.1", + false, + false + ], + "blessing": [ + "SQLite Blessing", + false, + false + ], + "BlueOak-1.0.0": [ + "Blue Oak Model License 1.0.0", + true, + false + ], + "Boehm-GC": [ + "Boehm-Demers-Weiser GC License", + false, + false + ], + "Boehm-GC-without-fee": [ + "Boehm-Demers-Weiser GC License (without fee)", + false, + false + ], + "BOLA-1.1": [ + "Buena Onda License Agreement v1.1", + false, + false + ], + "Borceux": [ + "Borceux license", + false, + false + ], + "Brian-Gladman-2-Clause": [ + "Brian Gladman 2-Clause License", + false, + false + ], + "Brian-Gladman-3-Clause": [ + "Brian Gladman 3-Clause License", + false, + false + ], + "BSD-1-Clause": [ + "BSD 1-Clause License", + true, + false + ], + "BSD-2-Clause": [ + "BSD 2-Clause \"Simplified\" License", + true, + false + ], + "BSD-2-Clause-Darwin": [ + "BSD 2-Clause - Ian Darwin variant", + false, + false + ], + "BSD-2-Clause-first-lines": [ + "BSD 2-Clause - first lines requirement", + false, + false + ], + "BSD-2-Clause-FreeBSD": [ + "BSD 2-Clause FreeBSD License", + false, + true + ], + "BSD-2-Clause-NetBSD": [ + "BSD 2-Clause NetBSD License", + false, + true + ], + "BSD-2-Clause-Patent": [ + "BSD-2-Clause Plus Patent License", + true, + false + ], + "BSD-2-Clause-pkgconf-disclaimer": [ + "BSD 2-Clause pkgconf disclaimer variant", + false, + false + ], + "BSD-2-Clause-Views": [ + "BSD 2-Clause with views sentence", + false, + false + ], + "BSD-3-Clause": [ + "BSD 3-Clause \"New\" or \"Revised\" License", + true, + false + ], + "BSD-3-Clause-acpica": [ + "BSD 3-Clause acpica variant", + false, + false + ], + "BSD-3-Clause-Attribution": [ + "BSD with attribution", + false, + false + ], + "BSD-3-Clause-Clear": [ + "BSD 3-Clause Clear License", + false, + false + ], + "BSD-3-Clause-flex": [ + "BSD 3-Clause Flex variant", + false, + false + ], + "BSD-3-Clause-HP": [ + "Hewlett-Packard BSD variant license", + false, + false + ], + "BSD-3-Clause-LBNL": [ + "Lawrence Berkeley National Labs BSD variant license", + true, + false + ], + "BSD-3-Clause-Modification": [ + "BSD 3-Clause Modification", + false, + false + ], + "BSD-3-Clause-No-Military-License": [ + "BSD 3-Clause No Military License", + false, + false + ], + "BSD-3-Clause-No-Nuclear-License": [ + "BSD 3-Clause No Nuclear License", + false, + false + ], + "BSD-3-Clause-No-Nuclear-License-2014": [ + "BSD 3-Clause No Nuclear License 2014", + false, + false + ], + "BSD-3-Clause-No-Nuclear-Warranty": [ + "BSD 3-Clause No Nuclear Warranty", + false, + false + ], + "BSD-3-Clause-Open-MPI": [ + "BSD 3-Clause Open MPI variant", + true, + false + ], + "BSD-3-Clause-Sun": [ + "BSD 3-Clause Sun Microsystems", + false, + false + ], + "BSD-3-Clause-Tso": [ + "BSD 3-Clause Tso variant", + false, + false + ], + "BSD-4-Clause": [ + "BSD 4-Clause \"Original\" or \"Old\" License", + false, + false + ], + "BSD-4-Clause-Shortened": [ + "BSD 4 Clause Shortened", + false, + false + ], + "BSD-4-Clause-UC": [ + "BSD-4-Clause (University of California-Specific)", + false, + false + ], + "BSD-4.3RENO": [ + "BSD 4.3 RENO License", + false, + false + ], + "BSD-4.3TAHOE": [ + "BSD 4.3 TAHOE License", + false, + false + ], + "BSD-Advertising-Acknowledgement": [ + "BSD Advertising Acknowledgement License", + false, + false + ], + "BSD-Attribution-HPND-disclaimer": [ + "BSD with Attribution and HPND disclaimer", + false, + false + ], + "BSD-Inferno-Nettverk": [ + "BSD-Inferno-Nettverk", + false, + false + ], + "BSD-Mark-Modifications": [ + "BSD Mark Modifications License", + false, + false + ], + "BSD-Protection": [ + "BSD Protection License", + false, + false + ], + "BSD-Source-beginning-file": [ + "BSD Source Code Attribution - beginning of file variant", + false, + false + ], + "BSD-Source-Code": [ + "BSD Source Code Attribution", + false, + false + ], + "BSD-Systemics": [ + "Systemics BSD variant license", + false, + false + ], + "BSD-Systemics-W3Works": [ + "Systemics W3Works BSD variant license", + false, + false + ], + "BSL-1.0": [ + "Boost Software License 1.0", + true, + false + ], + "Buddy": [ + "Buddy License", + false, + false + ], + "BUSL-1.1": [ + "Business Source License 1.1", + false, + false + ], + "bzip2-1.0.5": [ + "bzip2 and libbzip2 License v1.0.5", + false, + true + ], + "bzip2-1.0.6": [ + "bzip2 and libbzip2 License v1.0.6", + false, + false + ], + "C-UDA-1.0": [ + "Computational Use of Data Agreement v1.0", + false, + false + ], + "CAL-1.0": [ + "Cryptographic Autonomy License 1.0", + true, + false + ], + "CAL-1.0-Combined-Work-Exception": [ + "Cryptographic Autonomy License 1.0 (Combined Work Exception)", + true, + false + ], + "Caldera": [ + "Caldera License", + false, + false + ], + "Caldera-no-preamble": [ + "Caldera License (without preamble)", + false, + false + ], + "CAPEC-tou": [ + "Common Attack Pattern Enumeration and Classification License", + false, + false + ], + "Catharon": [ + "Catharon License", + false, + false + ], + "CATOSL-1.1": [ + "Computer Associates Trusted Open Source License 1.1", + true, + false + ], + "CC-BY-1.0": [ + "Creative Commons Attribution 1.0 Generic", + false, + false + ], + "CC-BY-2.0": [ + "Creative Commons Attribution 2.0 Generic", + false, + false + ], + "CC-BY-2.5": [ + "Creative Commons Attribution 2.5 Generic", + false, + false + ], + "CC-BY-2.5-AU": [ + "Creative Commons Attribution 2.5 Australia", + false, + false + ], + "CC-BY-3.0": [ + "Creative Commons Attribution 3.0 Unported", + false, + false + ], + "CC-BY-3.0-AT": [ + "Creative Commons Attribution 3.0 Austria", + false, + false + ], + "CC-BY-3.0-AU": [ + "Creative Commons Attribution 3.0 Australia", + false, + false + ], + "CC-BY-3.0-DE": [ + "Creative Commons Attribution 3.0 Germany", + false, + false + ], + "CC-BY-3.0-IGO": [ + "Creative Commons Attribution 3.0 IGO", + false, + false + ], + "CC-BY-3.0-NL": [ + "Creative Commons Attribution 3.0 Netherlands", + false, + false + ], + "CC-BY-3.0-US": [ + "Creative Commons Attribution 3.0 United States", + false, + false + ], + "CC-BY-4.0": [ + "Creative Commons Attribution 4.0 International", + false, + false + ], + "CC-BY-NC-1.0": [ + "Creative Commons Attribution Non Commercial 1.0 Generic", + false, + false + ], + "CC-BY-NC-2.0": [ + "Creative Commons Attribution Non Commercial 2.0 Generic", + false, + false + ], + "CC-BY-NC-2.5": [ + "Creative Commons Attribution Non Commercial 2.5 Generic", + false, + false + ], + "CC-BY-NC-3.0": [ + "Creative Commons Attribution Non Commercial 3.0 Unported", + false, + false + ], + "CC-BY-NC-3.0-DE": [ + "Creative Commons Attribution Non Commercial 3.0 Germany", + false, + false + ], + "CC-BY-NC-4.0": [ + "Creative Commons Attribution Non Commercial 4.0 International", + false, + false + ], + "CC-BY-NC-ND-1.0": [ + "Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic", + false, + false + ], + "CC-BY-NC-ND-2.0": [ + "Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic", + false, + false + ], + "CC-BY-NC-ND-2.5": [ + "Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic", + false, + false + ], + "CC-BY-NC-ND-3.0": [ + "Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported", + false, + false + ], + "CC-BY-NC-ND-3.0-DE": [ + "Creative Commons Attribution Non Commercial No Derivatives 3.0 Germany", + false, + false + ], + "CC-BY-NC-ND-3.0-IGO": [ + "Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO", + false, + false + ], + "CC-BY-NC-ND-4.0": [ + "Creative Commons Attribution Non Commercial No Derivatives 4.0 International", + false, + false + ], + "CC-BY-NC-SA-1.0": [ + "Creative Commons Attribution Non Commercial Share Alike 1.0 Generic", + false, + false + ], + "CC-BY-NC-SA-2.0": [ + "Creative Commons Attribution Non Commercial Share Alike 2.0 Generic", + false, + false + ], + "CC-BY-NC-SA-2.0-DE": [ + "Creative Commons Attribution Non Commercial Share Alike 2.0 Germany", + false, + false + ], + "CC-BY-NC-SA-2.0-FR": [ + "Creative Commons Attribution-NonCommercial-ShareAlike 2.0 France", + false, + false + ], + "CC-BY-NC-SA-2.0-UK": [ + "Creative Commons Attribution Non Commercial Share Alike 2.0 England and Wales", + false, + false + ], + "CC-BY-NC-SA-2.5": [ + "Creative Commons Attribution Non Commercial Share Alike 2.5 Generic", + false, + false + ], + "CC-BY-NC-SA-3.0": [ + "Creative Commons Attribution Non Commercial Share Alike 3.0 Unported", + false, + false + ], + "CC-BY-NC-SA-3.0-DE": [ + "Creative Commons Attribution Non Commercial Share Alike 3.0 Germany", + false, + false + ], + "CC-BY-NC-SA-3.0-IGO": [ + "Creative Commons Attribution Non Commercial Share Alike 3.0 IGO", + false, + false + ], + "CC-BY-NC-SA-4.0": [ + "Creative Commons Attribution Non Commercial Share Alike 4.0 International", + false, + false + ], + "CC-BY-ND-1.0": [ + "Creative Commons Attribution No Derivatives 1.0 Generic", + false, + false + ], + "CC-BY-ND-2.0": [ + "Creative Commons Attribution No Derivatives 2.0 Generic", + false, + false + ], + "CC-BY-ND-2.5": [ + "Creative Commons Attribution No Derivatives 2.5 Generic", + false, + false + ], + "CC-BY-ND-3.0": [ + "Creative Commons Attribution No Derivatives 3.0 Unported", + false, + false + ], + "CC-BY-ND-3.0-DE": [ + "Creative Commons Attribution No Derivatives 3.0 Germany", + false, + false + ], + "CC-BY-ND-4.0": [ + "Creative Commons Attribution No Derivatives 4.0 International", + false, + false + ], + "CC-BY-SA-1.0": [ + "Creative Commons Attribution Share Alike 1.0 Generic", + false, + false + ], + "CC-BY-SA-2.0": [ + "Creative Commons Attribution Share Alike 2.0 Generic", + false, + false + ], + "CC-BY-SA-2.0-UK": [ + "Creative Commons Attribution Share Alike 2.0 England and Wales", + false, + false + ], + "CC-BY-SA-2.1-JP": [ + "Creative Commons Attribution Share Alike 2.1 Japan", + false, + false + ], + "CC-BY-SA-2.5": [ + "Creative Commons Attribution Share Alike 2.5 Generic", + false, + false + ], + "CC-BY-SA-3.0": [ + "Creative Commons Attribution Share Alike 3.0 Unported", + false, + false + ], + "CC-BY-SA-3.0-AT": [ + "Creative Commons Attribution Share Alike 3.0 Austria", + false, + false + ], + "CC-BY-SA-3.0-DE": [ + "Creative Commons Attribution Share Alike 3.0 Germany", + false, + false + ], + "CC-BY-SA-3.0-IGO": [ + "Creative Commons Attribution-ShareAlike 3.0 IGO", + false, + false + ], + "CC-BY-SA-4.0": [ + "Creative Commons Attribution Share Alike 4.0 International", + false, + false + ], + "CC-PDDC": [ + "Creative Commons Public Domain Dedication and Certification", + false, + false + ], + "CC-PDM-1.0": [ + "Creative Commons Public Domain Mark 1.0 Universal", + false, + false + ], + "CC-SA-1.0": [ + "Creative Commons Share Alike 1.0 Generic", + false, + false + ], + "CC0-1.0": [ + "Creative Commons Zero v1.0 Universal", + false, + false + ], + "CDDL-1.0": [ + "Common Development and Distribution License 1.0", + true, + false + ], + "CDDL-1.1": [ + "Common Development and Distribution License 1.1", + false, + false + ], + "CDL-1.0": [ + "Common Documentation License 1.0", + false, + false + ], + "CDLA-Permissive-1.0": [ + "Community Data License Agreement Permissive 1.0", + false, + false + ], + "CDLA-Permissive-2.0": [ + "Community Data License Agreement Permissive 2.0", + false, + false + ], + "CDLA-Sharing-1.0": [ + "Community Data License Agreement Sharing 1.0", + false, + false + ], + "CECILL-1.0": [ + "CeCILL Free Software License Agreement v1.0", + false, + false + ], + "CECILL-1.1": [ + "CeCILL Free Software License Agreement v1.1", + false, + false + ], + "CECILL-2.0": [ + "CeCILL Free Software License Agreement v2.0", + false, + false + ], + "CECILL-2.1": [ + "CeCILL Free Software License Agreement v2.1", + true, + false + ], + "CECILL-B": [ + "CeCILL-B Free Software License Agreement", + false, + false + ], + "CECILL-C": [ + "CeCILL-C Free Software License Agreement", + false, + false + ], + "CERN-OHL-1.1": [ + "CERN Open Hardware Licence v1.1", + false, + false + ], + "CERN-OHL-1.2": [ + "CERN Open Hardware Licence v1.2", + false, + false + ], + "CERN-OHL-P-2.0": [ + "CERN Open Hardware Licence Version 2 - Permissive", + true, + false + ], + "CERN-OHL-S-2.0": [ + "CERN Open Hardware Licence Version 2 - Strongly Reciprocal", + true, + false + ], + "CERN-OHL-W-2.0": [ + "CERN Open Hardware Licence Version 2 - Weakly Reciprocal", + true, + false + ], + "CFITSIO": [ + "CFITSIO License", + false, + false + ], + "check-cvs": [ + "check-cvs License", + false, + false + ], + "checkmk": [ + "Checkmk License", + false, + false + ], + "ClArtistic": [ + "Clarified Artistic License", + false, + false + ], + "Clips": [ + "Clips License", + false, + false + ], + "CMU-Mach": [ + "CMU Mach License", + false, + false + ], + "CMU-Mach-nodoc": [ + "CMU Mach - no notices-in-documentation variant", + false, + false + ], + "CNRI-Jython": [ + "CNRI Jython License", + false, + false + ], + "CNRI-Python": [ + "CNRI Python License", + true, + false + ], + "CNRI-Python-GPL-Compatible": [ + "CNRI Python Open Source GPL Compatible License Agreement", + false, + false + ], + "COIL-1.0": [ + "Copyfree Open Innovation License", + false, + false + ], + "Community-Spec-1.0": [ + "Community Specification License 1.0", + false, + false + ], + "Condor-1.1": [ + "Condor Public License v1.1", + false, + false + ], + "copyleft-next-0.3.0": [ + "copyleft-next 0.3.0", + false, + false + ], + "copyleft-next-0.3.1": [ + "copyleft-next 0.3.1", + false, + false + ], + "Cornell-Lossless-JPEG": [ + "Cornell Lossless JPEG License", + false, + false + ], + "CPAL-1.0": [ + "Common Public Attribution License 1.0", + true, + false + ], + "CPL-1.0": [ + "Common Public License 1.0", + true, + false + ], + "CPOL-1.02": [ + "Code Project Open License 1.02", + false, + false + ], + "Cronyx": [ + "Cronyx License", + false, + false + ], + "Crossword": [ + "Crossword License", + false, + false + ], + "CryptoSwift": [ + "CryptoSwift License", + false, + false + ], + "CrystalStacker": [ + "CrystalStacker License", + false, + false + ], + "CUA-OPL-1.0": [ + "CUA Office Public License v1.0", + true, + false + ], + "Cube": [ + "Cube License", + false, + false + ], + "curl": [ + "curl License", + false, + false + ], + "cve-tou": [ + "Common Vulnerability Enumeration ToU License", + false, + false + ], + "D-FSL-1.0": [ + "Deutsche Freie Software Lizenz", + false, + false + ], + "DEC-3-Clause": [ + "DEC 3-Clause License", + false, + false + ], + "diffmark": [ + "diffmark license", + false, + false + ], + "DL-DE-BY-2.0": [ + "Data licence Germany \u2013 attribution \u2013 version 2.0", + false, + false + ], + "DL-DE-ZERO-2.0": [ + "Data licence Germany \u2013 zero \u2013 version 2.0", + false, + false + ], + "DOC": [ + "DOC License", + false, + false + ], + "DocBook-DTD": [ + "DocBook DTD License", + false, + false + ], + "DocBook-Schema": [ + "DocBook Schema License", + false, + false + ], + "DocBook-Stylesheet": [ + "DocBook Stylesheet License", + false, + false + ], + "DocBook-XML": [ + "DocBook XML License", + false, + false + ], + "Dotseqn": [ + "Dotseqn License", + false, + false + ], + "DRL-1.0": [ + "Detection Rule License 1.0", + false, + false + ], + "DRL-1.1": [ + "Detection Rule License 1.1", + false, + false + ], + "DSDP": [ + "DSDP License", + false, + false + ], + "dtoa": [ + "David M. Gay dtoa License", + false, + false + ], + "dvipdfm": [ + "dvipdfm License", + false, + false + ], + "ECL-1.0": [ + "Educational Community License v1.0", + true, + false + ], + "ECL-2.0": [ + "Educational Community License v2.0", + true, + false + ], + "eCos-2.0": [ + "eCos license version 2.0", + false, + true + ], + "EFL-1.0": [ + "Eiffel Forum License v1.0", + true, + false + ], + "EFL-2.0": [ + "Eiffel Forum License v2.0", + true, + false + ], + "eGenix": [ + "eGenix.com Public License 1.1.0", + false, + false + ], + "Elastic-2.0": [ + "Elastic License 2.0", + false, + false + ], + "Entessa": [ + "Entessa Public License v1.0", + true, + false + ], + "EPICS": [ + "EPICS Open License", + false, + false + ], + "EPL-1.0": [ + "Eclipse Public License 1.0", + true, + false + ], + "EPL-2.0": [ + "Eclipse Public License 2.0", + true, + false + ], + "ErlPL-1.1": [ + "Erlang Public License v1.1", + false, + false + ], + "ESA-PL-permissive-2.4": [ + "European Space Agency Public License \u2013 v2.4 \u2013 Permissive (Type 3)", + false, + false + ], + "ESA-PL-strong-copyleft-2.4": [ + "European Space Agency Public License (ESA-PL) - V2.4 - Strong Copyleft (Type 1)", + false, + false + ], + "ESA-PL-weak-copyleft-2.4": [ + "European Space Agency Public License \u2013 v2.4 \u2013 Weak Copyleft (Type 2)", + false, + false + ], + "etalab-2.0": [ + "Etalab Open License 2.0", + false, + false + ], + "EUDatagrid": [ + "EU DataGrid Software License", + true, + false + ], + "EUPL-1.0": [ + "European Union Public License 1.0", + false, + false + ], + "EUPL-1.1": [ + "European Union Public License 1.1", + true, + false + ], + "EUPL-1.2": [ + "European Union Public License 1.2", + true, + false + ], + "Eurosym": [ + "Eurosym License", + false, + false + ], + "Fair": [ + "Fair License", + true, + false + ], + "FBM": [ + "Fuzzy Bitmap License", + false, + false + ], + "FDK-AAC": [ + "Fraunhofer FDK AAC Codec Library", + false, + false + ], + "Ferguson-Twofish": [ + "Ferguson Twofish License", + false, + false + ], + "Frameworx-1.0": [ + "Frameworx Open License 1.0", + true, + false + ], + "FreeBSD-DOC": [ + "FreeBSD Documentation License", + false, + false + ], + "FreeImage": [ + "FreeImage Public License v1.0", + false, + false + ], + "FSFAP": [ + "FSF All Permissive License", + false, + false + ], + "FSFAP-no-warranty-disclaimer": [ + "FSF All Permissive License (without Warranty)", + false, + false + ], + "FSFUL": [ + "FSF Unlimited License", + false, + false + ], + "FSFULLR": [ + "FSF Unlimited License (with License Retention)", + false, + false + ], + "FSFULLRSD": [ + "FSF Unlimited License (with License Retention and Short Disclaimer)", + false, + false + ], + "FSFULLRWD": [ + "FSF Unlimited License (With License Retention and Warranty Disclaimer)", + false, + false + ], + "FSL-1.1-ALv2": [ + "Functional Source License, Version 1.1, ALv2 Future License", + false, + false + ], + "FSL-1.1-MIT": [ + "Functional Source License, Version 1.1, MIT Future License", + false, + false + ], + "FTL": [ + "Freetype Project License", + false, + false + ], + "Furuseth": [ + "Furuseth License", + false, + false + ], + "fwlw": [ + "fwlw License", + false, + false + ], + "Game-Programming-Gems": [ + "Game Programming Gems License", + false, + false + ], + "GCR-docs": [ + "Gnome GCR Documentation License", + false, + false + ], + "GD": [ + "GD License", + false, + false + ], + "generic-xts": [ + "Generic XTS License", + false, + false + ], + "GFDL-1.1": [ + "GNU Free Documentation License v1.1", + false, + true + ], + "GFDL-1.1-invariants-only": [ + "GNU Free Documentation License v1.1 only - invariants", + false, + false + ], + "GFDL-1.1-invariants-or-later": [ + "GNU Free Documentation License v1.1 or later - invariants", + false, + false + ], + "GFDL-1.1-no-invariants-only": [ + "GNU Free Documentation License v1.1 only - no invariants", + false, + false + ], + "GFDL-1.1-no-invariants-or-later": [ + "GNU Free Documentation License v1.1 or later - no invariants", + false, + false + ], + "GFDL-1.1-only": [ + "GNU Free Documentation License v1.1 only", + false, + false + ], + "GFDL-1.1-or-later": [ + "GNU Free Documentation License v1.1 or later", + false, + false + ], + "GFDL-1.2": [ + "GNU Free Documentation License v1.2", + false, + true + ], + "GFDL-1.2-invariants-only": [ + "GNU Free Documentation License v1.2 only - invariants", + false, + false + ], + "GFDL-1.2-invariants-or-later": [ + "GNU Free Documentation License v1.2 or later - invariants", + false, + false + ], + "GFDL-1.2-no-invariants-only": [ + "GNU Free Documentation License v1.2 only - no invariants", + false, + false + ], + "GFDL-1.2-no-invariants-or-later": [ + "GNU Free Documentation License v1.2 or later - no invariants", + false, + false + ], + "GFDL-1.2-only": [ + "GNU Free Documentation License v1.2 only", + false, + false + ], + "GFDL-1.2-or-later": [ + "GNU Free Documentation License v1.2 or later", + false, + false + ], + "GFDL-1.3": [ + "GNU Free Documentation License v1.3", + false, + true + ], + "GFDL-1.3-invariants-only": [ + "GNU Free Documentation License v1.3 only - invariants", + false, + false + ], + "GFDL-1.3-invariants-or-later": [ + "GNU Free Documentation License v1.3 or later - invariants", + false, + false + ], + "GFDL-1.3-no-invariants-only": [ + "GNU Free Documentation License v1.3 only - no invariants", + false, + false + ], + "GFDL-1.3-no-invariants-or-later": [ + "GNU Free Documentation License v1.3 or later - no invariants", + false, + false + ], + "GFDL-1.3-only": [ + "GNU Free Documentation License v1.3 only", + false, + false + ], + "GFDL-1.3-or-later": [ + "GNU Free Documentation License v1.3 or later", + false, + false + ], + "Giftware": [ + "Giftware License", + false, + false + ], + "GL2PS": [ + "GL2PS License", + false, + false + ], + "Glide": [ + "3dfx Glide License", + false, + false + ], + "Glulxe": [ + "Glulxe License", + false, + false + ], + "GLWTPL": [ + "Good Luck With That Public License", + false, + false + ], + "gnuplot": [ + "gnuplot License", + false, + false + ], + "GPL-1.0": [ + "GNU General Public License v1.0 only", + false, + true + ], + "GPL-1.0+": [ + "GNU General Public License v1.0 or later", + false, + true + ], + "GPL-1.0-only": [ + "GNU General Public License v1.0 only", + false, + false + ], + "GPL-1.0-or-later": [ + "GNU General Public License v1.0 or later", + false, + false + ], + "GPL-2.0": [ + "GNU General Public License v2.0 only", + true, + true + ], + "GPL-2.0+": [ + "GNU General Public License v2.0 or later", + true, + true + ], + "GPL-2.0-only": [ + "GNU General Public License v2.0 only", + true, + false + ], + "GPL-2.0-or-later": [ + "GNU General Public License v2.0 or later", + true, + false + ], + "GPL-2.0-with-autoconf-exception": [ + "GNU General Public License v2.0 w/Autoconf exception", + false, + true + ], + "GPL-2.0-with-bison-exception": [ + "GNU General Public License v2.0 w/Bison exception", + false, + true + ], + "GPL-2.0-with-classpath-exception": [ + "GNU General Public License v2.0 w/Classpath exception", + false, + true + ], + "GPL-2.0-with-font-exception": [ + "GNU General Public License v2.0 w/Font exception", + false, + true + ], + "GPL-2.0-with-GCC-exception": [ + "GNU General Public License v2.0 w/GCC Runtime Library exception", + false, + true + ], + "GPL-3.0": [ + "GNU General Public License v3.0 only", + true, + true + ], + "GPL-3.0+": [ + "GNU General Public License v3.0 or later", + true, + true + ], + "GPL-3.0-only": [ + "GNU General Public License v3.0 only", + true, + false + ], + "GPL-3.0-or-later": [ + "GNU General Public License v3.0 or later", + true, + false + ], + "GPL-3.0-with-autoconf-exception": [ + "GNU General Public License v3.0 w/Autoconf exception", + false, + true + ], + "GPL-3.0-with-GCC-exception": [ + "GNU General Public License v3.0 w/GCC Runtime Library exception", + true, + true + ], + "Graphics-Gems": [ + "Graphics Gems License", + false, + false + ], + "gSOAP-1.3b": [ + "gSOAP Public License v1.3b", + false, + false + ], + "gtkbook": [ + "gtkbook License", + false, + false + ], + "Gutmann": [ + "Gutmann License", + false, + false + ], + "HaskellReport": [ + "Haskell Language Report License", + false, + false + ], + "HDF5": [ + "HDF5 License", + false, + false + ], + "hdparm": [ + "hdparm License", + false, + false + ], + "HIDAPI": [ + "HIDAPI License", + false, + false + ], + "Hippocratic-2.1": [ + "Hippocratic License 2.1", + false, + false + ], + "HP-1986": [ + "Hewlett-Packard 1986 License", + false, + false + ], + "HP-1989": [ + "Hewlett-Packard 1989 License", + false, + false + ], + "HPND": [ + "Historical Permission Notice and Disclaimer", + true, + false + ], + "HPND-DEC": [ + "Historical Permission Notice and Disclaimer - DEC variant", + false, + false + ], + "HPND-doc": [ + "Historical Permission Notice and Disclaimer - documentation variant", + false, + false + ], + "HPND-doc-sell": [ + "Historical Permission Notice and Disclaimer - documentation sell variant", + false, + false + ], + "HPND-export-US": [ + "HPND with US Government export control warning", + false, + false + ], + "HPND-export-US-acknowledgement": [ + "HPND with US Government export control warning and acknowledgment", + false, + false + ], + "HPND-export-US-modify": [ + "HPND with US Government export control warning and modification rqmt", + false, + false + ], + "HPND-export2-US": [ + "HPND with US Government export control and 2 disclaimers", + false, + false + ], + "HPND-Fenneberg-Livingston": [ + "Historical Permission Notice and Disclaimer - Fenneberg-Livingston variant", + false, + false + ], + "HPND-INRIA-IMAG": [ + "Historical Permission Notice and Disclaimer - INRIA-IMAG variant", + false, + false + ], + "HPND-Intel": [ + "Historical Permission Notice and Disclaimer - Intel variant", + false, + false + ], + "HPND-Kevlin-Henney": [ + "Historical Permission Notice and Disclaimer - Kevlin Henney variant", + false, + false + ], + "HPND-Markus-Kuhn": [ + "Historical Permission Notice and Disclaimer - Markus Kuhn variant", + false, + false + ], + "HPND-merchantability-variant": [ + "Historical Permission Notice and Disclaimer - merchantability variant", + false, + false + ], + "HPND-MIT-disclaimer": [ + "Historical Permission Notice and Disclaimer with MIT disclaimer", + false, + false + ], + "HPND-Netrek": [ + "Historical Permission Notice and Disclaimer - Netrek variant", + false, + false + ], + "HPND-Pbmplus": [ + "Historical Permission Notice and Disclaimer - Pbmplus variant", + false, + false + ], + "HPND-sell-MIT-disclaimer-xserver": [ + "Historical Permission Notice and Disclaimer - sell xserver variant with MIT disclaimer", + false, + false + ], + "HPND-sell-regexpr": [ + "Historical Permission Notice and Disclaimer - sell regexpr variant", + false, + false + ], + "HPND-sell-variant": [ + "Historical Permission Notice and Disclaimer - sell variant", + false, + false + ], + "HPND-sell-variant-critical-systems": [ + "HPND - sell variant with safety critical systems clause", + false, + false + ], + "HPND-sell-variant-MIT-disclaimer": [ + "HPND sell variant with MIT disclaimer", + false, + false + ], + "HPND-sell-variant-MIT-disclaimer-rev": [ + "HPND sell variant with MIT disclaimer - reverse", + false, + false + ], + "HPND-SMC": [ + "Historical Permission Notice and Disclaimer - SMC variant", + false, + false + ], + "HPND-UC": [ + "Historical Permission Notice and Disclaimer - University of California variant", + false, + false + ], + "HPND-UC-export-US": [ + "Historical Permission Notice and Disclaimer - University of California, US export warning", + false, + false + ], + "HTMLTIDY": [ + "HTML Tidy License", + false, + false + ], + "hyphen-bulgarian": [ + "hyphen-bulgarian License", + false, + false + ], + "IBM-pibs": [ + "IBM PowerPC Initialization and Boot Software", + false, + false + ], + "ICU": [ + "ICU License", + true, + false + ], + "IEC-Code-Components-EULA": [ + "IEC Code Components End-user licence agreement", + false, + false + ], + "IJG": [ + "Independent JPEG Group License", + false, + false + ], + "IJG-short": [ + "Independent JPEG Group License - short", + false, + false + ], + "ImageMagick": [ + "ImageMagick License", + false, + false + ], + "iMatix": [ + "iMatix Standard Function Library Agreement", + false, + false + ], + "Imlib2": [ + "Imlib2 License", + false, + false + ], + "Info-ZIP": [ + "Info-ZIP License", + false, + false + ], + "Inner-Net-2.0": [ + "Inner Net License v2.0", + false, + false + ], + "InnoSetup": [ + "Inno Setup License", + false, + false + ], + "Intel": [ + "Intel Open Source License", + true, + false + ], + "Intel-ACPI": [ + "Intel ACPI Software License Agreement", + false, + false + ], + "Interbase-1.0": [ + "Interbase Public License v1.0", + false, + false + ], + "IPA": [ + "IPA Font License", + true, + false + ], + "IPL-1.0": [ + "IBM Public License v1.0", + true, + false + ], + "ISC": [ + "ISC License", + true, + false + ], + "ISC-Veillard": [ + "ISC Veillard variant", + false, + false + ], + "ISO-permission": [ + "ISO permission notice", + false, + false + ], + "Jam": [ + "Jam License", + true, + false + ], + "JasPer-2.0": [ + "JasPer License", + false, + false + ], + "jove": [ + "Jove License", + false, + false + ], + "JPL-image": [ + "JPL Image Use Policy", + false, + false + ], + "JPNIC": [ + "Japan Network Information Center License", + false, + false + ], + "JSON": [ + "JSON License", + false, + false + ], + "Kastrup": [ + "Kastrup License", + false, + false + ], + "Kazlib": [ + "Kazlib License", + false, + false + ], + "Knuth-CTAN": [ + "Knuth CTAN License", + false, + false + ], + "LAL-1.2": [ + "Licence Art Libre 1.2", + false, + false + ], + "LAL-1.3": [ + "Licence Art Libre 1.3", + false, + false + ], + "Latex2e": [ + "Latex2e License", + false, + false + ], + "Latex2e-translated-notice": [ + "Latex2e with translated notice permission", + false, + false + ], + "Leptonica": [ + "Leptonica License", + false, + false + ], + "LGPL-2.0": [ + "GNU Library General Public License v2 only", + true, + true + ], + "LGPL-2.0+": [ + "GNU Library General Public License v2 or later", + true, + true + ], + "LGPL-2.0-only": [ + "GNU Library General Public License v2 only", + true, + false + ], + "LGPL-2.0-or-later": [ + "GNU Library General Public License v2 or later", + true, + false + ], + "LGPL-2.1": [ + "GNU Lesser General Public License v2.1 only", + true, + true + ], + "LGPL-2.1+": [ + "GNU Lesser General Public License v2.1 or later", + true, + true + ], + "LGPL-2.1-only": [ + "GNU Lesser General Public License v2.1 only", + true, + false + ], + "LGPL-2.1-or-later": [ + "GNU Lesser General Public License v2.1 or later", + true, + false + ], + "LGPL-3.0": [ + "GNU Lesser General Public License v3.0 only", + true, + true + ], + "LGPL-3.0+": [ + "GNU Lesser General Public License v3.0 or later", + true, + true + ], + "LGPL-3.0-only": [ + "GNU Lesser General Public License v3.0 only", + true, + false + ], + "LGPL-3.0-or-later": [ + "GNU Lesser General Public License v3.0 or later", + true, + false + ], + "LGPLLR": [ + "Lesser General Public License For Linguistic Resources", + false, + false + ], + "Libpng": [ + "libpng License", + false, + false + ], + "libpng-1.6.35": [ + "PNG Reference Library License v1 (for libpng 0.5 through 1.6.35)", + false, + false + ], + "libpng-2.0": [ + "PNG Reference Library version 2", + false, + false + ], + "libselinux-1.0": [ + "libselinux public domain notice", + false, + false + ], + "libtiff": [ + "libtiff License", + false, + false + ], + "libutil-David-Nugent": [ + "libutil David Nugent License", + false, + false + ], + "LiLiQ-P-1.1": [ + "Licence Libre du Qu\u00e9bec \u2013 Permissive version 1.1", + true, + false + ], + "LiLiQ-R-1.1": [ + "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 version 1.1", + true, + false + ], + "LiLiQ-Rplus-1.1": [ + "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 forte version 1.1", + true, + false + ], + "Linux-man-pages-1-para": [ + "Linux man-pages - 1 paragraph", + false, + false + ], + "Linux-man-pages-copyleft": [ + "Linux man-pages Copyleft", + false, + false + ], + "Linux-man-pages-copyleft-2-para": [ + "Linux man-pages Copyleft - 2 paragraphs", + false, + false + ], + "Linux-man-pages-copyleft-var": [ + "Linux man-pages Copyleft Variant", + false, + false + ], + "Linux-OpenIB": [ + "Linux Kernel Variant of OpenIB.org license", + false, + false + ], + "LOOP": [ + "Common Lisp LOOP License", + false, + false + ], + "LPD-document": [ + "LPD Documentation License", + false, + false + ], + "LPL-1.0": [ + "Lucent Public License Version 1.0", + true, + false + ], + "LPL-1.02": [ + "Lucent Public License v1.02", + true, + false + ], + "LPPL-1.0": [ + "LaTeX Project Public License v1.0", + false, + false + ], + "LPPL-1.1": [ + "LaTeX Project Public License v1.1", + false, + false + ], + "LPPL-1.2": [ + "LaTeX Project Public License v1.2", + false, + false + ], + "LPPL-1.3a": [ + "LaTeX Project Public License v1.3a", + false, + false + ], + "LPPL-1.3c": [ + "LaTeX Project Public License v1.3c", + true, + false + ], + "lsof": [ + "lsof License", + false, + false + ], + "Lucida-Bitmap-Fonts": [ + "Lucida Bitmap Fonts License", + false, + false + ], + "LZMA-SDK-9.11-to-9.20": [ + "LZMA SDK License (versions 9.11 to 9.20)", + false, + false + ], + "LZMA-SDK-9.22": [ + "LZMA SDK License (versions 9.22 and beyond)", + false, + false + ], + "Mackerras-3-Clause": [ + "Mackerras 3-Clause License", + false, + false + ], + "Mackerras-3-Clause-acknowledgment": [ + "Mackerras 3-Clause - acknowledgment variant", + false, + false + ], + "magaz": [ + "magaz License", + false, + false + ], + "mailprio": [ + "mailprio License", + false, + false + ], + "MakeIndex": [ + "MakeIndex License", + false, + false + ], + "man2html": [ + "man2html License", + false, + false + ], + "Martin-Birgmeier": [ + "Martin Birgmeier License", + false, + false + ], + "McPhee-slideshow": [ + "McPhee Slideshow License", + false, + false + ], + "metamail": [ + "metamail License", + false, + false + ], + "Minpack": [ + "Minpack License", + false, + false + ], + "MIPS": [ + "MIPS License", + false, + false + ], + "MirOS": [ + "The MirOS Licence", + true, + false + ], + "MIT": [ + "MIT License", + true, + false + ], + "MIT-0": [ + "MIT No Attribution", + true, + false + ], + "MIT-advertising": [ + "Enlightenment License (e16)", + false, + false + ], + "MIT-Click": [ + "MIT Click License", + false, + false + ], + "MIT-CMU": [ + "CMU License", + false, + false + ], + "MIT-enna": [ + "enna License", + false, + false + ], + "MIT-feh": [ + "feh License", + false, + false + ], + "MIT-Festival": [ + "MIT Festival Variant", + false, + false + ], + "MIT-Khronos-old": [ + "MIT Khronos - old variant", + false, + false + ], + "MIT-Modern-Variant": [ + "MIT License Modern Variant", + true, + false + ], + "MIT-open-group": [ + "MIT Open Group variant", + false, + false + ], + "MIT-STK": [ + "MIT-STK License", + false, + false + ], + "MIT-testregex": [ + "MIT testregex Variant", + false, + false + ], + "MIT-Wu": [ + "MIT Tom Wu Variant", + false, + false + ], + "MITNFA": [ + "MIT +no-false-attribs license", + false, + false + ], + "MMIXware": [ + "MMIXware License", + false, + false + ], + "MMPL-1.0.1": [ + "Minecraft Mod Public License v1.0.1", + false, + false + ], + "Motosoto": [ + "Motosoto License", + true, + false + ], + "MPEG-SSG": [ + "MPEG Software Simulation", + false, + false + ], + "mpi-permissive": [ + "mpi Permissive License", + false, + false + ], + "mpich2": [ + "mpich2 License", + false, + false + ], + "MPL-1.0": [ + "Mozilla Public License 1.0", + true, + false + ], + "MPL-1.1": [ + "Mozilla Public License 1.1", + true, + false + ], + "MPL-2.0": [ + "Mozilla Public License 2.0", + true, + false + ], + "MPL-2.0-no-copyleft-exception": [ + "Mozilla Public License 2.0 (no copyleft exception)", + true, + false + ], + "mplus": [ + "mplus Font License", + false, + false + ], + "MS-LPL": [ + "Microsoft Limited Public License", + false, + false + ], + "MS-PL": [ + "Microsoft Public License", + true, + false + ], + "MS-RL": [ + "Microsoft Reciprocal License", + true, + false + ], + "MTLL": [ + "Matrix Template Library License", + false, + false + ], + "MulanPSL-1.0": [ + "Mulan Permissive Software License, Version 1", + false, + false + ], + "MulanPSL-2.0": [ + "Mulan Permissive Software License, Version 2", + true, + false + ], + "Multics": [ + "Multics License", + true, + false + ], + "Mup": [ + "Mup License", + false, + false + ], + "NAIST-2003": [ + "Nara Institute of Science and Technology License (2003)", + false, + false + ], + "NASA-1.3": [ + "NASA Open Source Agreement 1.3", + true, + false + ], + "Naumen": [ + "Naumen Public License", + true, + false + ], + "NBPL-1.0": [ + "Net Boolean Public License v1", + false, + false + ], + "NCBI-PD": [ + "NCBI Public Domain Notice", + false, + false + ], + "NCGL-UK-2.0": [ + "Non-Commercial Government Licence", + false, + false + ], + "NCL": [ + "NCL Source Code License", + false, + false + ], + "NCSA": [ + "University of Illinois/NCSA Open Source License", + true, + false + ], + "Net-SNMP": [ + "Net-SNMP License", + false, + true + ], + "NetCDF": [ + "NetCDF license", + false, + false + ], + "Newsletr": [ + "Newsletr License", + false, + false + ], + "NGPL": [ + "Nethack General Public License", + true, + false + ], + "ngrep": [ + "ngrep License", + false, + false + ], + "NICTA-1.0": [ + "NICTA Public Software License, Version 1.0", + false, + false + ], + "NIST-PD": [ + "NIST Public Domain Notice", + false, + false + ], + "NIST-PD-fallback": [ + "NIST Public Domain Notice with license fallback", + false, + false + ], + "NIST-PD-TNT": [ + "NIST Public Domain Notice TNT variant", + false, + false + ], + "NIST-Software": [ + "NIST Software License", + false, + false + ], + "NLOD-1.0": [ + "Norwegian Licence for Open Government Data (NLOD) 1.0", + false, + false + ], + "NLOD-2.0": [ + "Norwegian Licence for Open Government Data (NLOD) 2.0", + false, + false + ], + "NLPL": [ + "No Limit Public License", + false, + false + ], + "Nokia": [ + "Nokia Open Source License", + true, + false + ], + "NOSL": [ + "Netizen Open Source License", + false, + false + ], + "Noweb": [ + "Noweb License", + false, + false + ], + "NPL-1.0": [ + "Netscape Public License v1.0", + false, + false + ], + "NPL-1.1": [ + "Netscape Public License v1.1", + false, + false + ], + "NPOSL-3.0": [ + "Non-Profit Open Software License 3.0", + true, + false + ], + "NRL": [ + "NRL License", + false, + false + ], + "NTIA-PD": [ + "NTIA Public Domain Notice", + false, + false + ], + "NTP": [ + "NTP License", + true, + false + ], + "NTP-0": [ + "NTP No Attribution", + false, + false + ], + "Nunit": [ + "Nunit License", + false, + true + ], + "O-UDA-1.0": [ + "Open Use of Data Agreement v1.0", + false, + false + ], + "OAR": [ + "OAR License", + false, + false + ], + "OCCT-PL": [ + "Open CASCADE Technology Public License", + false, + false + ], + "OCLC-2.0": [ + "OCLC Research Public License 2.0", + true, + false + ], + "ODbL-1.0": [ + "Open Data Commons Open Database License v1.0", + false, + false + ], + "ODC-By-1.0": [ + "Open Data Commons Attribution License v1.0", + false, + false + ], + "OFFIS": [ + "OFFIS License", + false, + false + ], + "OFL-1.0": [ + "SIL Open Font License 1.0", + false, + false + ], + "OFL-1.0-no-RFN": [ + "SIL Open Font License 1.0 with no Reserved Font Name", + false, + false + ], + "OFL-1.0-RFN": [ + "SIL Open Font License 1.0 with Reserved Font Name", + false, + false + ], + "OFL-1.1": [ + "SIL Open Font License 1.1", + true, + false + ], + "OFL-1.1-no-RFN": [ + "SIL Open Font License 1.1 with no Reserved Font Name", + true, + false + ], + "OFL-1.1-RFN": [ + "SIL Open Font License 1.1 with Reserved Font Name", + true, + false + ], + "OGC-1.0": [ + "OGC Software License, Version 1.0", + false, + false + ], + "OGDL-Taiwan-1.0": [ + "Taiwan Open Government Data License, version 1.0", + false, + false + ], + "OGL-Canada-2.0": [ + "Open Government Licence - Canada", + false, + false + ], + "OGL-UK-1.0": [ + "Open Government Licence v1.0", + false, + false + ], + "OGL-UK-2.0": [ + "Open Government Licence v2.0", + false, + false + ], + "OGL-UK-3.0": [ + "Open Government Licence v3.0", + false, + false + ], + "OGTSL": [ + "Open Group Test Suite License", + true, + false + ], + "OLDAP-1.1": [ + "Open LDAP Public License v1.1", + false, + false + ], + "OLDAP-1.2": [ + "Open LDAP Public License v1.2", + false, + false + ], + "OLDAP-1.3": [ + "Open LDAP Public License v1.3", + false, + false + ], + "OLDAP-1.4": [ + "Open LDAP Public License v1.4", + false, + false + ], + "OLDAP-2.0": [ + "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", + false, + false + ], + "OLDAP-2.0.1": [ + "Open LDAP Public License v2.0.1", + false, + false + ], + "OLDAP-2.1": [ + "Open LDAP Public License v2.1", + false, + false + ], + "OLDAP-2.2": [ + "Open LDAP Public License v2.2", + false, + false + ], + "OLDAP-2.2.1": [ + "Open LDAP Public License v2.2.1", + false, + false + ], + "OLDAP-2.2.2": [ + "Open LDAP Public License 2.2.2", + false, + false + ], + "OLDAP-2.3": [ + "Open LDAP Public License v2.3", + false, + false + ], + "OLDAP-2.4": [ + "Open LDAP Public License v2.4", + false, + false + ], + "OLDAP-2.5": [ + "Open LDAP Public License v2.5", + false, + false + ], + "OLDAP-2.6": [ + "Open LDAP Public License v2.6", + false, + false + ], + "OLDAP-2.7": [ + "Open LDAP Public License v2.7", + false, + false + ], + "OLDAP-2.8": [ + "Open LDAP Public License v2.8", + true, + false + ], + "OLFL-1.3": [ + "Open Logistics Foundation License Version 1.3", + true, + false + ], + "OML": [ + "Open Market License", + false, + false + ], + "OpenMDW-1.0": [ + "OpenMDW License Agreement v1.0", + false, + false + ], + "OpenPBS-2.3": [ + "OpenPBS v2.3 Software License", + false, + false + ], + "OpenSSL": [ + "OpenSSL License", + false, + false + ], + "OpenSSL-standalone": [ + "OpenSSL License - standalone", + false, + false + ], + "OpenVision": [ + "OpenVision License", + false, + false + ], + "OPL-1.0": [ + "Open Public License v1.0", + false, + false + ], + "OPL-UK-3.0": [ + "United Kingdom Open Parliament Licence v3.0", + false, + false + ], + "OPUBL-1.0": [ + "Open Publication License v1.0", + false, + false + ], + "OSC-1.0": [ + "OSC License 1.0", + true, + false + ], + "OSET-PL-2.1": [ + "OSET Public License version 2.1", + true, + false + ], + "OSL-1.0": [ + "Open Software License 1.0", + true, + false + ], + "OSL-1.1": [ + "Open Software License 1.1", + false, + false + ], + "OSL-2.0": [ + "Open Software License 2.0", + true, + false + ], + "OSL-2.1": [ + "Open Software License 2.1", + true, + false + ], + "OSL-3.0": [ + "Open Software License 3.0", + true, + false + ], + "OSSP": [ + "OSSP License", + false, + false + ], + "PADL": [ + "PADL License", + false, + false + ], + "ParaType-Free-Font-1.3": [ + "ParaType Free Font Licensing Agreement v1.3", + false, + false + ], + "Parity-6.0.0": [ + "The Parity Public License 6.0.0", + false, + false + ], + "Parity-7.0.0": [ + "The Parity Public License 7.0.0", + false, + false + ], + "PDDL-1.0": [ + "Open Data Commons Public Domain Dedication & License 1.0", + false, + false + ], + "PHP-3.0": [ + "PHP License v3.0", + true, + false + ], + "PHP-3.01": [ + "PHP License v3.01", + true, + false + ], + "Pixar": [ + "Pixar License", + false, + false + ], + "pkgconf": [ + "pkgconf License", + false, + false + ], + "Plexus": [ + "Plexus Classworlds License", + false, + false + ], + "pnmstitch": [ + "pnmstitch License", + false, + false + ], + "PolyForm-Noncommercial-1.0.0": [ + "PolyForm Noncommercial License 1.0.0", + false, + false + ], + "PolyForm-Small-Business-1.0.0": [ + "PolyForm Small Business License 1.0.0", + false, + false + ], + "PostgreSQL": [ + "PostgreSQL License", + true, + false + ], + "PPL": [ + "Peer Production License", + false, + false + ], + "PSF-2.0": [ + "Python Software Foundation License 2.0", + false, + false + ], + "psfrag": [ + "psfrag License", + false, + false + ], + "psutils": [ + "psutils License", + false, + false + ], + "Python-2.0": [ + "Python License 2.0", + true, + false + ], + "Python-2.0.1": [ + "Python License 2.0.1", + false, + false + ], + "python-ldap": [ + "Python ldap License", + false, + false + ], + "Qhull": [ + "Qhull License", + false, + false + ], + "QPL-1.0": [ + "Q Public License 1.0", + true, + false + ], + "QPL-1.0-INRIA-2004": [ + "Q Public License 1.0 - INRIA 2004 variant", + false, + false + ], + "radvd": [ + "radvd License", + false, + false + ], + "Rdisc": [ + "Rdisc License", + false, + false + ], + "RHeCos-1.1": [ + "Red Hat eCos Public License v1.1", + false, + false + ], + "RPL-1.1": [ + "Reciprocal Public License 1.1", + true, + false + ], + "RPL-1.5": [ + "Reciprocal Public License 1.5", + true, + false + ], + "RPSL-1.0": [ + "RealNetworks Public Source License v1.0", + true, + false + ], + "RSA-MD": [ + "RSA Message-Digest License", + false, + false + ], + "RSCPL": [ + "Ricoh Source Code Public License", + true, + false + ], + "Ruby": [ + "Ruby License", + false, + false + ], + "Ruby-pty": [ + "Ruby pty extension license", + false, + false + ], + "SAX-PD": [ + "Sax Public Domain Notice", + false, + false + ], + "SAX-PD-2.0": [ + "Sax Public Domain Notice 2.0", + false, + false + ], + "Saxpath": [ + "Saxpath License", + false, + false + ], + "SCEA": [ + "SCEA Shared Source License", + false, + false + ], + "SchemeReport": [ + "Scheme Language Report License", + false, + false + ], + "Sendmail": [ + "Sendmail License", + false, + false + ], + "Sendmail-8.23": [ + "Sendmail License 8.23", + false, + false + ], + "Sendmail-Open-Source-1.1": [ + "Sendmail Open Source License v1.1", + false, + false + ], + "SGI-B-1.0": [ + "SGI Free Software License B v1.0", + false, + false + ], + "SGI-B-1.1": [ + "SGI Free Software License B v1.1", + false, + false + ], + "SGI-B-2.0": [ + "SGI Free Software License B v2.0", + false, + false + ], + "SGI-OpenGL": [ + "SGI OpenGL License", + false, + false + ], + "SGMLUG-PM": [ + "SGMLUG Parser Materials License", + false, + false + ], + "SGP4": [ + "SGP4 Permission Notice", + false, + false + ], + "SHL-0.5": [ + "Solderpad Hardware License v0.5", + false, + false + ], + "SHL-0.51": [ + "Solderpad Hardware License, Version 0.51", + false, + false + ], + "SimPL-2.0": [ + "Simple Public License 2.0", + true, + false + ], + "SISSL": [ + "Sun Industry Standards Source License v1.1", + true, + false + ], + "SISSL-1.2": [ + "Sun Industry Standards Source License v1.2", + false, + false + ], + "SL": [ + "SL License", + false, + false + ], + "Sleepycat": [ + "Sleepycat License", + true, + false + ], + "SMAIL-GPL": [ + "SMAIL General Public License", + false, + false + ], + "SMLNJ": [ + "Standard ML of New Jersey License", + false, + false + ], + "SMPPL": [ + "Secure Messaging Protocol Public License", + false, + false + ], + "SNIA": [ + "SNIA Public License 1.1", + false, + false + ], + "snprintf": [ + "snprintf License", + false, + false + ], + "SOFA": [ + "SOFA Software License", + false, + false + ], + "softSurfer": [ + "softSurfer License", + false, + false + ], + "Soundex": [ + "Soundex License", + false, + false + ], + "Spencer-86": [ + "Spencer License 86", + false, + false + ], + "Spencer-94": [ + "Spencer License 94", + false, + false + ], + "Spencer-99": [ + "Spencer License 99", + false, + false + ], + "SPL-1.0": [ + "Sun Public License v1.0", + true, + false + ], + "ssh-keyscan": [ + "ssh-keyscan License", + false, + false + ], + "SSH-OpenSSH": [ + "SSH OpenSSH license", + false, + false + ], + "SSH-short": [ + "SSH short notice", + false, + false + ], + "SSLeay-standalone": [ + "SSLeay License - standalone", + false, + false + ], + "SSPL-1.0": [ + "Server Side Public License, v 1", + false, + false + ], + "StandardML-NJ": [ + "Standard ML of New Jersey License", + false, + true + ], + "SugarCRM-1.1.3": [ + "SugarCRM Public License v1.1.3", + false, + false + ], + "SUL-1.0": [ + "Sustainable Use License v1.0", + false, + false + ], + "Sun-PPP": [ + "Sun PPP License", + false, + false + ], + "Sun-PPP-2000": [ + "Sun PPP License (2000)", + false, + false + ], + "SunPro": [ + "SunPro License", + false, + false + ], + "SWL": [ + "Scheme Widget Library (SWL) Software License Agreement", + false, + false + ], + "swrule": [ + "swrule License", + false, + false + ], + "Symlinks": [ + "Symlinks License", + false, + false + ], + "TAPR-OHL-1.0": [ + "TAPR Open Hardware License v1.0", + false, + false + ], + "TCL": [ + "TCL/TK License", + false, + false + ], + "TCP-wrappers": [ + "TCP Wrappers License", + false, + false + ], + "TekHVC": [ + "TekHVC License", + false, + false + ], + "TermReadKey": [ + "TermReadKey License", + false, + false + ], + "TGPPL-1.0": [ + "Transitive Grace Period Public Licence 1.0", + false, + false + ], + "ThirdEye": [ + "ThirdEye License", + false, + false + ], + "threeparttable": [ + "threeparttable License", + false, + false + ], + "TMate": [ + "TMate Open Source License", + false, + false + ], + "TORQUE-1.1": [ + "TORQUE v2.5+ Software License v1.1", + false, + false + ], + "TOSL": [ + "Trusster Open Source License", + false, + false + ], + "TPDL": [ + "Time::ParseDate License", + false, + false + ], + "TPL-1.0": [ + "THOR Public License 1.0", + false, + false + ], + "TrustedQSL": [ + "TrustedQSL License", + false, + false + ], + "TTWL": [ + "Text-Tabs+Wrap License", + false, + false + ], + "TTYP0": [ + "TTYP0 License", + false, + false + ], + "TU-Berlin-1.0": [ + "Technische Universitaet Berlin License 1.0", + false, + false + ], + "TU-Berlin-2.0": [ + "Technische Universitaet Berlin License 2.0", + false, + false + ], + "Ubuntu-font-1.0": [ + "Ubuntu Font Licence v1.0", + false, + false + ], + "UCAR": [ + "UCAR License", + false, + false + ], + "UCL-1.0": [ + "Upstream Compatibility License v1.0", + true, + false + ], + "ulem": [ + "ulem License", + false, + false + ], + "UMich-Merit": [ + "Michigan/Merit Networks License", + false, + false + ], + "Unicode-3.0": [ + "Unicode License v3", + true, + false + ], + "Unicode-DFS-2015": [ + "Unicode License Agreement - Data Files and Software (2015)", + false, + false + ], + "Unicode-DFS-2016": [ + "Unicode License Agreement - Data Files and Software (2016)", + true, + false + ], + "Unicode-TOU": [ + "Unicode Terms of Use", + false, + false + ], + "UnixCrypt": [ + "UnixCrypt License", + false, + false + ], + "Unlicense": [ + "The Unlicense", + true, + false + ], + "Unlicense-libtelnet": [ + "Unlicense - libtelnet variant", + false, + false + ], + "Unlicense-libwhirlpool": [ + "Unlicense - libwhirlpool variant", + false, + false + ], + "UnRAR": [ + "UnRAR License", + false, + false + ], + "UPL-1.0": [ + "Universal Permissive License v1.0", + true, + false + ], + "URT-RLE": [ + "Utah Raster Toolkit Run Length Encoded License", + false, + false + ], + "Vim": [ + "Vim License", + false, + false + ], + "Vixie-Cron": [ + "Vixie Cron License", + false, + false + ], + "VOSTROM": [ + "VOSTROM Public License for Open Source", + false, + false + ], + "VSL-1.0": [ + "Vovida Software License v1.0", + true, + false + ], + "W3C": [ + "W3C Software Notice and License (2002-12-31)", + true, + false + ], + "W3C-19980720": [ + "W3C Software Notice and License (1998-07-20)", + false, + false + ], + "W3C-20150513": [ + "W3C Software Notice and Document License (2015-05-13)", + true, + false + ], + "w3m": [ + "w3m License", + false, + false + ], + "Watcom-1.0": [ + "Sybase Open Watcom Public License 1.0", + true, + false + ], + "Widget-Workshop": [ + "Widget Workshop License", + false, + false + ], + "WordNet": [ + "WordNet License", + true, + false + ], + "Wsuipa": [ + "Wsuipa License", + false, + false + ], + "WTFNMFPL": [ + "Do What The F*ck You Want To But It's Not My Fault Public License", + false, + false + ], + "WTFPL": [ + "Do What The F*ck You Want To Public License", + false, + false + ], + "wwl": [ + "WWL License", + false, + false + ], + "wxWindows": [ + "wxWindows Library License", + true, + true + ], + "X11": [ + "X11 License", + false, + false + ], + "X11-distribute-modifications-variant": [ + "X11 License Distribution Modification Variant", + false, + false + ], + "X11-no-permit-persons": [ + "X11 no permit persons clause", + false, + false + ], + "X11-swapped": [ + "X11 swapped final paragraphs", + false, + false + ], + "Xdebug-1.03": [ + "Xdebug License v 1.03", + false, + false + ], + "Xerox": [ + "Xerox License", + false, + false + ], + "Xfig": [ + "Xfig License", + false, + false + ], + "XFree86-1.1": [ + "XFree86 License 1.1", + false, + false + ], + "xinetd": [ + "xinetd License", + false, + false + ], + "xkeyboard-config-Zinoviev": [ + "xkeyboard-config Zinoviev License", + false, + false + ], + "xlock": [ + "xlock License", + false, + false + ], + "Xnet": [ + "X.Net License", + true, + false + ], + "xpp": [ + "XPP License", + false, + false + ], + "XSkat": [ + "XSkat License", + false, + false + ], + "xzoom": [ + "xzoom License", + false, + false + ], + "YPL-1.0": [ + "Yahoo! Public License v1.0", + false, + false + ], + "YPL-1.1": [ + "Yahoo! Public License v1.1", + false, + false + ], + "Zed": [ + "Zed License", + false, + false + ], + "Zeeff": [ + "Zeeff License", + false, + false + ], + "Zend-2.0": [ + "Zend License v2.0", + false, + false + ], + "Zimbra-1.3": [ + "Zimbra Public License v1.3", + false, + false + ], + "Zimbra-1.4": [ + "Zimbra Public License v1.4", + false, + false + ], + "Zlib": [ + "zlib License", + true, + false + ], + "zlib-acknowledgement": [ + "zlib/libpng License with Acknowledgement", + false, + false + ], + "ZPL-1.1": [ + "Zope Public License 1.1", + false, + false + ], + "ZPL-2.0": [ + "Zope Public License 2.0", + true, + false + ], + "ZPL-2.1": [ + "Zope Public License 2.1", + true, + false + ] +} \ No newline at end of file diff --git a/vendor/composer/spdx-licenses/src/SpdxLicenses.php b/vendor/composer/spdx-licenses/src/SpdxLicenses.php new file mode 100644 index 000000000..312219888 --- /dev/null +++ b/vendor/composer/spdx-licenses/src/SpdxLicenses.php @@ -0,0 +1,357 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Spdx; + +class SpdxLicenses +{ + /** @var string */ + public const LICENSES_FILE = 'spdx-licenses.json'; + + /** @var string */ + public const EXCEPTIONS_FILE = 'spdx-exceptions.json'; + + /** + * Contains all the licenses. + * + * The array is indexed by license identifiers, which contain + * a numerically indexed array with license details. + * + * [ lowercased license identifier => + * [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ] + * , ... + * ] + * + * @var array + */ + private $licenses; + + /** + * @var string + */ + private $licensesExpression; + + /** + * Contains all the license exceptions. + * + * The array is indexed by license exception identifiers, which contain + * a numerically indexed array with license exception details. + * + * [ lowercased exception identifier => + * [ 0 => exception identifier (string), 1 => full name (string) ] + * , ... + * ] + * + * @var array + */ + private $exceptions; + + /** + * @var string + */ + private $exceptionsExpression; + + public function __construct() + { + $this->loadLicenses(); + $this->loadExceptions(); + } + + /** + * Returns license metadata by license identifier. + * + * This function adds a link to the full license text to the license metadata. + * The array returned is in the form of: + * + * [ 0 => full name (string), 1 => osi certified, 2 => link to license text (string), 3 => deprecation status (bool) ] + * + * @param string $identifier + * + * @return array{0: string, 1: bool, 2: string, 3: bool}|null + */ + public function getLicenseByIdentifier($identifier) + { + $key = strtolower($identifier); + + if (!isset($this->licenses[$key])) { + return null; + } + + [$identifier, $name, $isOsiApproved, $isDeprecatedLicenseId] = $this->licenses[$key]; + + return [ + $name, + $isOsiApproved, + 'https://spdx.org/licenses/' . $identifier . '.html#licenseText', + $isDeprecatedLicenseId, + ]; + } + + /** + * Returns all licenses information, keyed by the lowercased license identifier. + * + * @return array{0: string, 1: string, 2: bool, 3: bool}[] Each item is [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ] + */ + public function getLicenses() + { + return $this->licenses; + } + + /** + * Returns license exception metadata by license exception identifier. + * + * This function adds a link to the full license exception text to the license exception metadata. + * The array returned is in the form of: + * + * [ 0 => full name (string), 1 => link to license text (string) ] + * + * @param string $identifier + * + * @return array{0: string, 1: string}|null + */ + public function getExceptionByIdentifier($identifier) + { + $key = strtolower($identifier); + + if (!isset($this->exceptions[$key])) { + return null; + } + + [$identifier, $name] = $this->exceptions[$key]; + + return [ + $name, + 'https://spdx.org/licenses/' . $identifier . '.html#licenseExceptionText', + ]; + } + + /** + * Returns the short identifier of a license (or license exception) by full name. + * + * @param string $name + * + * @return string|null + */ + public function getIdentifierByName($name) + { + foreach ($this->licenses as $licenseData) { + if ($licenseData[1] === $name) { + return $licenseData[0]; + } + } + + foreach ($this->exceptions as $licenseData) { + if ($licenseData[1] === $name) { + return $licenseData[0]; + } + } + + return null; + } + + /** + * Returns the OSI Approved status for a license by identifier. + * + * @param string $identifier + * + * @return bool + */ + public function isOsiApprovedByIdentifier($identifier) + { + return $this->licenses[strtolower($identifier)][2]; + } + + /** + * Returns the deprecation status for a license by identifier. + * + * @param string $identifier + * + * @return bool + */ + public function isDeprecatedByIdentifier($identifier) + { + return $this->licenses[strtolower($identifier)][3]; + } + + /** + * @param string[]|string $license + * + * @throws \InvalidArgumentException + * + * @return bool + */ + public function validate($license) + { + if (is_array($license)) { + $count = count($license); + if ($count !== count(array_filter($license, 'is_string'))) { + throw new \InvalidArgumentException('Array of strings expected.'); + } + $license = $count > 1 ? '(' . implode(' OR ', $license) . ')' : (string) reset($license); + } + + if (!is_string($license)) { + throw new \InvalidArgumentException(sprintf( + 'Array or String expected, %s given.', + gettype($license) + )); + } + + return $this->isValidLicenseString($license); + } + + /** + * @return string + */ + public static function getResourcesDir() + { + return dirname(__DIR__) . '/res'; + } + + /** + * @return void + */ + private function loadLicenses() + { + if (null !== $this->licenses) { + return; + } + + $json = file_get_contents(self::getResourcesDir() . '/' . self::LICENSES_FILE); + if (false === $json) { + throw new \RuntimeException('Missing license file in ' . self::getResourcesDir() . '/' . self::LICENSES_FILE); + } + $this->licenses = []; + + foreach (json_decode($json, true) as $identifier => $license) { + $this->licenses[strtolower($identifier)] = [$identifier, $license[0], $license[1], $license[2]]; + } + } + + /** + * @return void + */ + private function loadExceptions() + { + if (null !== $this->exceptions) { + return; + } + + $json = file_get_contents(self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE); + if (false === $json) { + throw new \RuntimeException('Missing exceptions file in ' . self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE); + } + $this->exceptions = []; + + foreach (json_decode($json, true) as $identifier => $exception) { + $this->exceptions[strtolower($identifier)] = [$identifier, $exception[0]]; + } + } + + /** + * @return string + */ + private function getLicensesExpression() + { + if (null === $this->licensesExpression) { + $licenses = array_map('preg_quote', array_keys($this->licenses)); + rsort($licenses); + $licenses = implode('|', $licenses); + $this->licensesExpression = $licenses; + } + + return $this->licensesExpression; + } + + /** + * @return string + */ + private function getExceptionsExpression() + { + if (null === $this->exceptionsExpression) { + $exceptions = array_map('preg_quote', array_keys($this->exceptions)); + rsort($exceptions); + $exceptions = implode('|', $exceptions); + $this->exceptionsExpression = $exceptions; + } + + return $this->exceptionsExpression; + } + + /** + * @param string $license + * + * @throws \RuntimeException + * + * @return bool + */ + private function isValidLicenseString($license) + { + if (isset($this->licenses[strtolower($license)])) { + return true; + } + + $licenses = $this->getLicensesExpression(); + $exceptions = $this->getExceptionsExpression(); + + $regex = <<[\pL\pN.-]{1,}) + + # license-id: taken from list + (?{$licenses}) + + # license-exception-id: taken from list + (?{$exceptions}) + + # license-ref: [DocumentRef-1*(idstring):]LicenseRef-1*(idstring) + (?(?:DocumentRef-(?&idstring):)?LicenseRef-(?&idstring)) + + # simple-expresssion: license-id / license-id+ / license-ref + (?(?&licenseid)\+? | (?&licenseid) | (?&licenseref)) + + # compound-expression: 1*( + # simple-expression / + # simple-expression WITH license-exception-id / + # compound-expression AND compound-expression / + # compound-expression OR compound-expression + # ) / ( compound-expression ) ) + (? + (?&simple_expression) ( \s+ WITH \s+ (?&licenseexceptionid))? + | \( \s* (?&compound_expression) \s* \) + ) + (? + (?&compound_head) (?: \s+ (?:AND|OR) \s+ (?&compound_expression))? + ) + + # license-expression: 1*1(simple-expression / compound-expression) + (?(?&compound_expression) | (?&simple_expression)) +) # end of define + +^(NONE | NOASSERTION | (?&license_expression))$ +}xi +REGEX; + + $match = preg_match($regex, $license); + + if (0 === $match) { + return false; + } + + if (false === $match) { + throw new \RuntimeException('Regex failed to compile/run.'); + } + + return true; + } +} diff --git a/vendor/composer/xdebug-handler/CHANGELOG.md b/vendor/composer/xdebug-handler/CHANGELOG.md new file mode 100644 index 000000000..62ebe223a --- /dev/null +++ b/vendor/composer/xdebug-handler/CHANGELOG.md @@ -0,0 +1,143 @@ +## [Unreleased] + +## [3.0.5] - 2024-05-06 + * Fixed: fail restart if PHP_BINARY is not available + +## [3.0.4] - 2024-03-26 + * Added: Functional tests. + * Fixed: Incompatibility with PHPUnit 10. + +## [3.0.3] - 2022-02-25 + * Added: support for composer/pcre versions 2 and 3. + +## [3.0.2] - 2022-02-24 + * Fixed: regression in 3.0.1 affecting Xdebug 2 + +## [3.0.1] - 2022-01-04 + * Fixed: error when calling `isXdebugActive` before class instantiation. + +## [3.0.0] - 2021-12-23 + * Removed: support for legacy PHP versions (< PHP 7.2.5). + * Added: type declarations to arguments and return values. + * Added: strict typing to all classes. + +## [2.0.3] - 2021-12-08 + * Added: support, type annotations and refactoring for stricter PHPStan analysis. + +## [2.0.2] - 2021-07-31 + * Added: support for `xdebug_info('mode')` in Xdebug 3.1. + * Added: support for Psr\Log versions 2 and 3. + * Fixed: remove ini directives from non-cli HOST/PATH sections. + +## [2.0.1] - 2021-05-05 + * Fixed: don't restart if the cwd is a UNC path and cmd.exe will be invoked. + +## [2.0.0] - 2021-04-09 + * Break: this is a major release, see [UPGRADE.md](UPGRADE.md) for more information. + * Break: removed optional `$colorOption` constructor param and passthru fallback. + * Break: renamed `requiresRestart` param from `$isLoaded` to `$default`. + * Break: changed `restart` param `$command` from a string to an array. + * Added: support for Xdebug3 to only restart if Xdebug is not running with `xdebug.mode=off`. + * Added: `isXdebugActive()` method to determine if Xdebug is still running in the restart. + * Added: feature to bypass the shell in PHP-7.4+ by giving `proc_open` an array of arguments. + * Added: Process utility class to the API. + +## [1.4.6] - 2021-03-25 + * Fixed: fail restart if `proc_open` has been disabled in `disable_functions`. + * Fixed: enable Windows CTRL event handling in the restarted process. + +## [1.4.5] - 2020-11-13 + * Fixed: use `proc_open` when available for correct FD forwarding to the restarted process. + +## [1.4.4] - 2020-10-24 + * Fixed: exception if 'pcntl_signal' is disabled. + +## [1.4.3] - 2020-08-19 + * Fixed: restore SIGINT to default handler in restarted process if no other handler exists. + +## [1.4.2] - 2020-06-04 + * Fixed: ignore SIGINTs to let the restarted process handle them. + +## [1.4.1] - 2020-03-01 + * Fixed: restart fails if an ini file is empty. + +## [1.4.0] - 2019-11-06 + * Added: support for `NO_COLOR` environment variable: https://no-color.org + * Added: color support for Hyper terminal: https://github.com/zeit/hyper + * Fixed: correct capitalization of Xdebug (apparently). + * Fixed: improved handling for uopz extension. + +## [1.3.3] - 2019-05-27 + * Fixed: add environment changes to `$_ENV` if it is being used. + +## [1.3.2] - 2019-01-28 + * Fixed: exit call being blocked by uopz extension, resulting in application code running twice. + +## [1.3.1] - 2018-11-29 + * Fixed: fail restart if `passthru` has been disabled in `disable_functions`. + * Fixed: fail restart if an ini file cannot be opened, otherwise settings will be missing. + +## [1.3.0] - 2018-08-31 + * Added: `setPersistent` method to use environment variables for the restart. + * Fixed: improved debugging by writing output to stderr. + * Fixed: no restart when `php_ini_scanned_files` is not functional and is needed. + +## [1.2.1] - 2018-08-23 + * Fixed: fatal error with apc, when using `apc.mmap_file_mask`. + +## [1.2.0] - 2018-08-16 + * Added: debug information using `XDEBUG_HANDLER_DEBUG`. + * Added: fluent interface for setters. + * Added: `PhpConfig` helper class for calling PHP sub-processes. + * Added: `PHPRC` original value to restart stettings, for use in a restarted process. + * Changed: internal procedure to disable ini-scanning, using `-n` command-line option. + * Fixed: replaced `escapeshellarg` usage to avoid locale problems. + * Fixed: improved color-option handling to respect double-dash delimiter. + * Fixed: color-option handling regression from main script changes. + * Fixed: improved handling when checking main script. + * Fixed: handling for standard input, that never actually did anything. + * Fixed: fatal error when ctype extension is not available. + +## [1.1.0] - 2018-04-11 + * Added: `getRestartSettings` method for calling PHP processes in a restarted process. + * Added: API definition and @internal class annotations. + * Added: protected `requiresRestart` method for extending classes. + * Added: `setMainScript` method for applications that change the working directory. + * Changed: private `tmpIni` variable to protected for extending classes. + * Fixed: environment variables not available in $_SERVER when restored in the restart. + * Fixed: relative path problems caused by Phar::interceptFileFuncs. + * Fixed: incorrect handling when script file cannot be found. + +## [1.0.0] - 2018-03-08 + * Added: PSR3 logging for optional status output. + * Added: existing ini settings are merged to catch command-line overrides. + * Added: code, tests and other artefacts to decouple from Composer. + * Break: the following class was renamed: + - `Composer\XdebugHandler` -> `Composer\XdebugHandler\XdebugHandler` + +[Unreleased]: https://github.com/composer/xdebug-handler/compare/3.0.5...HEAD +[3.0.5]: https://github.com/composer/xdebug-handler/compare/3.0.4...3.0.5 +[3.0.4]: https://github.com/composer/xdebug-handler/compare/3.0.3...3.0.4 +[3.0.3]: https://github.com/composer/xdebug-handler/compare/3.0.2...3.0.3 +[3.0.2]: https://github.com/composer/xdebug-handler/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/composer/xdebug-handler/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/composer/xdebug-handler/compare/2.0.3...3.0.0 +[2.0.3]: https://github.com/composer/xdebug-handler/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/composer/xdebug-handler/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/composer/xdebug-handler/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/composer/xdebug-handler/compare/1.4.6...2.0.0 +[1.4.6]: https://github.com/composer/xdebug-handler/compare/1.4.5...1.4.6 +[1.4.5]: https://github.com/composer/xdebug-handler/compare/1.4.4...1.4.5 +[1.4.4]: https://github.com/composer/xdebug-handler/compare/1.4.3...1.4.4 +[1.4.3]: https://github.com/composer/xdebug-handler/compare/1.4.2...1.4.3 +[1.4.2]: https://github.com/composer/xdebug-handler/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/composer/xdebug-handler/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/composer/xdebug-handler/compare/1.3.3...1.4.0 +[1.3.3]: https://github.com/composer/xdebug-handler/compare/1.3.2...1.3.3 +[1.3.2]: https://github.com/composer/xdebug-handler/compare/1.3.1...1.3.2 +[1.3.1]: https://github.com/composer/xdebug-handler/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/composer/xdebug-handler/compare/1.2.1...1.3.0 +[1.2.1]: https://github.com/composer/xdebug-handler/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/composer/xdebug-handler/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/composer/xdebug-handler/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/composer/xdebug-handler/compare/d66f0d15cb57...1.0.0 diff --git a/vendor/composer/xdebug-handler/LICENSE b/vendor/composer/xdebug-handler/LICENSE new file mode 100644 index 000000000..963618a14 --- /dev/null +++ b/vendor/composer/xdebug-handler/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/xdebug-handler/README.md b/vendor/composer/xdebug-handler/README.md new file mode 100644 index 000000000..f7f581ac2 --- /dev/null +++ b/vendor/composer/xdebug-handler/README.md @@ -0,0 +1,305 @@ +# composer/xdebug-handler + +[![packagist](https://img.shields.io/packagist/v/composer/xdebug-handler)](https://packagist.org/packages/composer/xdebug-handler) +[![Continuous Integration](https://github.com/composer/xdebug-handler/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/xdebug-handler/actions?query=branch:main) +![license](https://img.shields.io/github/license/composer/xdebug-handler.svg) +![php](https://img.shields.io/packagist/php-v/composer/xdebug-handler?colorB=8892BF) + +Restart a CLI process without loading the Xdebug extension, unless `xdebug.mode=off`. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + +### Version 3 + +Removed support for legacy PHP versions and added type declarations. + +Long term support for version 2 (PHP 5.3.2 - 7.2.4) follows [Composer 2.2 LTS](https://blog.packagist.com/composer-2-2/) policy. + +## Installation + +Install the latest version with: + +```bash +$ composer require composer/xdebug-handler +``` + +## Requirements + +* PHP 7.2.5 minimum, although using the latest PHP version is highly recommended. + +## Basic Usage +```php +use Composer\XdebugHandler\XdebugHandler; + +$xdebug = new XdebugHandler('myapp'); +$xdebug->check(); +unset($xdebug); +``` + +The constructor takes a single parameter, `$envPrefix`, which is upper-cased and prepended to default base values to create two distinct environment variables. The above example enables the use of: + +- `MYAPP_ALLOW_XDEBUG=1` to override automatic restart and allow Xdebug +- `MYAPP_ORIGINAL_INIS` to obtain ini file locations in a restarted process + +## Advanced Usage + +* [How it works](#how-it-works) +* [Limitations](#limitations) +* [Helper methods](#helper-methods) +* [Setter methods](#setter-methods) +* [Process configuration](#process-configuration) +* [Troubleshooting](#troubleshooting) +* [Extending the library](#extending-the-library) +* [Examples](#examples) + +### How it works + +A temporary ini file is created from the loaded (and scanned) ini files, with any references to the Xdebug extension commented out. Current ini settings are merged, so that most ini settings made on the command-line or by the application are included (see [Limitations](#limitations)) + +* `MYAPP_ALLOW_XDEBUG` is set with internal data to flag and use in the restart. +* The command-line and environment are [configured](#process-configuration) for the restart. +* The application is restarted in a new process. + * The restart settings are stored in the environment. + * `MYAPP_ALLOW_XDEBUG` is unset. + * The application runs and exits. +* The main process exits with the exit code from the restarted process. + +See [Examples](#examples) for further information. + +#### Signal handling +Asynchronous signal handling is automatically enabled if the pcntl extension is loaded. `SIGINT` is set to `SIG_IGN` in the parent +process and restored to `SIG_DFL` in the restarted process (if no other handler has been set). + +From PHP 7.4 on Windows, `CTRL+C` and `CTRL+BREAK` handling is automatically enabled in the restarted process and ignored in the parent process. + +### Limitations +There are a few things to be aware of when running inside a restarted process. + +* Extensions set on the command-line will not be loaded. +* Ini file locations will be reported as per the restart - see [getAllIniFiles()](#getallinifiles-array). +* Php sub-processes may be loaded with Xdebug enabled - see [Process configuration](#process-configuration). + +### Helper methods +These static methods provide information from the current process, regardless of whether it has been restarted or not. + +#### _getAllIniFiles(): array_ +Returns an array of the original ini file locations. Use this instead of calling `php_ini_loaded_file` and `php_ini_scanned_files`, which will report the wrong values in a restarted process. + +```php +use Composer\XdebugHandler\XdebugHandler; + +$files = XdebugHandler::getAllIniFiles(); + +# $files[0] always exists, it could be an empty string +$loadedIni = array_shift($files); +$scannedInis = $files; +``` + +These locations are also available in the `MYAPP_ORIGINAL_INIS` environment variable. This is a path-separated string comprising the location returned from `php_ini_loaded_file`, which could be empty, followed by locations parsed from calling `php_ini_scanned_files`. + +#### _getRestartSettings(): ?array_ +Returns an array of settings that can be used with PHP [sub-processes](#sub-processes), or null if the process was not restarted. + +```php +use Composer\XdebugHandler\XdebugHandler; + +$settings = XdebugHandler::getRestartSettings(); +/** + * $settings: array (if the current process was restarted, + * or called with the settings from a previous restart), or null + * + * 'tmpIni' => the temporary ini file used in the restart (string) + * 'scannedInis' => if there were any scanned inis (bool) + * 'scanDir' => the original PHP_INI_SCAN_DIR value (false|string) + * 'phprc' => the original PHPRC value (false|string) + * 'inis' => the original inis from getAllIniFiles (array) + * 'skipped' => the skipped version from getSkippedVersion (string) + */ +``` + +#### _getSkippedVersion(): string_ +Returns the Xdebug version string that was skipped by the restart, or an empty string if there was no restart (or Xdebug is still loaded, perhaps by an extending class restarting for a reason other than removing Xdebug). + +```php +use Composer\XdebugHandler\XdebugHandler; + +$version = XdebugHandler::getSkippedVersion(); +# $version: '3.1.1' (for example), or an empty string +``` + +#### _isXdebugActive(): bool_ +Returns true if Xdebug is loaded and is running in an active mode (if it supports modes). Returns false if Xdebug is not loaded, or it is running with `xdebug.mode=off`. + +### Setter methods +These methods implement a fluent interface and must be called before the main `check()` method. + +#### _setLogger(LoggerInterface $logger): self_ +Enables the output of status messages to an external PSR3 logger. All messages are reported with either `DEBUG` or `WARNING` log levels. For example (showing the level and message): + +``` +// No restart +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (3.1.1) xdebug.mode=off +DEBUG No restart (APP_ALLOW_XDEBUG=0) Allowed by xdebug.mode + +// Restart overridden +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (3.1.1) xdebug.mode=coverage,debug,develop +DEBUG No restart (MYAPP_ALLOW_XDEBUG=1) + +// Failed restart +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (3.1.0) +WARNING No restart (Unable to create temp ini file at: ...) +``` + +Status messages can also be output with `XDEBUG_HANDLER_DEBUG`. See [Troubleshooting](#troubleshooting). + +#### _setMainScript(string $script): self_ +Sets the location of the main script to run in the restart. This is only needed in more esoteric use-cases, or if the `argv[0]` location is inaccessible. The script name `--` is supported for standard input. + +#### _setPersistent(): self_ +Configures the restart using [persistent settings](#persistent-settings), so that Xdebug is not loaded in any sub-process. + +Use this method if your application invokes one or more PHP sub-process and the Xdebug extension is not needed. This avoids the overhead of implementing specific [sub-process](#sub-processes) strategies. + +Alternatively, this method can be used to set up a default _Xdebug-free_ environment which can be changed if a sub-process requires Xdebug, then restored afterwards: + +```php +function SubProcessWithXdebug() +{ + $phpConfig = new Composer\XdebugHandler\PhpConfig(); + + # Set the environment to the original configuration + $phpConfig->useOriginal(); + + # run the process with Xdebug loaded + ... + + # Restore Xdebug-free environment + $phpConfig->usePersistent(); +} +``` + +### Process configuration +The library offers two strategies to invoke a new PHP process without loading Xdebug, using either _standard_ or _persistent_ settings. Note that this is only important if the application calls a PHP sub-process. + +#### Standard settings +Uses command-line options to remove Xdebug from the new process only. + +* The -n option is added to the command-line. This tells PHP not to scan for additional inis. +* The temporary ini is added to the command-line with the -c option. + +>_If the new process calls a PHP sub-process, Xdebug will be loaded in that sub-process (unless it implements xdebug-handler, in which case there will be another restart)._ + +This is the default strategy used in the restart. + +#### Persistent settings +Uses environment variables to remove Xdebug from the new process and persist these settings to any sub-process. + +* `PHP_INI_SCAN_DIR` is set to an empty string. This tells PHP not to scan for additional inis. +* `PHPRC` is set to the temporary ini. + +>_If the new process calls a PHP sub-process, Xdebug will not be loaded in that sub-process._ + +This strategy can be used in the restart by calling [setPersistent()](#setpersistent-self). + +#### Sub-processes +The `PhpConfig` helper class makes it easy to invoke a PHP sub-process (with or without Xdebug loaded), regardless of whether there has been a restart. + +Each of its methods returns an array of PHP options (to add to the command-line) and sets up the environment for the required strategy. The [getRestartSettings()](#getrestartsettings-array) method is used internally. + +* `useOriginal()` - Xdebug will be loaded in the new process. +* `useStandard()` - Xdebug will **not** be loaded in the new process - see [standard settings](#standard-settings). +* `userPersistent()` - Xdebug will **not** be loaded in the new process - see [persistent settings](#persistent-settings) + +If there was no restart, an empty options array is returned and the environment is not changed. + +```php +use Composer\XdebugHandler\PhpConfig; + +$config = new PhpConfig; + +$options = $config->useOriginal(); +# $options: empty array +# environment: PHPRC and PHP_INI_SCAN_DIR set to original values + +$options = $config->useStandard(); +# $options: [-n, -c, tmpIni] +# environment: PHPRC and PHP_INI_SCAN_DIR set to original values + +$options = $config->usePersistent(); +# $options: empty array +# environment: PHPRC=tmpIni, PHP_INI_SCAN_DIR='' +``` + +### Troubleshooting +The following environment settings can be used to troubleshoot unexpected behavior: + +* `XDEBUG_HANDLER_DEBUG=1` Outputs status messages to `STDERR`, if it is defined, irrespective of any PSR3 logger. Each message is prefixed `xdebug-handler[pid]`, where pid is the process identifier. + +* `XDEBUG_HANDLER_DEBUG=2` As above, but additionally saves the temporary ini file and reports its location in a status message. + +### Extending the library +The API is defined by classes and their accessible elements that are not annotated as @internal. The main class has two protected methods that can be overridden to provide additional functionality: + +#### _requiresRestart(bool $default): bool_ +By default the process will restart if Xdebug is loaded and not running with `xdebug.mode=off`. Extending this method allows an application to decide, by returning a boolean (or equivalent) value. +It is only called if `MYAPP_ALLOW_XDEBUG` is empty, so it will not be called in the restarted process (where this variable contains internal data), or if the restart has been overridden. + +Note that the [setMainScript()](#setmainscriptstring-script-self) and [setPersistent()](#setpersistent-self) setters can be used here, if required. + +#### _restart(array $command): void_ +An application can extend this to modify the temporary ini file, its location given in the `tmpIni` property. New settings can be safely appended to the end of the data, which is `PHP_EOL` terminated. + +The `$command` parameter is an array of unescaped command-line arguments that will be used for the new process. + +Remember to finish with `parent::restart($command)`. + +#### Example +This example demonstrates two ways to extend basic functionality: + +* To avoid the overhead of spinning up a new process, the restart is skipped if a simple help command is requested. + +* The application needs write-access to phar files, so it will force a restart if `phar.readonly` is set (regardless of whether Xdebug is loaded) and change this value in the temporary ini file. + +```php +use Composer\XdebugHandler\XdebugHandler; +use MyApp\Command; + +class MyRestarter extends XdebugHandler +{ + private $required; + + protected function requiresRestart(bool $default): bool + { + if (Command::isHelp()) { + # No need to disable Xdebug for this + return false; + } + + $this->required = (bool) ini_get('phar.readonly'); + return $this->required || $default; + } + + protected function restart(array $command): void + { + if ($this->required) { + # Add required ini setting to tmpIni + $content = file_get_contents($this->tmpIni); + $content .= 'phar.readonly=0'.PHP_EOL; + file_put_contents($this->tmpIni, $content); + } + + parent::restart($command); + } +} +``` + +### Examples +The `tests\App` directory contains command-line scripts that demonstrate the internal workings in a variety of scenarios. +See [Functional Test Scripts](./tests/App/README.md). + +## License +composer/xdebug-handler is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/xdebug-handler/composer.json b/vendor/composer/xdebug-handler/composer.json new file mode 100644 index 000000000..d205dc102 --- /dev/null +++ b/vendor/composer/xdebug-handler/composer.json @@ -0,0 +1,44 @@ +{ + "name": "composer/xdebug-handler", + "description": "Restarts a process without Xdebug.", + "type": "library", + "license": "MIT", + "keywords": [ + "xdebug", + "performance" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3", + "composer/pcre": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\XdebugHandler\\Tests\\": "tests" + } + }, + "scripts": { + "test": "@php vendor/bin/phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" + } +} diff --git a/vendor/composer/xdebug-handler/src/PhpConfig.php b/vendor/composer/xdebug-handler/src/PhpConfig.php new file mode 100644 index 000000000..7edac8882 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/PhpConfig.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\XdebugHandler; + +/** + * @author John Stevenson + * + * @phpstan-type restartData array{tmpIni: string, scannedInis: bool, scanDir: false|string, phprc: false|string, inis: string[], skipped: string} + */ +class PhpConfig +{ + /** + * Use the original PHP configuration + * + * @return string[] Empty array of PHP cli options + */ + public function useOriginal(): array + { + $this->getDataAndReset(); + return []; + } + + /** + * Use standard restart settings + * + * @return string[] PHP cli options + */ + public function useStandard(): array + { + $data = $this->getDataAndReset(); + if ($data !== null) { + return ['-n', '-c', $data['tmpIni']]; + } + + return []; + } + + /** + * Use environment variables to persist settings + * + * @return string[] Empty array of PHP cli options + */ + public function usePersistent(): array + { + $data = $this->getDataAndReset(); + if ($data !== null) { + $this->updateEnv('PHPRC', $data['tmpIni']); + $this->updateEnv('PHP_INI_SCAN_DIR', ''); + } + + return []; + } + + /** + * Returns restart data if available and resets the environment + * + * @phpstan-return restartData|null + */ + private function getDataAndReset(): ?array + { + $data = XdebugHandler::getRestartSettings(); + if ($data !== null) { + $this->updateEnv('PHPRC', $data['phprc']); + $this->updateEnv('PHP_INI_SCAN_DIR', $data['scanDir']); + } + + return $data; + } + + /** + * Updates a restart settings value in the environment + * + * @param string $name + * @param string|false $value + */ + private function updateEnv(string $name, $value): void + { + Process::setEnv($name, false !== $value ? $value : null); + } +} diff --git a/vendor/composer/xdebug-handler/src/Process.php b/vendor/composer/xdebug-handler/src/Process.php new file mode 100644 index 000000000..4e9f076bb --- /dev/null +++ b/vendor/composer/xdebug-handler/src/Process.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Composer\XdebugHandler; + +use Composer\Pcre\Preg; + +/** + * Process utility functions + * + * @author John Stevenson + */ +class Process +{ + /** + * Escapes a string to be used as a shell argument. + * + * From https://github.com/johnstevenson/winbox-args + * MIT Licensed (c) John Stevenson + * + * @param string $arg The argument to be escaped + * @param bool $meta Additionally escape cmd.exe meta characters + * @param bool $module The argument is the module to invoke + */ + public static function escape(string $arg, bool $meta = true, bool $module = false): string + { + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + return "'".str_replace("'", "'\\''", $arg)."'"; + } + + $quote = strpbrk($arg, " \t") !== false || $arg === ''; + + $arg = Preg::replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); + $dquotes = (bool) $dquotes; + + if ($meta) { + $meta = $dquotes || Preg::isMatch('/%[^%]+%/', $arg); + + if (!$meta) { + $quote = $quote || strpbrk($arg, '^&|<>()') !== false; + } elseif ($module && !$dquotes && $quote) { + $meta = false; + } + } + + if ($quote) { + $arg = '"'.(Preg::replace('/(\\\\*)$/', '$1$1', $arg)).'"'; + } + + if ($meta) { + $arg = Preg::replace('/(["^&|<>()%])/', '^$1', $arg); + } + + return $arg; + } + + /** + * Escapes an array of arguments that make up a shell command + * + * @param string[] $args Argument list, with the module name first + */ + public static function escapeShellCommand(array $args): string + { + $command = ''; + $module = array_shift($args); + + if ($module !== null) { + $command = self::escape($module, true, true); + + foreach ($args as $arg) { + $command .= ' '.self::escape($arg); + } + } + + return $command; + } + + /** + * Makes putenv environment changes available in $_SERVER and $_ENV + * + * @param string $name + * @param ?string $value A null value unsets the variable + */ + public static function setEnv(string $name, ?string $value = null): bool + { + $unset = null === $value; + + if (!putenv($unset ? $name : $name.'='.$value)) { + return false; + } + + if ($unset) { + unset($_SERVER[$name]); + } else { + $_SERVER[$name] = $value; + } + + // Update $_ENV if it is being used + if (false !== stripos((string) ini_get('variables_order'), 'E')) { + if ($unset) { + unset($_ENV[$name]); + } else { + $_ENV[$name] = $value; + } + } + + return true; + } +} diff --git a/vendor/composer/xdebug-handler/src/Status.php b/vendor/composer/xdebug-handler/src/Status.php new file mode 100644 index 000000000..96c5944a2 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/Status.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Composer\XdebugHandler; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +/** + * @author John Stevenson + * @internal + */ +class Status +{ + const ENV_RESTART = 'XDEBUG_HANDLER_RESTART'; + const CHECK = 'Check'; + const ERROR = 'Error'; + const INFO = 'Info'; + const NORESTART = 'NoRestart'; + const RESTART = 'Restart'; + const RESTARTING = 'Restarting'; + const RESTARTED = 'Restarted'; + + /** @var bool */ + private $debug; + + /** @var string */ + private $envAllowXdebug; + + /** @var string|null */ + private $loaded; + + /** @var LoggerInterface|null */ + private $logger; + + /** @var bool */ + private $modeOff; + + /** @var float */ + private $time; + + /** + * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name + * @param bool $debug Whether debug output is required + */ + public function __construct(string $envAllowXdebug, bool $debug) + { + $start = getenv(self::ENV_RESTART); + Process::setEnv(self::ENV_RESTART); + $this->time = is_numeric($start) ? round((microtime(true) - $start) * 1000) : 0; + + $this->envAllowXdebug = $envAllowXdebug; + $this->debug = $debug && defined('STDERR'); + $this->modeOff = false; + } + + /** + * Activates status message output to a PSR3 logger + * + * @return void + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * Calls a handler method to report a message + * + * @throws \InvalidArgumentException If $op is not known + */ + public function report(string $op, ?string $data): void + { + if ($this->logger !== null || $this->debug) { + $param = (string) $data; + + switch($op) { + case self::CHECK: + $this->reportCheck($param); + break; + case self::ERROR: + $this->reportError($param); + break; + case self::INFO: + $this->reportInfo($param); + break; + case self::NORESTART: + $this->reportNoRestart(); + break; + case self::RESTART: + $this->reportRestart(); + break; + case self::RESTARTED: + $this->reportRestarted(); + break; + case self::RESTARTING: + $this->reportRestarting($param); + break; + default: + throw new \InvalidArgumentException('Unknown op handler: '.$op); + } + } + } + + /** + * Outputs a status message + */ + private function output(string $text, ?string $level = null): void + { + if ($this->logger !== null) { + $this->logger->log($level !== null ? $level: LogLevel::DEBUG, $text); + } + + if ($this->debug) { + fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL)); + } + } + + /** + * Checking status message + */ + private function reportCheck(string $loaded): void + { + list($version, $mode) = explode('|', $loaded); + + if ($version !== '') { + $this->loaded = '('.$version.')'.($mode !== '' ? ' xdebug.mode='.$mode : ''); + } + $this->modeOff = $mode === 'off'; + $this->output('Checking '.$this->envAllowXdebug); + } + + /** + * Error status message + */ + private function reportError(string $error): void + { + $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING); + } + + /** + * Info status message + */ + private function reportInfo(string $info): void + { + $this->output($info); + } + + /** + * No restart status message + */ + private function reportNoRestart(): void + { + $this->output($this->getLoadedMessage()); + + if ($this->loaded !== null) { + $text = sprintf('No restart (%s)', $this->getEnvAllow()); + if (!((bool) getenv($this->envAllowXdebug))) { + $text .= ' Allowed by '.($this->modeOff ? 'xdebug.mode' : 'application'); + } + $this->output($text); + } + } + + /** + * Restart status message + */ + private function reportRestart(): void + { + $this->output($this->getLoadedMessage()); + Process::setEnv(self::ENV_RESTART, (string) microtime(true)); + } + + /** + * Restarted status message + */ + private function reportRestarted(): void + { + $loaded = $this->getLoadedMessage(); + $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded); + $level = $this->loaded !== null ? LogLevel::WARNING : null; + $this->output($text, $level); + } + + /** + * Restarting status message + */ + private function reportRestarting(string $command): void + { + $text = sprintf('Process restarting (%s)', $this->getEnvAllow()); + $this->output($text); + $text = 'Running: '.$command; + $this->output($text); + } + + /** + * Returns the _ALLOW_XDEBUG environment variable as name=value + */ + private function getEnvAllow(): string + { + return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug); + } + + /** + * Returns the Xdebug status and version + */ + private function getLoadedMessage(): string + { + $loaded = $this->loaded !== null ? sprintf('loaded %s', $this->loaded) : 'not loaded'; + return 'The Xdebug extension is '.$loaded; + } +} diff --git a/vendor/composer/xdebug-handler/src/XdebugHandler.php b/vendor/composer/xdebug-handler/src/XdebugHandler.php new file mode 100644 index 000000000..a665939d6 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/XdebugHandler.php @@ -0,0 +1,722 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Composer\XdebugHandler; + +use Composer\Pcre\Preg; +use Psr\Log\LoggerInterface; + +/** + * @author John Stevenson + * + * @phpstan-import-type restartData from PhpConfig + */ +class XdebugHandler +{ + const SUFFIX_ALLOW = '_ALLOW_XDEBUG'; + const SUFFIX_INIS = '_ORIGINAL_INIS'; + const RESTART_ID = 'internal'; + const RESTART_SETTINGS = 'XDEBUG_HANDLER_SETTINGS'; + const DEBUG = 'XDEBUG_HANDLER_DEBUG'; + + /** @var string|null */ + protected $tmpIni; + + /** @var bool */ + private static $inRestart; + + /** @var string */ + private static $name; + + /** @var string|null */ + private static $skipped; + + /** @var bool */ + private static $xdebugActive; + + /** @var string|null */ + private static $xdebugMode; + + /** @var string|null */ + private static $xdebugVersion; + + /** @var bool */ + private $cli; + + /** @var string|null */ + private $debug; + + /** @var string */ + private $envAllowXdebug; + + /** @var string */ + private $envOriginalInis; + + /** @var bool */ + private $persistent; + + /** @var string|null */ + private $script; + + /** @var Status */ + private $statusWriter; + + /** + * Constructor + * + * The $envPrefix is used to create distinct environment variables. It is + * uppercased and prepended to the default base values. For example 'myapp' + * would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS. + * + * @param string $envPrefix Value used in environment variables + * @throws \RuntimeException If the parameter is invalid + */ + public function __construct(string $envPrefix) + { + if ($envPrefix === '') { + throw new \RuntimeException('Invalid constructor parameter'); + } + + self::$name = strtoupper($envPrefix); + $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW; + $this->envOriginalInis = self::$name.self::SUFFIX_INIS; + + self::setXdebugDetails(); + self::$inRestart = false; + + if ($this->cli = PHP_SAPI === 'cli') { + $this->debug = (string) getenv(self::DEBUG); + } + + $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug); + } + + /** + * Activates status message output to a PSR3 logger + */ + public function setLogger(LoggerInterface $logger): self + { + $this->statusWriter->setLogger($logger); + return $this; + } + + /** + * Sets the main script location if it cannot be called from argv + */ + public function setMainScript(string $script): self + { + $this->script = $script; + return $this; + } + + /** + * Persist the settings to keep Xdebug out of sub-processes + */ + public function setPersistent(): self + { + $this->persistent = true; + return $this; + } + + /** + * Checks if Xdebug is loaded and the process needs to be restarted + * + * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG + * environment variable to 1. This variable is used internally so that + * the restarted process is created only once. + */ + public function check(): void + { + $this->notify(Status::CHECK, self::$xdebugVersion.'|'.self::$xdebugMode); + $envArgs = explode('|', (string) getenv($this->envAllowXdebug)); + + if (!((bool) $envArgs[0]) && $this->requiresRestart(self::$xdebugActive)) { + // Restart required + $this->notify(Status::RESTART); + $command = $this->prepareRestart(); + + if ($command !== null) { + $this->restart($command); + } + return; + } + + if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) { + // Restarted, so unset environment variable and use saved values + $this->notify(Status::RESTARTED); + + Process::setEnv($this->envAllowXdebug); + self::$inRestart = true; + + if (self::$xdebugVersion === null) { + // Skipped version is only set if Xdebug is not loaded + self::$skipped = $envArgs[1]; + } + + $this->tryEnableSignals(); + + // Put restart settings in the environment + $this->setEnvRestartSettings($envArgs); + return; + } + + $this->notify(Status::NORESTART); + $settings = self::getRestartSettings(); + + if ($settings !== null) { + // Called with existing settings, so sync our settings + $this->syncSettings($settings); + } + } + + /** + * Returns an array of php.ini locations with at least one entry + * + * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. + * The loaded ini location is the first entry and may be an empty string. + * + * @return non-empty-list + */ + public static function getAllIniFiles(): array + { + if (self::$name !== null) { + $env = getenv(self::$name.self::SUFFIX_INIS); + + if (false !== $env) { + return explode(PATH_SEPARATOR, $env); + } + } + + $paths = [(string) php_ini_loaded_file()]; + $scanned = php_ini_scanned_files(); + + if ($scanned !== false) { + $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); + } + + return $paths; + } + + /** + * Returns an array of restart settings or null + * + * Settings will be available if the current process was restarted, or + * called with the settings from an existing restart. + * + * @phpstan-return restartData|null + */ + public static function getRestartSettings(): ?array + { + $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS)); + + if (count($envArgs) !== 6 + || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) { + return null; + } + + return [ + 'tmpIni' => $envArgs[0], + 'scannedInis' => (bool) $envArgs[1], + 'scanDir' => '*' === $envArgs[2] ? false : $envArgs[2], + 'phprc' => '*' === $envArgs[3] ? false : $envArgs[3], + 'inis' => explode(PATH_SEPARATOR, $envArgs[4]), + 'skipped' => $envArgs[5], + ]; + } + + /** + * Returns the Xdebug version that triggered a successful restart + */ + public static function getSkippedVersion(): string + { + return (string) self::$skipped; + } + + /** + * Returns whether Xdebug is loaded and active + * + * true: if Xdebug is loaded and is running in an active mode. + * false: if Xdebug is not loaded, or it is running with xdebug.mode=off. + */ + public static function isXdebugActive(): bool + { + self::setXdebugDetails(); + return self::$xdebugActive; + } + + /** + * Allows an extending class to decide if there should be a restart + * + * The default is to restart if Xdebug is loaded and its mode is not "off". + */ + protected function requiresRestart(bool $default): bool + { + return $default; + } + + /** + * Allows an extending class to access the tmpIni + * + * @param non-empty-list $command + */ + protected function restart(array $command): void + { + $this->doRestart($command); + } + + /** + * Executes the restarted command then deletes the tmp ini + * + * @param non-empty-list $command + * @phpstan-return never + */ + private function doRestart(array $command): void + { + if (PHP_VERSION_ID >= 70400) { + $cmd = $command; + $displayCmd = sprintf('[%s]', implode(', ', $cmd)); + } else { + $cmd = Process::escapeShellCommand($command); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + // Outer quotes required on cmd string below PHP 8 + $cmd = '"'.$cmd.'"'; + } + $displayCmd = $cmd; + } + + $this->tryEnableSignals(); + $this->notify(Status::RESTARTING, $displayCmd); + + $process = proc_open($cmd, [], $pipes); + if (is_resource($process)) { + $exitCode = proc_close($process); + } + + if (!isset($exitCode)) { + // Unlikely that php or the default shell cannot be invoked + $this->notify(Status::ERROR, 'Unable to restart process'); + $exitCode = -1; + } else { + $this->notify(Status::INFO, 'Restarted process exited '.$exitCode); + } + + if ($this->debug === '2') { + $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni); + } else { + @unlink((string) $this->tmpIni); + } + + exit($exitCode); + } + + /** + * Returns the command line array if everything was written for the restart + * + * If any of the following fails (however unlikely) we must return false to + * stop potential recursion: + * - tmp ini file creation + * - environment variable creation + * + * @return non-empty-list|null + */ + private function prepareRestart(): ?array + { + if (!$this->cli) { + $this->notify(Status::ERROR, 'Unsupported SAPI: '.PHP_SAPI); + return null; + } + + if (($argv = $this->checkServerArgv()) === null) { + $this->notify(Status::ERROR, '$_SERVER[argv] is not as expected'); + return null; + } + + if (!$this->checkConfiguration($info)) { + $this->notify(Status::ERROR, $info); + return null; + } + + $mainScript = (string) $this->script; + if (!$this->checkMainScript($mainScript, $argv)) { + $this->notify(Status::ERROR, 'Unable to access main script: '.$mainScript); + return null; + } + + $tmpDir = sys_get_temp_dir(); + $iniError = 'Unable to create temp ini file at: '.$tmpDir; + + if (($tmpfile = @tempnam($tmpDir, '')) === false) { + $this->notify(Status::ERROR, $iniError); + return null; + } + + $error = null; + $iniFiles = self::getAllIniFiles(); + $scannedInis = count($iniFiles) > 1; + + if (!$this->writeTmpIni($tmpfile, $iniFiles, $error)) { + $this->notify(Status::ERROR, $error ?? $iniError); + @unlink($tmpfile); + return null; + } + + if (!$this->setEnvironment($scannedInis, $iniFiles, $tmpfile)) { + $this->notify(Status::ERROR, 'Unable to set environment variables'); + @unlink($tmpfile); + return null; + } + + $this->tmpIni = $tmpfile; + + return $this->getCommand($argv, $tmpfile, $mainScript); + } + + /** + * Returns true if the tmp ini file was written + * + * @param non-empty-list $iniFiles All ini files used in the current process + */ + private function writeTmpIni(string $tmpFile, array $iniFiles, ?string &$error): bool + { + // $iniFiles has at least one item and it may be empty + if ($iniFiles[0] === '') { + array_shift($iniFiles); + } + + $content = ''; + $sectionRegex = '/^\s*\[(?:PATH|HOST)\s*=/mi'; + $xdebugRegex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; + + foreach ($iniFiles as $file) { + // Check for inaccessible ini files + if (($data = @file_get_contents($file)) === false) { + $error = 'Unable to read ini: '.$file; + return false; + } + // Check and remove directives after HOST and PATH sections + if (Preg::isMatchWithOffsets($sectionRegex, $data, $matches)) { + $data = substr($data, 0, $matches[0][1]); + } + $content .= Preg::replace($xdebugRegex, ';$1', $data).PHP_EOL; + } + + // Merge loaded settings into our ini content, if it is valid + $config = parse_ini_string($content); + $loaded = ini_get_all(null, false); + + if (false === $config || false === $loaded) { + $error = 'Unable to parse ini data'; + return false; + } + + $content .= $this->mergeLoadedConfig($loaded, $config); + + // Work-around for https://bugs.php.net/bug.php?id=75932 + $content .= 'opcache.enable_cli=0'.PHP_EOL; + + return (bool) @file_put_contents($tmpFile, $content); + } + + /** + * Returns the command line arguments for the restart + * + * @param non-empty-list $argv + * @return non-empty-list + */ + private function getCommand(array $argv, string $tmpIni, string $mainScript): array + { + $php = [PHP_BINARY]; + $args = array_slice($argv, 1); + + if (!$this->persistent) { + // Use command-line options + array_push($php, '-n', '-c', $tmpIni); + } + + return array_merge($php, [$mainScript], $args); + } + + /** + * Returns true if the restart environment variables were set + * + * No need to update $_SERVER since this is set in the restarted process. + * + * @param non-empty-list $iniFiles All ini files used in the current process + */ + private function setEnvironment(bool $scannedInis, array $iniFiles, string $tmpIni): bool + { + $scanDir = getenv('PHP_INI_SCAN_DIR'); + $phprc = getenv('PHPRC'); + + // Make original inis available to restarted process + if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) { + return false; + } + + if ($this->persistent) { + // Use the environment to persist the settings + if (!putenv('PHP_INI_SCAN_DIR=') || !putenv('PHPRC='.$tmpIni)) { + return false; + } + } + + // Flag restarted process and save values for it to use + $envArgs = [ + self::RESTART_ID, + self::$xdebugVersion, + (int) $scannedInis, + false === $scanDir ? '*' : $scanDir, + false === $phprc ? '*' : $phprc, + ]; + + return putenv($this->envAllowXdebug.'='.implode('|', $envArgs)); + } + + /** + * Logs status messages + */ + private function notify(string $op, ?string $data = null): void + { + $this->statusWriter->report($op, $data); + } + + /** + * Returns default, changed and command-line ini settings + * + * @param mixed[] $loadedConfig All current ini settings + * @param mixed[] $iniConfig Settings from user ini files + * + */ + private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string + { + $content = ''; + + foreach ($loadedConfig as $name => $value) { + // Value will either be null, string or array (HHVM only) + if (!is_string($value) + || strpos($name, 'xdebug') === 0 + || $name === 'apc.mmap_file_mask') { + continue; + } + + if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { + // Double-quote escape each value + $content .= $name.'="'.addcslashes($value, '\\"').'"'.PHP_EOL; + } + } + + return $content; + } + + /** + * Returns true if the script name can be used + * + * @param non-empty-list $argv + */ + private function checkMainScript(string &$mainScript, array $argv): bool + { + if ($mainScript !== '') { + // Allow an application to set -- for standard input + return file_exists($mainScript) || '--' === $mainScript; + } + + if (file_exists($mainScript = $argv[0])) { + return true; + } + + // Use a backtrace to resolve Phar and chdir issues. + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $main = end($trace); + + if ($main !== false && isset($main['file'])) { + return file_exists($mainScript = $main['file']); + } + + return false; + } + + /** + * Adds restart settings to the environment + * + * @param non-empty-list $envArgs + */ + private function setEnvRestartSettings(array $envArgs): void + { + $settings = [ + php_ini_loaded_file(), + $envArgs[2], + $envArgs[3], + $envArgs[4], + getenv($this->envOriginalInis), + self::$skipped, + ]; + + Process::setEnv(self::RESTART_SETTINGS, implode('|', $settings)); + } + + /** + * Syncs settings and the environment if called with existing settings + * + * @phpstan-param restartData $settings + */ + private function syncSettings(array $settings): void + { + if (false === getenv($this->envOriginalInis)) { + // Called by another app, so make original inis available + Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis'])); + } + + self::$skipped = $settings['skipped']; + $this->notify(Status::INFO, 'Process called with existing restart settings'); + } + + /** + * Returns true if there are no known configuration issues + */ + private function checkConfiguration(?string &$info): bool + { + if (!function_exists('proc_open')) { + $info = 'proc_open function is disabled'; + return false; + } + + if (!file_exists(PHP_BINARY)) { + $info = 'PHP_BINARY is not available'; + return false; + } + + if (extension_loaded('uopz') && !((bool) ini_get('uopz.disable'))) { + // uopz works at opcode level and disables exit calls + if (function_exists('uopz_allow_exit')) { + @uopz_allow_exit(true); + } else { + $info = 'uopz extension is not compatible'; + return false; + } + } + + // Check UNC paths when using cmd.exe + if (defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID < 70400) { + $workingDir = getcwd(); + + if ($workingDir === false) { + $info = 'unable to determine working directory'; + return false; + } + + if (0 === strpos($workingDir, '\\\\')) { + $info = 'cmd.exe does not support UNC paths: '.$workingDir; + return false; + } + } + + return true; + } + + /** + * Enables async signals and control interrupts in the restarted process + * + * Available on Unix PHP 7.1+ with the pcntl extension and Windows PHP 7.4+. + */ + private function tryEnableSignals(): void + { + if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) { + pcntl_async_signals(true); + $message = 'Async signals enabled'; + + if (!self::$inRestart) { + // Restarting, so ignore SIGINT in parent + pcntl_signal(SIGINT, SIG_IGN); + } elseif (is_int(pcntl_signal_get_handler(SIGINT))) { + // Restarted, no handler set so force default action + pcntl_signal(SIGINT, SIG_DFL); + } + } + + if (!self::$inRestart && function_exists('sapi_windows_set_ctrl_handler')) { + // Restarting, so set a handler to ignore CTRL events in the parent. + // This ensures that CTRL+C events will be available in the child + // process without having to enable them there, which is unreliable. + sapi_windows_set_ctrl_handler(function ($evt) {}); + } + } + + /** + * Returns $_SERVER['argv'] if it is as expected + * + * @return non-empty-list|null + */ + private function checkServerArgv(): ?array + { + $result = []; + + if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) { + foreach ($_SERVER['argv'] as $value) { + if (!is_string($value)) { + return null; + } + + $result[] = $value; + } + } + + return count($result) > 0 ? $result : null; + } + + /** + * Sets static properties $xdebugActive, $xdebugVersion and $xdebugMode + */ + private static function setXdebugDetails(): void + { + if (self::$xdebugActive !== null) { + return; + } + + self::$xdebugActive = false; + if (!extension_loaded('xdebug')) { + return; + } + + $version = phpversion('xdebug'); + self::$xdebugVersion = $version !== false ? $version : 'unknown'; + + if (version_compare(self::$xdebugVersion, '3.1', '>=')) { + $modes = xdebug_info('mode'); + self::$xdebugMode = count($modes) === 0 ? 'off' : implode(',', $modes); + self::$xdebugActive = self::$xdebugMode !== 'off'; + return; + } + + // See if xdebug.mode is supported in this version + $iniMode = ini_get('xdebug.mode'); + if ($iniMode === false) { + self::$xdebugActive = true; + return; + } + + // Environment value wins but cannot be empty + $envMode = (string) getenv('XDEBUG_MODE'); + if ($envMode !== '') { + self::$xdebugMode = $envMode; + } else { + self::$xdebugMode = $iniMode !== '' ? $iniMode : 'off'; + } + + // An empty comma-separated list is treated as mode 'off' + if (Preg::isMatch('/^,+$/', str_replace(' ', '', self::$xdebugMode))) { + self::$xdebugMode = 'off'; + } + + self::$xdebugActive = self::$xdebugMode !== 'off'; + } +} diff --git a/vendor/deliciousbrains/wp-background-processing/.circleci/config.yml b/vendor/deliciousbrains/wp-background-processing/.circleci/config.yml new file mode 100644 index 000000000..212e51cee --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/.circleci/config.yml @@ -0,0 +1,93 @@ +workflows: + version: 2 + main: + jobs: + - php72-build + - php73-build + - php74-build + - php80-build + +version: 2 + +job-references: + mysql_image: &mysql_image + cimg/mysql:5.7 + + setup_environment: &setup_environment + name: "Setup Environment Variables" + command: | + echo "export PATH=$HOME/.composer/vendor/bin:$PATH" >> $BASH_ENV + source /home/circleci/.bashrc + + install_dependencies: &install_dependencies + name: "Install Dependencies" + command: | + sudo apt-get update && sudo apt-get install mysql-client subversion + + php_job: &php_job + environment: + - WP_TESTS_DIR: "/tmp/wordpress-tests-lib" + - WP_CORE_DIR: "/tmp/wordpress/" + steps: + - checkout + - run: php --version + - run: composer --version + - run: *setup_environment + - run: *install_dependencies + - run: + name: "Run Tests" + command: | + rm -rf $WP_TESTS_DIR $WP_CORE_DIR + bash bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1 latest + make test-unit + WP_MULTISITE=1 make test-unit + make test-style + +jobs: + php56-build: + <<: *php_job + docker: + - image: cimg/php:5.6 + - image: *mysql_image + + php70-build: + <<: *php_job + docker: + - image: cimg/php:7.0 + - image: *mysql_image + + php71-build: + <<: *php_job + docker: + - image: cimg/php:7.1 + - image: *mysql_image + + php72-build: + <<: *php_job + docker: + - image: cimg/php:7.2 + - image: *mysql_image + + php73-build: + <<: *php_job + docker: + - image: cimg/php:7.3 + - image: *mysql_image + + php74-build: + <<: *php_job + docker: + - image: cimg/php:7.4 + - image: *mysql_image + + php80-build: + <<: *php_job + docker: + - image: cimg/php:8.0 + - image: *mysql_image + + php81-build: + <<: *php_job + docker: + - image: cimg/php:8.1 + - image: *mysql_image diff --git a/vendor/deliciousbrains/wp-background-processing/.github/CODEOWNERS b/vendor/deliciousbrains/wp-background-processing/.github/CODEOWNERS new file mode 100644 index 000000000..f3381f7eb --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/.github/CODEOWNERS @@ -0,0 +1,3 @@ +* @deliciousbrains/deli-eng + +#jira:[18802] is where issues related to this repository should be ticketed] \ No newline at end of file diff --git a/vendor/deliciousbrains/wp-background-processing/.gitignore b/vendor/deliciousbrains/wp-background-processing/.gitignore new file mode 100644 index 000000000..23505a971 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +/.idea +*.cache diff --git a/vendor/deliciousbrains/wp-background-processing/.phpcs.xml b/vendor/deliciousbrains/wp-background-processing/.phpcs.xml new file mode 100644 index 000000000..903238362 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/.phpcs.xml @@ -0,0 +1,65 @@ + + + Generally-applicable sniffs for WordPress plugins. + + + . + /vendor/ + /node_modules/ + /tests/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/deliciousbrains/wp-background-processing/Makefile b/vendor/deliciousbrains/wp-background-processing/Makefile new file mode 100644 index 000000000..7ad63b81d --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/Makefile @@ -0,0 +1,18 @@ +.PHONY: test +test: test-unit test-style + +.PHONY: test-unit +test-unit: vendor + vendor/bin/phpunit + +.PHONY: test-style +test-style: vendor + vendor/bin/phpcs + +vendor: composer.json + composer install --ignore-platform-reqs + +.PHONY: clean +clean: + rm -rf vendor + rm -f tests/.phpunit.result.cache diff --git a/vendor/deliciousbrains/wp-background-processing/README.md b/vendor/deliciousbrains/wp-background-processing/README.md new file mode 100644 index 000000000..a6e534b87 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/README.md @@ -0,0 +1,438 @@ +# WP Background Processing + +WP Background Processing can be used to fire off non-blocking asynchronous requests or as a background processing tool, allowing you to queue tasks. Check out the [example plugin](https://github.com/A5hleyRich/wp-background-processing-example) or read the [accompanying article](https://deliciousbrains.com/background-processing-wordpress/). + +Inspired by [TechCrunch WP Asynchronous Tasks](https://github.com/techcrunch/wp-async-task). + +__Requires PHP 5.6+__ + +## Install + +The recommended way to install this library in your project is by loading it through Composer: + +```shell +composer require deliciousbrains/wp-background-processing +``` + +It is highly recommended to prefix wrap the library class files using [the Mozart package](https://packagist.org/packages/coenjacobs/mozart), to prevent collisions with other projects using this same library. + +## Usage + +### Async Request + +Async requests are useful for pushing slow one-off tasks such as sending emails to a background process. Once the request has been dispatched it will process in the background instantly. + +Extend the `WP_Async_Request` class: + +```php +class WP_Example_Request extends WP_Async_Request { + + /** + * @var string + */ + protected $prefix = 'my_plugin'; + + /** + * @var string + */ + protected $action = 'example_request'; + + /** + * Handle a dispatched request. + * + * Override this method to perform any actions required + * during the async request. + */ + protected function handle() { + // Actions to perform. + } + +} +``` + +#### `protected $prefix` + +Should be set to a unique prefix associated with your plugin, theme, or site's custom function prefix. + +#### `protected $action` + +Should be set to a unique name. + +#### `protected function handle()` + +Should contain any logic to perform during the non-blocking request. The data passed to the request will be accessible via `$_POST`. + +#### Dispatching Requests + +Instantiate your request: + +```php +$this->example_request = new WP_Example_Request(); +``` + +Add data to the request if required: + +```php +$this->example_request->data( array( 'value1' => $value1, 'value2' => $value2 ) ); +``` + +Fire off the request: + +```php +$this->example_request->dispatch(); +``` + +Chaining is also supported: + +```php +$this->example_request->data( array( 'data' => $data ) )->dispatch(); +``` + +### Background Process + +Background processes work in a similar fashion to async requests, but they allow you to queue tasks. Items pushed onto the queue will be processed in the background once the queue has been saved and dispatched. Queues will also scale based on available server resources, so higher end servers will process more items per batch. Once a batch has completed, the next batch will start instantly. + +Health checks run by default every 5 minutes to ensure the queue is running when queued items exist. If the queue has failed it will be restarted. + +Queues work on a first in first out basis, which allows additional items to be pushed to the queue even if it’s already processing. Saving a new batch of queued items and dispatching while another background processing instance is already running will result in the dispatch shortcutting out and the existing instance eventually picking up the new items and processing them when it is their turn. + +Extend the `WP_Background_Process` class: + +```php +class WP_Example_Process extends WP_Background_Process { + + /** + * @var string + */ + protected $prefix = 'my_plugin'; + + /** + * @var string + */ + protected $action = 'example_process'; + + /** + * Perform task with queued item. + * + * Override this method to perform any actions required on each + * queue item. Return the modified item for further processing + * in the next pass through. Or, return false to remove the + * item from the queue. + * + * @param mixed $item Queue item to iterate over. + * + * @return mixed + */ + protected function task( $item ) { + // Actions to perform. + + return false; + } + + /** + * Complete processing. + * + * Override if applicable, but ensure that the below actions are + * performed, or, call parent::complete(). + */ + protected function complete() { + parent::complete(); + + // Show notice to user or perform some other arbitrary task... + } + +} +``` + +#### `protected $prefix` + +Should be set to a unique prefix associated with your plugin, theme, or site's custom function prefix. + +#### `protected $action` + +Should be set to a unique name. + +#### `protected function task( $item )` + +Should contain any logic to perform on the queued item. Return `false` to remove the item from the queue or return `$item` to push it back onto the queue for further processing. If the item has been modified and is pushed back onto the queue the current state will be saved before the batch is exited. + +#### `protected function complete()` + +Optionally contain any logic to perform once the queue has completed. + +#### Dispatching Processes + +Instantiate your process: + +```php +$this->example_process = new WP_Example_Process(); +``` + +**Note:** You must instantiate your process unconditionally. All requests should do this, even if nothing is pushed to the queue. + +Push items to the queue: + +```php +foreach ( $items as $item ) { + $this->example_process->push_to_queue( $item ); +} +``` + +An item can be any valid PHP value, string, integer, array or object. If needed, the $item is serialized when written to the database. + +Save and dispatch the queue: + +```php +$this->example_process->save()->dispatch(); +``` + +#### Handling serialized objects in queue items + +Queue items that contain non-scalar values are serialized when stored in the database. To avoid potential security issues during unserialize, this library provides the option to set the `allowed_classes` option when calling `unserialize()` which limits which classes can be instantiated. It's kept internally as the protected `$allowed_batch_data_classes` property. + +To maintain backward compatibility the default value is `true`, meaning that any serialized object will be instantiated. Please note that this default behavior may change in a future major release. + +We encourage all users of this library to take advantage of setting a strict value for `$allowed_batch_data_classes`. If possible, set the value to `false` to disallow any objects from being instantiated, or a very limited list of class names, see examples below. + +Objects in the serialized string that are not allowed to be instantiated will instead get the class type `__PHP_Incomplete_Class`. + +##### Overriding the default `$allowed_batch_data_classes` + +The default behavior can be overridden by passing an array of allowed classes to the constructor: + +``` php +$allowed_batch_data_classes = array( MyCustomItem::class, MyItemHelper::class ); +$this->example_process = new WP_Example_Process( $allowed_batch_data_classes ); +``` + +Or, set the value to `false`: + +``` php +$this->example_process = new WP_Example_Process( false ); +``` + + +Another way to change the default is to override the `$allowed_batch_data_classes` property in your process class: + +``` php +class WP_Example_Process extends WP_Background_Process { + + /** + * @var string + */ + protected $prefix = 'my_plugin'; + + /** + * @var string + */ + protected $action = 'example_process'; + + /** + * + * @var bool|array + */ + protected $allowed_batch_data_classes = array( MyCustomItem::class, MyItemHelper::class ); + ... + +``` + +#### Background Process Status + +A background process can be queued, processing, paused, cancelled, or none of the above (not started or has completed). + +##### Queued + +To check whether a background process has queued items use `is_queued()`. + +```php +if ( $this->example_process->is_queued() ) { + // Do something because background process has queued items, e.g. add notice in admin UI. +} +``` + +##### Processing + +To check whether a background process is currently handling a queue of items use `is_processing()`. + +```php +if ( $this->example_process->is_processing() ) { + // Do something because background process is running, e.g. add notice in admin UI. +} +``` + +##### Paused + +You can pause a background process with `pause()`. + +```php +$this->example_process->pause(); +``` + +The currently processing batch will continue until it either completes or reaches the time or memory limit. At that point it'll unlock the process and either complete the batch if the queue is empty, or perform a dispatch that will result in the handler removing the healthcheck cron and firing a "paused" action. + +To check whether a background process is currently paused use `is_paused()`. + +```php +if ( $this->example_process->is_paused() ) { + // Do something because background process is paused, e.g. add notice in admin UI. +} +``` + +You can perform an action in response to background processing being paused by handling the "paused" action for the background process's identifier ($prefix + $action). + +```php +add_action( 'my_plugin_example_process_paused', function() { + // Do something because background process is paused, e.g. add notice in admin UI. +}); +``` + +You can resume a background process with `resume()`. + +```php +$this->example_process->resume(); +``` + +You can perform an action in response to background processing being resumed by handling the "resumed" action for the background process's identifier ($prefix + $action). + +```php +add_action( 'my_plugin_example_process_resumed', function() { + // Do something because background process is resumed, e.g. add notice in admin UI. +}); +``` + +##### Cancelled + +You can cancel a background process with `cancel()`. + +```php +$this->example_process->cancel(); +``` + +The currently processing batch will continue until it either completes or reaches the time or memory limit. At that point it'll unlock the process and either complete the batch if the queue is empty, or perform a dispatch that will result in the handler removing the healthcheck cron, deleting all batches of queued items and firing a "cancelled" action. + +To check whether a background process is currently cancelled use `is_cancelled()`. + +```php +if ( $this->example_process->is_cancelled() ) { + // Do something because background process is cancelled, e.g. add notice in admin UI. +} +``` + +You can perform an action in response to background processing being cancelled by handling the "cancelled" action for the background process's identifier ($prefix + $action). + +```php +add_action( 'my_plugin_example_process_cancelled', function() { + // Do something because background process is paused, e.g. add notice in admin UI. +}); +``` + +The "cancelled" action fires once the queue has been cleared down and cancelled status removed. After which `is_cancelled()` will no longer be true as the background process is now dormant. + +##### Active + +To check whether a background process has queued items, is processing, is paused, or is cancelling, use `is_active()`. + +```php +if ( $this->example_process->is_active() ) { + // Do something because background process is active, e.g. add notice in admin UI. +} +``` + +If a background process is not active, then it either has not had anything queued yet and not started, or has finished processing all queued items. + +### BasicAuth + +If your site is behind BasicAuth, both async requests and background processes will fail to complete. This is because WP Background Processing relies on the [WordPress HTTP API](https://developer.wordpress.org/plugins/http-api/), which requires you to attach your BasicAuth credentials to requests. The easiest way to do this is using the following filter: + +```php +function wpbp_http_request_args( $r, $url ) { + $r['headers']['Authorization'] = 'Basic ' . base64_encode( USERNAME . ':' . PASSWORD ); + + return $r; +} +add_filter( 'http_request_args', 'wpbp_http_request_args', 10, 2); +``` + +## Contributing + +Contributions are welcome via Pull Requests, but please do raise an issue before +working on anything to discuss the change if there isn't already an issue. If there +is an approved issue you'd like to tackle, please post a comment on it to let people know +you're going to have a go at it so that effort isn't wasted through duplicated work. + +### Unit & Style Tests + +When working on the library, please add unit tests to the appropriate file in the +`tests` directory that cover your changes. + +#### Setting Up + +We use the standard WordPress test libraries for running unit tests. + +Please run the following command to set up the libraries: + +```shell +bin/install-wp-tests.sh db_name db_user db_pass +``` + +Substitute `db_name`, `db_user` and `db_pass` as appropriate. + +Please be aware that running the unit tests is a **destructive operation**, *database +tables will be cleared*, so please use a database name dedicated to running unit tests. +The standard database name usually used by the WordPress community is `wordpress_test`, e.g. + +```shell +bin/install-wp-tests.sh wordpress_test root root +``` + +Please refer to the [Initialize the testing environment locally](https://make.wordpress.org/cli/handbook/misc/plugin-unit-tests/#3-initialize-the-testing-environment-locally) +section of the WordPress Handbook's [Plugin Integration Tests](https://make.wordpress.org/cli/handbook/misc/plugin-unit-tests/) +entry should you run into any issues. + +#### Running Unit Tests + +To run the unit tests, simply run: + +```shell +make test-unit +``` + +If the `composer` dependencies aren't in place, they'll be automatically installed first. + +#### Running Style Tests + +It's important that the code in the library use a consistent style to aid in quickly +understanding it, and to avoid some common issues. `PHP_Code_Sniffer` is used with +mostly standard WordPress rules to help check for consistency. + +To run the style tests, simply run: + +```shell +make test-style +``` + +If the `composer` dependencies aren't in place, they'll be automatically installed first. + +#### Running All Tests + +To make things super simple, just run the following to run all tests: + +```shell +make +``` + +If the `composer` dependencies aren't in place, they'll be automatically installed first. + +#### Creating a PR + +When creating a PR, please make sure to mention which GitHub issue is being resolved +at the top of the description, e.g.: + +`Resolves #123` + +The unit and style tests will be run automatically, the PR will not be eligible for +merge unless they pass, and the branch is up-to-date with `master`. + +## License + +[GPLv2+](http://www.gnu.org/licenses/gpl-2.0.html) diff --git a/vendor/deliciousbrains/wp-background-processing/bin/install-wp-tests.sh b/vendor/deliciousbrains/wp-background-processing/bin/install-wp-tests.sh new file mode 100755 index 000000000..ee05775c8 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/bin/install-wp-tests.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash + +if [ $# -lt 3 ]; then + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-false} + +TMPDIR=${TMPDIR-/tmp} +TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") +WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress} + +download() { + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then + wget -nv -O "$2" "$1" + fi +} + +if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then + WP_BRANCH=${WP_VERSION%\-*} + WP_TESTS_TAG="branches/$WP_BRANCH" + +elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then + WP_TESTS_TAG="branches/$WP_VERSION" +elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then + if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + WP_TESTS_TAG="tags/${WP_VERSION%??}" + else + WP_TESTS_TAG="tags/$WP_VERSION" + fi +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" +else + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" +fi +set -ex + +install_wp() { + + if [ -d $WP_CORE_DIR ]; then + return; + fi + + mkdir -p $WP_CORE_DIR + + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p $TMPDIR/wordpress-trunk + rm -rf $TMPDIR/wordpress-trunk/* + svn export --quiet https://core.svn.wordpress.org/trunk $TMPDIR/wordpress-trunk/wordpress + mv $TMPDIR/wordpress-trunk/wordpress/* $WP_CORE_DIR + else + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then + # https serves multiple offers, whereas http serves single. + download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json + if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + LATEST_VERSION=${WP_VERSION%??} + else + # otherwise, scan the releases and get the most up to date minor version of the major release + local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` + LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) + fi + if [[ -z "$LATEST_VERSION" ]]; then + local ARCHIVE_NAME="wordpress-$WP_VERSION" + else + local ARCHIVE_NAME="wordpress-$LATEST_VERSION" + fi + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz + tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR + fi + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php +} + +install_test_suite() { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i.bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [ ! -d $WP_TESTS_DIR ]; then + # set up testing suite + mkdir -p $WP_TESTS_DIR + rm -rf $WP_TESTS_DIR/{includes,data} + svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data + fi + + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php + fi + +} + +recreate_db() { + shopt -s nocasematch + if [[ $1 =~ ^(y|yes)$ ]] + then + mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA + create_db + echo "Recreated the database ($DB_NAME)." + else + echo "Leaving the existing database ($DB_NAME) in place." + fi + shopt -u nocasematch +} + +create_db() { + mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA +} + +install_db() { + + if [ ${SKIP_DB_CREATE} = "true" ]; then + return 0 + fi + + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z $DB_HOSTNAME ] ; then + if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [ -z $DB_SOCK_OR_PORT ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [ -z $DB_HOSTNAME ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + if [ $(mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep ^$DB_NAME$) ] + then + echo "Reinstalling will delete the existing test database ($DB_NAME)" + read -p 'Are you sure you want to proceed? [y/N]: ' DELETE_EXISTING_DB + recreate_db $DELETE_EXISTING_DB + else + create_db + fi +} + +install_wp +install_test_suite +install_db diff --git a/vendor/deliciousbrains/wp-background-processing/classes/wp-async-request.php b/vendor/deliciousbrains/wp-background-processing/classes/wp-async-request.php new file mode 100644 index 000000000..0503a6040 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/classes/wp-async-request.php @@ -0,0 +1,202 @@ +identifier = $this->prefix . '_' . $this->action; + + add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); + add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) ); + } + + /** + * Set data used during the request. + * + * @param array $data Data. + * + * @return $this + */ + public function data( $data ) { + $this->data = $data; + + return $this; + } + + /** + * Dispatch the async request. + * + * @return array|WP_Error|false HTTP Response array, WP_Error on failure, or false if not attempted. + */ + public function dispatch() { + $url = add_query_arg( $this->get_query_args(), $this->get_query_url() ); + $args = $this->get_post_args(); + + return wp_remote_post( esc_url_raw( $url ), $args ); + } + + /** + * Get query args. + * + * @return array + */ + protected function get_query_args() { + if ( property_exists( $this, 'query_args' ) ) { + return $this->query_args; + } + + $args = array( + 'action' => $this->identifier, + 'nonce' => wp_create_nonce( $this->identifier ), + ); + + /** + * Filters the post arguments used during an async request. + * + * @param array $url + */ + return apply_filters( $this->identifier . '_query_args', $args ); + } + + /** + * Get query URL. + * + * @return string + */ + protected function get_query_url() { + if ( property_exists( $this, 'query_url' ) ) { + return $this->query_url; + } + + $url = admin_url( 'admin-ajax.php' ); + + /** + * Filters the post arguments used during an async request. + * + * @param string $url + */ + return apply_filters( $this->identifier . '_query_url', $url ); + } + + /** + * Get post args. + * + * @return array + */ + protected function get_post_args() { + if ( property_exists( $this, 'post_args' ) ) { + return $this->post_args; + } + + $args = array( + 'timeout' => 5, + 'blocking' => false, + 'body' => $this->data, + 'cookies' => $_COOKIE, // Passing cookies ensures request is performed as initiating user. + 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // Local requests, fine to pass false. + ); + + /** + * Filters the post arguments used during an async request. + * + * @param array $args + */ + return apply_filters( $this->identifier . '_post_args', $args ); + } + + /** + * Maybe handle a dispatched request. + * + * Check for correct nonce and pass to handler. + * + * @return void|mixed + */ + public function maybe_handle() { + // Don't lock up other requests while processing. + session_write_close(); + + check_ajax_referer( $this->identifier, 'nonce' ); + + $this->handle(); + + return $this->maybe_wp_die(); + } + + /** + * Should the process exit with wp_die? + * + * @param mixed $return What to return if filter says don't die, default is null. + * + * @return void|mixed + */ + protected function maybe_wp_die( $return = null ) { + /** + * Should wp_die be used? + * + * @return bool + */ + if ( apply_filters( $this->identifier . '_wp_die', true ) ) { + wp_die(); + } + + return $return; + } + + /** + * Handle a dispatched request. + * + * Override this method to perform any actions required + * during the async request. + */ + abstract protected function handle(); +} diff --git a/vendor/deliciousbrains/wp-background-processing/classes/wp-background-process.php b/vendor/deliciousbrains/wp-background-processing/classes/wp-background-process.php new file mode 100644 index 000000000..2f2e74310 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/classes/wp-background-process.php @@ -0,0 +1,786 @@ +allowed_batch_data_classes || true !== $allowed_batch_data_classes ) { + $this->allowed_batch_data_classes = $allowed_batch_data_classes; + } + + $this->cron_hook_identifier = $this->identifier . '_cron'; + $this->cron_interval_identifier = $this->identifier . '_cron_interval'; + + add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) ); + add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) ); + } + + /** + * Schedule the cron healthcheck and dispatch an async request to start processing the queue. + * + * @access public + * @return array|WP_Error|false HTTP Response array, WP_Error on failure, or false if not attempted. + */ + public function dispatch() { + if ( $this->is_processing() ) { + // Process already running. + return false; + } + + // Schedule the cron healthcheck. + $this->schedule_event(); + + // Perform remote post. + return parent::dispatch(); + } + + /** + * Push to the queue. + * + * Note, save must be called in order to persist queued items to a batch for processing. + * + * @param mixed $data Data. + * + * @return $this + */ + public function push_to_queue( $data ) { + $this->data[] = $data; + + return $this; + } + + /** + * Save the queued items for future processing. + * + * @return $this + */ + public function save() { + $key = $this->generate_key(); + + if ( ! empty( $this->data ) ) { + update_site_option( $key, $this->data ); + } + + // Clean out data so that new data isn't prepended with closed session's data. + $this->data = array(); + + return $this; + } + + /** + * Update a batch's queued items. + * + * @param string $key Key. + * @param array $data Data. + * + * @return $this + */ + public function update( $key, $data ) { + if ( ! empty( $data ) ) { + update_site_option( $key, $data ); + } + + return $this; + } + + /** + * Delete a batch of queued items. + * + * @param string $key Key. + * + * @return $this + */ + public function delete( $key ) { + delete_site_option( $key ); + + return $this; + } + + /** + * Delete entire job queue. + */ + public function delete_all() { + $batches = $this->get_batches(); + + foreach ( $batches as $batch ) { + $this->delete( $batch->key ); + } + + delete_site_option( $this->get_status_key() ); + + $this->cancelled(); + } + + /** + * Cancel job on next batch. + */ + public function cancel() { + update_site_option( $this->get_status_key(), self::STATUS_CANCELLED ); + + // Just in case the job was paused at the time. + $this->dispatch(); + } + + /** + * Has the process been cancelled? + * + * @return bool + */ + public function is_cancelled() { + $status = get_site_option( $this->get_status_key(), 0 ); + + return absint( $status ) === self::STATUS_CANCELLED; + } + + /** + * Called when background process has been cancelled. + */ + protected function cancelled() { + do_action( $this->identifier . '_cancelled' ); + } + + /** + * Pause job on next batch. + */ + public function pause() { + update_site_option( $this->get_status_key(), self::STATUS_PAUSED ); + } + + /** + * Is the job paused? + * + * @return bool + */ + public function is_paused() { + $status = get_site_option( $this->get_status_key(), 0 ); + + return absint( $status ) === self::STATUS_PAUSED; + } + + /** + * Called when background process has been paused. + */ + protected function paused() { + do_action( $this->identifier . '_paused' ); + } + + /** + * Resume job. + */ + public function resume() { + delete_site_option( $this->get_status_key() ); + + $this->schedule_event(); + $this->dispatch(); + $this->resumed(); + } + + /** + * Called when background process has been resumed. + */ + protected function resumed() { + do_action( $this->identifier . '_resumed' ); + } + + /** + * Is queued? + * + * @return bool + */ + public function is_queued() { + return ! $this->is_queue_empty(); + } + + /** + * Is the tool currently active, e.g. starting, working, paused or cleaning up? + * + * @return bool + */ + public function is_active() { + return $this->is_queued() || $this->is_processing() || $this->is_paused() || $this->is_cancelled(); + } + + /** + * Generate key for a batch. + * + * Generates a unique key based on microtime. Queue items are + * given a unique key so that they can be merged upon save. + * + * @param int $length Optional max length to trim key to, defaults to 64 characters. + * @param string $key Optional string to append to identifier before hash, defaults to "batch". + * + * @return string + */ + protected function generate_key( $length = 64, $key = 'batch' ) { + $unique = md5( microtime() . wp_rand() ); + $prepend = $this->identifier . '_' . $key . '_'; + + return substr( $prepend . $unique, 0, $length ); + } + + /** + * Get the status key. + * + * @return string + */ + protected function get_status_key() { + return $this->identifier . '_status'; + } + + /** + * Maybe process a batch of queued items. + * + * Checks whether data exists within the queue and that + * the process is not already running. + */ + public function maybe_handle() { + // Don't lock up other requests while processing. + session_write_close(); + + if ( $this->is_processing() ) { + // Background process already running. + return $this->maybe_wp_die(); + } + + if ( $this->is_cancelled() ) { + $this->clear_scheduled_event(); + $this->delete_all(); + + return $this->maybe_wp_die(); + } + + if ( $this->is_paused() ) { + $this->clear_scheduled_event(); + $this->paused(); + + return $this->maybe_wp_die(); + } + + if ( $this->is_queue_empty() ) { + // No data to process. + return $this->maybe_wp_die(); + } + + check_ajax_referer( $this->identifier, 'nonce' ); + + $this->handle(); + + return $this->maybe_wp_die(); + } + + /** + * Is queue empty? + * + * @return bool + */ + protected function is_queue_empty() { + return empty( $this->get_batch() ); + } + + /** + * Is process running? + * + * Check whether the current process is already running + * in a background process. + * + * @return bool + * + * @deprecated 1.1.0 Superseded. + * @see is_processing() + */ + protected function is_process_running() { + return $this->is_processing(); + } + + /** + * Is the background process currently running? + * + * @return bool + */ + public function is_processing() { + if ( get_site_transient( $this->identifier . '_process_lock' ) ) { + // Process already running. + return true; + } + + return false; + } + + /** + * Lock process. + * + * Lock the process so that multiple instances can't run simultaneously. + * Override if applicable, but the duration should be greater than that + * defined in the time_exceeded() method. + */ + protected function lock_process() { + $this->start_time = time(); // Set start time of current process. + + $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute + $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration ); + + set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration ); + } + + /** + * Unlock process. + * + * Unlock the process so that other instances can spawn. + * + * @return $this + */ + protected function unlock_process() { + delete_site_transient( $this->identifier . '_process_lock' ); + + return $this; + } + + /** + * Get batch. + * + * @return stdClass Return the first batch of queued items. + */ + protected function get_batch() { + return array_reduce( + $this->get_batches( 1 ), + static function ( $carry, $batch ) { + return $batch; + }, + array() + ); + } + + /** + * Get batches. + * + * @param int $limit Number of batches to return, defaults to all. + * + * @return array of stdClass + */ + public function get_batches( $limit = 0 ) { + global $wpdb; + + if ( empty( $limit ) || ! is_int( $limit ) ) { + $limit = 0; + } + + $table = $wpdb->options; + $column = 'option_name'; + $key_column = 'option_id'; + $value_column = 'option_value'; + + if ( is_multisite() ) { + $table = $wpdb->sitemeta; + $column = 'meta_key'; + $key_column = 'meta_id'; + $value_column = 'meta_value'; + } + + $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; + + $sql = ' + SELECT * + FROM ' . $table . ' + WHERE ' . $column . ' LIKE %s + ORDER BY ' . $key_column . ' ASC + '; + + $args = array( $key ); + + if ( ! empty( $limit ) ) { + $sql .= ' LIMIT %d'; + + $args[] = $limit; + } + + $items = $wpdb->get_results( $wpdb->prepare( $sql, $args ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + $batches = array(); + + if ( ! empty( $items ) ) { + $allowed_classes = $this->allowed_batch_data_classes; + + $batches = array_map( + static function ( $item ) use ( $column, $value_column, $allowed_classes ) { + $batch = new stdClass(); + $batch->key = $item->{$column}; + $batch->data = static::maybe_unserialize( $item->{$value_column}, $allowed_classes ); + + return $batch; + }, + $items + ); + } + + return $batches; + } + + /** + * Handle a dispatched request. + * + * Pass each queue item to the task handler, while remaining + * within server memory and time limit constraints. + */ + protected function handle() { + $this->lock_process(); + + /** + * Number of seconds to sleep between batches. Defaults to 0 seconds, minimum 0. + * + * @param int $seconds + */ + $throttle_seconds = max( + 0, + apply_filters( + $this->identifier . '_seconds_between_batches', + apply_filters( + $this->prefix . '_seconds_between_batches', + 0 + ) + ) + ); + + do { + $batch = $this->get_batch(); + + foreach ( $batch->data as $key => $value ) { + $task = $this->task( $value ); + + if ( false !== $task ) { + $batch->data[ $key ] = $task; + } else { + unset( $batch->data[ $key ] ); + } + + // Keep the batch up to date while processing it. + if ( ! empty( $batch->data ) ) { + $this->update( $batch->key, $batch->data ); + } + + // Let the server breathe a little. + sleep( $throttle_seconds ); + + // Batch limits reached, or pause or cancel request. + if ( $this->time_exceeded() || $this->memory_exceeded() || $this->is_paused() || $this->is_cancelled() ) { + break; + } + } + + // Delete current batch if fully processed. + if ( empty( $batch->data ) ) { + $this->delete( $batch->key ); + } + } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() && ! $this->is_paused() && ! $this->is_cancelled() ); + + $this->unlock_process(); + + // Start next batch or complete process. + if ( ! $this->is_queue_empty() ) { + $this->dispatch(); + } else { + $this->complete(); + } + + return $this->maybe_wp_die(); + } + + /** + * Memory exceeded? + * + * Ensures the batch process never exceeds 90% + * of the maximum WordPress memory. + * + * @return bool + */ + protected function memory_exceeded() { + $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory + $current_memory = memory_get_usage( true ); + $return = false; + + if ( $current_memory >= $memory_limit ) { + $return = true; + } + + return apply_filters( $this->identifier . '_memory_exceeded', $return ); + } + + /** + * Get memory limit in bytes. + * + * @return int + */ + protected function get_memory_limit() { + if ( function_exists( 'ini_get' ) ) { + $memory_limit = ini_get( 'memory_limit' ); + } else { + // Sensible default. + $memory_limit = '128M'; + } + + if ( ! $memory_limit || -1 === intval( $memory_limit ) ) { + // Unlimited, set to 32GB. + $memory_limit = '32000M'; + } + + return wp_convert_hr_to_bytes( $memory_limit ); + } + + /** + * Time limit exceeded? + * + * Ensures the batch never exceeds a sensible time limit. + * A timeout limit of 30s is common on shared hosting. + * + * @return bool + */ + protected function time_exceeded() { + $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds + $return = false; + + if ( time() >= $finish ) { + $return = true; + } + + return apply_filters( $this->identifier . '_time_exceeded', $return ); + } + + /** + * Complete processing. + * + * Override if applicable, but ensure that the below actions are + * performed, or, call parent::complete(). + */ + protected function complete() { + delete_site_option( $this->get_status_key() ); + + // Remove the cron healthcheck job from the cron schedule. + $this->clear_scheduled_event(); + + $this->completed(); + } + + /** + * Called when background process has completed. + */ + protected function completed() { + do_action( $this->identifier . '_completed' ); + } + + /** + * Get the cron healthcheck interval in minutes. + * + * Default is 5 minutes, minimum is 1 minute. + * + * @return int + */ + public function get_cron_interval() { + $interval = 5; + + if ( property_exists( $this, 'cron_interval' ) ) { + $interval = $this->cron_interval; + } + + $interval = apply_filters( $this->cron_interval_identifier, $interval ); + + return is_int( $interval ) && 0 < $interval ? $interval : 5; + } + + /** + * Schedule the cron healthcheck job. + * + * @access public + * + * @param mixed $schedules Schedules. + * + * @return mixed + */ + public function schedule_cron_healthcheck( $schedules ) { + $interval = $this->get_cron_interval(); + + if ( 1 === $interval ) { + $display = __( 'Every Minute' ); + } else { + $display = sprintf( __( 'Every %d Minutes' ), $interval ); + } + + // Adds an "Every NNN Minute(s)" schedule to the existing cron schedules. + $schedules[ $this->cron_interval_identifier ] = array( + 'interval' => MINUTE_IN_SECONDS * $interval, + 'display' => $display, + ); + + return $schedules; + } + + /** + * Handle cron healthcheck event. + * + * Restart the background process if not already running + * and data exists in the queue. + */ + public function handle_cron_healthcheck() { + if ( $this->is_processing() ) { + // Background process already running. + exit; + } + + if ( $this->is_queue_empty() ) { + // No data to process. + $this->clear_scheduled_event(); + exit; + } + + $this->dispatch(); + } + + /** + * Schedule the cron healthcheck event. + */ + protected function schedule_event() { + if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { + wp_schedule_event( time() + ( $this->get_cron_interval() * MINUTE_IN_SECONDS ), $this->cron_interval_identifier, $this->cron_hook_identifier ); + } + } + + /** + * Clear scheduled cron healthcheck event. + */ + protected function clear_scheduled_event() { + $timestamp = wp_next_scheduled( $this->cron_hook_identifier ); + + if ( $timestamp ) { + wp_unschedule_event( $timestamp, $this->cron_hook_identifier ); + } + } + + /** + * Cancel the background process. + * + * Stop processing queue items, clear cron job and delete batch. + * + * @deprecated 1.1.0 Superseded. + * @see cancel() + */ + public function cancel_process() { + $this->cancel(); + } + + /** + * Perform task with queued item. + * + * Override this method to perform any actions required on each + * queue item. Return the modified item for further processing + * in the next pass through. Or, return false to remove the + * item from the queue. + * + * @param mixed $item Queue item to iterate over. + * + * @return mixed + */ + abstract protected function task( $item ); + + /** + * Maybe unserialize data, but not if an object. + * + * @param mixed $data Data to be unserialized. + * @param bool|array $allowed_classes Array of class names that can be unserialized. + * + * @return mixed + */ + protected static function maybe_unserialize( $data, $allowed_classes ) { + if ( is_serialized( $data ) ) { + $options = array(); + if ( is_bool( $allowed_classes ) || is_array( $allowed_classes ) ) { + $options['allowed_classes'] = $allowed_classes; + } + + return @unserialize( $data, $options ); // @phpcs:ignore + } + + return $data; + } +} diff --git a/vendor/deliciousbrains/wp-background-processing/composer.json b/vendor/deliciousbrains/wp-background-processing/composer.json new file mode 100644 index 000000000..7f65bbfb5 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/composer.json @@ -0,0 +1,35 @@ +{ + "name": "deliciousbrains/wp-background-processing", + "description": "WP Background Processing can be used to fire off non-blocking asynchronous requests or as a background processing tool, allowing you to queue tasks.", + "type": "library", + "require": { + "php": ">=7.0" + }, + "suggest": { + "coenjacobs/mozart": "Easily wrap this library with your own prefix, to prevent collisions when multiple plugins use this library" + }, + "license": "GPL-2.0-or-later", + "authors": [ + { + "name": "Delicious Brains", + "email": "nom@deliciousbrains.com" + } + ], + "autoload": { + "classmap": [ + "classes/" + ] + }, + "require-dev": { + "phpunit/phpunit": "^8.0", + "yoast/phpunit-polyfills": "^1.0", + "spryker/code-sniffer": "^0.17.18", + "phpcompatibility/phpcompatibility-wp": "*", + "wp-coding-standards/wpcs": "^2.3" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/vendor/deliciousbrains/wp-background-processing/composer.lock b/vendor/deliciousbrains/wp-background-processing/composer.lock new file mode 100644 index 000000000..00972a5d6 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/composer.lock @@ -0,0 +1,2048 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a9e05db155afa8fde60a18a19cc4f591", + "packages": [], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", + "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "time": "2022-10-25T01:46:02+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.4", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", + "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, + "time": "2022-10-24T09:00:36+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.25.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240", + "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0" + }, + "time": "2024-01-04T17:06:16+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "7.0.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "819f92bba8b001d4363065928088de22f25a3a48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", + "reference": "819f92bba8b001d4363065928088de22f25a3a48", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": ">=7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.1.3 || ^4.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.2.2", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.2.2" + }, + "suggest": { + "ext-xdebug": "^2.7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.15" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-07-26T12:20:09+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:42:26+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:20:02+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2020-08-04T08:28:15+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "8.5.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "9652df58e06a681429d8cfdaec3c43d6de581d5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9652df58e06a681429d8cfdaec3c43d6de581d5a", + "reference": "9652df58e06a681429d8cfdaec3c43d6de581d5a", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.0", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.2", + "phpunit/php-code-coverage": "^7.0.12", + "phpunit/php-file-iterator": "^2.0.4", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.5", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.3", + "sebastian/exporter": "^3.1.5", + "sebastian/global-state": "^3.0.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", + "sebastian/version": "^2.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage", + "phpunit/php-invoker": "To allow enforcing time limits" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.36" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2023-12-01T16:52:15+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:15:22+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dc7ceb4a24aede938c7af2a9ed1de09609ca770", + "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:31:48+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6296a0c086dd0117c1b78b059374d7fcbe7545ae", + "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:30:20+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:53:42+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/73a9676f2833b9a7c36968f9d882589cd75511e6", + "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:00:17+00:00" + }, + { + "name": "sebastian/global-state", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "66783ce213de415b451b904bfef9dda0cf9aeae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/66783ce213de415b451b904bfef9dda0cf9aeae0", + "reference": "66783ce213de415b451b904bfef9dda0cf9aeae0", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-02T09:23:32+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:40:27+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:37:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:34:24+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:30:19+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:25:11+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/master" + }, + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "8.14.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/fea1fd6f137cc84f9cba0ae30d549615dbc6a926", + "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.23.1", + "squizlabs/php_codesniffer": "^3.7.1" + }, + "require-dev": { + "phing/phing": "2.17.4", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.10.37", + "phpstan/phpstan-deprecation-rules": "1.1.4", + "phpstan/phpstan-phpunit": "1.3.14", + "phpstan/phpstan-strict-rules": "1.5.1", + "phpunit/phpunit": "8.5.21|9.6.8|10.3.5" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "keywords": [ + "dev", + "phpcs" + ], + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/8.14.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2023-10-08T07:28:08+00:00" + }, + { + "name": "spryker/code-sniffer", + "version": "0.17.20", + "source": { + "type": "git", + "url": "https://github.com/spryker/code-sniffer.git", + "reference": "5374a2c2ad29c361a6aeec0c81730aed438d08e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spryker/code-sniffer/zipball/5374a2c2ad29c361a6aeec0c81730aed438d08e4", + "reference": "5374a2c2ad29c361a6aeec0c81730aed438d08e4", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "slevomat/coding-standard": "^7.2.0 || ^8.0.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phpstan/phpstan": "^1.0.0", + "phpunit/phpunit": "^9.5" + }, + "bin": [ + "bin/tokenize" + ], + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "Spryker\\": "Spryker/", + "SprykerStrict\\": "SprykerStrict/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spryker", + "homepage": "https://spryker.com" + } + ], + "description": "Spryker Code Sniffer Standards", + "homepage": "https://spryker.com", + "keywords": [ + "codesniffer", + "framework", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/spryker/code-sniffer/issues", + "source": "https://github.com/spryker/code-sniffer" + }, + "time": "2024-01-04T17:11:52+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.8.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "14f5fff1e64118595db5408e946f3a22c75807f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7", + "reference": "14f5fff1e64118595db5408e946f3a22c75807f7", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-01-11T20:47:48+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2023-11-20T00:12:19+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "7da1894633f168fe244afc6de00d141f27517b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62", + "reference": "7da1894633f168fe244afc6de00d141f27517b62", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.3.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "time": "2020-05-13T23:57:56+00:00" + }, + { + "name": "yoast/phpunit-polyfills", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", + "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/224e4a1329c03d8bad520e3fc4ec980034a4b212", + "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "require-dev": { + "yoast/yoastcs": "^2.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "phpunitpolyfills-autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Team Yoast", + "email": "support@yoast.com", + "homepage": "https://yoast.com" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" + } + ], + "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", + "keywords": [ + "phpunit", + "polyfill", + "testing" + ], + "support": { + "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "source": "https://github.com/Yoast/PHPUnit-Polyfills" + }, + "time": "2023-08-19T14:25:08+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/vendor/deliciousbrains/wp-background-processing/license.txt b/vendor/deliciousbrains/wp-background-processing/license.txt new file mode 100644 index 000000000..a0939e921 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/license.txt @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110, USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/vendor/deliciousbrains/wp-background-processing/phpunit.xml b/vendor/deliciousbrains/wp-background-processing/phpunit.xml new file mode 100644 index 000000000..d6218767c --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/phpunit.xml @@ -0,0 +1,15 @@ + + + + + ./tests/ + + + diff --git a/vendor/deliciousbrains/wp-background-processing/tests/Test_Setup.php b/vendor/deliciousbrains/wp-background-processing/tests/Test_Setup.php new file mode 100644 index 000000000..cfbaa8ed0 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/tests/Test_Setup.php @@ -0,0 +1,19 @@ +assertTrue( true ); + } +} diff --git a/vendor/deliciousbrains/wp-background-processing/tests/Test_WP_Background_Process.php b/vendor/deliciousbrains/wp-background-processing/tests/Test_WP_Background_Process.php new file mode 100644 index 000000000..048523450 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/tests/Test_WP_Background_Process.php @@ -0,0 +1,672 @@ +wpbp = $this->getMockForAbstractClass( WP_Background_Process::class ); + + $this->wpbp->expects( $this->any() ) + ->method( 'task' ) + ->will( $this->returnValue( false ) ); + } + + /** + * Get a property value from WPBP regardless of accessibility. + * + * @param string $name + * + * @return mixed + */ + private function getWPBPProperty( string $name ) { + try { + $property = new ReflectionProperty( 'WP_Background_Process', $name ); + } catch ( Exception $e ) { + return new WP_Error( $e->getCode(), $e->getMessage() ); + } + $property->setAccessible( true ); + + return $property->getValue( $this->wpbp ); + } + + /** + * Set a property value on WPBP regardless of accessibility. + * + * @param string $name + * @param mixed $value + * + * @return mixed + */ + private function setWPBPProperty( string $name, $value ) { + try { + $property = new ReflectionProperty( 'WP_Background_Process', $name ); + } catch ( Exception $e ) { + return new WP_Error( $e->getCode(), $e->getMessage() ); + } + $property->setAccessible( true ); + + return $property->setValue( $this->wpbp, $value ); + } + + /** + * Execute a method of WPBP regardless of accessibility. + * + * @param string $name Method name. + * @param mixed $args None, one or more args to pass to method. + * + * @return mixed + */ + private function executeWPBPMethod( string $name, ...$args ) { + try { + $method = new ReflectionMethod( 'WP_Background_Process', $name ); + $method->setAccessible( true ); + + return $method->invoke( $this->wpbp, ...$args ); + } catch ( Exception $e ) { + return new WP_Error( $e->getCode(), $e->getMessage() ); + } + } + + /** + * Test push_to_queue. + * + * @return void + */ + public function test_push_to_queue() { + $this->assertClassHasAttribute( 'data', 'WP_Background_Process', 'class has data property' ); + $this->assertEmpty( $this->getWPBPProperty( 'data' ) ); + + $this->wpbp->push_to_queue( 'wibble' ); + $this->assertNotEmpty( $this->getWPBPProperty( 'data' ) ); + $this->assertEquals( array( 'wibble' ), $this->getWPBPProperty( 'data' ) ); + + $this->wpbp->push_to_queue( 'wobble' ); + $this->assertEquals( array( 'wibble', 'wobble' ), $this->getWPBPProperty( 'data' ) ); + } + + /** + * Test save. + * + * @return void + */ + public function test_save() { + $this->assertClassHasAttribute( 'data', 'WP_Background_Process', 'class has data property' ); + $this->assertEmpty( $this->getWPBPProperty( 'data' ) ); + $this->assertEmpty( $this->wpbp->get_batches(), 'no batches until save' ); + + $this->wpbp->push_to_queue( 'wibble' ); + $this->assertNotEmpty( $this->getWPBPProperty( 'data' ) ); + $this->assertEquals( array( 'wibble' ), $this->getWPBPProperty( 'data' ) ); + $this->wpbp->save(); + $this->assertEmpty( $this->getWPBPProperty( 'data' ), 'data emptied after save' ); + $this->assertNotEmpty( $this->wpbp->get_batches(), 'batches exist after save' ); + } + + /** + * Test get_batches. + * + * @return void + */ + public function test_get_batches() { + $this->assertEmpty( $this->wpbp->get_batches(), 'no batches until save' ); + + $this->wpbp->push_to_queue( 'wibble' ); + $this->assertNotEmpty( $this->getWPBPProperty( 'data' ) ); + $this->assertEquals( array( 'wibble' ), $this->getWPBPProperty( 'data' ) ); + $this->assertEmpty( $this->wpbp->get_batches(), 'no batches until save' ); + + $this->wpbp->push_to_queue( 'wobble' ); + $this->assertEquals( array( 'wibble', 'wobble' ), $this->getWPBPProperty( 'data' ) ); + $this->assertEmpty( $this->wpbp->get_batches(), 'no batches until save' ); + + $this->wpbp->save(); + $first_batch = $this->wpbp->get_batches(); + $this->assertNotEmpty( $first_batch ); + $this->assertCount( 1, $first_batch ); + + $this->wpbp->push_to_queue( 'more wibble' ); + $this->wpbp->save(); + $this->assertCount( 2, $this->wpbp->get_batches() ); + + $this->wpbp->push_to_queue( 'Wibble wobble all day long.' ); + $this->wpbp->save(); + $this->assertCount( 3, $this->wpbp->get_batches() ); + + $this->assertEquals( $first_batch, $this->wpbp->get_batches( 1 ) ); + $this->assertNotEquals( $first_batch, $this->wpbp->get_batches( 2 ) ); + $this->assertCount( 2, $this->wpbp->get_batches( 2 ) ); + $this->assertCount( 3, $this->wpbp->get_batches( 3 ) ); + $this->assertCount( 3, $this->wpbp->get_batches( 5 ) ); + } + + /** + * Test get_batch. + * + * @return void + */ + public function test_get_batch() { + $this->assertEmpty( $this->executeWPBPMethod( 'get_batch' ), 'no batches until save' ); + + $this->wpbp->push_to_queue( 'wibble' ); + $this->assertNotEmpty( $this->getWPBPProperty( 'data' ) ); + $this->assertEquals( array( 'wibble' ), $this->getWPBPProperty( 'data' ) ); + $this->assertEmpty( $this->executeWPBPMethod( 'get_batch' ), 'no batches until save' ); + + $this->wpbp->push_to_queue( 'wobble' ); + $this->assertEquals( array( 'wibble', 'wobble' ), $this->getWPBPProperty( 'data' ) ); + $this->assertEmpty( $this->executeWPBPMethod( 'get_batch' ), 'no batches until save' ); + + $this->wpbp->save(); + $first_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertNotEmpty( $first_batch ); + $this->assertInstanceOf( 'stdClass', $first_batch ); + $this->assertEquals( array( 'wibble', 'wobble' ), $first_batch->data ); + + $this->wpbp->push_to_queue( 'more wibble' ); + $this->wpbp->save(); + $second_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertNotEmpty( $second_batch ); + $this->assertInstanceOf( 'stdClass', $second_batch ); + $this->assertEquals( $first_batch, $second_batch, 'same 1st batch returned until deleted' ); + + $this->wpbp->delete( $first_batch->key ); + $second_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertNotEmpty( $second_batch ); + $this->assertInstanceOf( 'stdClass', $second_batch ); + $this->assertNotEquals( $first_batch, $second_batch, '2nd batch returned as 1st deleted' ); + $this->assertEquals( array( 'more wibble' ), $second_batch->data ); + + // Tests using a custom class for the $item. + $this->wpbp->delete( $second_batch->key ); + $batch_data_object = new Test_Batch_Data(); + $this->wpbp->push_to_queue( $batch_data_object ); + $this->assertNotEmpty( $this->getWPBPProperty( 'data' ) ); + $this->assertEquals( array( $batch_data_object ), $this->getWPBPProperty( 'data' ) ); + $this->wpbp->save(); + $third_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertCount( 1, $third_batch->data ); + $this->assertInstanceOf( Test_Batch_Data::class, $third_batch->data[0] ); + + // Explicitly set allowed classes to Test_Batch_Data. + $this->setWPBPProperty( 'allowed_batch_data_classes', array( Test_Batch_Data::class ) ); + $third_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertCount( 1, $third_batch->data ); + $this->assertInstanceOf( Test_Batch_Data::class, $third_batch->data[0] ); + + // Allow a different class. + $this->setWPBPProperty( 'allowed_batch_data_classes', array( stdClass::class ) ); + $third_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertCount( 1, $third_batch->data ); + $this->assertInstanceOf( __PHP_Incomplete_Class::class, $third_batch->data[0] ); + + // Disallow all classes. + $this->setWPBPProperty( 'allowed_batch_data_classes', false ); + $third_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertCount( 1, $third_batch->data ); + $this->assertInstanceOf( __PHP_Incomplete_Class::class, $third_batch->data[0] ); + + // Allow everything. + $this->setWPBPProperty( 'allowed_batch_data_classes', true ); + $third_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertCount( 1, $third_batch->data ); + $this->assertInstanceOf( Test_Batch_Data::class, $third_batch->data[0] ); + } + + /** + * Test cancel. + * + * @return void + */ + public function test_cancel() { + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertFalse( $this->wpbp->is_cancelled() ); + $this->wpbp->cancel(); + $this->assertTrue( $this->wpbp->is_cancelled() ); + } + + /** + * Test pause. + * + * @return void + */ + public function test_pause() { + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertFalse( $this->wpbp->is_paused() ); + $this->wpbp->pause(); + $this->assertTrue( $this->wpbp->is_paused() ); + } + + /** + * Test resume. + * + * @return void + */ + public function test_resume() { + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertFalse( $this->wpbp->is_paused() ); + $this->wpbp->pause(); + $this->assertTrue( $this->wpbp->is_paused() ); + $this->wpbp->resume(); + $this->assertFalse( $this->wpbp->is_paused() ); + } + + /** + * Test delete. + * + * @return void + */ + public function test_delete() { + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertCount( 1, $this->wpbp->get_batches() ); + $this->wpbp->push_to_queue( 'wobble' ); + $this->wpbp->save(); + $this->assertCount( 2, $this->wpbp->get_batches() ); + $first_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->wpbp->delete( $first_batch->key ); + $this->assertCount( 1, $this->wpbp->get_batches() ); + $second_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertNotEquals( $first_batch, $second_batch, '2nd batch returned as 1st deleted' ); + } + + /** + * Test delete_all. + * + * @return void + */ + public function test_delete_all() { + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertCount( 1, $this->wpbp->get_batches() ); + $this->wpbp->push_to_queue( 'wobble' ); + $this->wpbp->save(); + $this->assertCount( 2, $this->wpbp->get_batches() ); + $this->wpbp->delete_all(); + $this->assertCount( 0, $this->wpbp->get_batches() ); + } + + /** + * Test update. + * + * @return void + */ + public function test_update() { + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertCount( 1, $this->wpbp->get_batches() ); + $this->wpbp->push_to_queue( 'wobble' ); + $this->wpbp->save(); + $this->assertCount( 2, $this->wpbp->get_batches() ); + $first_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->wpbp->update( $first_batch->key, array( 'Wibble wobble all day long!' ) ); + $this->assertCount( 2, $this->wpbp->get_batches() ); + $updated_batch = $this->executeWPBPMethod( 'get_batch' ); + $this->assertNotEquals( $first_batch, $updated_batch, 'fetched updated batch different to 1st fetch' ); + $this->assertEquals( array( 'Wibble wobble all day long!' ), $updated_batch->data, 'fetched updated batch has expected data' ); + } + + /** + * Test maybe_handle when cancelling. + * + * @return void + */ + public function test_maybe_handle_cancelled() { + // Cancelled status results in cleared batches and action fired. + $cancelled_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_cancelled', function () use ( &$cancelled_fired ) { + $cancelled_fired = true; + } ); + // Paused action should not be fired though. + $paused_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_paused', function () use ( &$paused_fired ) { + $paused_fired = true; + } ); + // Completed action should not be fired though. + $completed_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_completed', function () use ( &$completed_fired ) { + $completed_fired = true; + } ); + add_filter( $this->getWPBPProperty( 'identifier' ) . '_wp_die', '__return_false' ); + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertCount( 1, $this->wpbp->get_batches() ); + $this->wpbp->push_to_queue( 'wobble' ); + $this->wpbp->save(); + $this->assertCount( 2, $this->wpbp->get_batches() ); + update_site_option( $this->executeWPBPMethod( 'get_status_key' ), WP_Background_Process::STATUS_CANCELLED ); + $this->assertTrue( $this->wpbp->is_cancelled(), 'is_cancelled' ); + $this->assertCount( 2, $this->wpbp->get_batches() ); + $this->assertFalse( $cancelled_fired, 'cancelled action not fired yet' ); + $this->assertFalse( $paused_fired, 'paused action not fired yet' ); + $this->assertFalse( $completed_fired, 'completed action not fired yet' ); + $this->wpbp->maybe_handle(); + $this->assertCount( 0, $this->wpbp->get_batches() ); + $this->assertTrue( $cancelled_fired, 'cancelled action fired' ); + $this->assertFalse( $paused_fired, 'paused action still not fired yet' ); + $this->assertFalse( $completed_fired, 'completed action not fired yet' ); + } + + /** + * Test maybe_handle when pausing and resuming. + * + * @return void + */ + public function test_maybe_handle_paused_resumed() { + // Cancelled action should not be fired. + $cancelled_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_cancelled', function () use ( &$cancelled_fired ) { + $cancelled_fired = true; + } ); + // Paused action should fire and batches remain intact. + $paused_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_paused', function () use ( &$paused_fired ) { + $paused_fired = true; + } ); + // Resumed action should fire on resume before batches handled. + $resumed_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_resumed', function () use ( &$resumed_fired ) { + $resumed_fired = true; + } ); + // Completed action should fire after batches handled. + $completed_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_completed', function () use ( &$completed_fired ) { + $completed_fired = true; + } ); + add_filter( $this->getWPBPProperty( 'identifier' ) . '_wp_die', '__return_false' ); + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertCount( 1, $this->wpbp->get_batches() ); + $this->wpbp->push_to_queue( 'wobble' ); + $this->wpbp->save(); + $this->assertCount( 2, $this->wpbp->get_batches() ); + $this->wpbp->pause(); + $this->assertTrue( $this->wpbp->is_paused(), 'is_paused' ); + $this->assertCount( 2, $this->wpbp->get_batches() ); + $this->assertFalse( $cancelled_fired, 'cancelled action not fired yet' ); + $this->assertFalse( $paused_fired, 'paused action not fired yet' ); + $this->assertFalse( $resumed_fired, 'resumed action not fired yet' ); + $this->assertFalse( $completed_fired, 'completed action not fired yet' ); + $this->wpbp->maybe_handle(); + $this->assertCount( 2, $this->wpbp->get_batches() ); + $this->assertFalse( $cancelled_fired, 'cancelled action still not fired yet' ); + $this->assertTrue( $paused_fired, 'paused action fired' ); + $this->assertFalse( $resumed_fired, 'resumed action still not fired yet' ); + $this->assertFalse( $completed_fired, 'completed action not fired yet' ); + + // Reset for resume and ensure dispatch does nothing to that maybe_handle can be monitored. + $paused_fired = false; + add_filter( 'pre_http_request', '__return_true' ); + $this->wpbp->resume(); + remove_filter( 'pre_http_request', '__return_true' ); + $this->assertFalse( $this->wpbp->is_paused(), 'not is_paused after resume' ); + $this->assertCount( 2, $this->wpbp->get_batches() ); + $this->assertFalse( $cancelled_fired, 'cancelled action not fired yet' ); + $this->assertFalse( $paused_fired, 'paused action not fired yet' ); + $this->assertTrue( $resumed_fired, 'resumed action fired' ); + $this->assertFalse( $completed_fired, 'completed action not fired yet' ); + + // Don't expect resumed to be fired again, and batches to be handled with valid security. + $resumed_fired = false; + $_REQUEST['nonce'] = wp_create_nonce( $this->getWPBPProperty( 'identifier' ) ); + $this->wpbp->maybe_handle(); + $this->assertCount( 0, $this->wpbp->get_batches(), 'after resume all batches processed with maybe_handle' ); + $this->assertFalse( $cancelled_fired, 'cancelled action still not fired yet' ); + $this->assertFalse( $paused_fired, 'paused action not fired yet' ); + $this->assertFalse( $resumed_fired, 'resumed action still not fired yet' ); + $this->assertTrue( $completed_fired, 'completed action fired' ); + } + + /** + * Test maybe_handle when handling a single batch. + * + * @return void + */ + public function test_maybe_handle_single_batch() { + // Cancelled action should not be fired. + $cancelled_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_cancelled', function () use ( &$cancelled_fired ) { + $cancelled_fired = true; + } ); + // Paused action should not be fired. + $paused_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_paused', function () use ( &$paused_fired ) { + $paused_fired = true; + } ); + // Resumed action should not be fired. + $resumed_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_resumed', function () use ( &$resumed_fired ) { + $resumed_fired = true; + } ); + // Completed action should fire after batches handled. + $completed_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_completed', function () use ( &$completed_fired ) { + $completed_fired = true; + } ); + add_filter( $this->getWPBPProperty( 'identifier' ) . '_wp_die', '__return_false' ); + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertCount( 1, $this->wpbp->get_batches() ); + $this->assertFalse( $cancelled_fired, 'cancelled action not fired yet' ); + $this->assertFalse( $paused_fired, 'paused action not fired yet' ); + $this->assertFalse( $resumed_fired, 'resumed action not fired yet' ); + $this->assertFalse( $completed_fired, 'completed action not fired yet' ); + + $_REQUEST['nonce'] = wp_create_nonce( $this->getWPBPProperty( 'identifier' ) ); + $this->wpbp->maybe_handle(); + $this->assertCount( 0, $this->wpbp->get_batches(), 'after resume all batches processed with maybe_handle' ); + $this->assertFalse( $cancelled_fired, 'cancelled action still not fired yet' ); + $this->assertFalse( $paused_fired, 'paused action not fired yet' ); + $this->assertFalse( $resumed_fired, 'resumed action still not fired yet' ); + $this->assertTrue( $completed_fired, 'completed action fired' ); + } + + /** + * Test maybe_handle when handling nothing. + * + * @return void + */ + public function test_maybe_handle_nothing() { + // Cancelled action should not be fired. + $cancelled_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_cancelled', function () use ( &$cancelled_fired ) { + $cancelled_fired = true; + } ); + // Paused action should not be fired. + $paused_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_paused', function () use ( &$paused_fired ) { + $paused_fired = true; + } ); + // Resumed action should not be fired. + $resumed_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_resumed', function () use ( &$resumed_fired ) { + $resumed_fired = true; + } ); + // Completed action should not be fired. + $completed_fired = false; + add_action( $this->getWPBPProperty( 'identifier' ) . '_completed', function () use ( &$completed_fired ) { + $completed_fired = true; + } ); + add_filter( $this->getWPBPProperty( 'identifier' ) . '_wp_die', '__return_false' ); + $this->assertCount( 0, $this->wpbp->get_batches() ); + $this->assertFalse( $cancelled_fired, 'cancelled action not fired yet' ); + $this->assertFalse( $paused_fired, 'paused action not fired yet' ); + $this->assertFalse( $resumed_fired, 'resumed action not fired yet' ); + $this->assertFalse( $completed_fired, 'completed action not fired yet' ); + + $this->wpbp->maybe_handle(); + $this->assertCount( 0, $this->wpbp->get_batches(), 'after resume all batches processed with maybe_handle' ); + $this->assertFalse( $cancelled_fired, 'cancelled action still not fired yet' ); + $this->assertFalse( $paused_fired, 'paused action not fired yet' ); + $this->assertFalse( $resumed_fired, 'resumed action still not fired yet' ); + $this->assertFalse( $completed_fired, 'completed action not fired yet' ); + } + + /** + * Test is_processing. + * + * @return void + */ + public function test_is_processing() { + $this->assertFalse( $this->wpbp->is_processing(), 'not processing yet' ); + $this->executeWPBPMethod( 'lock_process' ); + $this->assertTrue( $this->wpbp->is_processing(), 'processing' ); + + // With batches to be processed, maybe_handle does nothing as "another instance is processing". + add_filter( $this->getWPBPProperty( 'identifier' ) . '_wp_die', '__return_false' ); + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertCount( 1, $this->wpbp->get_batches() ); + $this->wpbp->maybe_handle(); + $this->assertCount( 1, $this->wpbp->get_batches() ); + + // Unlock and maybe_handle can process the batch. + $this->executeWPBPMethod( 'unlock_process' ); + $this->assertFalse( $this->wpbp->is_processing(), 'not processing yet' ); + $this->assertCount( 1, $this->wpbp->get_batches() ); + $_REQUEST['nonce'] = wp_create_nonce( $this->getWPBPProperty( 'identifier' ) ); + $this->wpbp->maybe_handle(); + $this->assertCount( 0, $this->wpbp->get_batches() ); + $this->assertFalse( $this->wpbp->is_processing(), 'not left processing on complete' ); + } + + /** + * Test is_queued. + * + * @return void + */ + public function test_is_queued() { + $this->assertFalse( $this->wpbp->is_queued(), 'nothing queued until save' ); + + $this->wpbp->push_to_queue( 'wibble' ); + $this->assertFalse( $this->wpbp->is_queued(), 'nothing queued until save' ); + + $this->wpbp->save(); + $this->assertTrue( $this->wpbp->is_queued(), 'queued items exist' ); + + $this->wpbp->push_to_queue( 'wobble' ); + $this->wpbp->save(); + $this->assertTrue( $this->wpbp->is_queued(), 'queued items exist' ); + + $this->wpbp->delete_all(); + $this->assertFalse( $this->wpbp->is_queued(), 'queue emptied' ); + } + + /** + * Test is_active. + * + * @return void + */ + public function test_is_active() { + $this->assertFalse( $this->wpbp->is_active(), 'not queued, processing, paused or cancelling' ); + + // Queued. + $this->wpbp->push_to_queue( 'wibble' ); + $this->assertFalse( $this->wpbp->is_active(), 'nothing queued until save' ); + + $this->wpbp->save(); + $this->assertTrue( $this->wpbp->is_active(), 'queued items exist, so now active' ); + + $this->wpbp->delete_all(); + $this->assertFalse( $this->wpbp->is_active(), 'queue emptied, so no longer active' ); + + // Processing. + $this->executeWPBPMethod( 'lock_process' ); + $this->assertTrue( $this->wpbp->is_active(), 'processing, so now active' ); + + $this->executeWPBPMethod( 'unlock_process' ); + $this->assertFalse( $this->wpbp->is_active(), 'not processing, so no longer active' ); + + // Paused. + $this->wpbp->pause(); + $this->assertTrue( $this->wpbp->is_active(), 'paused, so now active' ); + + $this->wpbp->resume(); + $this->assertFalse( $this->wpbp->is_active(), 'not paused, nothing queued, so no longer active' ); + + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertTrue( $this->wpbp->is_active(), 'queued items exist, so now active' ); + $this->wpbp->pause(); + $this->assertTrue( $this->wpbp->is_active(), 'paused, so still active' ); + add_filter( 'pre_http_request', '__return_true' ); + $this->wpbp->resume(); + remove_filter( 'pre_http_request', '__return_true' ); + $this->assertTrue( $this->wpbp->is_active(), 'resumed but with queued items, so still active' ); + $this->wpbp->delete_all(); + $this->assertFalse( $this->wpbp->is_active(), 'queue emptied, so no longer active' ); + + // Cancelled. + add_filter( 'pre_http_request', '__return_true' ); + $this->wpbp->cancel(); + remove_filter( 'pre_http_request', '__return_true' ); + $this->assertTrue( $this->wpbp->is_active(), 'cancelling, so now active' ); + + add_filter( $this->getWPBPProperty( 'identifier' ) . '_wp_die', '__return_false' ); + $this->wpbp->maybe_handle(); + $this->assertFalse( $this->wpbp->is_active(), 'cancel handled, so no longer active' ); + + $this->wpbp->push_to_queue( 'wibble' ); + $this->wpbp->save(); + $this->assertTrue( $this->wpbp->is_active(), 'queued items exist, so now active' ); + add_filter( 'pre_http_request', '__return_true' ); + $this->wpbp->cancel(); + remove_filter( 'pre_http_request', '__return_true' ); + $this->assertTrue( $this->wpbp->is_active(), 'cancelling, so still active' ); + $this->wpbp->maybe_handle(); + $this->assertFalse( $this->wpbp->is_active(), 'cancel handled, queue emptied, so no longer active' ); + } + + /** + * Test get_cron_interval. + * + * @return void + */ + public function test_get_cron_interval() { + // Default value. + $this->assertEquals( 5, $this->wpbp->get_cron_interval() ); + + // Override via property (usually on subclass). + $this->wpbp->cron_interval = 3; + $this->assertEquals( 3, $this->wpbp->get_cron_interval() ); + + // Override via filter. + $callback = function ( $interval ) { + return 1; + }; + add_filter( $this->getWPBPProperty( 'identifier' ) . '_cron_interval', $callback ); + $this->assertEquals( 1, $this->wpbp->get_cron_interval() ); + + remove_filter( $this->getWPBPProperty( 'identifier' ) . '_cron_interval', $callback ); + $this->assertEquals( 3, $this->wpbp->get_cron_interval() ); + + unset( $this->wpbp->cron_interval ); + $this->assertEquals( 5, $this->wpbp->get_cron_interval() ); + } +} diff --git a/vendor/deliciousbrains/wp-background-processing/tests/bootstrap.php b/vendor/deliciousbrains/wp-background-processing/tests/bootstrap.php new file mode 100644 index 000000000..94036dc18 --- /dev/null +++ b/vendor/deliciousbrains/wp-background-processing/tests/bootstrap.php @@ -0,0 +1,38 @@ + $count) { + echo $identifier . " was triggered " . $count . " times\n"; +} +``` + +### Suppressing Specific Deprecations + +Disable triggering about specific deprecations: + +```php +\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier"); +``` + +Disable all deprecations from a package + +```php +\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm"); +``` + +### Other Operations + +When used within PHPUnit or other tools that could collect multiple instances of the same deprecations +the deduplication can be disabled: + +```php +\Doctrine\Deprecations\Deprecation::withoutDeduplication(); +``` + +Disable deprecation tracking again: + +```php +\Doctrine\Deprecations\Deprecation::disable(); +``` + +## Usage from a library/producer perspective: + +When you want to unconditionally trigger a deprecation even when called +from the library itself then the `trigger` method is the way to go: + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +If variable arguments are provided at the end, they are used with `sprintf` on +the message. + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://github.com/doctrine/orm/issue/1234", + "message %s %d", + "foo", + 1234 +); +``` + +When you want to trigger a deprecation only when it is called by a function +outside of the current package, but not trigger when the package itself is the cause, +then use: + +```php +\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +Based on the issue link each deprecation message is only triggered once per +request. + +A limited stacktrace is included in the deprecation message to find the +offending location. + +Note: A producer/library should never call `Deprecation::enableWith` methods +and leave the decision how to handle deprecations to application and +frameworks. + +## Usage in PHPUnit tests + +There is a `VerifyDeprecations` trait that you can use to make assertions on +the occurrence of deprecations within a test. + +```php +use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; + +class MyTest extends TestCase +{ + use VerifyDeprecations; + + public function testSomethingDeprecation() + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithDeprecation(); + } + + public function testSomethingDeprecationFixed() + { + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithoutDeprecation(); + } +} +``` + +## Displaying deprecations after running a PHPUnit test suite + +It is possible to integrate this library with PHPUnit to display all +deprecations triggered during the test suite execution. + +```xml + + + + + + + + + + + + src + + + +``` + +Note that you can still trigger Deprecations in your code, provided you use the +`#[IgnoreDeprecations]` to ignore them for tests that call it. + +At the moment, it is not possible to disable deduplication with an environment +variable, but you can use a bootstrap file to achieve that: + +```php +// tests/bootstrap.php + + … + +``` + +## What is a deprecation identifier? + +An identifier for deprecations is just a link to any resource, most often a +Github Issue or Pull Request explaining the deprecation and potentially its +alternative. diff --git a/vendor/doctrine/deprecations/composer.json b/vendor/doctrine/deprecations/composer.json new file mode 100644 index 000000000..14a81cffd --- /dev/null +++ b/vendor/doctrine/deprecations/composer.json @@ -0,0 +1,39 @@ +{ + "name": "doctrine/deprecations", + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "license": "MIT", + "type": "library", + "homepage": "https://www.doctrine-project.org/", + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "DeprecationTests\\": "test_fixtures/src", + "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/vendor/doctrine/deprecations/src/Deprecation.php b/vendor/doctrine/deprecations/src/Deprecation.php new file mode 100644 index 000000000..1801e6c7c --- /dev/null +++ b/vendor/doctrine/deprecations/src/Deprecation.php @@ -0,0 +1,309 @@ +|null */ + private static $type; + + /** @var LoggerInterface|null */ + private static $logger; + + /** @var array */ + private static $ignoredPackages = []; + + /** @var array */ + private static $triggeredDeprecations = []; + + /** @var array */ + private static $ignoredLinks = []; + + /** @var bool */ + private static $deduplication = true; + + /** + * Trigger a deprecation for the given package and identfier. + * + * The link should point to a Github issue or Wiki entry detailing the + * deprecation. It is additionally used to de-duplicate the trigger of the + * same deprecation during a request. + * + * @param float|int|string $args + */ + public static function trigger(string $package, string $link, string $message, ...$args): void + { + $type = self::$type ?? self::getTypeFromEnv(); + + if ($type === self::TYPE_NONE) { + return; + } + + if (isset(self::$ignoredLinks[$link])) { + return; + } + + if (array_key_exists($link, self::$triggeredDeprecations)) { + self::$triggeredDeprecations[$link]++; + } else { + self::$triggeredDeprecations[$link] = 1; + } + + if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * Trigger a deprecation for the given package and identifier when called from outside. + * + * "Outside" means we assume that $package is currently installed as a + * dependency and the caller is not a file in that package. When $package + * is installed as a root package then deprecations triggered from the + * tests folder are also considered "outside". + * + * This deprecation method assumes that you are using Composer to install + * the dependency and are using the default /vendor/ folder and not a + * Composer plugin to change the install location. The assumption is also + * that $package is the exact composer packge name. + * + * Compared to {@link trigger()} this method causes some overhead when + * deprecation tracking is enabled even during deduplication, because it + * needs to call {@link debug_backtrace()} + * + * @param float|int|string $args + */ + public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void + { + $type = self::$type ?? self::getTypeFromEnv(); + + if ($type === self::TYPE_NONE) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + // first check that the caller is not from a tests folder, in which case we always let deprecations pass + if (isset($backtrace[1]['file'], $backtrace[0]['file']) && strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { + $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $package) . DIRECTORY_SEPARATOR; + + if (strpos($backtrace[0]['file'], $path) === false) { + return; + } + + if (strpos($backtrace[1]['file'], $path) !== false) { + return; + } + } + + if (isset(self::$ignoredLinks[$link])) { + return; + } + + if (array_key_exists($link, self::$triggeredDeprecations)) { + self::$triggeredDeprecations[$link]++; + } else { + self::$triggeredDeprecations[$link] = 1; + } + + if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** @param list $backtrace */ + private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void + { + $type = self::$type ?? self::getTypeFromEnv(); + + if (($type & self::TYPE_PSR_LOGGER) > 0) { + $context = [ + 'file' => $backtrace[0]['file'] ?? null, + 'line' => $backtrace[0]['line'] ?? null, + 'package' => $package, + 'link' => $link, + ]; + + assert(self::$logger !== null); + + self::$logger->notice($message, $context); + } + + if (! (($type & self::TYPE_TRIGGER_ERROR) > 0)) { + return; + } + + $message .= sprintf( + ' (%s:%d called by %s:%d, %s, package %s)', + self::basename($backtrace[0]['file'] ?? 'native code'), + $backtrace[0]['line'] ?? 0, + self::basename($backtrace[1]['file'] ?? 'native code'), + $backtrace[1]['line'] ?? 0, + $link, + $package + ); + + @trigger_error($message, E_USER_DEPRECATED); + } + + /** + * A non-local-aware version of PHPs basename function. + */ + private static function basename(string $filename): string + { + $pos = strrpos($filename, DIRECTORY_SEPARATOR); + + if ($pos === false) { + return $filename; + } + + return substr($filename, $pos + 1); + } + + public static function enableTrackingDeprecations(): void + { + self::$type = self::$type ?? self::getTypeFromEnv(); + self::$type |= self::TYPE_TRACK_DEPRECATIONS; + } + + public static function enableWithTriggerError(): void + { + self::$type = self::$type ?? self::getTypeFromEnv(); + self::$type |= self::TYPE_TRIGGER_ERROR; + } + + public static function enableWithPsrLogger(LoggerInterface $logger): void + { + self::$type = self::$type ?? self::getTypeFromEnv(); + self::$type |= self::TYPE_PSR_LOGGER; + self::$logger = $logger; + } + + public static function withoutDeduplication(): void + { + self::$deduplication = false; + } + + public static function disable(): void + { + self::$type = self::TYPE_NONE; + self::$logger = null; + self::$deduplication = true; + self::$ignoredLinks = []; + + foreach (self::$triggeredDeprecations as $link => $count) { + self::$triggeredDeprecations[$link] = 0; + } + } + + public static function ignorePackage(string $packageName): void + { + self::$ignoredPackages[$packageName] = true; + } + + public static function ignoreDeprecations(string ...$links): void + { + foreach ($links as $link) { + self::$ignoredLinks[$link] = true; + } + } + + public static function getUniqueTriggeredDeprecationsCount(): int + { + return array_reduce(self::$triggeredDeprecations, static function (int $carry, int $count) { + return $carry + $count; + }, 0); + } + + /** + * Returns each triggered deprecation link identifier and the amount of occurrences. + * + * @return array + */ + public static function getTriggeredDeprecations(): array + { + return self::$triggeredDeprecations; + } + + /** @return int-mask-of */ + private static function getTypeFromEnv(): int + { + switch ($_SERVER['DOCTRINE_DEPRECATIONS'] ?? $_ENV['DOCTRINE_DEPRECATIONS'] ?? null) { + case 'trigger': + self::$type = self::TYPE_TRIGGER_ERROR; + break; + + case 'track': + self::$type = self::TYPE_TRACK_DEPRECATIONS; + break; + + default: + self::$type = self::TYPE_NONE; + break; + } + + return self::$type; + } +} diff --git a/vendor/doctrine/deprecations/src/PHPUnit/VerifyDeprecations.php b/vendor/doctrine/deprecations/src/PHPUnit/VerifyDeprecations.php new file mode 100644 index 000000000..7e168bd2c --- /dev/null +++ b/vendor/doctrine/deprecations/src/PHPUnit/VerifyDeprecations.php @@ -0,0 +1,66 @@ + */ + private $doctrineDeprecationsExpectations = []; + + /** @var array */ + private $doctrineNoDeprecationsExpectations = []; + + public function expectDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + public function expectNoDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + /** @before */ + #[Before] + public function enableDeprecationTracking(): void + { + Deprecation::enableTrackingDeprecations(); + } + + /** @after */ + #[After] + public function verifyDeprecationsAreTriggered(): void + { + foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount > $expectation, + sprintf( + "Expected deprecation with identifier '%s' was not triggered by code executed in test.", + $identifier + ) + ); + } + + foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount === $expectation, + sprintf( + "Deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", + $identifier + ) + ); + } + } +} diff --git a/vendor/firebase/php-jwt/CHANGELOG.md b/vendor/firebase/php-jwt/CHANGELOG.md index 7b5f6ce40..498fb0007 100644 --- a/vendor/firebase/php-jwt/CHANGELOG.md +++ b/vendor/firebase/php-jwt/CHANGELOG.md @@ -1,5 +1,51 @@ # Changelog +## [7.0.5](https://github.com/firebase/php-jwt/compare/v7.0.4...v7.0.5) (2026-03-31) + + +### Bug Fixes + +* RSA from JWK sometimes returns empty Instance ([#628](https://github.com/firebase/php-jwt/issues/628)) ([b4c78aa](https://github.com/firebase/php-jwt/commit/b4c78aa731664122198ad36c0033aa29e807397a)) + +## [7.0.4](https://github.com/firebase/php-jwt/compare/v7.0.3...v7.0.4) (2026-03-27) + + +### Bug Fixes + +* readme examples, add tests for all examples ([#626](https://github.com/firebase/php-jwt/issues/626)) ([510a00c](https://github.com/firebase/php-jwt/commit/510a00c0e6353bc7d68412fab67e57a13954cb46)) +* use urlsafeB64Decode everywhere ([#627](https://github.com/firebase/php-jwt/issues/627)) ([b889495](https://github.com/firebase/php-jwt/commit/b889495c83ddc3f3885ca3f0b65b41b1cb37a3b1)) + +## [7.0.3](https://github.com/firebase/php-jwt/compare/v7.0.2...v7.0.3) (2026-02-18) + + +### Miscellaneous Chores + +* add environment for Release Please job ([#619](https://github.com/firebase/php-jwt/issues/619)) ([300fd02](https://github.com/firebase/php-jwt/commit/300fd02c883f096c9067df652dbd23f62cb5e2a7)) + +## [7.0.2](https://github.com/firebase/php-jwt/compare/v7.0.1...v7.0.2) (2025-12-16) + + +### Bug Fixes + +* add key length validation for ec keys ([#615](https://github.com/firebase/php-jwt/issues/615)) ([7044f9a](https://github.com/firebase/php-jwt/commit/7044f9ae7e7d175d28cca71714feb236f1c0e252)) + +## [7.0.0](https://github.com/firebase/php-jwt/compare/v6.11.1...v7.0.0) (2025-12-15) + + +### ⚠️ ⚠️ ⚠️ Security Fixes ⚠️ ⚠️ ⚠️ + * add key size validation ([#613](https://github.com/firebase/php-jwt/issues/613)) ([6b80341](https://github.com/firebase/php-jwt/commit/6b80341bf57838ea2d011487917337901cd71576)) + **NOTE**: This fix will cause keys with a size below the minimally allowed size to break. + +### Features + +* add SensitiveParameter attribute to security-critical parameters ([#603](https://github.com/firebase/php-jwt/issues/603)) ([4dbfac0](https://github.com/firebase/php-jwt/commit/4dbfac0260eeb0e9e643063c99998e3219cc539b)) +* store timestamp in `ExpiredException` ([#604](https://github.com/firebase/php-jwt/issues/604)) ([f174826](https://github.com/firebase/php-jwt/commit/f1748260d218a856b6a0c23715ac7fae1d7ca95b)) + + +### Bug Fixes + +* validate iat and nbf on payload ([#568](https://github.com/firebase/php-jwt/issues/568)) ([953b2c8](https://github.com/firebase/php-jwt/commit/953b2c88bb445b7e3bb82a5141928f13d7343afd)) + ## [6.11.1](https://github.com/firebase/php-jwt/compare/v6.11.0...v6.11.1) (2025-04-09) diff --git a/vendor/firebase/php-jwt/README.md b/vendor/firebase/php-jwt/README.md index e45ccb808..2ca716224 100644 --- a/vendor/firebase/php-jwt/README.md +++ b/vendor/firebase/php-jwt/README.md @@ -23,16 +23,16 @@ php env does not have libsodium installed: composer require paragonie/sodium_compat ``` -Example -------- +## Example + ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; -$key = 'example_key'; +$key = 'example_key_of_sufficient_length'; $payload = [ - 'iss' => 'http://example.org', - 'aud' => 'http://example.com', + 'iss' => 'example.org', + 'aud' => 'example.com', 'iat' => 1356999524, 'nbf' => 1357000000 ]; @@ -69,8 +69,9 @@ $decoded_array = (array) $decoded; JWT::$leeway = 60; // $leeway in seconds $decoded = JWT::decode($jwt, new Key($key, 'HS256')); ``` -Example encode/decode headers -------- + +## Example encode/decode headers + Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by this library. This is because without verifying the JWT, the header values could have been tampered with. Any value pulled from an unverified header should be treated as if it could be any string sent in from an @@ -80,10 +81,10 @@ header part: ```php use Firebase\JWT\JWT; -$key = 'example_key'; +$key = 'example_key_of_sufficient_length'; $payload = [ - 'iss' => 'http://example.org', - 'aud' => 'http://example.com', + 'iss' => 'example.org', + 'aud' => 'example.com', 'iat' => 1356999524, 'nbf' => 1357000000 ]; @@ -103,8 +104,9 @@ $decoded = json_decode(base64_decode($headersB64), true); print_r($decoded); ``` -Example with RS256 (openssl) ----------------------------- + +## Example with RS256 (openssl) + ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; @@ -172,8 +174,7 @@ $decoded_array = (array) $decoded; echo "Decode:\n" . print_r($decoded_array, true) . "\n"; ``` -Example with a passphrase -------------------------- +## Example with a passphrase ```php use Firebase\JWT\JWT; @@ -186,7 +187,7 @@ $passphrase = '[YOUR_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" +/** @var OpenSSLAsymmetricKey $privateKey */ $privateKey = openssl_pkey_get_private( file_get_contents($privateKeyFile), $passphrase @@ -209,8 +210,8 @@ $decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; ``` -Example with EdDSA (libsodium and Ed25519 signature) ----------------------------- +## Example with EdDSA (libsodium and Ed25519 signature) + ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; @@ -238,21 +239,21 @@ 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"; -```` +``` + +## Example with multiple keys -Example with multiple keys --------------------------- ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; // Example RSA keys from previous example -// $privateKey1 = '...'; -// $publicKey1 = '...'; +// $privateRsKey = '...'; +// $publicRsKey = '...'; // Example EdDSA keys from previous example -// $privateKey2 = '...'; -// $publicKey2 = '...'; +// $privateEcKey = '...'; +// $publicEcKey = '...'; $payload = [ 'iss' => 'example.org', @@ -261,14 +262,14 @@ $payload = [ 'nbf' => 1357000000 ]; -$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1'); -$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2'); +$jwt1 = JWT::encode($payload, $privateRsKey, 'RS256', 'kid1'); +$jwt2 = JWT::encode($payload, $privateEcKey, 'EdDSA', 'kid2'); echo "Encode 1:\n" . print_r($jwt1, true) . "\n"; echo "Encode 2:\n" . print_r($jwt2, true) . "\n"; $keys = [ - 'kid1' => new Key($publicKey1, 'RS256'), - 'kid2' => new Key($publicKey2, 'EdDSA'), + 'kid1' => new Key($publicRsKey, 'RS256'), + 'kid2' => new Key($publicEcKey, 'EdDSA'), ]; $decoded1 = JWT::decode($jwt1, $keys); @@ -278,8 +279,7 @@ echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n"; echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n"; ``` -Using JWKs ----------- +## Using JWKs ```php use Firebase\JWT\JWK; @@ -291,11 +291,11 @@ $jwks = ['keys' => []]; // JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key // objects. Pass this as the second parameter to JWT::decode. -JWT::decode($jwt, JWK::parseKeySet($jwks)); +$decoded = JWT::decode($jwt, JWK::parseKeySet($jwks)); +print_r($decoded); ``` -Using Cached Key Sets ---------------------- +## Using Cached Key Sets The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI. This has the following advantages: @@ -315,7 +315,7 @@ $jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk'; $httpClient = new GuzzleHttp\Client(); // Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory) -$httpFactory = new GuzzleHttp\Psr\HttpFactory(); +$httpFactory = new GuzzleHttp\Psr7\HttpFactory(); // Create a cache item pool (can be any PSR-6 compatible cache item pool) $cacheItemPool = Phpfastcache\CacheManager::getInstance('files'); @@ -406,8 +406,8 @@ Tests Run the tests using phpunit: ```bash -$ pear install PHPUnit -$ phpunit --configuration phpunit.xml.dist +$ composer update +$ vendor/bin/phpunit -c phpunit.xml.dist PHPUnit 3.7.10 by Sebastian Bergmann. ..... Time: 0 seconds, Memory: 2.50Mb diff --git a/vendor/firebase/php-jwt/composer.json b/vendor/firebase/php-jwt/composer.json index 816cfd0bd..4b988631d 100644 --- a/vendor/firebase/php-jwt/composer.json +++ b/vendor/firebase/php-jwt/composer.json @@ -37,6 +37,7 @@ "phpunit/phpunit": "^9.5", "psr/cache": "^2.0||^3.0", "psr/http-client": "^1.0", - "psr/http-factory": "^1.0" + "psr/http-factory": "^1.0", + "phpfastcache/phpfastcache": "^9.2" } } diff --git a/vendor/firebase/php-jwt/src/CachedKeySet.php b/vendor/firebase/php-jwt/src/CachedKeySet.php index 8e8e8d68c..37c3f94d2 100644 --- a/vendor/firebase/php-jwt/src/CachedKeySet.php +++ b/vendor/firebase/php-jwt/src/CachedKeySet.php @@ -180,7 +180,8 @@ private function keyIdExists(string $keyId): bool $jwksResponse = $this->httpClient->sendRequest($request); if ($jwksResponse->getStatusCode() !== 200) { throw new UnexpectedValueException( - \sprintf('HTTP Error: %d %s for URI "%s"', + \sprintf( + 'HTTP Error: %d %s for URI "%s"', $jwksResponse->getStatusCode(), $jwksResponse->getReasonPhrase(), $this->jwksUri, diff --git a/vendor/firebase/php-jwt/src/ExpiredException.php b/vendor/firebase/php-jwt/src/ExpiredException.php index 12fef0944..25f445132 100644 --- a/vendor/firebase/php-jwt/src/ExpiredException.php +++ b/vendor/firebase/php-jwt/src/ExpiredException.php @@ -6,6 +6,8 @@ class ExpiredException extends \UnexpectedValueException implements JWTException { private object $payload; + private ?int $timestamp = null; + public function setPayload(object $payload): void { $this->payload = $payload; @@ -15,4 +17,14 @@ public function getPayload(): object { return $this->payload; } + + public function setTimestamp(int $timestamp): void + { + $this->timestamp = $timestamp; + } + + public function getTimestamp(): ?int + { + return $this->timestamp; + } } diff --git a/vendor/firebase/php-jwt/src/JWK.php b/vendor/firebase/php-jwt/src/JWK.php index 405dcc49b..e083c224b 100644 --- a/vendor/firebase/php-jwt/src/JWK.php +++ b/vendor/firebase/php-jwt/src/JWK.php @@ -52,7 +52,7 @@ class JWK * * @uses parseKey */ - public static function parseKeySet(array $jwks, ?string $defaultAlg = null): array + public static function parseKeySet(#[\SensitiveParameter] array $jwks, ?string $defaultAlg = null): array { $keys = []; @@ -93,7 +93,7 @@ public static function parseKeySet(array $jwks, ?string $defaultAlg = null): arr * * @uses createPemFromModulusAndExponent */ - public static function parseKey(array $jwk, ?string $defaultAlg = null): ?Key + public static function parseKey(#[\SensitiveParameter] array $jwk, ?string $defaultAlg = null): ?Key { if (empty($jwk)) { throw new InvalidArgumentException('JWK must not be empty'); @@ -240,6 +240,14 @@ private static function createPemFromModulusAndExponent( ): string { $mod = JWT::urlsafeB64Decode($n); $exp = JWT::urlsafeB64Decode($e); + // Correct encoding for ASN1, as ints are represented as unsigned in jwk + // but signed in ASN1. Prepending null byte makes it unsigned. + if (\strlen($mod) > 0 && \ord($mod[0]) >= 128) { + $mod = \chr(0) . $mod; + } + if (\strlen($exp) > 0 && \ord($exp[0]) >= 128) { + $exp = \chr(0) . $exp; + } $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod); $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp); diff --git a/vendor/firebase/php-jwt/src/JWT.php b/vendor/firebase/php-jwt/src/JWT.php index 833a415e6..0d2e47c95 100644 --- a/vendor/firebase/php-jwt/src/JWT.php +++ b/vendor/firebase/php-jwt/src/JWT.php @@ -31,6 +31,8 @@ class JWT private const ASN1_SEQUENCE = 0x10; private const ASN1_BIT_STRING = 0x03; + private const RSA_KEY_MIN_LENGTH = 2048; + /** * When checking nbf, iat or expiration times, * we want to provide some extra leeway time to @@ -95,7 +97,7 @@ class JWT */ public static function decode( string $jwt, - $keyOrKeyArray, + #[\SensitiveParameter] $keyOrKeyArray, ?stdClass &$headers = null ): stdClass { // Validate JWT @@ -127,6 +129,16 @@ public static function decode( if (!$payload instanceof stdClass) { throw new UnexpectedValueException('Payload must be a JSON object'); } + if (isset($payload->iat) && !\is_numeric($payload->iat)) { + throw new UnexpectedValueException('Payload iat must be a number'); + } + if (isset($payload->nbf) && !\is_numeric($payload->nbf)) { + throw new UnexpectedValueException('Payload nbf must be a number'); + } + if (isset($payload->exp) && !\is_numeric($payload->exp)) { + throw new UnexpectedValueException('Payload exp must be a number'); + } + $sig = static::urlsafeB64Decode($cryptob64); if (empty($header->alg)) { throw new UnexpectedValueException('Empty algorithm'); @@ -154,7 +166,7 @@ public static function decode( // token can actually be used. If it's not yet that time, abort. if (isset($payload->nbf) && floor($payload->nbf) > ($timestamp + static::$leeway)) { $ex = new BeforeValidException( - 'Cannot handle token with nbf prior to ' . \date(DateTime::ISO8601, (int) floor($payload->nbf)) + 'Cannot handle token with nbf prior to ' . \date(DateTime::ATOM, (int) floor($payload->nbf)) ); $ex->setPayload($payload); throw $ex; @@ -165,7 +177,7 @@ public static function decode( // correctly used the nbf claim). if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) { $ex = new BeforeValidException( - 'Cannot handle token with iat prior to ' . \date(DateTime::ISO8601, (int) floor($payload->iat)) + 'Cannot handle token with iat prior to ' . \date(DateTime::ATOM, (int) floor($payload->iat)) ); $ex->setPayload($payload); throw $ex; @@ -175,6 +187,7 @@ public static function decode( if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { $ex = new ExpiredException('Expired token'); $ex->setPayload($payload); + $ex->setTimestamp($timestamp); throw $ex; } @@ -185,11 +198,11 @@ public static function decode( * Converts and signs a PHP array into a JWT string. * * @param array $payload PHP array - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256', * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' * @param string $keyId - * @param array $head An array with header elements to attach + * @param array $head An array with header elements to attach * * @return string A signed JWT * @@ -198,7 +211,7 @@ public static function decode( */ public static function encode( array $payload, - $key, + #[\SensitiveParameter] $key, string $alg, ?string $keyId = null, ?array $head = null @@ -226,7 +239,7 @@ public static function encode( * Sign a string with a given key and algorithm. * * @param string $msg The message to sign - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. * @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256', * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' * @@ -236,7 +249,7 @@ public static function encode( */ public static function sign( string $msg, - $key, + #[\SensitiveParameter] $key, string $alg ): string { if (empty(static::$supported_algs[$alg])) { @@ -248,13 +261,19 @@ public static function sign( if (!\is_string($key)) { throw new InvalidArgumentException('key must be a string when using hmac'); } + self::validateHmacKeyLength($key, $algorithm); return \hash_hmac($algorithm, $msg, $key, true); case 'openssl': $signature = ''; - if (!\is_resource($key) && !openssl_pkey_get_private($key)) { + if (!$key = openssl_pkey_get_private($key)) { throw new DomainException('OpenSSL unable to validate key'); } - $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line + if (str_starts_with($alg, 'RS')) { + self::validateRsaKeyLength($key); + } elseif (str_starts_with($alg, 'ES')) { + self::validateEcKeyLength($key, $alg); + } + $success = \openssl_sign($msg, $signature, $key, $algorithm); if (!$success) { throw new DomainException('OpenSSL unable to sign data'); } @@ -265,20 +284,8 @@ public static function sign( } return $signature; case 'sodium_crypto': - if (!\function_exists('sodium_crypto_sign_detached')) { - throw new DomainException('libsodium is not available'); - } - if (!\is_string($key)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); - } try { - // The last non-empty line is used as the key. - $lines = array_filter(explode("\n", $key)); - $key = base64_decode((string) end($lines)); - if (\strlen($key) === 0) { - throw new DomainException('Key cannot be empty string'); - } - return sodium_crypto_sign_detached($msg, $key); + return sodium_crypto_sign_detached($msg, self::validateEdDSAKey($key)); } catch (Exception $e) { throw new DomainException($e->getMessage(), 0, $e); } @@ -293,7 +300,7 @@ public static function sign( * * @param string $msg The original message (header and body) * @param string $signature The original signature - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey * @param string $alg The algorithm * * @return bool @@ -303,7 +310,7 @@ public static function sign( private static function verify( string $msg, string $signature, - $keyMaterial, + #[\SensitiveParameter] $keyMaterial, string $alg ): bool { if (empty(static::$supported_algs[$alg])) { @@ -313,7 +320,15 @@ private static function verify( list($function, $algorithm) = static::$supported_algs[$alg]; switch ($function) { case 'openssl': - $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line + if (!$key = openssl_pkey_get_public($keyMaterial)) { + throw new DomainException('OpenSSL unable to validate key'); + } + if (str_starts_with($alg, 'RS')) { + self::validateRsaKeyLength($key); + } elseif (str_starts_with($alg, 'ES')) { + self::validateEcKeyLength($key, $alg); + } + $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); if ($success === 1) { return true; } @@ -325,19 +340,8 @@ private static function verify( 'OpenSSL error: ' . \openssl_error_string() ); case 'sodium_crypto': - if (!\function_exists('sodium_crypto_sign_verify_detached')) { - throw new DomainException('libsodium is not available'); - } - if (!\is_string($keyMaterial)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); - } try { - // The last non-empty line is used as the key. - $lines = array_filter(explode("\n", $keyMaterial)); - $key = base64_decode((string) end($lines)); - if (\strlen($key) === 0) { - throw new DomainException('Key cannot be empty string'); - } + $key = self::validateEdDSAKey($keyMaterial); if (\strlen($signature) === 0) { throw new DomainException('Signature cannot be empty string'); } @@ -350,6 +354,7 @@ private static function verify( if (!\is_string($keyMaterial)) { throw new InvalidArgumentException('key must be a string when using hmac'); } + self::validateHmacKeyLength($keyMaterial, $algorithm); $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); return self::constantTimeEquals($hash, $signature); } @@ -445,7 +450,6 @@ public static function urlsafeB64Encode(string $input): string return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); } - /** * Determine if an algorithm has been provided for each Key * @@ -457,7 +461,7 @@ public static function urlsafeB64Encode(string $input): string * @return Key */ private static function getKey( - $keyOrKeyArray, + #[\SensitiveParameter] $keyOrKeyArray, ?string $kid ): Key { if ($keyOrKeyArray instanceof Key) { @@ -664,4 +668,78 @@ private static function readDER(string $der, int $offset = 0): array return [$pos, $data]; } + + /** + * Validate HMAC key length + * + * @param string $key HMAC key material + * @param string $algorithm The algorithm + * + * @throws DomainException Provided key is too short + */ + private static function validateHmacKeyLength(string $key, string $algorithm): void + { + $keyLength = \strlen($key) * 8; + $minKeyLength = (int) \str_replace('SHA', '', $algorithm); + if ($keyLength < $minKeyLength) { + throw new DomainException('Provided key is too short'); + } + } + + /** + * Validate RSA key length + * + * @param OpenSSLAsymmetricKey $key RSA key material + * @throws DomainException Provided key is too short + */ + private static function validateRsaKeyLength(#[\SensitiveParameter] OpenSSLAsymmetricKey $key): void + { + if (!$keyDetails = openssl_pkey_get_details($key)) { + throw new DomainException('Unable to validate key'); + } + if ($keyDetails['bits'] < self::RSA_KEY_MIN_LENGTH) { + throw new DomainException('Provided key is too short'); + } + } + + /** + * Validate RSA key length + * + * @param OpenSSLAsymmetricKey $key RSA key material + * @param string $algorithm The algorithm + * @throws DomainException Provided key is too short + */ + private static function validateEcKeyLength( + #[\SensitiveParameter] OpenSSLAsymmetricKey $key, + string $algorithm + ): void { + if (!$keyDetails = openssl_pkey_get_details($key)) { + throw new DomainException('Unable to validate key'); + } + $minKeyLength = (int) \str_replace('ES', '', $algorithm); + if ($keyDetails['bits'] < $minKeyLength) { + throw new DomainException('Provided key is too short'); + } + } + + /** + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial + * @return non-empty-string + */ + private static function validateEdDSAKey(#[\SensitiveParameter] $keyMaterial): string + { + if (!\function_exists('sodium_crypto_sign_verify_detached')) { + throw new DomainException('libsodium is not available'); + } + if (!\is_string($keyMaterial)) { + throw new InvalidArgumentException('key must be a string when using EdDSA'); + } + // The last non-empty line is used as the key. + $lines = array_filter(explode("\n", $keyMaterial)); + $key = self::urlsafeB64Decode((string) end($lines)); + if (\strlen($key) === 0) { + throw new DomainException('Key cannot be empty string'); + } + return $key; + } } diff --git a/vendor/firebase/php-jwt/src/Key.php b/vendor/firebase/php-jwt/src/Key.php index b34eae258..694d3b13b 100644 --- a/vendor/firebase/php-jwt/src/Key.php +++ b/vendor/firebase/php-jwt/src/Key.php @@ -10,20 +10,19 @@ class Key { /** - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial * @param string $algorithm */ public function __construct( - private $keyMaterial, + #[\SensitiveParameter] private $keyMaterial, private string $algorithm ) { if ( !\is_string($keyMaterial) && !$keyMaterial instanceof OpenSSLAsymmetricKey && !$keyMaterial instanceof OpenSSLCertificate - && !\is_resource($keyMaterial) ) { - throw new TypeError('Key material must be a string, resource, or OpenSSLAsymmetricKey'); + throw new TypeError('Key material must be a string, OpenSSLCertificate, or OpenSSLAsymmetricKey'); } if (empty($keyMaterial)) { @@ -46,7 +45,7 @@ public function getAlgorithm(): string } /** - * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate + * @return string|OpenSSLAsymmetricKey|OpenSSLCertificate */ public function getKeyMaterial() { diff --git a/vendor/jetpack-autoloader/class-autoloader-handler.php b/vendor/jetpack-autoloader/class-autoloader-handler.php new file mode 100644 index 000000000..807c02555 --- /dev/null +++ b/vendor/jetpack-autoloader/class-autoloader-handler.php @@ -0,0 +1,147 @@ +php_autoloader = $php_autoloader; + $this->hook_manager = $hook_manager; + $this->manifest_reader = $manifest_reader; + $this->version_selector = $version_selector; + } + + /** + * Checks to see whether or not an autoloader is currently in the process of initializing. + * + * @return bool + */ + public function is_initializing() { + // If no version has been set it means that no autoloader has started initializing yet. + global $jetpack_autoloader_latest_version; + if ( ! isset( $jetpack_autoloader_latest_version ) ) { + return false; + } + + // When the version is set but the classmap is not it ALWAYS means that this is the + // latest autoloader and is being included by an older one. + global $jetpack_packages_classmap; + if ( empty( $jetpack_packages_classmap ) ) { + return true; + } + + // Version 2.4.0 added a new global and altered the reset semantics. We need to check + // the other global as well since it may also point at initialization. + // Note: We don't need to check for the class first because every autoloader that + // will set the latest version global requires this class in the classmap. + $replacing_version = $jetpack_packages_classmap[ AutoloadGenerator::class ]['version']; + if ( $this->version_selector->is_dev_version( $replacing_version ) || version_compare( $replacing_version, '2.4.0.0', '>=' ) ) { + global $jetpack_autoloader_loader; + if ( ! isset( $jetpack_autoloader_loader ) ) { + return true; + } + } + + return false; + } + + /** + * Activates an autoloader using the given plugins and activates it. + * + * @param string[] $plugins The plugins to initialize the autoloader for. + */ + public function activate_autoloader( $plugins ) { + global $jetpack_packages_psr4; + $jetpack_packages_psr4 = array(); + $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_psr4.php', $jetpack_packages_psr4 ); + + global $jetpack_packages_classmap; + $jetpack_packages_classmap = array(); + $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_classmap.php', $jetpack_packages_classmap ); + + global $jetpack_packages_filemap; + $jetpack_packages_filemap = array(); + $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_filemap.php', $jetpack_packages_filemap ); + + $loader = new Version_Loader( + $this->version_selector, + $jetpack_packages_classmap, + $jetpack_packages_psr4, + $jetpack_packages_filemap + ); + + $this->php_autoloader->register_autoloader( $loader ); + + // Now that the autoloader is active we can load the filemap. + $loader->load_filemap(); + } + + /** + * Resets the active autoloader and all related global state. + */ + public function reset_autoloader() { + $this->php_autoloader->unregister_autoloader(); + $this->hook_manager->reset(); + + // Clear all of the autoloader globals so that older autoloaders don't do anything strange. + global $jetpack_autoloader_latest_version; + $jetpack_autoloader_latest_version = null; + + global $jetpack_packages_classmap; + $jetpack_packages_classmap = array(); // Must be array to avoid exceptions in old autoloaders! + + global $jetpack_packages_psr4; + $jetpack_packages_psr4 = array(); // Must be array to avoid exceptions in old autoloaders! + + global $jetpack_packages_filemap; + $jetpack_packages_filemap = array(); // Must be array to avoid exceptions in old autoloaders! + } +} diff --git a/vendor/jetpack-autoloader/class-autoloader-locator.php b/vendor/jetpack-autoloader/class-autoloader-locator.php new file mode 100644 index 000000000..1ceef5a64 --- /dev/null +++ b/vendor/jetpack-autoloader/class-autoloader-locator.php @@ -0,0 +1,90 @@ +version_selector = $version_selector; + } + + /** + * Finds the path to the plugin with the latest autoloader. + * + * @param array $plugin_paths An array of plugin paths. + * @param string $latest_version The latest version reference. @phan-output-reference. + * + * @return string|null + */ + public function find_latest_autoloader( $plugin_paths, &$latest_version ) { + $latest_plugin = null; + + foreach ( $plugin_paths as $plugin_path ) { + $version = $this->get_autoloader_version( $plugin_path ); + if ( ! $version || ! $this->version_selector->is_version_update_required( $latest_version, $version ) ) { + continue; + } + + $latest_version = $version; + $latest_plugin = $plugin_path; + } + + return $latest_plugin; + } + + /** + * Gets the path to the autoloader. + * + * @param string $plugin_path The path to the plugin. + * + * @return string + */ + public function get_autoloader_path( $plugin_path ) { + return trailingslashit( $plugin_path ) . 'vendor/autoload_packages.php'; + } + + /** + * Gets the version for the autoloader. + * + * @param string $plugin_path The path to the plugin. + * + * @return string|null + */ + public function get_autoloader_version( $plugin_path ) { + $classmap = trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_classmap.php'; + if ( ! file_exists( $classmap ) ) { + return null; + } + + $classmap = require $classmap; + if ( isset( $classmap[ AutoloadGenerator::class ] ) ) { + return $classmap[ AutoloadGenerator::class ]['version']; + } + + return null; + } +} diff --git a/vendor/jetpack-autoloader/class-autoloader.php b/vendor/jetpack-autoloader/class-autoloader.php new file mode 100644 index 000000000..1ff3e76b2 --- /dev/null +++ b/vendor/jetpack-autoloader/class-autoloader.php @@ -0,0 +1,93 @@ +get( Autoloader_Handler::class ); + + // If the autoloader is already initializing it means that it has included us as the latest. + $was_included_by_autoloader = $autoloader_handler->is_initializing(); + + /** @var Plugin_Locator $plugin_locator */ + $plugin_locator = $container->get( Plugin_Locator::class ); + + /** @var Plugins_Handler $plugins_handler */ + $plugins_handler = $container->get( Plugins_Handler::class ); + + // The current plugin is the one that we are attempting to initialize here. + $current_plugin = $plugin_locator->find_current_plugin(); + + // The active plugins are those that we were able to discover on the site. This list will not + // include mu-plugins, those activated by code, or those who are hidden by filtering. We also + // want to take care to not consider the current plugin unknown if it was included by an + // autoloader. This avoids the case where a plugin will be marked "active" while deactivated + // due to it having the latest autoloader. + $active_plugins = $plugins_handler->get_active_plugins( true, ! $was_included_by_autoloader ); + + // The cached plugins are all of those that were active or discovered by the autoloader during a previous request. + // Note that it's possible this list will include plugins that have since been deactivated, but after a request + // the cache should be updated and the deactivated plugins will be removed. + $cached_plugins = $plugins_handler->get_cached_plugins(); + + // We combine the active list and cached list to preemptively load classes for plugins that are + // presently unknown but will be loaded during the request. While this may result in us considering packages in + // deactivated plugins there shouldn't be any problems as a result and the eventual consistency is sufficient. + $all_plugins = array_merge( $active_plugins, $cached_plugins ); + + // In particular we also include the current plugin to address the case where it is the latest autoloader + // but also unknown (and not cached). We don't want it in the active list because we don't know that it + // is active but we need it in the all plugins list so that it is considered by the autoloader. + $all_plugins[] = $current_plugin; + + // We require uniqueness in the array to avoid processing the same plugin more than once. + $all_plugins = array_values( array_unique( $all_plugins ) ); + + /** @var Latest_Autoloader_Guard $guard */ + $guard = $container->get( Latest_Autoloader_Guard::class ); + if ( $guard->should_stop_init( $current_plugin, $all_plugins, $was_included_by_autoloader ) ) { + return; + } + + // Initialize the autoloader using the handler now that we're ready. + $autoloader_handler->activate_autoloader( $all_plugins ); + + /** @var Hook_Manager $hook_manager */ + $hook_manager = $container->get( Hook_Manager::class ); + + // Register a shutdown handler to clean up the autoloader. + $hook_manager->add_action( 'shutdown', new Shutdown_Handler( $plugins_handler, $cached_plugins, $was_included_by_autoloader ) ); + + // Register a plugins_loaded handler to check for conflicting autoloaders. + $hook_manager->add_action( 'plugins_loaded', array( $guard, 'check_for_conflicting_autoloaders' ), 1 ); + + // phpcs:enable Generic.Commenting.DocComment.MissingShort + } +} diff --git a/vendor/jetpack-autoloader/class-container.php b/vendor/jetpack-autoloader/class-container.php new file mode 100644 index 000000000..d030b84cb --- /dev/null +++ b/vendor/jetpack-autoloader/class-container.php @@ -0,0 +1,150 @@ + 'Hook_Manager', + ); + + /** + * A map of all the dependencies we've registered with the container and created. + * + * @var array + */ + protected $dependencies; + + /** + * The constructor. + */ + public function __construct() { + $this->dependencies = array(); + + $this->register_shared_dependencies(); + $this->register_dependencies(); + $this->initialize_globals(); + } + + /** + * Gets a dependency out of the container. + * + * @param string $class The class to fetch. + * + * @return mixed + * @throws \InvalidArgumentException When a class that isn't registered with the container is fetched. + */ + public function get( $class ) { + if ( ! isset( $this->dependencies[ $class ] ) ) { + throw new \InvalidArgumentException( "Class '$class' is not registered with the container." ); + } + + return $this->dependencies[ $class ]; + } + + /** + * Registers all of the dependencies that are shared between all instances of the autoloader. + */ + private function register_shared_dependencies() { + global $jetpack_autoloader_container_shared; + if ( ! isset( $jetpack_autoloader_container_shared ) ) { + $jetpack_autoloader_container_shared = array(); + } + + $key = self::SHARED_DEPENDENCY_KEYS[ Hook_Manager::class ]; + if ( ! isset( $jetpack_autoloader_container_shared[ $key ] ) ) { + require_once __DIR__ . '/class-hook-manager.php'; + $jetpack_autoloader_container_shared[ $key ] = new Hook_Manager(); + } + $this->dependencies[ Hook_Manager::class ] = &$jetpack_autoloader_container_shared[ $key ]; + } + + /** + * Registers all of the dependencies with the container. + */ + private function register_dependencies() { + require_once __DIR__ . '/class-path-processor.php'; + $this->dependencies[ Path_Processor::class ] = new Path_Processor(); + + require_once __DIR__ . '/class-plugin-locator.php'; + $this->dependencies[ Plugin_Locator::class ] = new Plugin_Locator( + $this->get( Path_Processor::class ) + ); + + require_once __DIR__ . '/class-version-selector.php'; + $this->dependencies[ Version_Selector::class ] = new Version_Selector(); + + require_once __DIR__ . '/class-autoloader-locator.php'; + $this->dependencies[ Autoloader_Locator::class ] = new Autoloader_Locator( + $this->get( Version_Selector::class ) + ); + + require_once __DIR__ . '/class-php-autoloader.php'; + $this->dependencies[ PHP_Autoloader::class ] = new PHP_Autoloader(); + + require_once __DIR__ . '/class-manifest-reader.php'; + $this->dependencies[ Manifest_Reader::class ] = new Manifest_Reader( + $this->get( Version_Selector::class ) + ); + + require_once __DIR__ . '/class-plugins-handler.php'; + $this->dependencies[ Plugins_Handler::class ] = new Plugins_Handler( + $this->get( Plugin_Locator::class ), + $this->get( Path_Processor::class ) + ); + + require_once __DIR__ . '/class-autoloader-handler.php'; + $this->dependencies[ Autoloader_Handler::class ] = new Autoloader_Handler( + $this->get( PHP_Autoloader::class ), + $this->get( Hook_Manager::class ), + $this->get( Manifest_Reader::class ), + $this->get( Version_Selector::class ) + ); + + require_once __DIR__ . '/class-latest-autoloader-guard.php'; + $this->dependencies[ Latest_Autoloader_Guard::class ] = new Latest_Autoloader_Guard( + $this->get( Plugins_Handler::class ), + $this->get( Autoloader_Handler::class ), + $this->get( Autoloader_Locator::class ) + ); + + // Register any classes that we will use elsewhere. + require_once __DIR__ . '/class-version-loader.php'; + require_once __DIR__ . '/class-shutdown-handler.php'; + } + + /** + * Initializes any of the globals needed by the autoloader. + */ + private function initialize_globals() { + /* + * This global was retired in version 2.9. The value is set to 'false' to maintain + * compatibility with older versions of the autoloader. + */ + global $jetpack_autoloader_including_latest; + $jetpack_autoloader_including_latest = false; + + // Not all plugins can be found using the locator. In cases where a plugin loads the autoloader + // but was not discoverable, we will record them in this array to track them as "active". + global $jetpack_autoloader_activating_plugins_paths; + if ( ! isset( $jetpack_autoloader_activating_plugins_paths ) ) { + $jetpack_autoloader_activating_plugins_paths = array(); + } + } +} diff --git a/vendor/jetpack-autoloader/class-hook-manager.php b/vendor/jetpack-autoloader/class-hook-manager.php new file mode 100644 index 000000000..4d55a64eb --- /dev/null +++ b/vendor/jetpack-autoloader/class-hook-manager.php @@ -0,0 +1,76 @@ +registered_hooks = array(); + } + + /** + * Adds an action to WordPress and registers it internally. + * + * @param string $tag The name of the action which is hooked. + * @param callable $callable The function to call. + * @param int $priority Used to specify the priority of the action. + * @param int $accepted_args Used to specify the number of arguments the callable accepts. + */ + public function add_action( $tag, $callable, $priority = 10, $accepted_args = 1 ) { + $this->registered_hooks[ $tag ][] = array( + 'priority' => $priority, + 'callable' => $callable, + ); + + add_action( $tag, $callable, $priority, $accepted_args ); + } + + /** + * Adds a filter to WordPress and registers it internally. + * + * @param string $tag The name of the filter which is hooked. + * @param callable $callable The function to call. + * @param int $priority Used to specify the priority of the filter. + * @param int $accepted_args Used to specify the number of arguments the callable accepts. + */ + public function add_filter( $tag, $callable, $priority = 10, $accepted_args = 1 ) { + $this->registered_hooks[ $tag ][] = array( + 'priority' => $priority, + 'callable' => $callable, + ); + + add_filter( $tag, $callable, $priority, $accepted_args ); + } + + /** + * Removes all of the registered hooks. + */ + public function reset() { + foreach ( $this->registered_hooks as $tag => $hooks ) { + foreach ( $hooks as $hook ) { + remove_filter( $tag, $hook['callable'], $hook['priority'] ); + } + } + $this->registered_hooks = array(); + } +} diff --git a/vendor/jetpack-autoloader/class-latest-autoloader-guard.php b/vendor/jetpack-autoloader/class-latest-autoloader-guard.php new file mode 100644 index 000000000..fb64770e6 --- /dev/null +++ b/vendor/jetpack-autoloader/class-latest-autoloader-guard.php @@ -0,0 +1,165 @@ +plugins_handler = $plugins_handler; + $this->autoloader_handler = $autoloader_handler; + $this->autoloader_locator = $autoloader_locator; + } + + /** + * Indicates whether or not the autoloader should be initialized. Note that this function + * has the side-effect of actually loading the latest autoloader in the event that this + * is not it. + * + * @param string $current_plugin The current plugin we're checking. + * @param string[] $plugins The active plugins to check for autoloaders in. + * @param bool $was_included_by_autoloader Indicates whether or not this autoloader was included by another. + * + * @return bool True if we should stop initialization, otherwise false. + */ + public function should_stop_init( $current_plugin, $plugins, $was_included_by_autoloader ) { + global $jetpack_autoloader_latest_version; + + // We need to reset the autoloader when the plugins change because + // that means the autoloader was generated with a different list. + if ( $this->plugins_handler->have_plugins_changed( $plugins ) ) { + $this->autoloader_handler->reset_autoloader(); + } + + // When the latest autoloader has already been found we don't need to search for it again. + // We should take care however because this will also trigger if the autoloader has been + // included by an older one. + if ( isset( $jetpack_autoloader_latest_version ) && ! $was_included_by_autoloader ) { + return true; + } + + $latest_plugin = $this->autoloader_locator->find_latest_autoloader( $plugins, $jetpack_autoloader_latest_version ); + if ( isset( $latest_plugin ) && $latest_plugin !== $current_plugin ) { + require $this->autoloader_locator->get_autoloader_path( $latest_plugin ); + return true; + } + + return false; + } + + /** + * Check for conflicting autoloaders. + * + * A common source of strange and confusing problems is when another plugin + * registers a Composer autoloader at a higher priority that us. If enabled, + * check for this problem and warn about it. + * + * Called from the plugins_loaded hook. + * + * @since 3.1.0 + * @return void + */ + public function check_for_conflicting_autoloaders() { + if ( ! defined( 'JETPACK_AUTOLOAD_DEBUG_CONFLICTING_LOADERS' ) || ! JETPACK_AUTOLOAD_DEBUG_CONFLICTING_LOADERS ) { + return; + } + + global $jetpack_autoloader_loader; + if ( ! isset( $jetpack_autoloader_loader ) ) { + return; + } + $prefixes = array(); + foreach ( ( $jetpack_autoloader_loader->get_class_map() ?? array() ) as $classname => $data ) { + $parts = explode( '\\', trim( $classname, '\\' ) ); + array_pop( $parts ); + while ( $parts ) { + $prefixes[ implode( '\\', $parts ) . '\\' ] = true; + array_pop( $parts ); + } + } + foreach ( ( $jetpack_autoloader_loader->get_psr4_map() ?? array() ) as $prefix => $data ) { + $parts = explode( '\\', trim( $prefix, '\\' ) ); + while ( $parts ) { + $prefixes[ implode( '\\', $parts ) . '\\' ] = true; + array_pop( $parts ); + } + } + + $autoload_chain = spl_autoload_functions(); + if ( ! $autoload_chain ) { + return; + } + + foreach ( $autoload_chain as $autoloader ) { + // No need to check anything after us. + if ( is_array( $autoloader ) && is_string( $autoloader[0] ) && substr( $autoloader[0], 0, strlen( __NAMESPACE__ ) + 1 ) === __NAMESPACE__ . '\\' ) { + break; + } + + // We can check Composer autoloaders easily enough. + if ( is_array( $autoloader ) && $autoloader[0] instanceof \Composer\Autoload\ClassLoader && is_callable( array( $autoloader[0], 'getPrefixesPsr4' ) ) ) { + $composer_autoloader = $autoloader[0]; + foreach ( $composer_autoloader->getClassMap() as $classname => $path ) { + if ( $jetpack_autoloader_loader->find_class_file( $classname ) ) { + $msg = "A Composer autoloader is registered with a higher priority than the Jetpack Autoloader and would also handle some of the classes we handle (e.g. $classname => $path). This may cause strange and confusing problems."; + wp_trigger_error( '', $msg ); + continue 2; + } + } + foreach ( $composer_autoloader->getPrefixesPsr4() as $prefix => $paths ) { + if ( isset( $prefixes[ $prefix ] ) ) { + $path = array_pop( $paths ); + $msg = "A Composer autoloader is registered with a higher priority than the Jetpack Autoloader and would also handle some of the namespaces we handle (e.g. $prefix => $path). This may cause strange and confusing problems."; + wp_trigger_error( '', $msg ); + continue 2; + } + } + foreach ( $composer_autoloader->getPrefixes() as $prefix => $paths ) { + if ( isset( $prefixes[ $prefix ] ) ) { + $path = array_pop( $paths ); + $msg = "A Composer autoloader is registered with a higher priority than the Jetpack Autoloader and would also handle some of the namespaces we handle (e.g. $prefix => $path). This may cause strange and confusing problems."; + wp_trigger_error( '', $msg ); + continue 2; + } + } + } + } + } +} diff --git a/vendor/jetpack-autoloader/class-manifest-reader.php b/vendor/jetpack-autoloader/class-manifest-reader.php new file mode 100644 index 000000000..2e979a315 --- /dev/null +++ b/vendor/jetpack-autoloader/class-manifest-reader.php @@ -0,0 +1,99 @@ +version_selector = $version_selector; + } + + /** + * Reads all of the manifests in the given plugin paths. + * + * @param array $plugin_paths The paths to the plugins we're loading the manifest in. + * @param string $manifest_path The path that we're loading the manifest from in each plugin. + * @param array $path_map The path map to add the contents of the manifests to. + * + * @return array $path_map The path map we've built using the manifests in each plugin. + */ + public function read_manifests( $plugin_paths, $manifest_path, &$path_map ) { + $file_paths = array_map( + function ( $path ) use ( $manifest_path ) { + return trailingslashit( $path ) . $manifest_path; + }, + $plugin_paths + ); + + foreach ( $file_paths as $path ) { + $this->register_manifest( $path, $path_map ); + } + + return $path_map; + } + + /** + * Registers a plugin's manifest file with the path map. + * + * @param string $manifest_path The absolute path to the manifest that we're loading. + * @param array $path_map The path map to add the contents of the manifest to. + */ + protected function register_manifest( $manifest_path, &$path_map ) { + if ( ! is_readable( $manifest_path ) ) { + return; + } + + $manifest = require $manifest_path; + if ( ! is_array( $manifest ) ) { + return; + } + + foreach ( $manifest as $key => $data ) { + $this->register_record( $key, $data, $path_map ); + } + } + + /** + * Registers an entry from the manifest in the path map. + * + * @param string $key The identifier for the entry we're registering. + * @param array $data The data for the entry we're registering. + * @param array $path_map The path map to add the contents of the manifest to. + */ + protected function register_record( $key, $data, &$path_map ) { + if ( isset( $path_map[ $key ]['version'] ) ) { + $selected_version = $path_map[ $key ]['version']; + } else { + $selected_version = null; + } + + if ( $this->version_selector->is_version_update_required( $selected_version, $data['version'] ) ) { + $path_map[ $key ] = array( + 'version' => $data['version'], + 'path' => $data['path'], + ); + } + } +} diff --git a/vendor/jetpack-autoloader/class-path-processor.php b/vendor/jetpack-autoloader/class-path-processor.php new file mode 100644 index 000000000..544cbfa73 --- /dev/null +++ b/vendor/jetpack-autoloader/class-path-processor.php @@ -0,0 +1,194 @@ +get_normalized_constants(); + foreach ( $constants as $constant => $constant_path ) { + $len = strlen( $constant_path ); + if ( substr( $path, 0, $len ) !== $constant_path ) { + continue; + } + + return substr_replace( $path, '{{' . $constant . '}}', 0, $len ); + } + + return $path; + } + + /** + * Given a path this will replace any of the path constant tokens with the expanded path. + * + * @param string $tokenized_path The path we want to process. + * + * @return string The expanded path. + */ + public function untokenize_path_constants( $tokenized_path ) { + $tokenized_path = wp_normalize_path( $tokenized_path ); + + $constants = $this->get_normalized_constants(); + foreach ( $constants as $constant => $constant_path ) { + $constant = '{{' . $constant . '}}'; + + $len = strlen( $constant ); + if ( substr( $tokenized_path, 0, $len ) !== $constant ) { + continue; + } + + return $this->get_real_path( substr_replace( $tokenized_path, $constant_path, 0, $len ) ); + } + + return $tokenized_path; + } + + /** + * Given a file and an array of places it might be, this will find the absolute path and return it. + * + * @param string $file The plugin or theme file to resolve. + * @param array $directories_to_check The directories we should check for the file if it isn't an absolute path. + * + * @return string|false Returns the absolute path to the directory, otherwise false. + */ + public function find_directory_with_autoloader( $file, $directories_to_check ) { + $file = wp_normalize_path( $file ); + + if ( ! $this->is_absolute_path( $file ) ) { + $file = $this->find_absolute_plugin_path( $file, $directories_to_check ); + if ( ! isset( $file ) ) { + return false; + } + } + + // We need the real path for consistency with __DIR__ paths. + $file = $this->get_real_path( $file ); + + // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged + $directory = @is_file( $file ) ? dirname( $file ) : $file; + if ( ! @is_file( $directory . '/vendor/composer/jetpack_autoload_classmap.php' ) ) { + return false; + } + // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged + + return $directory; + } + + /** + * Fetches an array of normalized paths keyed by the constant they came from. + * + * @return string[] The normalized paths keyed by the constant. + */ + private function get_normalized_constants() { + $raw_constants = array( + // Order the constants from most-specific to least-specific. + 'WP_PLUGIN_DIR', + 'WPMU_PLUGIN_DIR', + 'WP_CONTENT_DIR', + 'ABSPATH', + ); + + $constants = array(); + foreach ( $raw_constants as $raw ) { + if ( ! defined( $raw ) ) { + continue; + } + + $path = wp_normalize_path( constant( $raw ) ); + if ( isset( $path ) ) { + $constants[ $raw ] = $path; + } + } + + return $constants; + } + + /** + * Indicates whether or not a path is absolute. + * + * @param string $path The path to check. + * + * @return bool True if the path is absolute, otherwise false. + */ + private function is_absolute_path( $path ) { + if ( empty( $path ) || 0 === strlen( $path ) || '.' === $path[0] ) { + return false; + } + + // Absolute paths on Windows may begin with a drive letter. + if ( preg_match( '/^[a-zA-Z]:[\/\\\\]/', $path ) ) { + return true; + } + + // A path starting with / or \ is absolute; anything else is relative. + return ( '/' === $path[0] || '\\' === $path[0] ); + } + + /** + * Given a file and a list of directories to check, this method will try to figure out + * the absolute path to the file in question. + * + * @param string $normalized_path The normalized path to the plugin or theme file to resolve. + * @param array $directories_to_check The directories we should check for the file if it isn't an absolute path. + * + * @return string|null The absolute path to the plugin file, otherwise null. + */ + private function find_absolute_plugin_path( $normalized_path, $directories_to_check ) { + // We're only able to find the absolute path for plugin/theme PHP files. + if ( ! is_string( $normalized_path ) || '.php' !== substr( $normalized_path, -4 ) ) { + return null; + } + + foreach ( $directories_to_check as $directory ) { + $normalized_check = wp_normalize_path( trailingslashit( $directory ) ) . $normalized_path; + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + if ( @is_file( $normalized_check ) ) { + return $normalized_check; + } + } + + return null; + } + + /** + * Given a path this will figure out the real path that we should be using. + * + * @param string $path The path to resolve. + * + * @return string The resolved path. + */ + private function get_real_path( $path ) { + // We want to resolve symbolic links for consistency with __DIR__ paths. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + $real_path = @realpath( $path ); + if ( false === $real_path ) { + // Let the autoloader deal with paths that don't exist. + $real_path = $path; + } + + // Using realpath will make it platform-specific so we must normalize it after. + if ( $path !== $real_path ) { + $real_path = wp_normalize_path( $real_path ); + } + + return $real_path; + } +} diff --git a/vendor/jetpack-autoloader/class-php-autoloader.php b/vendor/jetpack-autoloader/class-php-autoloader.php new file mode 100644 index 000000000..9b67c79be --- /dev/null +++ b/vendor/jetpack-autoloader/class-php-autoloader.php @@ -0,0 +1,105 @@ +unregister_autoloader(); + + // Set the global so that it can be used to load classes. + global $jetpack_autoloader_loader; + $jetpack_autoloader_loader = $version_loader; + + // Ensure that the autoloader is first to avoid contention with others. + spl_autoload_register( array( self::class, 'load_class' ), true, true ); + } + + /** + * Unregisters the active autoloader so that it will no longer autoload classes. + */ + public function unregister_autoloader() { + // Remove any v2 autoloader that we've already registered. + $autoload_chain = spl_autoload_functions(); + if ( ! $autoload_chain ) { + return; + } + foreach ( $autoload_chain as $autoloader ) { + // We can identify a v2 autoloader using the namespace. + $namespace_check = null; + + // Functions are recorded as strings. + if ( is_string( $autoloader ) ) { + $namespace_check = $autoloader; + } elseif ( is_array( $autoloader ) && is_string( $autoloader[0] ) ) { + // Static method calls have the class as the first array element. + $namespace_check = $autoloader[0]; + } else { + // Since the autoloader has only ever been a function or a static method we don't currently need to check anything else. + continue; + } + + // Check for the namespace without the generated suffix. + if ( 'Automattic\\Jetpack\\Autoloader\\jp' === substr( $namespace_check, 0, 32 ) ) { + spl_autoload_unregister( $autoloader ); + } + } + + // Clear the global now that the autoloader has been unregistered. + global $jetpack_autoloader_loader; + $jetpack_autoloader_loader = null; + } + + /** + * Loads a class file if one could be found. + * + * Note: This function is static so that the autoloader can be easily unregistered. If + * it was a class method we would have to unwrap the object to check the namespace. + * + * @param string $class_name The name of the class to autoload. + * + * @return bool Indicates whether or not a class file was loaded. + */ + public static function load_class( $class_name ) { + global $jetpack_autoloader_loader; + if ( ! isset( $jetpack_autoloader_loader ) ) { + return false; + } + + $file = $jetpack_autoloader_loader->find_class_file( $class_name ); + if ( ! isset( $file ) ) { + return false; + } + + // A common source of strange and confusing problems is when a vendor + // file is autoloaded before all plugins have had a chance to register + // with the autoloader. Detect that, if a development constant is set. + if ( defined( 'JETPACK_AUTOLOAD_DEBUG_EARLY_LOADS' ) && JETPACK_AUTOLOAD_DEBUG_EARLY_LOADS && + ( strpos( $file, '/vendor/' ) !== false || strpos( $file, '/jetpack_vendor/' ) !== false ) && + is_callable( 'did_action' ) && ! did_action( 'plugins_loaded' ) + ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary -- This is a debug log message. + $msg = "Jetpack Autoloader: Autoloading `$class_name` before the plugins_loaded hook may cause strange and confusing problems. " . wp_debug_backtrace_summary( '', 1 ); + wp_trigger_error( '', $msg ); + } + + require $file; + return true; + } +} diff --git a/vendor/jetpack-autoloader/class-plugin-locator.php b/vendor/jetpack-autoloader/class-plugin-locator.php new file mode 100644 index 000000000..2beb6217a --- /dev/null +++ b/vendor/jetpack-autoloader/class-plugin-locator.php @@ -0,0 +1,153 @@ +path_processor = $path_processor; + } + + /** + * Finds the path to the current plugin. + * + * @return string $path The path to the current plugin. + * + * @throws \RuntimeException If the current plugin does not have an autoloader. + */ + public function find_current_plugin() { + // Escape from `vendor/__DIR__` to root plugin directory. + $plugin_directory = dirname( __DIR__, 2 ); + + // Use the path processor to ensure that this is an autoloader we're referencing. + $path = $this->path_processor->find_directory_with_autoloader( $plugin_directory, array() ); + if ( false === $path ) { + throw new \RuntimeException( 'Failed to locate plugin ' . $plugin_directory ); + } + + return $path; + } + + /** + * Checks a given option for plugin paths. + * + * @param string $option_name The option that we want to check for plugin information. + * @param bool $site_option Indicates whether or not we want to check the site option. + * + * @return array $plugin_paths The list of absolute paths we've found. + */ + public function find_using_option( $option_name, $site_option = false ) { + $raw = $site_option ? get_site_option( $option_name ) : get_option( $option_name ); + if ( false === $raw ) { + return array(); + } + + return $this->convert_plugins_to_paths( $raw ); + } + + /** + * Checks for plugins in the `action` request parameter. + * + * @param string[] $allowed_actions The actions that we're allowed to return plugins for. + * + * @return array $plugin_paths The list of absolute paths we've found. + */ + public function find_using_request_action( $allowed_actions ) { + /** + * Note: we're not actually checking the nonce here because it's too early + * in the execution. The pluggable functions are not yet loaded to give + * plugins a chance to plug their versions. Therefore we're doing the bare + * minimum: checking whether the nonce exists and it's in the right place. + * The request will fail later if the nonce doesn't pass the check. + */ + if ( empty( $_REQUEST['_wpnonce'] ) ) { + return array(); + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated just below. + $action = isset( $_REQUEST['action'] ) ? wp_unslash( $_REQUEST['action'] ) : false; + if ( ! in_array( $action, $allowed_actions, true ) ) { + return array(); + } + + $plugin_slugs = array(); + switch ( $action ) { + case 'activate': + case 'deactivate': + if ( empty( $_REQUEST['plugin'] ) ) { + break; + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated by convert_plugins_to_paths. + $plugin_slugs[] = wp_unslash( $_REQUEST['plugin'] ); + break; + + case 'activate-selected': + case 'deactivate-selected': + if ( empty( $_REQUEST['checked'] ) ) { + break; + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated by convert_plugins_to_paths. + $plugin_slugs = wp_unslash( $_REQUEST['checked'] ); + break; + } + + return $this->convert_plugins_to_paths( $plugin_slugs ); + } + + /** + * Given an array of plugin slugs or paths, this will convert them to absolute paths and filter + * out the plugins that are not directory plugins. Note that array keys will also be included + * if they are plugin paths! + * + * @param string[] $plugins Plugin paths or slugs to filter. + * + * @return string[] + */ + private function convert_plugins_to_paths( $plugins ) { + if ( ! is_array( $plugins ) || empty( $plugins ) ) { + return array(); + } + + // We're going to look for plugins in the standard directories. + $path_constants = array( WP_PLUGIN_DIR, WPMU_PLUGIN_DIR ); + + $plugin_paths = array(); + foreach ( $plugins as $key => $value ) { + $path = $this->path_processor->find_directory_with_autoloader( $key, $path_constants ); + if ( $path ) { + $plugin_paths[] = $path; + } + + $path = $this->path_processor->find_directory_with_autoloader( $value, $path_constants ); + if ( $path ) { + $plugin_paths[] = $path; + } + } + + return $plugin_paths; + } +} diff --git a/vendor/jetpack-autoloader/class-plugins-handler.php b/vendor/jetpack-autoloader/class-plugins-handler.php new file mode 100644 index 000000000..ad60a0140 --- /dev/null +++ b/vendor/jetpack-autoloader/class-plugins-handler.php @@ -0,0 +1,164 @@ +plugin_locator = $plugin_locator; + $this->path_processor = $path_processor; + } + + /** + * Gets all of the active plugins we can find. + * + * @param bool $include_deactivating When true, plugins deactivating this request will be considered active. + * @param bool $record_unknown When true, the current plugin will be marked as active and recorded when unknown. + * + * @return string[] + */ + public function get_active_plugins( $include_deactivating, $record_unknown ) { + global $jetpack_autoloader_activating_plugins_paths; + + // We're going to build a unique list of plugins from a few different sources + // to find all of our "active" plugins. While we need to return an integer + // array, we're going to use an associative array internally to reduce + // the amount of time that we're going to spend checking uniqueness + // and merging different arrays together to form the output. + $active_plugins = array(); + + // Make sure that plugins which have activated this request are considered as "active" even though + // they probably won't be present in any option. + if ( is_array( $jetpack_autoloader_activating_plugins_paths ) ) { + foreach ( $jetpack_autoloader_activating_plugins_paths as $path ) { + $active_plugins[ $path ] = $path; + } + } + + // This option contains all of the plugins that have been activated. + $plugins = $this->plugin_locator->find_using_option( 'active_plugins' ); + foreach ( $plugins as $path ) { + $active_plugins[ $path ] = $path; + } + + // This option contains all of the multisite plugins that have been activated. + if ( is_multisite() ) { + $plugins = $this->plugin_locator->find_using_option( 'active_sitewide_plugins', true ); + foreach ( $plugins as $path ) { + $active_plugins[ $path ] = $path; + } + } + + // These actions contain plugins that are being activated/deactivated during this request. + $plugins = $this->plugin_locator->find_using_request_action( array( 'activate', 'activate-selected', 'deactivate', 'deactivate-selected' ) ); + foreach ( $plugins as $path ) { + $active_plugins[ $path ] = $path; + } + + // When the current plugin isn't considered "active" there's a problem. + // Since we're here, the plugin is active and currently being loaded. + // We can support this case (mu-plugins and non-standard activation) + // by adding the current plugin to the active list and marking it + // as an unknown (activating) plugin. This also has the benefit + // of causing a reset because the active plugins list has + // been changed since it was saved in the global. + $current_plugin = $this->plugin_locator->find_current_plugin(); + if ( $record_unknown && ! in_array( $current_plugin, $active_plugins, true ) ) { + $active_plugins[ $current_plugin ] = $current_plugin; + $jetpack_autoloader_activating_plugins_paths[] = $current_plugin; + } + + // When deactivating plugins aren't desired we should entirely remove them from the active list. + if ( ! $include_deactivating ) { + // These actions contain plugins that are being deactivated during this request. + $plugins = $this->plugin_locator->find_using_request_action( array( 'deactivate', 'deactivate-selected' ) ); + foreach ( $plugins as $path ) { + unset( $active_plugins[ $path ] ); + } + } + + // Transform the array so that we don't have to worry about the keys interacting with other array types later. + return array_values( $active_plugins ); + } + + /** + * Gets all of the cached plugins if there are any. + * + * @return string[] + */ + public function get_cached_plugins() { + $cached = get_transient( self::TRANSIENT_KEY ); + if ( ! is_array( $cached ) || empty( $cached ) ) { + return array(); + } + + // We need to expand the tokens to an absolute path for this webserver. + return array_map( array( $this->path_processor, 'untokenize_path_constants' ), $cached ); + } + + /** + * Saves the plugin list to the cache. + * + * @param array $plugins The plugin list to save to the cache. + */ + public function cache_plugins( $plugins ) { + // We store the paths in a tokenized form so that that webservers with different absolute paths don't break. + $plugins = array_map( array( $this->path_processor, 'tokenize_path_constants' ), $plugins ); + + set_transient( self::TRANSIENT_KEY, $plugins ); + } + + /** + * Checks to see whether or not the plugin list given has changed when compared to the + * shared `$jetpack_autoloader_cached_plugin_paths` global. This allows us to deal + * with cases where the active list may change due to filtering.. + * + * @param string[] $plugins The plugins list to check against the global cache. + * + * @return bool True if the plugins have changed, otherwise false. + */ + public function have_plugins_changed( $plugins ) { + global $jetpack_autoloader_cached_plugin_paths; + + if ( $jetpack_autoloader_cached_plugin_paths !== $plugins ) { + $jetpack_autoloader_cached_plugin_paths = $plugins; + return true; + } + + return false; + } +} diff --git a/vendor/jetpack-autoloader/class-shutdown-handler.php b/vendor/jetpack-autoloader/class-shutdown-handler.php new file mode 100644 index 000000000..dd0de88c9 --- /dev/null +++ b/vendor/jetpack-autoloader/class-shutdown-handler.php @@ -0,0 +1,92 @@ +plugins_handler = $plugins_handler; + $this->cached_plugins = $cached_plugins; + $this->was_included_by_autoloader = $was_included_by_autoloader; + } + + /** + * Handles the shutdown of the autoloader. + */ + public function __invoke() { + // Don't save a broken cache if an error happens during some plugin's initialization. + if ( ! did_action( 'plugins_loaded' ) ) { + // Ensure that the cache is emptied to prevent consecutive failures if the cache is to blame. + if ( ! empty( $this->cached_plugins ) ) { + $this->plugins_handler->cache_plugins( array() ); + } + + return; + } + + // Load the active plugins fresh since the list we pulled earlier might not contain + // plugins that were activated but did not reset the autoloader. This happens + // when a plugin is in the cache but not "active" when the autoloader loads. + // We also want to make sure that plugins which are deactivating are not + // considered "active" so that they will be removed from the cache now. + try { + $active_plugins = $this->plugins_handler->get_active_plugins( false, ! $this->was_included_by_autoloader ); + } catch ( \Exception $ex ) { + // When the package is deleted before shutdown it will throw an exception. + // In the event this happens we should erase the cache. + if ( ! empty( $this->cached_plugins ) ) { + $this->plugins_handler->cache_plugins( array() ); + } + return; + } + + // The paths should be sorted for easy comparisons with those loaded from the cache. + // Note we don't need to sort the cached entries because they're already sorted. + sort( $active_plugins ); + + // We don't want to waste time saving a cache that hasn't changed. + if ( $this->cached_plugins === $active_plugins ) { + return; + } + + $this->plugins_handler->cache_plugins( $active_plugins ); + } +} diff --git a/vendor/jetpack-autoloader/class-version-loader.php b/vendor/jetpack-autoloader/class-version-loader.php new file mode 100644 index 000000000..c4401830d --- /dev/null +++ b/vendor/jetpack-autoloader/class-version-loader.php @@ -0,0 +1,184 @@ +version_selector = $version_selector; + $this->classmap = $classmap; + $this->psr4_map = $psr4_map; + $this->filemap = $filemap; + } + + /** + * Fetch the classmap. + * + * @since 3.1.0 + * @return array + */ + public function get_class_map() { + return $this->classmap; + } + + /** + * Fetch the psr-4 mappings. + * + * @since 3.1.0 + * @return array + */ + public function get_psr4_map() { + return $this->psr4_map; + } + + /** + * Finds the file path for the given class. + * + * @param string $class_name The class to find. + * + * @return string|null $file_path The path to the file if found, null if no class was found. + */ + public function find_class_file( $class_name ) { + $data = $this->select_newest_file( + $this->classmap[ $class_name ] ?? null, + $this->find_psr4_file( $class_name ) + ); + if ( ! isset( $data ) ) { + return null; + } + + return $data['path']; + } + + /** + * Load all of the files in the filemap. + */ + public function load_filemap() { + if ( empty( $this->filemap ) ) { + return; + } + + foreach ( $this->filemap as $file_identifier => $file_data ) { + if ( empty( $GLOBALS['__composer_autoload_files'][ $file_identifier ] ) ) { + require_once $file_data['path']; + + $GLOBALS['__composer_autoload_files'][ $file_identifier ] = true; + } + } + } + + /** + * Compares different class sources and returns the newest. + * + * @param array|null $classmap_data The classmap class data. + * @param array|null $psr4_data The PSR-4 class data. + * + * @return array|null $data + */ + private function select_newest_file( $classmap_data, $psr4_data ) { + if ( ! isset( $classmap_data ) ) { + return $psr4_data; + } elseif ( ! isset( $psr4_data ) ) { + return $classmap_data; + } + + if ( $this->version_selector->is_version_update_required( $classmap_data['version'], $psr4_data['version'] ) ) { + return $psr4_data; + } + + return $classmap_data; + } + + /** + * Finds the file for a given class in a PSR-4 namespace. + * + * @param string $class_name The class to find. + * + * @return array|null $data The version and path path to the file if found, null otherwise. + */ + private function find_psr4_file( $class_name ) { + if ( empty( $this->psr4_map ) ) { + return null; + } + + // Don't bother with classes that have no namespace. + $class_index = strrpos( $class_name, '\\' ); + if ( ! $class_index ) { + return null; + } + $class_for_path = str_replace( '\\', '/', $class_name ); + + // Search for the namespace by iteratively cutting off the last segment until + // we find a match. This allows us to check the most-specific namespaces + // first as well as minimize the amount of time spent looking. + for ( + $class_namespace = substr( $class_name, 0, $class_index ); + ! empty( $class_namespace ); + $class_namespace = substr( $class_namespace, 0, strrpos( $class_namespace, '\\' ) ) + ) { + $namespace = $class_namespace . '\\'; + if ( ! isset( $this->psr4_map[ $namespace ] ) ) { + continue; + } + $data = $this->psr4_map[ $namespace ]; + + foreach ( $data['path'] as $path ) { + $path .= '/' . substr( $class_for_path, strlen( $namespace ) ) . '.php'; + if ( file_exists( $path ) ) { + return array( + 'version' => $data['version'], + 'path' => $path, + ); + } + } + } + + return null; + } +} diff --git a/vendor/jetpack-autoloader/class-version-selector.php b/vendor/jetpack-autoloader/class-version-selector.php new file mode 100644 index 000000000..167699b13 --- /dev/null +++ b/vendor/jetpack-autoloader/class-version-selector.php @@ -0,0 +1,69 @@ +is_dev_version( $selected_version ) ) { + return false; + } + + if ( $this->is_dev_version( $compare_version ) ) { + if ( $use_dev_versions ) { + return true; + } else { + return false; + } + } + + if ( version_compare( $selected_version, $compare_version, '<' ) ) { + return true; + } + + return false; + } + + /** + * Checks whether the given package version is a development version. + * + * @param String $version The package version. + * + * @return bool True if the version is a dev version, else false. + */ + public function is_dev_version( $version ) { + if ( 'dev-' === substr( $version, 0, 4 ) || '9999999-dev' === $version ) { + return true; + } + + return false; + } +} diff --git a/vendor/json-mapper/json-mapper/.coveralls.yml b/vendor/json-mapper/json-mapper/.coveralls.yml new file mode 100644 index 000000000..d88852f36 --- /dev/null +++ b/vendor/json-mapper/json-mapper/.coveralls.yml @@ -0,0 +1 @@ +coverage_clover: build/logs/clover-*.xml \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/.github/FUNDING.yml b/vendor/json-mapper/json-mapper/.github/FUNDING.yml new file mode 100644 index 000000000..70f70a4e9 --- /dev/null +++ b/vendor/json-mapper/json-mapper/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [DannyvdSluijs] \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/.github/ISSUE_TEMPLATE/bug_report.yml b/vendor/json-mapper/json-mapper/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..88a249fa2 --- /dev/null +++ b/vendor/json-mapper/json-mapper/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,43 @@ +name: Bug Report +description: "Create a report to help us improve" +title: "[Bug]: " +labels: ["bug", "triage"] +assignees: + - DannyvdSluijs +body: +- type: input + attributes: + label: JsonMapper Version + description: Provide the Laravel version that you are using. + placeholder: 2.21.0 + validations: + required: true + +- type: input + attributes: + label: PHP Version + description: Provide the PHP version that you are using. + placeholder: 8.1.4 + validations: + required: true + +- type: textarea + attributes: + label: Description + description: Provide a detailed description of the issue you are facing. + validations: + required: true + +- type: textarea + attributes: + label: Steps To Reproduce + description: Provide detailed steps to reproduce your issue. + validations: + required: true + +- type: textarea + attributes: + label: Stacktrace + description: If applicable, add the stack trace to help explain your problem. + validations: + required: false \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/.github/ISSUE_TEMPLATE/config.yml b/vendor/json-mapper/json-mapper/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..26e8c5e35 --- /dev/null +++ b/vendor/json-mapper/json-mapper/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: JsonMapper Community Support + url: https://github.com/JsonMapper/JsonMapper/discussions + about: Please ask and answer questions here. \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/.github/ISSUE_TEMPLATE/feature_request.yml b/vendor/json-mapper/json-mapper/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..b2df98917 --- /dev/null +++ b/vendor/json-mapper/json-mapper/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,30 @@ +name: Feature request +description: "Suggest an idea for this project" +title: "[Feature]: " +labels: ["feature", "triage"] +assignees: + - DannyvdSluijs +body: +- type: textarea + attributes: + label: Description + description: Is your feature request related to a problem? Please describe. + placeholder: Ex. I'm always frustrated when [...] + validations: + required: true + +- type: textarea + attributes: + label: Desired solution + description: Describe the solution you'd like. + placeholder: A clear and concise description of what you want to happen. + validations: + required: true + +- type: textarea + attributes: + label: Alternatives + description: Describe alternatives you've considered. + placeholder: A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/.github/PULL_REQUEST_TEMPLATE.md b/vendor/json-mapper/json-mapper/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..750ebc5bc --- /dev/null +++ b/vendor/json-mapper/json-mapper/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +| Q | A | +|---------------|------------------------------------------------------------------------------------| +| Branch? | develop for features / main for bug fixes | +| Bug fix? | yes/no | +| New feature? | yes/no | +| Deprecations? | yes/no | +| Tickets | Fix #... | +| License | MIT | +| Changelog | Each PR should contain a changelog entry, see https://keepachangelog.com/en/1.0.0/ | +| Doc PR | JsonMapper/jsonmapper.github.io#... | + + diff --git a/vendor/json-mapper/json-mapper/.github/workflows/build.yaml b/vendor/json-mapper/json-mapper/.github/workflows/build.yaml new file mode 100644 index 000000000..b2d5ec0cf --- /dev/null +++ b/vendor/json-mapper/json-mapper/.github/workflows/build.yaml @@ -0,0 +1,120 @@ +name: Build + +on: + push: + branches: [ develop, main ] + pull_request: + branches: [ develop, main ] + +jobs: + build: + name: PHP ${{ matrix.name }} + strategy: + matrix: + include: + - php: 7.4 + allow_fail: false + name: 'PHP 7.4 with latest deps' + - php: 7.4 + allow_fail: false + composer_update_flags: '--prefer-lowest --prefer-stable' + name: 'PHP 7.4 with lowest stable deps' + - php: 8.0 + allow_fail: false + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.0 with latest deps' + - php: 8.0 + allow_fail: false + composer_update_flags: '--prefer-lowest --prefer-stable' + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.0 with lowest stable deps' + - php: 8.1 + allow_fail: false + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.1 with latest deps' + - php: 8.1 + allow_fail: false + composer_update_flags: '--prefer-lowest --prefer-stable' + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.1 with lowest stable deps' + - php: 8.2 + allow_fail: false + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.2 with latest deps' + - php: 8.2 + allow_fail: false + composer_update_flags: '--prefer-lowest --prefer-stable' + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.2 with lowest stable deps' + - php: 8.3 + allow_fail: false + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.3 with latest deps' + - php: 8.3 + allow_fail: false + composer_update_flags: '--prefer-lowest --prefer-stable' + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.3 with lowest stable deps' + - php: 8.4 + allow_fail: false + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.4 with latest deps' + - php: 8.4 + allow_fail: false + composer_update_flags: '--prefer-lowest --prefer-stable' + php_ini: 'xdebug.coverage_enable=On' + name: 'PHP 8.4 with lowest stable deps' + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + ini-values: ${{ matrix.php_ini }} + + - name: Install dependencies + run: composer install ${{ matrix.composer_flags }} + continue-on-error: ${{ matrix.allow_fail }} + + - name: Update dependencies + if: matrix.composer_update_flags + run: composer update ${{ matrix.composer_update_flags }} && composer update phpunit/phpunit --with-dependencies + continue-on-error: ${{ matrix.allow_fail }} + + - name: Create log folder + run: mkdir -p build/logs; + + - name: Run unit tests + run: composer unit-tests + continue-on-error: ${{ matrix.allow_fail }} + + - name: Run integration tests + run: composer integration-tests + continue-on-error: ${{ matrix.allow_fail }} + + - name: Upload test coverage + run: php vendor/bin/php-coveralls -vvv + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: true + COVERALLS_FLAG_NAME: ${{ matrix.name }} + continue-on-error: ${{ matrix.allow_fail }} + + finish: + needs: build + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + run: | + curl --header "Content-Type: application/json" \ + --request POST \ + --data '{"repo_token":"${{ secrets.GITHUB_TOKEN }}","repo_name":"JsonMapper/JsonMapper", "payload": {"build_num": "${{ github.sha }}", "status": "done"}}' \ + https://coveralls.io/webhook diff --git a/vendor/json-mapper/json-mapper/.github/workflows/phpcs.yaml b/vendor/json-mapper/json-mapper/.github/workflows/phpcs.yaml new file mode 100644 index 000000000..3eb6ff8ce --- /dev/null +++ b/vendor/json-mapper/json-mapper/.github/workflows/phpcs.yaml @@ -0,0 +1,30 @@ +name: "CodeStyle" + +on: + pull_request: + paths: + - "**.php" + - "phpcs.xml.dist" + - ".github/workflows/phpcs.yaml" + +jobs: + phpcs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # important! + + # we may use whatever way to install phpcs, just specify the path on the next step + # however, curl seems to be the fastest + - name: Install PHP_CodeSniffer + run: | + curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar + php phpcs.phar --version + + - uses: thenabeel/action-phpcs@v8 + with: + files: "**.php" # you may customize glob as needed + phpcs_path: php phpcs.phar + standard: phpcs.xml.dist + fail_on_warnings: false diff --git a/vendor/json-mapper/json-mapper/.gitignore b/vendor/json-mapper/json-mapper/.gitignore new file mode 100644 index 000000000..d1a84b0ec --- /dev/null +++ b/vendor/json-mapper/json-mapper/.gitignore @@ -0,0 +1,11 @@ +/composer.lock +/vendor/ +/.phpunit.result.cache +/build/ +/.idea/ + +# Ignore files from the gh-pages +/node_modules/ +/.DS_Store +/.jekyll-cache +/_site \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/CHANGELOG.md b/vendor/json-mapper/json-mapper/CHANGELOG.md new file mode 100644 index 000000000..ca7407777 --- /dev/null +++ b/vendor/json-mapper/json-mapper/CHANGELOG.md @@ -0,0 +1,289 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [2.25.1] - 2025-05-26 +### Fixed +- Nullable union property with null value cannot be mapped [PR#200](https://github.com/JsonMapper/JsonMapper/pull/200) + +## [2.25.0] - 2025-04-29 +### Fixed +- Replace docblock type parsing with reflection docblock library. [PR#199](https://github.com/JsonMapper/JsonMapper/pull/199) +### Removed +- Support for PHP 7.1, 7.2 and 7.3 has been removed. [PR#197](https://github.com/JsonMapper/JsonMapper/pull/197) + +## [2.24.0] - 2025-04-08 +### Added +- Add support list and array in constructor middleware [PR#194](https://github.com/JsonMapper/JsonMapper/pull/194) + +## [2.23.0] - 2025-04-08 +### Added +- Include PHP 8.4 in build matrix [PR#187](https://github.com/JsonMapper/JsonMapper/pull/187) +- Add support for list and array from DocBlocks [PR#193](https://github.com/JsonMapper/JsonMapper/pull/193) + +## [2.22.3] - 2025-02-03 +### Fixed +- Fix implicit null deprecations [PR#189](https://github.com/JsonMapper/JsonMapper/pull/189) + +## [2.22.2] - 2024-05-14 +### Changed +- Added support for nikic/php-parser:^5.0 [PR#185](https://github.com/JsonMapper/JsonMapper/pull/185) +### Fixed +- Resolve Nodejs16 deprecations warnings [PR#186](https://github.com/JsonMapper/JsonMapper/pull/186) +- Add dark mode logo [PR#188](https://github.com/JsonMapper/JsonMapper/pull/188) + +## [2.22.1] - 2024-05-04 +### Changed +- Add compatibility with symfony/cache:^7.0 [PR#184](https://github.com/JsonMapper/JsonMapper/pull/184) + +## [2.22.0] - 2024-03-05 +### Added +- Include PHP 8.3 in build matrix [PR#178](https://github.com/JsonMapper/JsonMapper/pull/178) +### Changed +- Tweak pull request template [PR#179](https://github.com/JsonMapper/JsonMapper/pull/179) +- Update badges [PR#181](https://github.com/JsonMapper/JsonMapper/pull/181) +- Update issue templates [PR#182](https://github.com/JsonMapper/JsonMapper/pull/182) +### Removed +- Remove dependabot [PR#177](https://github.com/JsonMapper/JsonMapper/pull/177) +- Remove dependabot workflow [PR#180](https://github.com/JsonMapper/JsonMapper/pull/180) + +## [2.21.0] - 2023-12-12 +### Fixed +- Corrected master for main in workflows and docs [PR#174](https://github.com/JsonMapper/JsonMapper/pull/174) +- Fix FinalCallback not called after a previous exception [PR#173](https://github.com/JsonMapper/JsonMapper/pull/173). Thanks to [hyde1](https://github.com/hyde1) for creating the PR. + + +## [2.20.0] - 2023-10-09 +### Fixed +- Support public properties comments when using Constructor middleware [PR#171](https://github.com/JsonMapper/JsonMapper/pull/171) + +## [2.19.0] - 2023-06-06 +### Fixed +- Fix constructor not being called on nested array of objects [PR#164](https://github.com/JsonMapper/JsonMapper/pull/164). Thanks to [o15a3d4l11s2](https://github.com/o15a3d4l11s2) for reporting the issue. +- Avoid integer keys being replaced in CaseConversion middleware [PR#166](https://github.com/JsonMapper/JsonMapper/pull/166) + +## [2.18.0] - 2023-05-12 +### Fixed +- Support private properties from parent class in DocBlockAnnotations and TypedProperties [PR#161](https://github.com/JsonMapper/JsonMapper/pull/161) + +## [2.17.0] - 2023-05-02 +### Changed +- Allow vimeo/psalm 5.0 as dev dependency +- Allow recursive traversal in CaseConversion middleware [PR#160](https://github.com/JsonMapper/JsonMapper/pull/160) +### Fixed +- Fix null value reaching native php class factories [PR#159](https://github.com/JsonMapper/JsonMapper/pull/159). Thanks to [template-provider](https://github.com/template-provider) for reporting the issue. + +## [2.16.0] - 2023-02-13 +### Fixed +- Make it compatible with Laravel 9 [PR#156](https://github.com/JsonMapper/JsonMapper/pull/156). Thanks to [MarcelGDC](https://github.com/MarcelGDC) for reporting the issue. + +## [2.15.0] - 2023-02-07 +### Fixed +- Collection Mapping does not work with more than one item [PR#153](https://github.com/JsonMapper/JsonMapper/pull/153). Thanks to [template-provider](https://github.com/template-provider) for reporting the issue. + +## [2.14.4] - 2023-01-19 +### Fixed +- Map to array of enums when combination of PHPDoc block and native array typehint. [PR#152](https://github.com/JsonMapper/JsonMapper/pull/152). Thanks to [uchuu-me](https://github.com/uchuu-me) for reporting the issue. + +## [2.14.3] - 2023-01-10 +### Fixed +- Namespace resolving is unable to resolve when using partial use combined with nested namespace in PHPDoc [PR#150](https://github.com/JsonMapper/JsonMapper/pull/150). + +## [2.14.2] - 2023-01-07 +### Added +- Add PHP 8.2 to build matrix [PR#149](https://github.com/JsonMapper/JsonMapper/pull/149). +### Fixed +- Undefined array key 0 when using object construction middleware [PR#148](https://github.com/JsonMapper/JsonMapper/pull/148). Thanks to [template-provider](https://github.com/template-provider) for reporting the issue. + +## [2.14.1] - 2022-11-15 +### Fixed +- Cannot map to native php types using constructor middleware [PR#144](https://github.com/JsonMapper/JsonMapper/pull/144). Thanks to [template-provider](https://github.com/template-provider) for reporting the issue. + +## [2.14.0] - 2022-11-01 +### Added +- Constructor middleware; Add support for mapping to class name [PR#141](https://github.com/JsonMapper/JsonMapper/pull/141) + +## [2.13.0] - 2022-08-17 +### Added +- Support multi-dimensional array notation [PR#137](https://github.com/JsonMapper/JsonMapper/pull/137) +### Fixed +- Correct several typos [PR#136](https://github.com/JsonMapper/JsonMapper/pull/136) Thanks to [Pankaj Aagjal](https://github.com/aagjalpankaj) for fixing the typos + +## [2.12.0] - 2022-03-31 +### Fixed +- Cannot lookup property of namespace in parent class [PR#135](https://github.com/JsonMapper/JsonMapper/pull/135) Thanks to [jg-development](https://github.com/jg-development) for reporting the issue + +## [2.11.1] - 2022-03-08 +### Fixed +- Flex myclabs/php-enum constraints [PR#132](https://github.com/JsonMapper/JsonMapper/pull/132) Thanks to [Christopher Reimer](https://github.com/CReimer) for reporting the issue + +## [2.11.0] - 2022-02-09 +### Changed +- Merging of the property map is optimised with an early return if left side and right side are equal [PR#128](https://github.com/JsonMapper/JsonMapper/pull/128) +### Fixed +- Flex psr/log constraints [PR#129](https://github.com/JsonMapper/JsonMapper/pull/129) + +## [2.10.0] - 2022-01-16 +### Added +- Support was added for strict scalar casting [PR#119](https://github.com/JsonMapper/JsonMapper/pull/119) Thanks to [template-provider](https://github.com/template-provider) for reporting the issue +- All **Map** functions now return the mapped object(s) and uses [Psalm](https://psalm.dev) to assist with autocompletion. [PR#122](https://github.com/JsonMapper/JsonMapper/pull/122) +### Fixed +- Replace duplicates in middleware with object wrapper calls. [PR#123](https://github.com/JsonMapper/JsonMapper/pull/123) +- Correct code style issues. [PR#124](https://github.com/JsonMapper/JsonMapper/pull/124) +- Return empty array for union type with an array type when value is an empty array. [PR#126](https://github.com/JsonMapper/JsonMapper/pull/126) Thanks to [template-provider](https://github.com/template-provider) for reporting the issue + +## [2.9.1] - 2021-11-12 +### Fixed +- Namespace resolving improved to include imports from parent classes [PR#117](https://github.com/JsonMapper/JsonMapper/pull/117) Thanks to [template-provider](https://github.com/template-provider) for reporting the issue + +## [2.9.0] - 2021-11-09 +### Added +- The value transformation middleware was added to apply a callback to the values of the json object [PR#111](https://github.com/JsonMapper/JsonMapper/pull/111) Thanks to [Philipp Dahse](https://github.com/dahse89) +- Introduce psalm annotations [PR#110](https://github.com/JsonMapper/JsonMapper/pull/110) +### Fixed +- Namespace resolving was strengthened to avoid partial matches and now also includes support for using `alias` in `use` statements [PR#112](https://github.com/JsonMapper/JsonMapper/pull/112) Thanks to [Christopher Reimer](https://github.com/CReimer) + +## [2.8.0] - 2021-10-05 +### Added +- Support for PHP 8.1 Enum [PR#105](https://github.com/JsonMapper/JsonMapper/pull/105) + +## [2.7.0] - 2021-08-31 +### Fixed +- Correctly map types for array type with reused internal classname withing same namespace [PR#103](https://github.com/JsonMapper/JsonMapper/pull/103) +### Changed +- Invoke PHP native functions with fq namespace to improve speed. [PR#100](https://github.com/JsonMapper/JsonMapper/pull/100) + +## [2.6.0] - 2021-07-15 +### Added +- Support PHP 7.1 [PR#97](https://github.com/JsonMapper/JsonMapper/pull/97) + +## [2.5.1] - 2021-07-06 +### Fixed +- Preserve cache keys uniqueness within a single cache instance [PR#95](https://github.com/JsonMapper/JsonMapper/pull/95) + +## [2.5.0] - 2021-05-17 +### Added +- Map to stdClass was added to allow for generic objects [PR#89](https://github.com/JsonMapper/JsonMapper/pull/89) +- Map interfaces and abstract classes using factories [PR#84](https://github.com/JsonMapper/JsonMapper/pull/84) +- Suggestions where added to the composer.json file to inform about the laravel and symfony libs we have available. [PR#90](https://github.com/JsonMapper/JsonMapper/pull/90) +### Changed +- Split integration test into smaller test cases divided by individual features [PR#91](https://github.com/JsonMapper/JsonMapper/pull/91) + +## [2.4.1] - 2021-05-07 +### Fixed +- Namespace resolver merging replaces property instead of merging old and new property types [PR#86](https://github.com/JsonMapper/JsonMapper/pull/86) + +## [2.4.0] - 2021-04-15 +### Added +- Caching to the namespace resolver was added to reduce time and memory footprint [PR#82](https://github.com/JsonMapper/JsonMapper/pull/82) + +## [2.3.1] - 2021-03-30 +### Fixed +- Property identified by the same name is merged with the previous property [PR#79](https://github.com/JsonMapper/JsonMapper/pull/79) + +## [2.3.0] - 2021-03-18 +### Added +- JsonMapperBuilder offers a fluent interface for building a JsonMapper instance [PR#76](https://github.com/JsonMapper/JsonMapper/pull/76) + +## [2.2.0] - 2021-02-04 +### Added +- Support for renaming JSON properties before mapping values onto an object [PR#75](https://github.com/JsonMapper/JsonMapper/pull/75) + +## [2.1.0] - 2021-01-28 +### Added +- Support variadic setter function. [PR#68](https://github.com/JsonMapper/JsonMapper/pull/68) +### Fixed +- Include PHP 8.0 in the build matrix. [PR#70](https://github.com/JsonMapper/JsonMapper/pull/70) +- Complete switch to GitHub Actions and remove Travis. [PR#73](https://github.com/JsonMapper/JsonMapper/pull/73) +- Resolve code style and static analysis issues. [PR#71](https://github.com/JsonMapper/JsonMapper/pull/71) + +## [2.0.0] - 2021-01-07 +### Changed +- Improve the test using PropertyAssertionChain. [PR#62](https://github.com/JsonMapper/JsonMapper/pull/62) +- Added support for union types in JsonMapper. [PR#65](https://github.com/JsonMapper/JsonMapper/pull/65) + +## [1.4.2] - 2020-10-30 +## Fixed +- Fix null array support in DocBlock middleware. [PR#60](https://github.com/JsonMapper/JsonMapper/pull/60) + +## [1.4.1] - 2020-10-27 +## Fixed +- Fix null values provided from JSON. [PR#59](https://github.com/JsonMapper/JsonMapper/pull/59) + +## [1.4.0] - 2020-10-22 +### Added +- Add support for mapping from strings [PR#46](https://github.com/JsonMapper/JsonMapper/pull/46) +- Add support for class factories [PR#54](https://github.com/JsonMapper/JsonMapper/pull/54) +- Add Attributes middleware [PR#55](https://github.com/JsonMapper/JsonMapper/pull/55) + +## [1.3.0] - 2020-08-11 +### Added +- Add support for mixed type [PR#39](https://github.com/JsonMapper/JsonMapper/pull/39) +### Changed +- Improved internal representation of scalar types, introducing ScalarType Enum class. [PR#34](https://github.com/JsonMapper/JsonMapper/pull/34) +## Fixed +- Fix mapping to a class from the same namespace when using PHP 7.4 namespace is prefixed twice. [PR#41](https://github.com/JsonMapper/JsonMapper/pull/41) + +## [1.2.0] - 2020-07-12 +### Added +- Introduce pop, unshift, shift, remove, removeByName methods to the JsonMapperInterface [PR#32](https://github.com/JsonMapper/JsonMapper/pull/32) +### Fixed +- Resolved several issues found by PHPStan [PR#29](https://github.com/JsonMapper/JsonMapper/pull/29) +- Properties marked as array are casted to enable object to array mapping [PR#36](https://github.com/JsonMapper/JsonMapper/pull/36) +### Changed +- Reduced a single used helper splitting into the core and into the doc block middleware. [PR#30](https://github.com/JsonMapper/JsonMapper/pull/30) + +## [1.1.0] - 2020-05-29 +### Added +- Support for arrays using square bracket notation (e.g. User[]) in DocBlockAnnotations middleware. (PR#27/#28) + +## [1.0.1] - 2020-05-04 +### Fixed +- Case conversion removing attribute when replacement key is same as the original key + +## [1.0.0] - 2020-04-23 +### Added +- New Debugger middleware to help debug the in between middleware +- Caching support to the DocBlockAnnotations and TypedProperties middleware + +## [0.3.0] - 2020-04-13 +### Added +- New FinalCallback middleware to invoke a final callback when mapping is completed. +- New CaseConversion middleware to handle difference between text notation in JSON and object + +## [0.2.1] - 2020-03-25 +### Fixed +- Correct badge urls in readme + +## [0.2.0] - 2020-03-25 +### Changed +- Changed top level namespace + +## [0.1.0] - 2020-03-25 +### Added +- Factory for easy creation of new JsonMapper instance +### Changed +- Replaced strategies with middleware to allow chaining of multiple middleware to increase configuration +- Readme was updated to reflect the usage and customizing of JsonMapper +- Updated license to MIT + +## [0.0.2] - 2020-03-22 +### Added +- Support custom classes with recursion +- Support for custom classes with imported namespace +- Support to map an array of objects +### Fixed +- Fixed missing coveralls dependency +- Cleanup strategies from duplication + +## [0.0.1] - 2020-03-15 +### Added +- Add PHP 7.4 typed properties based strategy +- Add DocBlock based strategy +- Add support for DateTime types +- Add typecasting +- Add value setting logic based on strategy diff --git a/vendor/json-mapper/json-mapper/CODE_OF_CONDUCT.md b/vendor/json-mapper/json-mapper/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..8dd3333e0 --- /dev/null +++ b/vendor/json-mapper/json-mapper/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at danny.vandersluijs@icloud.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/vendor/json-mapper/json-mapper/CONTRIBUTING.md b/vendor/json-mapper/json-mapper/CONTRIBUTING.md new file mode 100644 index 000000000..3b6d5731f --- /dev/null +++ b/vendor/json-mapper/json-mapper/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Contributing to JsonMapper + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +Since JsonMapper currently only is a small project (though it has great ambitions) we welcome any contribution +that helps us move forward. The project could use help with any of the following subjects: + +* *Middleware*: Any new ideas for middleware that would be helpful for the project users are welcomed. +* *Performance*: Mapping should be fast pointing out or resolving any bottlenecks in performance are very helpful. +* *Documentation*: The project should have an up-to-date version of documentation. Any help here is appreciated. + +## Ideal pull request +To avoid going back and forth the project `composer.json` was setup in such a way that it allows you to run +the same checks we do. These checks are: +```bash +composer unit-tests # For running the unit tests +composer integration-tests # For running the integration tests +composer phpcbf # For applying the correct code style (PSR-12) to the sources +composer phpcs # For scanning the sources for the correct style (PSR-12) being used +composer phpstan # For analysing the sources for potential bugs +``` diff --git a/vendor/json-mapper/json-mapper/LICENSE b/vendor/json-mapper/json-mapper/LICENSE new file mode 100644 index 000000000..291370ce8 --- /dev/null +++ b/vendor/json-mapper/json-mapper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Danny van der Sluijs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/json-mapper/json-mapper/README.md b/vendor/json-mapper/json-mapper/README.md new file mode 100644 index 000000000..75ef645a1 --- /dev/null +++ b/vendor/json-mapper/json-mapper/README.md @@ -0,0 +1,109 @@ + + + JsonMapper logo + + +--- +JsonMapper is a PHP library that allows you to map a JSON response to your PHP objects that are either annotated using doc blocks or use typed properties. +For more information see the project website: https://jsonmapper.net/ + +[![GitHub](https://img.shields.io/github/license/JsonMapper/JsonMapper)](https://choosealicense.com/licenses/mit/) +[![Packagist Version](https://img.shields.io/packagist/v/json-mapper/json-mapper)](https://packagist.org/packages/json-mapper/json-mapper) +![Packagist Downloads](https://img.shields.io/packagist/dm/json-mapper/json-mapper) +[![PHP from Packagist](https://img.shields.io/packagist/php-v/json-mapper/json-mapper)](#) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/JsonMapper/JsonMapper/build.yaml) +[![Coverage Status](https://coveralls.io/repos/github/JsonMapper/JsonMapper/badge.svg?branch=develop)](https://coveralls.io/github/JsonMapper/JsonMapper?branch=develop) + +# Why use JsonMapper +Continuously mapping your JSON responses to your own objects becomes tedious and is error-prone. Not mentioning the +tests that needs to be written for said mapping. + +JsonMapper has been build with the most common usages in mind. In order to allow for those edge cases which are not +supported by default, it can easily be extended as its core has been designed using middleware. + +JsonMapper supports the following features + * Case conversion + * Debugging + * DocBlock annotations + * Final callback + * Namespace resolving + * PHP 7.4 Types properties + +# Installing JsonMapper +The installation of JsonMapper can easily be done with [Composer](https://getcomposer.org) +```bash +$ composer require json-mapper/json-mapper +``` +The example shown above assumes that `composer` is on your `$PATH`. + +# How do I use JsonMapper +Given the following class definition +```php +namespace JsonMapper\Tests\Implementation; + +class SimpleObject +{ + /** @var string */ + private $name; + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } +} +``` +Combined with the following JsonMapper code as part of your application +```php +$mapper = (new \JsonMapper\JsonMapperFactory())->default(); +$object = new \JsonMapper\Tests\Implementation\SimpleObject(); + +$mapper->mapObject(json_decode('{ "name": "John Doe" }'), $object); + +var_dump($object); +``` +The above example will output: +```text +class JsonMapper\Tests\Implementation\SimpleObject#1 (1) { + private $name => + string(8) "John Doe" +} +``` + +# Customizing JsonMapper +Writing your own middleware has been made as easy as possible with an `AbstractMiddleware` that can be extended with the functionality +you need for your project. + +```php +use JsonMapper; + +$mapper = (new JsonMapper\JsonMapperFactory())->bestFit(); +$mapper->push(new class extends JsonMapper\Middleware\AbstractMiddleware { + public function handle( + \stdClass $json, + JsonMapper\Wrapper\ObjectWrapper $object, + JsonMapper\ValueObjects\PropertyMap $map, + JsonMapper\JsonMapperInterface $mapper + ): void { + /* Custom logic here */ + } +}); +``` + +# Contributing +Please refer to [CONTRIBUTING.md](https://github.com/JsonMapper/JsonMapper/blob/main/CONTRIBUTING.md) for information on how to contribute to JsonMapper. + +## List of Contributors +Thanks to everyone who has contributed to JsonMapper! You can find a detailed list of contributors of JsonMapper on [GitHub](https://github.com/JsonMapper/JsonMapper/graphs/contributors). + +## Sponsoring +[![JetBrains](https://jsonmapper.net/images/jetbrains-variant-3.png?)](https://www.jetbrains.com/?from=JsonMapper) + +This project is sponsored by JetBrains providing an open source license to continue building on JsonMapper without cost. + +# License +The MIT License (MIT). Please see [License File](https://github.com/JsonMapper/JsonMapper/blob/main/LICENSE) for more information. diff --git a/vendor/json-mapper/json-mapper/SECURITY.md b/vendor/json-mapper/json-mapper/SECURITY.md new file mode 100644 index 000000000..b94738395 --- /dev/null +++ b/vendor/json-mapper/json-mapper/SECURITY.md @@ -0,0 +1,17 @@ +# Security Policy +**PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).** + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 2.x | :white_check_mark: | +| 1.x | :x: | +| < 1.x | :x: | + +## Reporting a Vulnerability + +JsonMapper uses the GitHub feature to safely report vulnerabilities, please report them using https://github.com/JsonMapper/JsonMapper/security diff --git a/vendor/json-mapper/json-mapper/Version b/vendor/json-mapper/json-mapper/Version new file mode 100644 index 000000000..be5069562 --- /dev/null +++ b/vendor/json-mapper/json-mapper/Version @@ -0,0 +1 @@ +2.25.1 \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/composer.json b/vendor/json-mapper/json-mapper/composer.json new file mode 100644 index 000000000..5d624ebae --- /dev/null +++ b/vendor/json-mapper/json-mapper/composer.json @@ -0,0 +1,66 @@ +{ + "name": "json-mapper/json-mapper", + "description": "Map JSON structures to PHP classes", + "keywords": [ + "json", + "mapper", + "JsonMapper", + "middleware" + ], + "homepage": "https://jsonmapper.net", + "type": "library", + "license": "MIT", + "minimum-stability": "stable", + "require": { + "php": "^7.4 || ^8.0", + "ext-json": "*", + "myclabs/php-enum": "^1.7", + "nikic/php-parser": "^4.13 || ^5.0", + "phpdocumentor/reflection-docblock": "^5.6", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "psr/simple-cache": " ^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/polyfill-php73": "^1.18" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5 || ^7.0", + "php-coveralls/php-coveralls": "^2.4", + "phpstan/phpstan": "^0.12.14", + "phpstan/phpstan-phpunit": "^0.12.17", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0", + "vimeo/psalm": "^4.10 || ^5.0" + }, + "autoload": { + "psr-4": { + "JsonMapper\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "JsonMapper\\Tests\\": "tests/" + } + }, + "scripts": { + "phpcs": "phpcs src tests", + "phpcbf": "phpcbf src tests", + "phpstan": "phpstan analyse", + "psalm": "psalm", + "unit-tests": "phpunit --testsuite unit --testdox --coverage-clover=build/logs/clover-unit-tests.xml", + "integration-tests": "phpunit --testsuite integration --testdox --coverage-clover=build/logs/clover-integration-tests.xml", + "benchmarks": "phpbench run tests/benchmark --report=default" + }, + "suggest": { + "json-mapper/laravel-package": "Use JsonMapper directly with Laravel", + "json-mapper/symfony-bundle": "Use JsonMapper directly with Symfony" + }, + "support": { + "issues": "https://github.com/JsonMapper/JsonMapper/issues", + "source": "https://github.com/JsonMapper/JsonMapper", + "docs": "https://jsonmapper.net" + }, + "config": { + "sort-packages": true + } +} diff --git a/vendor/json-mapper/json-mapper/phpbench.json b/vendor/json-mapper/json-mapper/phpbench.json new file mode 100644 index 000000000..6bdeae9db --- /dev/null +++ b/vendor/json-mapper/json-mapper/phpbench.json @@ -0,0 +1,3 @@ +{ + "runner.bootstrap": "vendor/autoload.php" +} \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/phpcs.xml.dist b/vendor/json-mapper/json-mapper/phpcs.xml.dist new file mode 100644 index 000000000..3c6fed930 --- /dev/null +++ b/vendor/json-mapper/json-mapper/phpcs.xml.dist @@ -0,0 +1,26 @@ + + + JsonMapper Coding standard + + . + + + */vendor/* + + + + + + + + + + + + + + + tests/Unit/Middleware/CaseConversionTest\.php + tests/Unit/ValueObjects/PropertyMapTest\.php + + \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/phpstan.neon.dist b/vendor/json-mapper/json-mapper/phpstan.neon.dist new file mode 100644 index 000000000..f81ce62b6 --- /dev/null +++ b/vendor/json-mapper/json-mapper/phpstan.neon.dist @@ -0,0 +1,11 @@ +parameters: + level: max + checkMissingIterableValueType: false + checkGenericClassInNonGenericObjectType: false + treatPhpDocTypesAsCertain: false + paths: + - src + - tests +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon \ No newline at end of file diff --git a/vendor/json-mapper/json-mapper/phpunit.xml.dist b/vendor/json-mapper/json-mapper/phpunit.xml.dist new file mode 100644 index 000000000..4512893b1 --- /dev/null +++ b/vendor/json-mapper/json-mapper/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + tests/Unit + + + tests/Integration + + + + + + src + + + + + + + diff --git a/vendor/json-mapper/json-mapper/psalm.xml b/vendor/json-mapper/json-mapper/psalm.xml new file mode 100644 index 000000000..30258a709 --- /dev/null +++ b/vendor/json-mapper/json-mapper/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/vendor/json-mapper/json-mapper/src/Builders/PropertyBuilder.php b/vendor/json-mapper/json-mapper/src/Builders/PropertyBuilder.php new file mode 100644 index 000000000..2611a6d23 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Builders/PropertyBuilder.php @@ -0,0 +1,87 @@ +name, + $this->visibility, + $this->isNullable, + ...$this->types + ); + } + + public function setName(string $name): self + { + $this->name = $name; + return $this; + } + + public function setTypes(PropertyType ...$types): self + { + $this->types = $types; + return $this; + } + + public function addType(string $type, ArrayInformation $arrayInformation): self + { + $this->types[] = new PropertyType($type, $arrayInformation); + return $this; + } + + public function addTypes(PropertyType ...$types): self + { + $this->types = array_merge($this->types, $types); + return $this; + } + + public function setIsNullable(bool $isNullable): self + { + $this->isNullable = $isNullable; + return $this; + } + + public function setVisibility(Visibility $visibility): self + { + $this->visibility = $visibility; + return $this; + } + + public function hasAnyType(): bool + { + return count($this->types) !== 0; + } + + public function getTypes(): array + { + return $this->types; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Builders/PropertyMapperBuilder.php b/vendor/json-mapper/json-mapper/src/Builders/PropertyMapperBuilder.php new file mode 100644 index 000000000..0b2f42712 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Builders/PropertyMapperBuilder.php @@ -0,0 +1,50 @@ +classFactoryRegistry, $this->nonInstantiableTypeResolver, $this->scalarCaster); + } + + public function withClassFactoryRegistry(FactoryRegistry $classFactoryRegistry): PropertyMapperBuilder + { + $this->classFactoryRegistry = $classFactoryRegistry; + + return $this; + } + + public function withNonInstantiableTypeResolver(FactoryRegistry $nonInstantiableTypeResolver): PropertyMapperBuilder + { + $this->nonInstantiableTypeResolver = $nonInstantiableTypeResolver; + + return $this; + } + + public function withScalarCaster(IScalarCaster $scalarCaster): PropertyMapperBuilder + { + $this->scalarCaster = $scalarCaster; + + return $this; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Cache/ArrayCache.php b/vendor/json-mapper/json-mapper/src/Cache/ArrayCache.php new file mode 100644 index 000000000..10940a299 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Cache/ArrayCache.php @@ -0,0 +1,15 @@ +middleware = $middleware; + $this->name = $name; + } + + public function getName(): string + { + return $this->name; + } + + public function getMiddleware(): callable + { + return $this->middleware; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Enums/ScalarType.php b/vendor/json-mapper/json-mapper/src/Enums/ScalarType.php new file mode 100644 index 000000000..d564d0a44 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Enums/ScalarType.php @@ -0,0 +1,31 @@ +isPublic()) { + return self::PUBLIC(); + } + if ($property->isProtected()) { + return self::PROTECTED(); + } + return self::PRIVATE(); + } +} diff --git a/vendor/json-mapper/json-mapper/src/Exception/BuilderException.php b/vendor/json-mapper/json-mapper/src/Exception/BuilderException.php new file mode 100644 index 000000000..526981375 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Exception/BuilderException.php @@ -0,0 +1,18 @@ +addFactory(\DateTime::class, static function (string $value) { + return new \DateTime($value); + }); + $factory->addFactory(\DateTimeImmutable::class, static function (string $value) { + return new \DateTimeImmutable($value); + }); + $factory->addFactory(\stdClass::class, static function ($value) { + return (object) $value; + }); + + return $factory; + } + + public function addFactory(string $className, callable $factory): self + { + if ($this->hasFactory($className)) { + throw ClassFactoryException::forDuplicateClassname($className); + } + + $this->factories[$this->sanitiseClassName($className)] = $factory; + + return $this; + } + + public function hasFactory(string $className): bool + { + return \array_key_exists($this->sanitiseClassName($className), $this->factories); + } + + /** + * @param mixed $params + * @return mixed + */ + public function create(string $className, $params) + { + if (!$this->hasFactory($className)) { + throw ClassFactoryException::forMissingClassname($className); + } + + $factory = $this->factories[$this->sanitiseClassName($className)]; + + return $factory($params); + } + + private function sanitiseClassName(string $className): string + { + /* Erase leading slash as ::class doesn't contain leading slash */ + if (\strpos($className, '\\') === 0) { + $className = \substr($className, 1); + } + + return $className; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Handler/PropertyMapper.php b/vendor/json-mapper/json-mapper/src/Handler/PropertyMapper.php new file mode 100644 index 000000000..251bdf599 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Handler/PropertyMapper.php @@ -0,0 +1,112 @@ +classFactoryRegistry = $classFactoryRegistry; + $this->valueFactory = new ValueFactory($casterHelper, $classFactoryRegistry, $nonInstantiableTypeResolver); + } + + public function __invoke( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + // If the type we are mapping has a last minute factory use it. + if ($this->classFactoryRegistry->hasFactory($object->getName())) { + $result = $this->classFactoryRegistry->create($object->getName(), $json); + + $object->setObject($result); + return; + } + + $values = (array) $json; + foreach ($values as $key => $value) { + if (! $propertyMap->hasProperty($key)) { + continue; + } + + $property = $propertyMap->getProperty($key); + + if (! $property->isNullable() && \is_null($value)) { + throw new \RuntimeException( + "Null provided in json where {$object->getName()}::{$key} doesn't allow null value" + ); + } + + if ($property->isNullable() && \is_null($value)) { + $this->setValue($object, $property, null); + continue; + } + + $value = $this->valueFactory->build($mapper, $property, $value); + $this->setValue($object, $property, $value); + } + } + + /** + * @param mixed $value + */ + private function setValue(ObjectWrapper $object, Property $propertyInfo, $value): void + { + if ($propertyInfo->getVisibility()->equals(Visibility::PUBLIC())) { + $object->getObject()->{$propertyInfo->getName()} = $value; + return; + } + + $methodName = 'set' . \ucfirst($propertyInfo->getName()); + if (\method_exists($object->getObject(), $methodName)) { + $method = new \ReflectionMethod($object->getObject(), $methodName); + $parameters = $method->getParameters(); + + if (\is_array($value) && \count($parameters) === 1 && $parameters[0]->isVariadic()) { + $object->getObject()->$methodName(...$value); + return; + } + + $object->getObject()->$methodName($value); + return; + } + + throw new \RuntimeException( + "{$object->getName()}::{$propertyInfo->getName()} is non-public and no setter method was found" + ); + } +} diff --git a/vendor/json-mapper/json-mapper/src/Handler/ValueFactory.php b/vendor/json-mapper/json-mapper/src/Handler/ValueFactory.php new file mode 100644 index 000000000..1340fb535 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Handler/ValueFactory.php @@ -0,0 +1,363 @@ +scalarCaster = $scalarCaster; + $this->classFactoryRegistry = $classFactoryRegistry; + $this->nonInstantiableTypeResolver = $nonInstantiableTypeResolver; + } + + /** + * @param mixed $value + * @return mixed + */ + public function build(JsonMapperInterface $mapper, Property $property, $value) + { + if (\is_null($value) && $property->isNullable()) { + return null; + } + + // For union types, loop through and see if value is a match with the type + if (\count($property->getPropertyTypes()) > 1) { + foreach ($property->getPropertyTypes() as $type) { + if (\is_array($value) && $type->isArray() && count($value) === 0) { + return []; + } + + if (\is_array($value) && $type->isArray()) { + $copy = $value; + $firstValue = \array_shift($copy); + + /* Array of scalar values */ + if ($this->propertyTypeAndValueTypeAreScalarAndSameType($type, $firstValue)) { + $scalarType = new ScalarType($type->getType()); + return \array_map(function ($v) use ($scalarType) { + return $this->scalarCaster->cast($scalarType, $v); + }, $value); + } + + if (PHP_VERSION_ID >= 80100 && enum_exists($type->getType())) { + return $this->mapToEnum($type, $value); + } + + // Array of registered class @todo how do you know it was the correct type? + if ($this->classFactoryRegistry->hasFactory($type->getType())) { + return $this->mapToObjectsUsingFactory($type, $value); + } + + // Array of existing class @todo how do you know it was the correct type? + if ((class_exists($type->getType()) || interface_exists($type->getType()))) { + return $this->mapToObjects($type, $value, $mapper); + } + + continue; + } + + // If the type we are mapping has a last minute factory use it. + if ($this->classFactoryRegistry->hasFactory($type->getType())) { + return $this->mapToObjectsUsingFactory($type, $value); + } + + // Single scalar value + if ($this->propertyTypeAndValueTypeAreScalarAndSameType($type, $value)) { + return $this->scalarCaster->cast(new ScalarType($type->getType()), $value); + } + + if (PHP_VERSION_ID >= 80100 && enum_exists($type->getType())) { + return $this->mapToEnum($type, $value); + } + + // Single existing class @todo how do you know it was the correct type? + if (\class_exists($type->getType())) { + return $this->mapToSingleObject($type->getType(), $value, $mapper); + } + } + } + + // No match was found (or there was only one option) lets assume the first is the right one. + $types = $property->getPropertyTypes(); + $type = \array_shift($types); + + if ($type === null) { + // Return the value as is as there is no type info. + return $value; + } + + if (ScalarType::isValid($type->getType())) { + return $this->mapToScalarValues($type, $value); + } + + if (PHP_VERSION_ID >= 80100 && enum_exists($type->getType())) { + return $this->mapToEnum($type, $value); + } + + if ($this->classFactoryRegistry->hasFactory($type->getType())) { + return $this->mapToObjectsUsingFactory($type, $value); + } + + if ((class_exists($type->getType()) || interface_exists($type->getType()))) { + return $this->mapToObjects($type, $value, $mapper); + } + + throw new \Exception("Unable to map to {$type->getType()}"); + } + + /** + * @param mixed $value + * @psalm-assert-if-true scalar $value + */ + private function propertyTypeAndValueTypeAreScalarAndSameType(PropertyType $type, $value): bool + { + if (! \is_scalar($value) || ! ScalarType::isValid($type->getType())) { + return false; + } + + $valueType = \gettype($value); + if ($valueType === 'double') { + $valueType = 'float'; + } + + return $type->getType() === $valueType; + } + + private function mapToEnum(PropertyType $type, $value) + { + if ($type->isMultiDimensionalArray()) { + return $this->recursiveMapToArrayOfEnum($type->getType(), $value); + } + if ($type->isArray()) { + return $this->mapToArrayOfEnum($type->getType(), $value); + } + return $this->mapToSingleEnum($type->getType(), $value); + } + + /** + * @template T + * @psalm-param class-string $type + * @param $value mixed + * @return array + */ + private function mapToArrayOfEnum(string $type, $value): array + { + return \array_map(function ($v) use ($type) { + return $this->mapToSingleEnum($type, $v); + }, (array) $value); + } + + /** + * @template T + * @psalm-param class-string $type + * @param mixed $value + */ + private function recursiveMapToArrayOfEnum(string $type, $value): array + { + return \array_map(function ($v) use ($type) { + if (is_array($v)) { + return $this->recursiveMapToArrayOfEnum($type, $v); + } + + return $this->mapToSingleEnum($type, $v); + }, (array) $value); + } + + /** + * @template T + * @psalm-param class-string $type + * @param mixed $value + * @return T + */ + private function mapToSingleEnum(string $type, $value) + { + return call_user_func("{$type}::from", $value); + } + + private function mapToScalarValues(PropertyType $type, $value) + { + if ($type->isMultiDimensionalArray()) { + return $this->recursiveMapToArrayOfScalarValue($type->getType(), $value); + } + if ($type->isArray()) { + return $this->mapToArrayOfScalarValue($type->getType(), $value); + } + return $this->mapToSingleScalarValue($type->getType(), $value); + } + + /** + * @param mixed $value + * @return string|bool|int|float + */ + private function mapToSingleScalarValue(string $type, $value) + { + $scalar = new ScalarType($type); + + return $this->scalarCaster->cast($scalar, $value); + } + + /** + * @param mixed $value + * @return array + */ + private function mapToArrayOfScalarValue(string $type, $value): array + { + $scalar = new ScalarType($type); + return \array_map(function ($v) use ($type, $scalar) { + return $this->scalarCaster->cast($scalar, $v); + }, (array) $value); + } + + /** + * @param mixed $value + * @return array + */ + private function recursiveMapToArrayOfScalarValue(string $type, $value): array + { + $scalar = new ScalarType($type); + return \array_map(function ($v) use ($type, $scalar) { + if (is_array($v)) { + return $this->recursiveMapToArrayOfScalarValue($type, $v); + } + + return $this->scalarCaster->cast($scalar, $v); + }, (array) $value); + } + + private function mapToObjectsUsingFactory(PropertyType $type, $value) + { + if ($type->isMultiDimensionalArray()) { + return $this->recursiveMapToArrayOfObjectsUsingFactory($type->getType(), $value); + } + if ($type->isArray()) { + return $this->mapToArrayOfObjectsUsingFactory($type->getType(), $value); + } + return $this->mapToSingleObjectUsingFactory($type->getType(), $value); + } + + /** + * @param mixed $value + * @return mixed + */ + private function mapToSingleObjectUsingFactory(string $type, $value) + { + return $this->classFactoryRegistry->create($type, $value); + } + + private function mapToArrayOfObjectsUsingFactory(string $type, $value): array + { + return \array_map(function ($v) use ($type) { + return $this->mapToSingleObjectUsingFactory($type, $v); + }, (array) $value); + } + + private function recursiveMapToArrayOfObjectsUsingFactory(string $type, $value): array + { + return \array_map(function ($v) use ($type) { + if (is_array($v)) { + return $this->recursiveMapToArrayOfObjectsUsingFactory($type, $v); + } + + return $this->mapToSingleObjectUsingFactory($type, $v); + }, (array) $value); + } + + private function mapToObjects(PropertyType $type, $value, JsonMapperInterface $mapper) + { + if ($type->isMultiDimensionalArray()) { + return $this->recursiveMapToArrayOfObjects($type->getType(), $value, $mapper); + } + if ($type->isArray()) { + return $this->mapToArrayOfObjects($type->getType(), $value, $mapper); + } + return $this->mapToSingleObject($type->getType(), $value, $mapper); + } + + /** + * @template T + * @psalm-param class-string $type + * @param mixed $value + * @return array + */ + private function recursiveMapToArrayOfObjects(string $type, $value, JsonMapperInterface $mapper): array + { + return \array_map(function ($v) use ($type, $mapper) { + if (is_array($v)) { + return $this->recursiveMapToArrayOfObjects($type, $v, $mapper); + } + + return $this->mapToSingleObject($type, $v, $mapper); + }, (array) $value); + } + + /** + * @template T + * @psalm-param class-string $type + * @param mixed $value + * @return array + */ + private function mapToArrayOfObjects(string $type, $value, JsonMapperInterface $mapper): array + { + return \array_map(function ($v) use ($type, $mapper) { + return $this->mapToSingleObject($type, $v, $mapper); + }, (array) $value); + } + + /** + * @template T + * @psalm-param class-string $type + * @param mixed $value + * @return T + */ + private function mapToSingleObject(string $type, $value, JsonMapperInterface $mapper) + { + $reflectionType = new \ReflectionClass($type); + if (!$reflectionType->isInstantiable()) { + return $this->resolveUnInstantiableType($type, $value, $mapper); + } + + return $mapper->mapToClass($value, $type); + } + + /** + * @template T + * @psalm-param class-string $type + * @param mixed $value + * @return T + */ + private function resolveUnInstantiableType(string $type, $value, JsonMapperInterface $mapper) + { + try { + $instance = $this->nonInstantiableTypeResolver->create($type, $value); + $mapper->mapObject($value, $instance); + return $instance; + } catch (ClassFactoryException $e) { + throw new \RuntimeException( + "Unable to resolve un-instantiable {$type} as no factory was registered", + 0, + $e + ); + } + } +} diff --git a/vendor/json-mapper/json-mapper/src/Helpers/ClassHelper.php b/vendor/json-mapper/json-mapper/src/Helpers/ClassHelper.php new file mode 100644 index 000000000..8e8a1cae0 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Helpers/ClassHelper.php @@ -0,0 +1,31 @@ +isInternal(); + } + + public static function isCustom(string $type): bool + { + if ($type === 'mixed' || ScalarType::isValid($type) || ! \class_exists($type)) { + return false; + } + + $reflection = new ReflectionClass($type); + return !$reflection->isInternal(); + } +} diff --git a/vendor/json-mapper/json-mapper/src/Helpers/IScalarCaster.php b/vendor/json-mapper/json-mapper/src/Helpers/IScalarCaster.php new file mode 100644 index 000000000..113675427 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Helpers/IScalarCaster.php @@ -0,0 +1,13 @@ +hasAlias() && $import->getAlias() === $type) { + return true; + } + + return $nameSpacedType === \substr($import->getImport(), -strlen($nameSpacedType)); + } + ); + + $firstMatch = array_shift($matches); + if (! \is_null($firstMatch)) { + return $firstMatch->getImport(); + } + + if (class_exists($contextNamespace . '\\' . $type)) { + return $contextNamespace . '\\' . $type; + } + + return $type; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Helpers/ScalarCaster.php b/vendor/json-mapper/json-mapper/src/Helpers/ScalarCaster.php new file mode 100644 index 000000000..57e9310b0 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Helpers/ScalarCaster.php @@ -0,0 +1,31 @@ +equals(ScalarType::MIXED())) { + return $value; + } + if ($scalarType->equals(ScalarType::STRING())) { + return (string) $value; + } + if ($scalarType->equals(ScalarType::BOOLEAN()) || $scalarType->equals(ScalarType::BOOL())) { + return (bool) $value; + } + if ($scalarType->equals(ScalarType::INTEGER()) || $scalarType->equals(ScalarType::INT())) { + return (int) $value; + } + if ($scalarType->equals(ScalarType::DOUBLE()) || $scalarType->equals(ScalarType::FLOAT())) { + return (float) $value; + } + + throw new \LogicException("Missing {$scalarType->getValue()} in cast method"); + } +} diff --git a/vendor/json-mapper/json-mapper/src/Helpers/StrictScalarCaster.php b/vendor/json-mapper/json-mapper/src/Helpers/StrictScalarCaster.php new file mode 100644 index 000000000..71a6da182 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Helpers/StrictScalarCaster.php @@ -0,0 +1,40 @@ +equals(ScalarType::STRING())) { + throw new \Exception("Expected type string, type {$type} given"); + } + if ( + ! is_bool($value) && + ($scalarType->equals(ScalarType::BOOLEAN()) || $scalarType->equals(ScalarType::BOOL())) + ) { + throw new \Exception("Expected type string, type {$type} given"); + } + if ( + ! is_int($value) && + ($scalarType->equals(ScalarType::INTEGER()) || $scalarType->equals(ScalarType::INT())) + ) { + throw new \Exception("Expected type string, type {$type} given"); + } + if ( + ! is_float($value) + && ($scalarType->equals(ScalarType::DOUBLE()) || $scalarType->equals(ScalarType::FLOAT())) + ) { + throw new \Exception("Expected type string, type {$type} given"); + } + + return $value; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Helpers/UseStatementHelper.php b/vendor/json-mapper/json-mapper/src/Helpers/UseStatementHelper.php new file mode 100644 index 000000000..02b3efb40 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Helpers/UseStatementHelper.php @@ -0,0 +1,72 @@ +isUserDefined()) { + return []; + } + + $filename = $class->getFileName(); + if ($filename === false || \substr($filename, -13) === self::$evaldCodeFileNameEnding) { + throw new \RuntimeException("Class {$class->getName()} has no filename available"); + } + + if ($class->getParentClass() === false) { + return self::getImportsForFileName($filename); + } + + return array_unique( + array_merge(self::getImportsForFileName($filename), self::getImports($class->getParentClass())), + SORT_REGULAR + ); + } + + /** @return Import[] */ + private static function getImportsForFileName(string $filename): array + { + if (! \is_readable($filename)) { + throw new \RuntimeException("Unable to read {$filename}"); + } + + $contents = \file_get_contents($filename); + if ($contents === false) { + throw new \RuntimeException("Unable to read {$filename}"); + } + + $parser = method_exists(ParserFactory::class, 'createForNewestSupportedVersion') + ? (new ParserFactory())->createForNewestSupportedVersion() + : (new ParserFactory())->create(ParserFactory::PREFER_PHP7); + + try { + $ast = $parser->parse($contents); + if (\is_null($ast)) { + throw new PhpFileParseException("Failed to parse {$filename}"); + } + } catch (\Throwable $e) { + throw new PhpFileParseException("Failed to parse {$filename}"); + } + + $traverser = new NodeTraverser(); + $visitor = new UseNodeVisitor(); + $traverser->addVisitor($visitor); + $traverser->traverse($ast); + + return $visitor->getImports(); + } +} diff --git a/vendor/json-mapper/json-mapper/src/JsonMapper.php b/vendor/json-mapper/json-mapper/src/JsonMapper.php new file mode 100644 index 000000000..d7e2695ab --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/JsonMapper.php @@ -0,0 +1,238 @@ +propertyMapper = $propertyMapper; + } + + public function setPropertyMapper(callable $propertyMapper): JsonMapperInterface + { + $this->propertyMapper = $propertyMapper; + $this->cached = null; + + return $this; + } + + public function push(callable $middleware, string $name = ''): JsonMapperInterface + { + $this->stack[] = new NamedMiddleware($middleware, $name); + $this->cached = null; + + return $this; + } + + public function pop(): JsonMapperInterface + { + \array_pop($this->stack); + $this->cached = null; + + return $this; + } + + public function unshift(callable $middleware, string $name = ''): JsonMapperInterface + { + \array_unshift($this->stack, new NamedMiddleware($middleware, $name)); + $this->cached = null; + + return $this; + } + + public function shift(): JsonMapperInterface + { + \array_shift($this->stack); + $this->cached = null; + + return $this; + } + + public function remove(callable $remove): JsonMapperInterface + { + $this->stack = \array_values(\array_filter( + $this->stack, + static function (NamedMiddleware $namedMiddleware) use ($remove) { + return $namedMiddleware->getMiddleware() !== $remove; + } + )); + $this->cached = null; + + return $this; + } + + public function removeByName(string $remove): JsonMapperInterface + { + $this->stack = \array_values(\array_filter( + $this->stack, + static function (NamedMiddleware $namedMiddleware) use ($remove) { + return $namedMiddleware->getName() !== $remove; + } + )); + $this->cached = null; + + return $this; + } + + public function mapToClass(\stdClass $json, string $class) + { + if (! \class_exists($class)) { + throw TypeError::forArgument(__METHOD__, 'class-string', $class, 2, '$class'); + } + + $propertyMap = new PropertyMap(); + + $handler = $this->resolve(); + $wrapper = new ObjectWrapper(null, $class); + $handler($json, $wrapper, $propertyMap, $this); + + return $wrapper->getObject(); + } + + public function mapObject(\stdClass $json, $object) + { + if (! \is_object($object)) { + throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object'); + } + + $propertyMap = new PropertyMap(); + + $handler = $this->resolve(); + $handler($json, new ObjectWrapper($object), $propertyMap, $this); + + return $object; + } + + public function mapArray(array $json, $object): array + { + if (! \is_object($object)) { + throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object'); + } + + $results = []; + foreach ($json as $key => $value) { + $results[$key] = clone $object; + $this->mapObject($value, $results[$key]); + } + + return $results; + } + + public function mapToClassArray(array $json, string $class): array + { + if (! \class_exists($class)) { + throw TypeError::forArgument(__METHOD__, 'class-string', $class, 2, '$class'); + } + + return array_map( + function (\stdClass $value) use ($class) { + return $this->mapToClass($value, $class); + }, + $json + ); + } + + public function mapToClassFromString(string $json, string $class) + { + if (! \class_exists($class)) { + throw TypeError::forArgument(__METHOD__, 'class-string', $class, 2, '$class'); + } + + $data = $this->decodeJsonString($json); + if (! $data instanceof \stdClass) { + throw new \RuntimeException('Provided string is not a json encoded object'); + } + + return $this->mapToClass($data, $class); + } + + public function mapObjectFromString(string $json, $object) + { + if (! \is_object($object)) { + throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object'); + } + + $data = $this->decodeJsonString($json); + + if (! $data instanceof \stdClass) { + throw new \RuntimeException('Provided string is not a json encoded object'); + } + + $this->mapObject($data, $object); + + return $object; + } + + public function mapArrayFromString(string $json, $object): array + { + if (! \is_object($object)) { + throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object'); + } + + $data = $this->decodeJsonString($json); + + if (! \is_array($data)) { + throw new \RuntimeException('Provided string is not a json encoded array'); + } + + $results = []; + foreach ($data as $key => $value) { + $results[$key] = clone $object; + $this->mapObject($value, $results[$key]); + } + + return $results; + } + + public function mapToClassArrayFromString(string $json, string $class): array + { + if (! \class_exists($class)) { + throw TypeError::forArgument(__METHOD__, 'class-string', $class, 2, '$class'); + } + + $data = $this->decodeJsonString($json); + if (! \is_array($data)) { + throw new \RuntimeException('Provided string is not a json encoded array'); + } + + return $this->mapToClassArray($data, $class); + } + + /** @return \stdClass|\stdClass[] */ + private function decodeJsonString(string $json) + { + return \json_decode($json, false, 512, JSON_THROW_ON_ERROR); + } + + private function resolve(): callable + { + if (!$this->cached) { + $prev = $this->propertyMapper; + if (\is_null($prev)) { + throw new \RuntimeException('Property mapper has not been defined'); + } + foreach (\array_reverse($this->stack) as $namedMiddleware) { + $prev = $namedMiddleware->getMiddleware()($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } +} diff --git a/vendor/json-mapper/json-mapper/src/JsonMapperBuilder.php b/vendor/json-mapper/json-mapper/src/JsonMapperBuilder.php new file mode 100644 index 000000000..eee8602f7 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/JsonMapperBuilder.php @@ -0,0 +1,168 @@ + + */ + protected $jsonMapperClassName = JsonMapper::class; + /** @var PropertyMapper */ + protected $propertyMapper; + /** @var CacheInterface */ + protected $defaultCache; + /** @var NamedMiddleware[] */ + protected $namedMiddleware = []; + + public static function new(): JsonMapperBuilder + { + return new JsonMapperBuilder(); + } + + public function __construct() + { + $this->withPropertyMapper(new PropertyMapper()) + ->withDefaultCache(new ArrayCache()); + } + + public function build(): JsonMapperInterface + { + if (empty($this->namedMiddleware)) { + throw BuilderException::forBuildingWithoutMiddleware(); + } + + /** @var JsonMapperInterface $mapper */ + $mapper = new $this->jsonMapperClassName(); + $mapper->setPropertyMapper($this->propertyMapper); + foreach ($this->namedMiddleware as $namedMiddleware) { + $mapper->push($namedMiddleware->getMiddleware(), $namedMiddleware->getName()); + } + + return $mapper; + } + + /** @psalm-param class-string $jsonMapperClassName */ + public function withJsonMapperClassName(string $jsonMapperClassName): JsonMapperBuilder + { + $reflectedClass = new \ReflectionClass($jsonMapperClassName); + if (!$reflectedClass->implementsInterface(JsonMapperInterface::class)) { + throw BuilderException::invalidJsonMapperClassName($jsonMapperClassName); + } + + $this->jsonMapperClassName = $jsonMapperClassName; + + return $this; + } + + public function withPropertyMapper(PropertyMapper $propertyMapper): JsonMapperBuilder + { + $this->propertyMapper = $propertyMapper; + + return $this; + } + + public function withDefaultCache(CacheInterface $defaultCache): JsonMapperBuilder + { + $this->defaultCache = $defaultCache; + + return $this; + } + + public function withDocBlockAnnotationsMiddleware(?CacheInterface $cache = null): JsonMapperBuilder + { + return $this->withMiddleware( + new DocBlockAnnotations($cache ?: $this->defaultCache), + DocBlockAnnotations::class + ); + } + + public function withNamespaceResolverMiddleware(?CacheInterface $cache = null): JsonMapperBuilder + { + return $this->withMiddleware( + new NamespaceResolver($cache ?: $this->defaultCache), + NamespaceResolver::class + ); + } + + public function withTypedPropertiesMiddleware(?CacheInterface $cache = null): JsonMapperBuilder + { + return $this->withMiddleware( + new TypedProperties($cache ?: $this->defaultCache), + TypedProperties::class + ); + } + + public function withAttributesMiddleware(): JsonMapperBuilder + { + return $this->withMiddleware(new Attributes(), Attributes::class); + } + + public function withRenameMiddleware(Mapping ...$mapping): JsonMapperBuilder + { + return $this->withMiddleware(new Rename(...$mapping), Rename::class); + } + + public function withCaseConversionMiddleware( + TextNotation $searchSeparator, + TextNotation $replacementSeparator + ): JsonMapperBuilder { + return $this->withMiddleware( + new CaseConversion($searchSeparator, $replacementSeparator), + CaseConversion::class + ); + } + + public function withDebuggerMiddleware(LoggerInterface $logger): JsonMapperBuilder + { + return $this->withMiddleware(new Debugger($logger), Debugger::class); + } + + public function withFinalCallbackMiddleware( + callable $callback, + bool $onlyApplyCallBackOnTopLevel = true + ): JsonMapperBuilder { + return $this->withMiddleware(new FinalCallback($callback, $onlyApplyCallBackOnTopLevel), FinalCallback::class); + } + + public function withObjectConstructorMiddleware(FactoryRegistry $factoryRegistry): JsonMapperBuilder + { + // @TODO Add the registry from the builder context? + return $this->withMiddleware( + new Constructor($factoryRegistry), // How to get logical fallback the factory registry + Constructor::class + ); + } + + public function withMiddleware(callable $middleware, ?string $name = null): JsonMapperBuilder + { + $fallbackName = is_object($middleware) ? get_class($middleware) : ''; + $this->namedMiddleware[] = new NamedMiddleware($middleware, $name ?: $fallbackName); + + return $this; + } +} diff --git a/vendor/json-mapper/json-mapper/src/JsonMapperFactory.php b/vendor/json-mapper/json-mapper/src/JsonMapperFactory.php new file mode 100644 index 000000000..5b867991f --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/JsonMapperFactory.php @@ -0,0 +1,47 @@ +builder = $builder ?? JsonMapperBuilder::new(); + } + + public function create(?PropertyMapper $propertyMapper = null, MiddlewareInterface ...$handlers): JsonMapperInterface + { + $builder = clone ($this->builder); + $builder->withPropertyMapper($propertyMapper ?? new PropertyMapper()); + foreach ($handlers as $handler) { + $builder->withMiddleware($handler); + } + + return $builder->build(); + } + + public function default(): JsonMapperInterface + { + $builder = clone ($this->builder); + return $builder->withDocBlockAnnotationsMiddleware() + ->withNamespaceResolverMiddleware() + ->build(); + } + + public function bestFit(): JsonMapperInterface + { + $builder = clone ($this->builder); + return $builder->withDocBlockAnnotationsMiddleware() + ->withTypedPropertiesMiddleware() + ->withNamespaceResolverMiddleware() + ->build(); + } +} diff --git a/vendor/json-mapper/json-mapper/src/JsonMapperInterface.php b/vendor/json-mapper/json-mapper/src/JsonMapperInterface.php new file mode 100644 index 000000000..8dd123b3d --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/JsonMapperInterface.php @@ -0,0 +1,78 @@ + $class + * @return T + */ + public function mapToClass(\stdClass $json, string $class); + + /** + * @template T of object + * @psalm-param T $object + * @return array + */ + public function mapArray(array $json, $object): array; + + /** + * @template T of object + * @psalm-param class-string $class + * @return array + */ + public function mapToClassArray(array $json, string $class): array; + + /** + * @template T of object + * @psalm-param T $object + * @return T + */ + public function mapObjectFromString(string $json, $object); + + /** + * @template T of object + * @psalm-param class-string $class + * @return T + */ + public function mapToClassFromString(string $json, string $class); + + /** + * @template T of object + * @psalm-param T $object + * @return array + */ + public function mapArrayFromString(string $json, $object): array; + + /** + * @template T of object + * @psalm-param class-string $class + * @return array + */ + public function mapToClassArrayFromString(string $json, string $class): array; +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/AbstractMiddleware.php b/vendor/json-mapper/json-mapper/src/Middleware/AbstractMiddleware.php new file mode 100644 index 000000000..3d75e3103 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/AbstractMiddleware.php @@ -0,0 +1,35 @@ +handle($json, $object, $map, $mapper); + + $handler($json, $object, $map, $mapper); + }; + } + + abstract public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void; +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/Attributes/Attributes.php b/vendor/json-mapper/json-mapper/src/Middleware/Attributes/Attributes.php new file mode 100644 index 000000000..cae833969 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/Attributes/Attributes.php @@ -0,0 +1,40 @@ +getReflectedObject()->getProperties() as $property) { + $attributes = $property->getAttributes(MapFrom::class); + + foreach ($attributes as $attribute) { + /** @var MapFrom $mapFrom */ + $mapFrom = $attribute->newInstance(); + $source = $mapFrom->source; + $target = $property->name; + + if ($source === $target) { + continue; + } + + if (isset($json->$source)) { + $json->$target = $json->$source; + unset($json->$source); + } + } + } + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/Attributes/MapFrom.php b/vendor/json-mapper/json-mapper/src/Middleware/Attributes/MapFrom.php new file mode 100644 index 000000000..18cdf388d --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/Attributes/MapFrom.php @@ -0,0 +1,19 @@ +source = $source; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/CaseConversion.php b/vendor/json-mapper/json-mapper/src/Middleware/CaseConversion.php new file mode 100644 index 000000000..304d02d00 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/CaseConversion.php @@ -0,0 +1,153 @@ +searchSeparator = $searchSeparator; + $this->replacementSeparator = $replacementSeparator; + $this->applyRecursive = $applyRecursive; + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + if ($this->searchSeparator->equals($this->replacementSeparator)) { + return; + } + + $this->replace($json); + } + + /** @param \stdClass|array $json */ + private function replace($json): void + { + if (is_array($json)) { + array_walk( + $json, + function ($value) { + $this->replace($value); + } + ); + return; + } + + $keys = \array_keys((array) $json); + foreach ($keys as $key) { + if (is_int($key)) { + break; + } + $replacementKey = $this->getReplacementKey($key); + + if ($replacementKey === $key) { + continue; + } + + $value = $json->$key; + $json->$replacementKey = $value; + unset($json->$key); + + if ($this->applyRecursive && (is_array($value) || $value instanceof \stdClass)) { + $this->replace($value); + } + } + } + + private function getReplacementKey(string $key): string + { + switch ($this->searchSeparator) { + case TextNotation::CAMEL_CASE(): + return $this->replaceFromCamelCase($key); + case TextNotation::STUDLY_CAPS(): + return $this->replaceFromStudlyCaps($key); + case TextNotation::UNDERSCORE(): + return $this->replaceFromUnderscore($key); + case TextNotation::KEBAB_CASE(): + return $this->replaceFromKebabCase($key); + default: + return $key; + } + } + + private function replaceFromCamelCase(string $key): string + { + switch ($this->replacementSeparator) { + case TextNotation::STUDLY_CAPS(): + return \ucfirst($key); + case TextNotation::UNDERSCORE(): + return \strtolower((string) \preg_replace('/(?replacementSeparator) { + case TextNotation::CAMEL_CASE(): + return \lcfirst($key); + case TextNotation::UNDERSCORE(): + return \strtolower((string) \preg_replace('/(?replacementSeparator) { + case TextNotation::STUDLY_CAPS(): + return \ucfirst(\str_replace(' ', '', \ucwords(\str_replace('_', ' ', $key)))); + case TextNotation::CAMEL_CASE(): + return lcfirst(\str_replace(' ', '', \ucwords(\str_replace('_', ' ', $key)))); + case TextNotation::KEBAB_CASE(): + return \str_replace('_', '-', $key); + case TextNotation::UNDERSCORE(): + default: + return $key; + } + } + + private function replaceFromKebabCase(string $key): string + { + switch ($this->replacementSeparator) { + case TextNotation::STUDLY_CAPS(): + return \ucfirst(\str_replace(' ', '', \ucwords(\str_replace('-', ' ', $key)))); + case TextNotation::CAMEL_CASE(): + return \lcfirst(\str_replace(' ', '', \ucwords(\str_replace('-', ' ', $key)))); + case TextNotation::UNDERSCORE(): + return \str_replace('-', '_', $key); + case TextNotation::KEBAB_CASE(): + default: + return $key; + } + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/Constructor/Constructor.php b/vendor/json-mapper/json-mapper/src/Middleware/Constructor/Constructor.php new file mode 100644 index 000000000..f5688c505 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/Constructor/Constructor.php @@ -0,0 +1,50 @@ +factoryRegistry = $factoryRegistry; + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + if ($this->factoryRegistry->hasFactory($object->getName())) { + return; + } + + $reflectedConstructor = $object->getReflectedObject()->getConstructor(); + if (\is_null($reflectedConstructor) || $reflectedConstructor->getNumberOfParameters() === 0) { + return; + } + + $this->factoryRegistry->addFactory( + $object->getName(), + new DefaultFactory( + $object->getName(), + $reflectedConstructor, + $mapper, + new ScalarCaster(), // @TODO Copy current caster ?? + $this->factoryRegistry + ) + ); + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/Constructor/DefaultFactory.php b/vendor/json-mapper/json-mapper/src/Middleware/Constructor/DefaultFactory.php new file mode 100644 index 000000000..ef7429b68 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/Constructor/DefaultFactory.php @@ -0,0 +1,122 @@ + */ + private $parameterMap = []; + /** @var array */ + private $parameterDefaults = []; + + public function __construct( + string $objectName, + ReflectionMethod $reflectedConstructor, + JsonMapperInterface $mapper, + IScalarCaster $scalarCaster, + FactoryRegistry $classFactoryRegistry, + ?FactoryRegistry $nonInstantiableTypeResolver = null + ) { + $reflectedClass = $reflectedConstructor->getDeclaringClass(); + $this->objectName = $objectName; + $this->mapper = $mapper; + if ($nonInstantiableTypeResolver === null) { + $nonInstantiableTypeResolver = new FactoryRegistry(); + } + $this->propertyMap = new PropertyMap(); + $this->valueFactory = new ValueFactory($scalarCaster, $classFactoryRegistry, $nonInstantiableTypeResolver); + + $imports = UseStatementHelper::getImports($reflectedConstructor->getDeclaringClass()); // @todo imports in annotations + $constructorDocBlock = $reflectedConstructor->getDocComment(); + $constructorAnnotations = null; + if (is_string($constructorDocBlock) && !empty($constructorDocBlock)) { + $constructorAnnotations = new LazyAnnotationMap( + $constructorDocBlock, + $reflectedClass->getNamespaceName(), + $imports + ); + } + + foreach ($reflectedConstructor->getParameters() as $param) { + $builder = PropertyBuilder::new(); + if (!\is_null($constructorAnnotations) && $constructorAnnotations->hasParam($param->getName())) { + $builder = $constructorAnnotations->tagToPropertyBuilder('param', $param->getName()); + } + + $builder->setName($param->getName()) + ->setVisibility(Visibility::PUBLIC()); + + $reflectedType = $param->getType(); + $builder->setIsNullable(is_null($reflectedType)); + if (! \is_null($reflectedType)) { + $type = $reflectedType->getName(); + if ($type === 'array') { + $builder->addType('mixed', ArrayInformation::singleDimension()); + } else { + $builder->addType($type, ArrayInformation::notAnArray()); + } + + $builder->setIsNullable($reflectedType->allowsNull()); + } + + if ($reflectedClass->hasProperty($param->getName())) { + $propertyDocComment = $reflectedClass->getProperty($param->getName())->getDocComment(); + if (is_string($propertyDocComment) && !empty($propertyDocComment)) { + $propertyAnnotations = new LazyAnnotationMap( + $propertyDocComment, + $reflectedClass->getNamespaceName(), + $imports + ); + $builder->addTypes(...$propertyAnnotations->tagToPropertyBuilder('var')->getTypes()); + } + } + + if (!$builder->hasAnyType()) { + $builder->addType('mixed', ArrayInformation::notAnArray()); + } + + $this->propertyMap->addProperty($builder->build()); + $this->parameterMap[$param->getPosition()] = $param->getName(); + $this->parameterDefaults[$param->getName()] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null; + } + + ksort($this->parameterMap); + } + + public function __invoke(\stdClass $json) + { + $values = []; + + foreach ($this->parameterMap as $position => $name) { + $values[$position] = $this->valueFactory->build( + $this->mapper, + $this->propertyMap->getProperty($name), + $json->$name ?? $this->parameterDefaults[$name] + ); + } + + return new $this->objectName(...$values); + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/Debugger.php b/vendor/json-mapper/json-mapper/src/Middleware/Debugger.php new file mode 100644 index 000000000..d06051885 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/Debugger.php @@ -0,0 +1,37 @@ +logger = $logger; + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + $this->logger->debug( + 'Current state attributes passed through JsonMapper middleware', + [ + 'json' => \json_encode($json), + 'object' => $object->getName(), + 'propertyMap' => $propertyMap->toString() + ] + ); + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/DocBlockAnnotations.php b/vendor/json-mapper/json-mapper/src/Middleware/DocBlockAnnotations.php new file mode 100644 index 000000000..2aa93ced3 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/DocBlockAnnotations.php @@ -0,0 +1,79 @@ +cache = $cache; + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + $propertyMap->merge($this->fetchPropertyMapForObject($object)); + } + + private function fetchPropertyMapForObject(ObjectWrapper $object): PropertyMap + { + $cacheKey = \sprintf( + '%sCache%s', + str_replace(['{', '}', '(', ')', '/', '\\', '@', ':' ], '', __CLASS__), + str_replace(['{', '}', '(', ')', '/', '\\', '@', ':' ], '', $object->getName()) + ); + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + + $intermediatePropertyMap = new PropertyMap(); + foreach ($this->getObjectPropertiesIncludingParents($object) as $property) { + $docBlock = $property->getDocComment(); + if ($docBlock === false) { + continue; + } + + $annotations = new LazyAnnotationMap($docBlock); + if (! $annotations->hasVar()) { + continue; + } + + $property = $annotations->tagToPropertyBuilder('var') + ->setName($property->getName()) + ->setVisibility(Visibility::fromReflectionProperty($property)) + ->build(); + + $intermediatePropertyMap->addProperty($property); + } + + $this->cache->set($cacheKey, $intermediatePropertyMap); + + return $intermediatePropertyMap; + } + + /** @return \ReflectionProperty[] */ + private function getObjectPropertiesIncludingParents(ObjectWrapper $object): array + { + $properties = []; + $reflectionClass = $object->getReflectedObject(); + do { + $properties[] = $reflectionClass->getProperties(); + } while ($reflectionClass = $reflectionClass->getParentClass()); + + return array_merge(...$properties); + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/FinalCallback.php b/vendor/json-mapper/json-mapper/src/Middleware/FinalCallback.php new file mode 100644 index 000000000..cf7b0015f --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/FinalCallback.php @@ -0,0 +1,49 @@ +callback = $callback; + $this->onlyApplyCallBackOnTopLevel = $onlyApplyCallBackOnTopLevel; + } + + public function __invoke(callable $handler): callable + { + return function ( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $map, + JsonMapperInterface $mapper + ) use ( + $handler + ) { + self::$nestingLevel++; + try { + $handler($json, $object, $map, $mapper); + } finally { + self::$nestingLevel--; + } + + if (! $this->onlyApplyCallBackOnTopLevel || self::$nestingLevel === 0) { + \call_user_func($this->callback, $json, $object, $map, $mapper); + } + }; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/MiddlewareInterface.php b/vendor/json-mapper/json-mapper/src/Middleware/MiddlewareInterface.php new file mode 100644 index 000000000..65ef8a1a1 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/MiddlewareInterface.php @@ -0,0 +1,10 @@ +cache = $cache ?? new NullCache(); + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + foreach ($this->fetchPropertyMapForObject($object, $propertyMap) as $property) { + $propertyMap->addProperty($property); + } + } + + private function fetchPropertyMapForObject(ObjectWrapper $object, PropertyMap $originalPropertyMap): PropertyMap + { + $cacheKey = \sprintf( + '%sCache%s', + str_replace(['{', '}', '(', ')', '/', '\\', '@', ':' ], '', __CLASS__), + str_replace(['{', '}', '(', ')', '/', '\\', '@', ':' ], '', $object->getName()) + ); + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + + $intermediatePropertyMap = new PropertyMap(); + $imports = UseStatementHelper::getImports($object->getReflectedObject()); + + /** @var Property $property */ + foreach ($originalPropertyMap as $property) { + $types = $property->getPropertyTypes(); + foreach ($types as $index => $type) { + $types[$index] = $this->resolveSingleType($type, $object, $imports); + } + $intermediatePropertyMap->addProperty($property->asBuilder()->setTypes(...$types)->build()); + } + + $this->cache->set($cacheKey, $intermediatePropertyMap); + + return $intermediatePropertyMap; + } + + /** @param Import[] $imports */ + private function resolveSingleType(PropertyType $type, ObjectWrapper $object, array $imports): PropertyType + { + if (ScalarType::isValid($type->getType())) { + return $type; + } + + $pos = strpos($type->getType(), '\\'); + if ($pos === false) { + $pos = strlen($type->getType()); + } + $nameSpacedFirstChunk = '\\' . substr($type->getType(), 0, $pos); + + $matches = \array_filter( + $imports, + static function (Import $import) use ($nameSpacedFirstChunk) { + if ($import->hasAlias() && '\\' . $import->getAlias() === $nameSpacedFirstChunk) { + return true; + } + + return $nameSpacedFirstChunk === \substr($import->getImport(), -strlen($nameSpacedFirstChunk)); + } + ); + + if (count($matches) > 0) { + $match = \array_shift($matches); + if ($match->hasAlias()) { + $strippedType = \substr($type->getType(), strlen($nameSpacedFirstChunk)); + $fullyQualifiedType = $match->getImport() . '\\' . $strippedType; + } else { + $strippedMatch = \substr($match->getImport(), 0, -strlen($nameSpacedFirstChunk)); + $fullyQualifiedType = $strippedMatch . '\\' . $type->getType(); + } + + return new PropertyType(rtrim($fullyQualifiedType, '\\'), $type->getArrayInformation()); + } + + $reflectedObject = $object->getReflectedObject(); + while (true) { + if (class_exists($reflectedObject->getNamespaceName() . '\\' . $type->getType())) { + return new PropertyType( + $reflectedObject->getNamespaceName() . '\\' . $type->getType(), + $type->getArrayInformation() + ); + } + + $reflectedObject = $reflectedObject->getParentClass(); + if (! $reflectedObject) { + break; + } + } + + return $type; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/Rename/Mapping.php b/vendor/json-mapper/json-mapper/src/Middleware/Rename/Mapping.php new file mode 100644 index 000000000..07ea6875e --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/Rename/Mapping.php @@ -0,0 +1,37 @@ +class = $class; + $this->from = $from; + $this->to = $to; + } + + public function getClass(): string + { + return $this->class; + } + + public function getFrom(): string + { + return $this->from; + } + + public function getTo(): string + { + return $this->to; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/Rename/Rename.php b/vendor/json-mapper/json-mapper/src/Middleware/Rename/Rename.php new file mode 100644 index 000000000..8e3761859 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/Rename/Rename.php @@ -0,0 +1,46 @@ +mapping = $mapping; + } + + public function addMapping(string $class, string $from, string $to): void + { + $this->mapping[] = new Mapping($class, $from, $to); + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + $mapping = \array_filter($this->mapping, static function ($map) use ($object) { + return $map->getClass() === $object->getName(); + }); + foreach ($mapping as $map) { + $from = $map->getFrom(); + $to = $map->getTo(); + + if (isset($json->$from)) { + $json->$to = $json->$from; + unset($json->$from); + } + } + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/TypedProperties.php b/vendor/json-mapper/json-mapper/src/Middleware/TypedProperties.php new file mode 100644 index 000000000..602a0e842 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/TypedProperties.php @@ -0,0 +1,110 @@ +cache = $cache; + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + $propertyMap->merge($this->fetchPropertyMapForObject($object)); + } + + private function fetchPropertyMapForObject(ObjectWrapper $object): PropertyMap + { + $cacheKey = \sprintf( + '%sCache%s', + str_replace(['{', '}', '(', ')', '/', '\\', '@', ':' ], '', __CLASS__), + str_replace(['{', '}', '(', ')', '/', '\\', '@', ':' ], '', $object->getName()) + ); + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + + $intermediatePropertyMap = new PropertyMap(); + + foreach ($this->getObjectPropertiesIncludingParents($object) as $reflectionProperty) { + $type = $reflectionProperty->getType(); + + if ($type instanceof ReflectionNamedType) { + $isArray = $type->getName() === 'array'; + $propertyType = $isArray ? 'mixed' : $type->getName(); + $property = PropertyBuilder::new() + ->setName($reflectionProperty->getName()) + ->addType( + $propertyType, + $isArray ? ArrayInformation::singleDimension() : ArrayInformation::notAnArray() + ) + ->setIsNullable($type->allowsNull() || ((!$isArray) && $propertyType === 'mixed')) + ->setVisibility(Visibility::fromReflectionProperty($reflectionProperty)) + ->build(); + $intermediatePropertyMap->addProperty($property); + + continue; + } + + if ($type instanceof ReflectionUnionType) { + $types = \array_map(static function (ReflectionNamedType $t): string { + return $t->getName(); + }, $type->getTypes()); + $isArray = \in_array('array', $types, true); + + $builder = PropertyBuilder::new() + ->setName($reflectionProperty->getName()) + ->setVisibility(Visibility::fromReflectionProperty($reflectionProperty)) + ->setIsNullable($type->allowsNull()); + + /* A union type that has one of its types defined as array is to complex to understand */ + if ($isArray) { + $property = $builder->addType('mixed', ArrayInformation::singleDimension())->build(); + $intermediatePropertyMap->addProperty($property); + continue; + } + + foreach ($types as $type) { + $builder->addType($type, ArrayInformation::notAnArray()); + } + $property = $builder->build(); + $intermediatePropertyMap->addProperty($property); + } + } + + $this->cache->set($cacheKey, $intermediatePropertyMap); + + return $intermediatePropertyMap; + } + + /** @return \ReflectionProperty[] */ + public function getObjectPropertiesIncludingParents(ObjectWrapper $object): array + { + $properties = []; + $reflectionClass = $object->getReflectedObject(); + do { + $properties = array_merge($properties, $reflectionClass->getProperties()); + } while ($reflectionClass = $reflectionClass->getParentClass()); + return $properties; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Middleware/ValueTransformation.php b/vendor/json-mapper/json-mapper/src/Middleware/ValueTransformation.php new file mode 100644 index 000000000..e9dfb4ab1 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Middleware/ValueTransformation.php @@ -0,0 +1,42 @@ +mapFunction = $mapFunction; + $this->includeKey = $includeKey; + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + $mapFunction = $this->mapFunction; + + foreach ((array) $json as $key => $value) { + if ($this->includeKey) { + $json->$key = $mapFunction($key, $value); + continue; + } + + $json->$key = $mapFunction($value); + } + } +} diff --git a/vendor/json-mapper/json-mapper/src/Parser/Import.php b/vendor/json-mapper/json-mapper/src/Parser/Import.php new file mode 100644 index 000000000..711e27306 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Parser/Import.php @@ -0,0 +1,35 @@ +import = $import; + $this->alias = $alias; + } + + public function getImport(): string + { + return $this->import; + } + + public function getAlias(): ?string + { + return $this->alias; + } + + public function hasAlias(): bool + { + return ! \is_null($this->alias); + } +} diff --git a/vendor/json-mapper/json-mapper/src/Parser/UseNodeVisitor.php b/vendor/json-mapper/json-mapper/src/Parser/UseNodeVisitor.php new file mode 100644 index 000000000..f1e94a8cc --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Parser/UseNodeVisitor.php @@ -0,0 +1,42 @@ +uses as $use) { + $this->imports[] = new Import($use->name->toString(), \is_null($use->alias) ? null : $use->alias->name); + } + } elseif ($node instanceof Stmt\GroupUse) { + foreach ($node->uses as $use) { + $this->imports[] = new Import( + "{$node->prefix}\\{$use->name}", + \is_null($use->alias) ? null : $use->alias->name + ); + } + } + + return null; + } + + /** @return Import[] */ + public function getImports(): array + { + return $this->imports; + } +} diff --git a/vendor/json-mapper/json-mapper/src/ValueObjects/ArrayInformation.php b/vendor/json-mapper/json-mapper/src/ValueObjects/ArrayInformation.php new file mode 100644 index 000000000..bfd16ab25 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/ValueObjects/ArrayInformation.php @@ -0,0 +1,63 @@ +isArray = $isArray; + $this->dimensions = $dimensions; + } + + public static function notAnArray(): self + { + return new self(false, 0); + } + + public static function singleDimension(): self + { + return new self(true, 1); + } + + public static function multiDimension(int $dimension): self + { + return new self(true, $dimension); + } + + public function isArray(): bool + { + return $this->isArray; + } + + public function getDimensions(): int + { + return $this->dimensions; + } + + public function isMultiDimensionalArray(): bool + { + return $this->isArray && $this->dimensions > 1; + } + + public function jsonSerialize(): array + { + return [ + 'isArray' => $this->isArray, + 'dimensions' => $this->dimensions + ]; + } + + public function equals(self $other): bool + { + return $this->isArray === $other->isArray + && $this->dimensions === $other->dimensions; + } +} diff --git a/vendor/json-mapper/json-mapper/src/ValueObjects/LazyAnnotationMap.php b/vendor/json-mapper/json-mapper/src/ValueObjects/LazyAnnotationMap.php new file mode 100644 index 000000000..cd08f9391 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/ValueObjects/LazyAnnotationMap.php @@ -0,0 +1,198 @@ +input = $input; + $this->namespace = $namespace; + $this->imports = $imports; + } + + public function hasVar(): bool + { + $this->initialize(); + + return !is_null($this->getFirstTagByNameAndVariableName('var')); + } + + public function hasParam(string $variableName): bool + { + $this->initialize(); + + return !is_null($this->getFirstTagByNameAndVariableName('param', $variableName)); + } + + public function tagToPropertyBuilder(string $tagName, ?string $variableName = null): PropertyBuilder + { + $this->initialize(); + $tag = $this->getFirstTagByNameAndVariableName($tagName, $variableName); + + if (\is_null($tag)) { + throw new \RuntimeException('Missing tag with name ' . $tagName . ' for variable ' . $variableName); + } + + $type = $tag->getType(); + if (is_null($type)) { + throw new \RuntimeException('Tag has no type'); + } + + $builder = PropertyBuilder::new() + ->setIsNullable($type instanceof Types\Nullable); + + // Unpack nullable type (non-compound type prefixed with question mark) + if ($type instanceof Types\Nullable) { + $type = $type->getActualType(); + } + // Unpack pseudo type + if ($type instanceof PseudoType && ! $type instanceof PseudoTypes\List_) { + $type = $type->underlyingType(); + } + if ($type instanceof Types\Compound) { + $types = $type->getIterator()->getArrayCopy(); + } else { + $types = [$type]; + } + + foreach ($types as $type) { + switch (get_class($type)) { + case Types\Null_::class: + $builder->setIsNullable(true); + break; + case Types\String_::class: + case Types\Boolean::class: + case Types\Float_::class: + case Types\Integer::class: + case Types\Object_::class: + case Types\Mixed_::class: + case Types\Array_::class: + case PseudoTypes\List_::class: + case PseudoTypes\ArrayShape::class: + $builder->addType($this->mapDocBlockTypeClassToPropertyType($type), $this->mapDoCBlockTypeToArrayDimension($type)); + break; + default: + throw new \RuntimeException('Unexpected type ' . get_class($type)); + } + } + + return $builder; + } + + private function initialize(): void + { + if (!\is_null($this->docBlock)) { + return; + } + + $factory = DocBlockFactory::createInstance(); + + $namespaceAliases = []; + foreach ($this->imports as $import) { + if ($import->hasAlias()) { + $namespaceAliases[$import->getAlias()] = $import->getImport(); + continue; + } + + $farMostRightNamespaceSeparator = strrpos($import->getImport(), '\\'); + if ($farMostRightNamespaceSeparator === false) { + $namespaceAliases[$import->getImport()] = $import->getImport(); + continue; + } + + $key = substr($import->getImport(), $farMostRightNamespaceSeparator + 1); + $namespaceAliases[$key] = $import->getImport(); + } + $this->docBlock = $factory->create( + $this->input, + new Context( + $this->namespace, + $namespaceAliases, + ) + ); + } + + private function getFirstTagByNameAndVariableName(string $name, ?string $variableName = null): ?DocBlock\Tags\TagWithType + { + $tags = $this->docBlock->getTagsWithTypeByName($name); + + if (\is_null($variableName)) { + return array_shift($tags); + } + + $matches = array_filter($tags, static function (DocBlock\Tags\TagWithType $tag) use ($variableName): bool { + if (!$tag instanceof DocBlock\Tags\Param && !$tag instanceof DocBlock\Tags\Var_) { + return false; + } + return $tag->getVariableName() === $variableName; + }); + + return array_shift($matches); + } + + private function mapDocBlockTypeClassToPropertyType(Type $type): string + { + switch (get_class($type)) { + case Types\String_::class: + case Types\ClassString::class: + return 'string'; + case Types\Boolean::class: + return 'bool'; + case Types\Float_::class: + return 'float'; + case Types\Integer::class: + return 'int'; + case Types\Mixed_::class: + return 'mixed'; + case Types\Object_::class: + return ltrim($type->__toString(), '\\'); + case Types\Array_::class: + case PseudoTypes\List_::class: + return $this->mapDocBlockTypeClassToPropertyType($type->getValueType()); + case PseudoTypes\ArrayShape::class: + return $this->mapDocBlockTypeClassToPropertyType($type->underlyingType()); + default: + throw new \RuntimeException('Unexpected type ' . get_class($type)); + } + } + + private function mapDoCBlockTypeToArrayDimension(Type $type): ArrayInformation + { + $dimensions = 0; + while ($type instanceof Types\Array_) { + $dimensions++; + $type = $type->getValueType(); + } + + if ($dimensions === 0) { + return ArrayInformation::notAnArray(); + } + + return ArrayInformation::multiDimension($dimensions); + } +} diff --git a/vendor/json-mapper/json-mapper/src/ValueObjects/Property.php b/vendor/json-mapper/json-mapper/src/ValueObjects/Property.php new file mode 100644 index 000000000..bd7803dc8 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/ValueObjects/Property.php @@ -0,0 +1,77 @@ +name = $name; + $this->visibility = $visibility; + $this->isNullable = $isNullable; + $this->propertyTypes = $types; + } + + public function getName(): string + { + return $this->name; + } + + /** @return PropertyType[] */ + public function getPropertyTypes(): array + { + return $this->propertyTypes; + } + + public function getVisibility(): Visibility + { + return $this->visibility; + } + + public function isNullable(): bool + { + return $this->isNullable; + } + + public function isUnion(): bool + { + return \count($this->propertyTypes) > 1; + } + + public function asBuilder(): PropertyBuilder + { + return PropertyBuilder::new() + ->setName($this->name) + ->setTypes(...$this->propertyTypes) + ->setIsNullable($this->isNullable()) + ->setVisibility($this->visibility); + } + + public function jsonSerialize(): array + { + return [ + 'name' => $this->name, + 'types' => $this->propertyTypes, + 'visibility' => $this->visibility, + 'isNullable' => $this->isNullable, + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/src/ValueObjects/PropertyMap.php b/vendor/json-mapper/json-mapper/src/ValueObjects/PropertyMap.php new file mode 100644 index 000000000..0848de421 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/ValueObjects/PropertyMap.php @@ -0,0 +1,80 @@ +map[$property->getName()] = $property; + $this->iterator = null; + } + + public function hasProperty(string $name): bool + { + return \array_key_exists($name, $this->map); + } + + public function getProperty(string $key): Property + { + if (! $this->hasProperty($key)) { + throw new \Exception("There is no property named $key"); + } + + return $this->map[$key]; + } + + public function merge(self $other): void + { + /** @var Property $property */ + foreach ($other as $property) { + if (! $this->hasProperty($property->getName())) { + $this->addProperty($property); + continue; + } + + if ($property == $this->getProperty($property->getName())) { + continue; + } + + $current = $this->getProperty($property->getName()); + $builder = $current->asBuilder(); + + $builder->setIsNullable($current->isNullable() || $property->isNullable()); + foreach ($property->getPropertyTypes() as $propertyType) { + $builder->addType($propertyType->getType(), $propertyType->getArrayInformation()); + } + + $this->addProperty($builder->build()); + } + $this->iterator = null; + } + + public function getIterator(): \ArrayIterator + { + if (\is_null($this->iterator)) { + $this->iterator = new \ArrayIterator($this->map); + } + + return $this->iterator; + } + + public function jsonSerialize(): array + { + return [ + 'properties' => $this->map, + ]; + } + + public function toString(): string + { + return (string) \json_encode($this); + } +} diff --git a/vendor/json-mapper/json-mapper/src/ValueObjects/PropertyType.php b/vendor/json-mapper/json-mapper/src/ValueObjects/PropertyType.php new file mode 100644 index 000000000..d864f5228 --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/ValueObjects/PropertyType.php @@ -0,0 +1,51 @@ +type = $type; + $this->arrayInformation = $isArray; + } + + public function getType(): string + { + return $this->type; + } + + public function isArray(): bool + { + return $this->arrayInformation->isArray(); + } + + public function isMultiDimensionalArray(): bool + { + return $this->arrayInformation->isMultiDimensionalArray(); + } + + public function getArrayInformation(): ArrayInformation + { + return $this->arrayInformation; + } + + public function jsonSerialize(): array + { + return [ + 'type' => $this->type, + 'isArray' => $this->arrayInformation->isArray(), + 'arrayInformation' => $this->arrayInformation, + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/src/Wrapper/ObjectWrapper.php b/vendor/json-mapper/json-mapper/src/Wrapper/ObjectWrapper.php new file mode 100644 index 000000000..b2024abfd --- /dev/null +++ b/vendor/json-mapper/json-mapper/src/Wrapper/ObjectWrapper.php @@ -0,0 +1,83 @@ +object = $object; + $this->className = $className; + } + + /** @param object|null $object */ + public function setObject($object): void + { + $this->object = $object; + $this->reflectedObject = null; + } + + /** @return object */ + public function getObject() + { + if (\is_null($this->object)) { + $constructor = $this->getReflectedObject()->getConstructor(); + if (\is_null($constructor) || $constructor->getNumberOfParameters() === 0) { + $this->object = $this->getReflectedObject()->newInstance(); + } else { + $this->object = $this->getReflectedObject()->newInstanceWithoutConstructor(); + } + } + + return $this->object; + } + + /** @return class-string */ + public function getClassName(): ?string + { + return $this->className ; + } + + public function getReflectedObject(): \ReflectionClass + { + if ($this->reflectedObject === null) { + $objectOrClass = ! \is_null($this->object) ? $this->object : $this->className; + $this->reflectedObject = new \ReflectionClass($objectOrClass); + } + + return $this->reflectedObject; + } + + public function getName(): string + { + return $this->getReflectedObject()->getName(); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Helpers/AssertThatPropertyTrait.php b/vendor/json-mapper/json-mapper/tests/Helpers/AssertThatPropertyTrait.php new file mode 100644 index 000000000..db338387e --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Helpers/AssertThatPropertyTrait.php @@ -0,0 +1,15 @@ +property = $property; + } + + public function hasName(string $name): PropertyAssertionChain + { + Assert::assertSame($name, $this->property->getName()); + + return $this; + } + + public function hasType(string $type, ArrayInformation $arrayInformation): PropertyAssertionChain + { + $matches = array_filter( + $this->property->getPropertyTypes(), + static function ($p) use ($type, $arrayInformation) { + return $p->getType() === $type && $p->getArrayInformation()->equals($arrayInformation); + } + ); + + Assert::assertGreaterThanOrEqual(1, count($matches)); + + return $this; + } + + public function onlyHasType(string $type, ArrayInformation $isArray): PropertyAssertionChain + { + Assert::assertEquals([new PropertyType($type, $isArray)], $this->property->getPropertyTypes()); + + return $this; + } + + public function hasVisibility(Visibility $visibility): PropertyAssertionChain + { + Assert::assertTrue($this->property->getVisibility()->equals($visibility)); + + return $this; + } + + public function isNullable(): PropertyAssertionChain + { + Assert::assertTrue($this->property->isNullable()); + + return $this; + } + + public function isNotNullable(): PropertyAssertionChain + { + Assert::assertFalse($this->property->isNullable()); + + return $this; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Api/ChuckNorris/Php71/Joke.php b/vendor/json-mapper/json-mapper/tests/Implementation/Api/ChuckNorris/Php71/Joke.php new file mode 100644 index 000000000..679bc0953 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Api/ChuckNorris/Php71/Joke.php @@ -0,0 +1,23 @@ + */ + public $objects = []; +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Bar/CustomerState.php b/vendor/json-mapper/json-mapper/tests/Implementation/Bar/CustomerState.php new file mode 100644 index 000000000..8b3f26436 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Bar/CustomerState.php @@ -0,0 +1,11 @@ +child; + } + + public function setChild(?SimpleObject $child): void + { + $this->child = $child; + } + + public function getChildren(): array + { + return $this->children; + } + + public function setChildren(array $children): void + { + $this->children = $children; + } + + public function getUser(): User + { + return $this->user; + } + + public function setUser(User $user): void + { + $this->user = $user; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/DatePopoWithConstructor.php b/vendor/json-mapper/json-mapper/tests/Implementation/DatePopoWithConstructor.php new file mode 100644 index 000000000..aa6545b85 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/DatePopoWithConstructor.php @@ -0,0 +1,27 @@ +date = $date; + } + + public function getDate(): ?DateTimeImmutable + { + return $this->date; + } + + public function setDate(?DateTimeImmutable $date): void + { + $this->date = $date; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Foo/Customer.php b/vendor/json-mapper/json-mapper/tests/Implementation/Foo/Customer.php new file mode 100644 index 000000000..1105da4af --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Foo/Customer.php @@ -0,0 +1,11 @@ +called = true; + } + + public function isCalled(): bool + { + return $this->called; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/IsCalledMiddleware.php b/vendor/json-mapper/json-mapper/tests/Implementation/IsCalledMiddleware.php new file mode 100644 index 000000000..aa4175fc5 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/IsCalledMiddleware.php @@ -0,0 +1,30 @@ +called; + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + $this->called = true; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/JsonMapper.php b/vendor/json-mapper/json-mapper/tests/Implementation/JsonMapper.php new file mode 100644 index 000000000..ba4cb3f59 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/JsonMapper.php @@ -0,0 +1,241 @@ +handler = $handler; + + return $this; + } + + public function push(callable $middleware, string $name = ''): JsonMapperInterface + { + $this->stack[] = new NamedMiddleware($middleware, $name); + $this->cached = null; + + return $this; + } + + public function pop(): JsonMapperInterface + { + array_pop($this->stack); + $this->cached = null; + + return $this; + } + + public function unshift(callable $middleware, string $name = ''): JsonMapperInterface + { + array_unshift($this->stack, new NamedMiddleware($middleware, $name)); + $this->cached = null; + + return $this; + } + + public function shift(): JsonMapperInterface + { + array_shift($this->stack); + $this->cached = null; + + return $this; + } + + public function remove(callable $remove): JsonMapperInterface + { + $this->stack = array_values(array_filter( + $this->stack, + static function (NamedMiddleware $namedMiddleware) use ($remove) { + return $namedMiddleware->getMiddleware() !== $remove; + } + )); + $this->cached = null; + + return $this; + } + + public function removeByName(string $remove): JsonMapperInterface + { + $this->stack = array_values(array_filter( + $this->stack, + static function (NamedMiddleware $namedMiddleware) use ($remove) { + return $namedMiddleware->getName() !== $remove; + } + )); + $this->cached = null; + + return $this; + } + + public function mapToClass(\stdClass $json, string $class) + { + if (! \class_exists($class)) { + throw new \Exception(); // @todo proper exception message + } + + $propertyMap = new PropertyMap(); + + $handler = $this->resolve(); + $placeholder = new \stdClass(); // @todo remove placeholder + $wrapper = new ObjectWrapper($placeholder, $class); + $handler($json, $wrapper, $propertyMap, $this); + + return $wrapper->getObject(); + } + + /** @param object $object */ + public function mapObject(\stdClass $json, $object): void + { + if (!is_object($object)) { + throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object'); + } + + $propertyMap = new PropertyMap(); + + $handler = $this->resolve(); + $handler($json, new ObjectWrapper($object), $propertyMap, $this); + } + + /** @param object $object */ + public function mapArray(array $json, $object): array + { + if (!is_object($object)) { + throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object'); + } + + $results = []; + foreach ($json as $key => $value) { + $results[$key] = clone $object; + $this->mapObject($value, $results[$key]); + } + + return $results; + } + + public function mapToClassArray(array $json, string $class): array + { + if (! \class_exists($class)) { + throw TypeError::forArgument(__METHOD__, 'class-string', $class, 2, '$class'); + } + + return array_map( + function (\stdClass $value) use ($class) { + return $this->mapToClass($value, $class); + }, + $json + ); + } + + public function mapToClassFromString(string $json, string $class) + { + if (! \class_exists($class)) { + throw TypeError::forArgument(__METHOD__, 'class-string', $class, 2, '$class'); + } + + $data = $this->decodeJsonString($json); + if (! $data instanceof \stdClass) { + throw new \RuntimeException('Provided string is not a json encoded object'); + } + + return $this->mapToClass($data, $class); + } + + /** @param object $object */ + public function mapObjectFromString(string $json, $object): void + { + if (!is_object($object)) { + throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object'); + } + + $data = $this->decodeJsonString($json); + + if (! $data instanceof \stdClass) { + throw new \RuntimeException('Provided string is not a json encoded object'); + } + + $this->mapObject($data, $object); + } + + /** @param object $object */ + public function mapArrayFromString(string $json, $object): array + { + if (!is_object($object)) { + throw TypeError::forArgument(__METHOD__, 'object', $object, 2, '$object'); + } + + $data = $this->decodeJsonString($json); + + if (! is_array($data)) { + throw new \RuntimeException('Provided string is not a json encoded array'); + } + + $results = []; + foreach ($data as $key => $value) { + $results[$key] = clone $object; + $this->mapObject($value, $results[$key]); + } + + return $results; + } + + public function mapToClassArrayFromString(string $json, string $class): array + { + if (! \class_exists($class)) { + throw TypeError::forArgument(__METHOD__, 'class-string', $class, 2, '$class'); + } + + $data = $this->decodeJsonString($json); + if (! \is_array($data)) { + throw new \RuntimeException('Provided string is not a json encoded array'); + } + + return $this->mapToClassArray($data, $class); + } + + /** @return \stdClass|\stdClass[] */ + private function decodeJsonString(string $json) + { + if (PHP_VERSION_ID >= 70300) { + $data = json_decode($json, false, 512, JSON_THROW_ON_ERROR); + } else { + $data = json_decode($json, false); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new JsonException(json_last_error_msg(), json_last_error()); + } + } + + return $data; + } + + private function resolve(): callable + { + if (!$this->cached) { + $prev = $this->handler; + foreach (array_reverse($this->stack) as $namedMiddleware) { + $prev = $namedMiddleware->getMiddleware()($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/ListOfSimpleObjects.php b/vendor/json-mapper/json-mapper/tests/Implementation/ListOfSimpleObjects.php new file mode 100644 index 000000000..cb7494a99 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/ListOfSimpleObjects.php @@ -0,0 +1,11 @@ + */ + public $objects = []; +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Models/AbstractShape.php b/vendor/json-mapper/json-mapper/tests/Implementation/Models/AbstractShape.php new file mode 100644 index 000000000..dc9cca2c3 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Models/AbstractShape.php @@ -0,0 +1,15 @@ +radius * M_PI; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Models/IShape.php b/vendor/json-mapper/json-mapper/tests/Implementation/Models/IShape.php new file mode 100644 index 000000000..dc81e8f76 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Models/IShape.php @@ -0,0 +1,11 @@ +type) { + case 'square': + return new Square(); + case 'circle': + return new Circle(); + default: + throw new \RuntimeException("Unable to create shape for type {$data->type}"); + } + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Models/Square.php b/vendor/json-mapper/json-mapper/tests/Implementation/Models/Square.php new file mode 100644 index 000000000..e88f8b73f --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Models/Square.php @@ -0,0 +1,24 @@ +width = $width; + $this->length = $length; + } + + public function getCircumference(): float + { + return ($this->length * 2) + ($this->width * 2); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Models/Sub/AnotherValueHolder.php b/vendor/json-mapper/json-mapper/tests/Implementation/Models/Sub/AnotherValueHolder.php new file mode 100644 index 000000000..f188783ab --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Models/Sub/AnotherValueHolder.php @@ -0,0 +1,9 @@ +id; + } + + public function setId(int $id): void + { + $this->id = $id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Models/UserWithConstructor.php b/vendor/json-mapper/json-mapper/tests/Implementation/Models/UserWithConstructor.php new file mode 100644 index 000000000..5b3d09a79 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Models/UserWithConstructor.php @@ -0,0 +1,29 @@ +id = $id; + $this->name = $name; + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Models/ValueHolder.php b/vendor/json-mapper/json-mapper/tests/Implementation/Models/ValueHolder.php new file mode 100644 index 000000000..f36c9547e --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Models/ValueHolder.php @@ -0,0 +1,9 @@ +shape; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Models/Wrappers/IShapeAware.php b/vendor/json-mapper/json-mapper/tests/Implementation/Models/Wrappers/IShapeAware.php new file mode 100644 index 000000000..f597beae2 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Models/Wrappers/IShapeAware.php @@ -0,0 +1,12 @@ +shape; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Php74/Article/Article.php b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/Article/Article.php new file mode 100644 index 000000000..3ba6e0429 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/Article/Article.php @@ -0,0 +1,16 @@ +configuration = "Works?"; + } + + public function getConfiguration(): string + { + return $this->configuration; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Php74/BuiltinExt/DateTime.php b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/BuiltinExt/DateTime.php new file mode 100644 index 000000000..2d0fe2b84 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/BuiltinExt/DateTime.php @@ -0,0 +1,17 @@ +date = $date; + $this->time = $time; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Php74/BuiltinExt/DateTimeCollection.php b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/BuiltinExt/DateTimeCollection.php new file mode 100644 index 000000000..2cd5dc580 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/BuiltinExt/DateTimeCollection.php @@ -0,0 +1,16 @@ +items = $items; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Php74/Popo.php b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/Popo.php new file mode 100644 index 000000000..49a041121 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/Popo.php @@ -0,0 +1,12 @@ +name = $name; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Php74/SimpleObjectExtension.php b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/SimpleObjectExtension.php new file mode 100644 index 000000000..6601c8cca --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Php74/SimpleObjectExtension.php @@ -0,0 +1,9 @@ +status = $status; + } + + public function getStatus(): Status + { + return $this->status; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Php81/Foo/BarItem.php b/vendor/json-mapper/json-mapper/tests/Implementation/Php81/Foo/BarItem.php new file mode 100644 index 000000000..8af8ca262 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Php81/Foo/BarItem.php @@ -0,0 +1,13 @@ +value; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/Php81/WithConstructorReadOnlyDateTimePropertyPromotion.php b/vendor/json-mapper/json-mapper/tests/Implementation/Php81/WithConstructorReadOnlyDateTimePropertyPromotion.php new file mode 100644 index 000000000..6af6ddac2 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/Php81/WithConstructorReadOnlyDateTimePropertyPromotion.php @@ -0,0 +1,13 @@ + */ + private $items; + + /** @param array $items */ + public function __construct(array $items) + { + $this->items = $items; + } + + /** @return array */ + public function getItems(): array + { + return $this->items; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/PopoContainer.php b/vendor/json-mapper/json-mapper/tests/Implementation/PopoContainer.php new file mode 100644 index 000000000..a49197f23 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/PopoContainer.php @@ -0,0 +1,23 @@ +items = $items; + } + + /** @return Popo[] */ + public function getItems(): array + { + return $this->items; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/PopoList.php b/vendor/json-mapper/json-mapper/tests/Implementation/PopoList.php new file mode 100644 index 000000000..faf848bb7 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/PopoList.php @@ -0,0 +1,23 @@ + */ + private $items; + + /** @param list $items */ + public function __construct(array $items) + { + $this->items = $items; + } + + /** @return list */ + public function getItems(): array + { + return $this->items; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/PopoWrapperWithConstructor.php b/vendor/json-mapper/json-mapper/tests/Implementation/PopoWrapperWithConstructor.php new file mode 100644 index 000000000..ec8b1f7bc --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/PopoWrapperWithConstructor.php @@ -0,0 +1,21 @@ +popo = $popo; + } + + public function getPopo(): Popo + { + return $this->popo; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/PrivatePropertyWithoutSetter.php b/vendor/json-mapper/json-mapper/tests/Implementation/PrivatePropertyWithoutSetter.php new file mode 100644 index 000000000..2372c0fe7 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/PrivatePropertyWithoutSetter.php @@ -0,0 +1,11 @@ +name = $name; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Implementation/SimpleObjectExtension.php b/vendor/json-mapper/json-mapper/tests/Implementation/SimpleObjectExtension.php new file mode 100644 index 000000000..730a9fb85 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Implementation/SimpleObjectExtension.php @@ -0,0 +1,9 @@ +addMapping(User::class, 'Full-Name', 'name'); + $rename->addMapping(User::class, 'Identifier', 'id'); + $mapper = (new JsonMapperFactory())->bestFit(); + $mapper->unshift($rename); + $object = new User(); + $json = (object) ['Full-Name' => 'John Doe', 'Identifier' => '42']; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertEquals('John Doe', $object->getName()); + self::assertEquals(42, $object->getId()); + } + + public function testItCanRenameJsonPropertiesOnNestedObjects(): void + { + // Arrange + $rename = new Rename(); + $rename->addMapping(SimpleObject::class, 'FULL-NAME', 'name'); + $rename->addMapping(ComplexObject::class, 'sub', 'children'); + $mapper = (new JsonMapperFactory())->bestFit(); + $mapper->unshift($rename); + $object = new ComplexObject(); + $json = (object) ['sub' => [(object) ['FULL-NAME' => 'John Doe'], (object) ['FULL-NAME' => 'Jane Doe']]]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertCount(2, $object->getChildren()); + self::assertEquals([new SimpleObject('John Doe'), new SimpleObject('Jane Doe')], $object->getChildren()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsConstructorsWithParametersTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsConstructorsWithParametersTest.php new file mode 100644 index 000000000..5e30023c5 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsConstructorsWithParametersTest.php @@ -0,0 +1,205 @@ +withDocBlockAnnotationsMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->build(); + + $json = (object) [ + 'popo' => (object) [ + 'name' => 'John Doe' + ] + ]; + + $result = $mapper->mapToClass($json, PopoWrapperWithConstructor::class); + + self::assertInstanceOf(PopoWrapperWithConstructor::class, $result); + self::assertEquals($json->popo->name, $result->getPopo()->name); + } + + /** + * @requires PHP >= 8.1 + */ + public function testCanHandleCustomConstructorsWithPropertyPromotion(): void + { + $factoryRegistry = new FactoryRegistry(); + $mapper = JsonMapperBuilder::new() + ->withDocBlockAnnotationsMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->build(); + + $json = (object) [ + 'value' => 'John Doe', + ]; + + $result = $mapper->mapToClass($json, WithConstructorPropertyPromotion::class); + + self::assertInstanceOf(WithConstructorPropertyPromotion::class, $result); + self::assertEquals($json->value, $result->getValue()); + } + + /** + * @requires PHP >= 8.1 + */ + public function testCanHandleCustomConstructorsWithReadonlyPropertyPromotion(): void + { + $factoryRegistry = new FactoryRegistry(); + $mapper = JsonMapperBuilder::new() + ->withDocBlockAnnotationsMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->build(); + + $json = (object) [ + 'value' => 'John Doe', + ]; + + $result = $mapper->mapToClass($json, WithConstructorReadOnlyPropertyPromotion::class); + + self::assertInstanceOf(WithConstructorReadOnlyPropertyPromotion::class, $result); + self::assertEquals($json->value, $result->value); + } + + /** + * @requires PHP >= 8.1 + */ + public function testCanHandleCustomConstructorsWithNestedCustomConstructorReadonlyPropertyPromotion(): void + { + $factoryRegistry = new FactoryRegistry(); + $mapper = JsonMapperBuilder::new() + ->withDocBlockAnnotationsMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->build(); + + $json = (object) [ + 'value' => (object) [ + 'value' => 'John Doe', + ], + ]; + + $result = $mapper->mapToClass($json, WrapperWithConstructorReadOnlyPropertyPromotion::class); + + self::assertInstanceOf(WrapperWithConstructorReadOnlyPropertyPromotion::class, $result); + self::assertEquals($json->value->value, $result->value->value); + } + + /** + * @requires PHP >= 8.1 + */ + public function testCanHandleCustomConstructorsWithReadonlyDateTimePropertyPromotion(): void + { + $factoryRegistry = FactoryRegistry::withNativePhpClassesAdded(); + $jsonMapper = JsonMapperBuilder::new() + ->withDocBlockAnnotationsMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->build(); + $json = (object) [ + 'date' => '1987-10-03T14:14:32+01:00', + ]; + + $result = $jsonMapper->mapToClass($json, WithConstructorReadOnlyDateTimePropertyPromotion::class); + + self::assertInstanceOf(WithConstructorReadOnlyDateTimePropertyPromotion::class, $result); + self::assertInstanceOf(\DateTimeImmutable::class, $result->date); + } + + /** + * @requires PHP >= 8.1 + */ + public function testCanHandleCustomConstructorsWithEmptyArray(): void + { + $factoryRegistry = new FactoryRegistry(); + $mapper = JsonMapperBuilder::new() + ->withDocBlockAnnotationsMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->build(); + + $json = (object) [ + 'simples' => [], + ]; + + $result = $mapper->mapToClass($json, WithConstructorReadOnlyPropertyCollection::class); + + self::assertInstanceOf(WithConstructorReadOnlyPropertyCollection::class, $result); + self::assertIsArray($result->simples); + self::assertEmpty($result->simples); + } + + /** + * @requires PHP >= 8.1 + */ + public function testCanHandleCustomConstructorsWithArray(): void + { + $factoryRegistry = new FactoryRegistry(); + $mapper = JsonMapperBuilder::new() + ->withDocBlockAnnotationsMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->build(); + + $status = 5; + $json = (object) [ + 'simples' => [(object) ['status' => $status]], + ]; + + $result = $mapper->mapToClass($json, WithConstructorReadOnlyPropertyCollection::class); + + self::assertInstanceOf(WithConstructorReadOnlyPropertyCollection::class, $result); + self::assertIsArray($result->simples); + self::assertInstanceOf(WithConstructorReadOnlyPropertySimple::class, $result->simples[0]); + self::assertEquals($status, $result->simples[0]->status); + } + + /** + * @requires PHP >= 8.1 + */ + public function testHandleCollectionMapping(): void + { + $factoryRegistry = FactoryRegistry::withNativePhpClassesAdded(); + $propertyMapper = new PropertyMapper($factoryRegistry); + $jsonMapper = JsonMapperBuilder::new() + ->withDocBlockAnnotationsMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->withPropertyMapper($propertyMapper) + ->build(); + + $json = (object) ['items' => [ + (object) ['name' => 'foo', 'orders' => [(object) ['name' => 'bar']]], + (object) ['name' => 'foo', 'orders' => [(object) ['name' => 'bar']]], + ]]; + + $result = $jsonMapper->mapToClass($json, FooCollection::class); + + static::assertInstanceOf(FooCollection::class, $result); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsDateTimeTypesTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsDateTimeTypesTest.php new file mode 100644 index 000000000..876559319 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsDateTimeTypesTest.php @@ -0,0 +1,29 @@ +bestFit(); + $object = new Popo(); + $json = (object) ['date' => '2020-03-08 12:42:14']; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertEquals(new \DateTimeImmutable('2020-03-08 12:42:14'), $object->date); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsDetectionOfIncorrectScalarValues.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsDetectionOfIncorrectScalarValues.php new file mode 100644 index 000000000..158364044 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsDetectionOfIncorrectScalarValues.php @@ -0,0 +1,36 @@ +withScalarCaster(new StrictScalarCaster()) + ->build(); + $mapper = JsonMapperBuilder::new() + ->withDocBlockAnnotationsMiddleware() + ->withTypedPropertiesMiddleware() + ->withPropertyMapper($propertyMapper) + ->build(); + $object = new SimpleObject(); + $json = (object) ['name' => 42]; + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Expected type string, type integer given'); + $mapper->mapObject($json, $object); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsEnumsTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsEnumsTest.php new file mode 100644 index 000000000..4aeeb76b1 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsEnumsTest.php @@ -0,0 +1,77 @@ += 8.1 + */ + public function testItCanMapAnEnumType(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Php81\BlogPost(); + $json = (object) ['status' => 'draft']; + + $mapper->mapObject($json, $object); + + self::assertSame(Php81\Status::DRAFT, $object->status); + } + + /** + * @requires PHP >= 8.1 + */ + public function testItCanMapToAnArrayOfEnumType(): void + { + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new class { + /** @var Php81\Status[] */ + public $states; + }; + $json = (object) ['states' => ['draft', 'archived']]; + + $mapper->mapObject($json, $object); + + self::assertSame([Php81\Status::DRAFT, Php81\Status::ARCHIVED], $object->states); + } + + /** + * @requires PHP >= 8.1 + */ + public function testItCanMapToAnArrayOfEnumTypeWithArrayTypeHint(): void + { + $mapper = (new JsonMapperFactory())->bestFit(); + $json = (object) ['states' => ['draft', 'archived']]; + + $object = $mapper->mapToClass($json, Php81\GroupOfStatuses::class); + + self::assertSame([Php81\Status::DRAFT, Php81\Status::ARCHIVED], $object->states); + } + + /** + * @requires PHP >= 8.1 + */ + public function testItCanMapToAMultiDimensionalArrayOfEnumType(): void + { + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new class { + /** @var Php81\Status[][] */ + public $states; + }; + $json = (object) ['states' => [['draft', 'archived'], ['published']]]; + + $mapper->mapObject($json, $object); + + self::assertSame([[Php81\Status::DRAFT, Php81\Status::ARCHIVED], [Php81\Status::PUBLISHED]], $object->states); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingFromJsonArrayTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingFromJsonArrayTest.php new file mode 100644 index 000000000..53f0a93a5 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingFromJsonArrayTest.php @@ -0,0 +1,31 @@ +bestFit(); + $object = new SimpleObject(); + $json = [(object) ['name' => 'one'], (object) ['name' => 'two']]; + + // Act + $result = $mapper->mapArray($json, $object); + + // Assert + self::assertContainsOnly(SimpleObject::class, $result); + self::assertSame('one', $result[0]->getName()); + self::assertSame('two', $result[1]->getName()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingFromJsonStringTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingFromJsonStringTest.php new file mode 100644 index 000000000..f80b1a312 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingFromJsonStringTest.php @@ -0,0 +1,82 @@ +bestFit(); + $object = new Popo(); + $json = '{"name": "one"}'; + + // Act + $mapper->mapObjectFromString($json, $object); + + // Assert + self::assertSame('one', $object->name); + } + + public function testItCanMapArrayFromString(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new SimpleObject(); + $json = '[{"name": "one"}, {"name": "two"}]'; + + // Act + $result = $mapper->mapArrayFromString($json, $object); + + // Assert + self::assertContainsOnly(SimpleObject::class, $result); + self::assertSame('one', $result[0]->getName()); + self::assertSame('two', $result[1]->getName()); + } + + public function testItWillThrowAnExceptionWhenMappingArrayFromStringWithJsonObject(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Popo(); + $json = '{"name": "one"}'; + $this->expectException(\RuntimeException::class); + + // Act + $mapper->mapArrayFromString($json, $object); + } + + public function testItWillThrowAnExceptionWhenMappingObjectFromStringWithJsonArray(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Popo(); + $json = '[{"name": "one"}, {"name": "two"}]'; + $this->expectException(\RuntimeException::class); + + // Act + $mapper->mapObjectFromString($json, $object); + } + + public function testItWillThrowExceptionOnInvalidJson(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Popo(); + $jsonString = '{"name": one}'; + $this->expectException(\JsonException::class); + + // Act + $mapper->mapObjectFromString($jsonString, $object); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToArrayTypesTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToArrayTypesTest.php new file mode 100644 index 000000000..ea7df7ed7 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToArrayTypesTest.php @@ -0,0 +1,113 @@ += 7.4 + */ + public function testItCanMapArrayOfObjectWithTypeHintAndDocBlock(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $response = new Php74\Response(); + $json = (object) ['data' => [(object) ['name' => 'John Doe'], (object) ['name' => 'Jane Doe']]]; + + // Act + $mapper->mapObject($json, $response); + + // Assert + self::assertCount(2, $response->data); + self::assertContainsOnlyInstancesOf(Php74\Popo::class, $response->data); + $john = new Php74\Popo(); + $john->name = 'John Doe'; + $jane = new Php74\Popo(); + $jane->name = 'Jane Doe'; + self::assertEquals([$john, $jane], $response->data); + } + + public function testItCanMapAnObjectWithAnArrayOfScalarValues(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new ComplexObject(); + $one = new SimpleObject(); + $one->setName('ONE'); + $two = new SimpleObject(); + $two->setName('TWO'); + $json = (object) ['children' => [(object) ['name' => 'ONE'], (object) ['name' => 'TWO']]]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertIsArray($object->getChildren()); + self::assertContainsOnly(SimpleObject::class, $object->getChildren()); + self::assertEquals([$one, $two], $object->getChildren()); + } + + public function testItHandlesPropertyDocumentedAsArrayProvidedAsObject(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Popo(); + $json = (object) ['notes' => (object) ['one' => __METHOD__, 'two' => __CLASS__]]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame(['one' => __METHOD__, 'two' => __CLASS__], $object->notes); + } + + public function testItHandlesPropertyDocumentedAsListOfObjects(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $json = json_encode((object) ['objects' => [(object) ['name' => 'ONE'], (object) ['name' => 'TWO']]]); + + // Act + $object = $mapper->mapToClassFromString($json, ListOfSimpleObjects::class); + + // Assert + $result = new ListOfSimpleObjects(); + $result->objects = [ + new SimpleObject('ONE'), + new SimpleObject('TWO'), + ]; + self::assertEquals($result, $object); + } + + public function testItHandlesPropertyDocumentedAsArrayGtLtOfObjects(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $json = json_encode((object) ['objects' => [(object) ['name' => 'ONE'], (object) ['name' => 'TWO']]]); + + // Act + $object = $mapper->mapToClassFromString($json, ArrayOfSimpleObjects::class); + + // Assert + $result = new ArrayOfSimpleObjects(); + $result->objects = [ + new SimpleObject('ONE'), + new SimpleObject('TWO'), + ]; + self::assertEquals($result, $object); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToInterfaceAndAbstractClassTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToInterfaceAndAbstractClassTest.php new file mode 100644 index 000000000..02007be41 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToInterfaceAndAbstractClassTest.php @@ -0,0 +1,55 @@ +addFactory($className, new ShapeInstanceFactory()); + $propertyMapper = PropertyMapperBuilder::new() + ->withNonInstantiableTypeResolver($interfaceResolver) + ->build(); + $mapper = JsonMapperBuilder::new() + ->withDocBlockAnnotationsMiddleware() + ->withNamespaceResolverMiddleware() + ->withPropertyMapper($propertyMapper) + ->build(); + + $mapper->mapObjectFromString('{"shape": {"type": "square", "width": 5, "length": 6}}', $object); + + self::assertInstanceOf(Square::class, $object->shape); + self::assertEquals(5, $object->shape->width); + self::assertEquals(6, $object->shape->length); + } + + public function nonInstantiableTypes(): array + { + return [ + 'interface' => [new IShapeWrapper(), IShape::class], + 'abstract' => [new AbstractShapeWrapper(), AbstractShape::class] + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToMixedTypeTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToMixedTypeTest.php new file mode 100644 index 000000000..cf49f157f --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToMixedTypeTest.php @@ -0,0 +1,43 @@ +bestFit(); + $object = new ComplexObject(); + $json = (object) ['mixedParam' => $value]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame($value, $object->mixedParam); + } + + public function scalarValueDataTypes(): array + { + return [ + 'string' => ['Some string'], + 'boolean' => [true], + 'integer' => [1], + 'float' => [M_PI], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToNullableTypesTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToNullableTypesTest.php new file mode 100644 index 000000000..88e21bcdf --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToNullableTypesTest.php @@ -0,0 +1,63 @@ +bestFit(); + $object = new class { + /** @var int[]|null */ + public $numbers; + }; + $json = (object) ['numbers' => null]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertNull($object->numbers); + } + + public function testItCanMapANullableArrayOfObjects(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new class { + /** @var \DateTime[]|null */ + public $dates; + }; + $json = (object) ['dates' => null]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertNull($object->dates); + } + + public function testItCanMapAnObjectWithANullClassAttribute(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new ComplexObject(); + $json = (object) ['child' => null]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertNull($object->getChild()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToParentPrivateProperties.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToParentPrivateProperties.php new file mode 100644 index 000000000..7e811e57c --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToParentPrivateProperties.php @@ -0,0 +1,46 @@ +withDocBlockAnnotationsMiddleware() + ->build(); + $json = (object) ['name' => __METHOD__]; + + // Act + $object = $mapper->mapToClass($json, SimpleObjectExtension::class); + + // Assert + self::assertSame(__METHOD__, $object->getName()); + } + + public function testItCanMapAnObjectUsingPrivatePropertiesInTheParentClassUsingTypedProperties(): void + { + // Arrange + $mapper = JsonMapperBuilder::new() + ->withTypedPropertiesMiddleware() + ->build(); + $json = (object) ['name' => __METHOD__]; + + // Act + $object = $mapper->mapToClass($json, TypedSimpleObjectExtension::class); + + // Assert + self::assertSame(__METHOD__, $object->getName()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToPublicPropertiesTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToPublicPropertiesTest.php new file mode 100644 index 000000000..7db3dcf5a --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToPublicPropertiesTest.php @@ -0,0 +1,43 @@ +bestFit(); + $object = new Popo(); + $json = (object) ['name' => __METHOD__]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame(__METHOD__, $object->name); + } + + public function testItAppliesTypeCastingWhenMappingAnObjectUsingAPublicProperty(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Popo(); + $json = (object) ['name' => 42]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame('42', $object->name); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToPublicSettersTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToPublicSettersTest.php new file mode 100644 index 000000000..71c56125f --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToPublicSettersTest.php @@ -0,0 +1,43 @@ +bestFit(); + $object = new SimpleObject(); + $json = (object) ['name' => __METHOD__]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame(__METHOD__, $object->getName()); + } + + public function testItAppliesTypeCastingWhenMappingAnObjectUsingAPublicSetter(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new SimpleObject(); + $json = (object) ['name' => 42]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame('42', $object->getName()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToStdClass.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToStdClass.php new file mode 100644 index 000000000..665fe6b3f --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsMappingToStdClass.php @@ -0,0 +1,48 @@ +bestFit(); + $response = new class { + /** @var \stdClass */ + public $properties; + }; + $json = (object) ['properties' => (object) ['one' => 1, 'two' => 2]]; + + // Act + $mapper->mapObject($json, $response); + + // Assert + self::assertEquals((object) ['one' => 1, 'two' => 2], $response->properties); + } + + public function testItCanMapCustomClassWithStdClassPropertyFromArray(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $response = new class { + /** @var \stdClass */ + public $properties; + }; + $json = (object) ['properties' => ['one' => 1, 'two' => 2]]; + + // Act + $mapper->mapObject($json, $response); + + // Assert + self::assertEquals((object) ['one' => 1, 'two' => 2], $response->properties); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsNamespacesTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsNamespacesTest.php new file mode 100644 index 000000000..d4f38d244 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsNamespacesTest.php @@ -0,0 +1,48 @@ += 7.4 + */ + public function testItMapsClassFromTheSameNamespace(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Php74\PopoWrapper(); + $json = (object) ['wrappee' => (object) ['name' => 'two']]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertNotNull($object->wrappee); + self::assertSame('two', $object->wrappee->name); + } + + public function testItCanMapAnObjectWithACustomClassAttributeFromAnotherNamespace(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new ComplexObject(); + $json = (object) ['user' => (object) ['name' => __METHOD__]]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame(__METHOD__, $object->getUser()->getName()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsTypedPropertiesTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsTypedPropertiesTest.php new file mode 100644 index 000000000..8fb6a2ddb --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsTypedPropertiesTest.php @@ -0,0 +1,66 @@ += 7.4 + */ + public function testItCanMapAnObjectWithTypedProperties(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Php74\Popo(); + $json = (object) ['name' => __METHOD__]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame(__METHOD__, $object->name); + } + + /** + * @requires PHP >= 7.4 + */ + public function testItAppliesTypeCastingMappingAnObjectWithTypedProperties(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Php74\Popo(); + $json = (object) ['name' => 42]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame('42', $object->name); + } + + /** + * @requires PHP >= 7.4 + */ + public function testItHandlesPropertyTypedAsArray(): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new Php74\Popo(); + $json = (object) ['friends' => [__METHOD__, __CLASS__]]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame([__METHOD__, __CLASS__], $object->friends); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsUnionTypesTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsUnionTypesTest.php new file mode 100644 index 000000000..03873949f --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsUnionTypesTest.php @@ -0,0 +1,87 @@ +bestFit(); + $object = new class { + /** @var int|float|string|bool */ + public $value; + }; + $json = (object) ['value' => $value]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertEquals($value, $object->value); + } + + /** + * @dataProvider scalarValueDataTypes + * @param int|float|string|bool $value + */ + public function testItCanMapAnArrayOfScalarUnionType($value): void + { + // Arrange + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new class { + /** @var int[]|float[]|string[]|bool[] */ + public $values; + }; + $json = (object) ['values' => [$value]]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertEquals([$value], $object->values); + } + + public function testItCanMapAUnionOfUnixTimeStampAndDateTimeWithDateTimeObject(): void + { + // Arrange + $now = new \DateTime(); + $mapper = (new JsonMapperFactory())->bestFit(); + $object = new class { + /** + * Either a unix timestamp (int) or a date time object + * @var int|\DateTime + */ + public $moment; + }; + $json = (object) ['moment' => $now->format('Y-m-d\TH:i:s.uP')]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertEquals($now, $object->moment); + } + + public function scalarValueDataTypes(): array + { + return [ + 'string' => ['Some string'], + 'boolean' => [true], + 'integer' => [1], + 'float' => [M_PI], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsUserDefinedClassesTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsUserDefinedClassesTest.php new file mode 100644 index 000000000..a22d83829 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsUserDefinedClassesTest.php @@ -0,0 +1,31 @@ +bestFit(); + $object = new ComplexObject(); + $json = (object) ['child' => (object) ['name' => __METHOD__]]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + $child = $object->getChild(); + self::assertNotNull($child); + self::assertSame(__METHOD__, $child->getName()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsValueTransformation.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsValueTransformation.php new file mode 100644 index 000000000..2a206a09c --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsValueTransformation.php @@ -0,0 +1,57 @@ +withMiddleware(new ValueTransformation('strtolower')) + ->withDocBlockAnnotationsMiddleware() + ->withNamespaceResolverMiddleware() + ->build(); + $object = new Popo(); + $json = (object) ['name' => __METHOD__]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame(strtolower(__METHOD__), $object->name); + } + + public function testItCanTransformValuesWithStaticFunctionCallable(): void + { + // Arrange + $now = new \DateTimeImmutable('2021-10-28T20:40:15+01:00'); + $mapper = JsonMapperBuilder::new() + ->withMiddleware(new ValueTransformation(static function ($key, $value) { + return $key === 'date' ? base64_decode($value) : $value; + }, true)) + ->withDocBlockAnnotationsMiddleware() + ->withNamespaceResolverMiddleware() + ->build(); + $object = new Popo(); + $json = (object) ['name' => __METHOD__, 'date' => base64_encode($now->format('c'))]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertSame(__METHOD__, $object->name); + self::assertEquals($now, $object->date); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsVariadicSetterTest.php b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsVariadicSetterTest.php new file mode 100644 index 000000000..1bffe1249 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/FeatureSupportsVariadicSetterTest.php @@ -0,0 +1,43 @@ +bestFit(); + $object = new class { + /** @var Popo[] */ + private $popos; + + public function setPopos(Popo ...$popos): void + { + $this->popos = $popos; + } + + public function getPopos(): array + { + return $this->popos; + } + }; + $json = (object) ['popos' => [(object) ['name' => 'one'], (object) ['name' => 'two']]]; + + // Act + $mapper->mapObject($json, $object); + + // Assert + self::assertEquals($object->getPopos()[0]->name, $json->popos[0]->name); + self::assertEquals($object->getPopos()[1]->name, $json->popos[1]->name); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Middleware/Attributes/AttributeTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Middleware/Attributes/AttributeTest.php new file mode 100644 index 000000000..b9c028434 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Middleware/Attributes/AttributeTest.php @@ -0,0 +1,37 @@ += 8.0 + */ + public function testAttributesMiddlewareDoesMapFrom(): void + { + $cache = new NullCache(); + $mapper = JsonMapperBuilder::new() + ->withAttributesMiddleware() + ->withTypedPropertiesMiddleware($cache) + ->build(); + $object = new AttributePopo(); + $json = '{"Identifier": 42, "UserName": "John Doe"}'; + + $mapper->mapObjectFromString($json, $object); + + self::assertSame(42, $object->id); + self::assertSame('John Doe', $object->name); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Middleware/CaseConversionTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Middleware/CaseConversionTest.php new file mode 100644 index 000000000..6fdc0cdcb --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Middleware/CaseConversionTest.php @@ -0,0 +1,29 @@ +default(); + $mapper->push(new CaseConversion(TextNotation::STUDLY_CAPS(), TextNotation::CAMEL_CASE())); + $object = new ComplexObject(); + $json = (object) ['User' => (object) ['Name' => __METHOD__]]; + + $mapper->mapObject($json, $object); + + self::assertEquals(__METHOD__, $object->getUser()->getName()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Middleware/DebuggerTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Middleware/DebuggerTest.php new file mode 100644 index 000000000..b8fddd541 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Middleware/DebuggerTest.php @@ -0,0 +1,38 @@ +default(); + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('debug') + ->with( + 'Current state attributes passed through JsonMapper middleware', + $this->logicalAnd( + $this->arrayHasKey('json'), + $this->arrayHasKey('object'), + $this->arrayHasKey('propertyMap') + ) + ); + $mapper->push(new Debugger($logger)); + $json = (object) ['User' => (object) ['Name' => __METHOD__]]; + $object = new ComplexObject(); + + $mapper->mapObject($json, $object); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Middleware/FinalCallbackTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Middleware/FinalCallbackTest.php new file mode 100644 index 000000000..8ca43162d --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Middleware/FinalCallbackTest.php @@ -0,0 +1,78 @@ +default(); + $mapper->push(new FinalCallback($callback)); + $object = new ComplexObject(); + $json = (object) ['user' => (object) ['name' => __METHOD__]]; + + $mapper->mapObject($json, $object); + + self::assertEquals(1, $invocationCount); + } + + /** + * @covers \JsonMapper\Middleware\FinalCallback + */ + public function testCallbackIsInvokedEvenAfterException(): void + { + $invocationCount = 0; + $callback = static function () use (&$invocationCount) { + $invocationCount++; + }; + $mapper = (new JsonMapperFactory())->default(); + $mapper->push(new FinalCallback($callback)); + $object = new ComplexObject(); + $invalidJson = (object) ['user' => (object) ['name' => new \DateTime()]]; + + try { + $mapper->mapObject($invalidJson, $object); + self::fail('Should throw exception!'); + } catch (\Throwable $e) { + self::assertEquals('Object of class DateTime could not be converted to string', $e->getMessage()); + } + + $json = (object) ['user' => (object) ['name' => __METHOD__]]; + $mapper->mapObject($json, $object); + + self::assertEquals(1, $invocationCount); + } + + /** + * @covers \JsonMapper\Middleware\FinalCallback + */ + public function testCallbackIsInvokedForEveryPass(): void + { + $invocationCount = 0; + $callback = static function () use (&$invocationCount) { + $invocationCount++; + }; + $mapper = (new JsonMapperFactory())->default(); + $mapper->push(new FinalCallback($callback, false)); + $object = new ComplexObject(); + $json = (object) ['user' => (object) ['name' => __METHOD__]]; + + $mapper->mapObject($json, $object); + + self::assertEquals(2, $invocationCount); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Namespaces/One/Example.php b/vendor/json-mapper/json-mapper/tests/Integration/Namespaces/One/Example.php new file mode 100644 index 000000000..263dcd1fb --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Namespaces/One/Example.php @@ -0,0 +1,13 @@ += 7.4 + * @coversNothing + */ + public function jsonMapperDoesNotLookupClassNameCorrectlyInSameNameSpaceForReusedBuiltinClassName(): void + { + $mapper = (new JsonMapperFactory())->bestFit(); + $collection = new DateTimeCollection(); + $data = (object) ['items' => [ + (object) ['date' => '2021-08-31', 'time' => '11:23'], + (object) ['date' => '2021-08-31', 'time' => '11:24'], + ]]; + + $mapper->mapObject($data, $collection); + + self::assertEquals( + new DateTimeCollection( + new DateTime($data->items[0]->date, $data->items[0]->time), + new DateTime($data->items[1]->date, $data->items[1]->time) + ), + $collection + ); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug113RegressionTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug113RegressionTest.php new file mode 100644 index 000000000..0164f3c4d --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug113RegressionTest.php @@ -0,0 +1,79 @@ +bestFit(); + + $object = new class { + /** @var string[][] */ + public $value; + }; + $json = (object) [ + 'value' => [ + 'a' => [ + 'key-a' => 'value-a' + ], + 'b' => [ + 'key-b' => 'value-b' + ] + ] + ]; + + $result = $mapper->mapObject($json, $object); + + self::assertArrayHasKey('a', $result->value); + self::assertArrayHasKey('key-a', $result->value['a']); + self::assertEquals('value-a', $result->value['a']['key-a']); + self::assertArrayHasKey('b', $result->value); + self::assertArrayHasKey('key-b', $result->value['b']); + self::assertEquals('value-b', $result->value['b']['key-b']); + } + + /** + * @test + * @coversNothing + * @requires PHP >= 8.1 + */ + public function jsonMapperCanHandleMultidimensionalArraysWithEnumType(): void + { + $mapper = (new JsonMapperFactory())->bestFit(); + + $object = new class { + /** @var Status[][] */ + public $value; + }; + $json = (object) [ + 'value' => [ + 'a' => [ + 'status-a' => 'draft' + ], + 'b' => [ + 'status-b' => 'published' + ] + ] + ]; + + $result = $mapper->mapObject($json, $object); + + self::assertArrayHasKey('a', $result->value); + self::assertArrayHasKey('status-a', $result->value['a']); + self::assertEquals(Status::DRAFT, $result->value['a']['status-a']); + self::assertArrayHasKey('b', $result->value); + self::assertArrayHasKey('status-b', $result->value['b']); + self::assertEquals(Status::PUBLISHED, $result->value['b']['status-b']); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug116RegressionTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug116RegressionTest.php new file mode 100644 index 000000000..0bd65b7b6 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug116RegressionTest.php @@ -0,0 +1,29 @@ += 7.4 + * @coversNothing + */ + public function jsonMapperDoesImportLookupForParentClasses(): void + { + $mapper = (new JsonMapperFactory())->bestFit(); + $expected = 'some-type-string'; + $data = (object) ['meta' => (object) ['type' => $expected]]; + $object = new Test(); + + $mapper->mapObject($data, $object); + + self::assertSame($expected, $object->meta->type); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug120RegressionTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug120RegressionTest.php new file mode 100644 index 000000000..8d44cf5a4 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug120RegressionTest.php @@ -0,0 +1,31 @@ +bestFit(); + $object = new class { + /** @var string|string[] */ + public $value; + }; + $json = (object) [ + 'value' => [] + ]; + + $mapper->mapObject($json, $object); + + self::assertEquals($json->value, $object->value); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug140RegressionTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug140RegressionTest.php new file mode 100644 index 000000000..316f0c1eb --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug140RegressionTest.php @@ -0,0 +1,63 @@ + (object) ['source' => 'the moon']]; + $wrapper = new ObjectWrapper($class); + $propertyMap = new ValueObjects\PropertyMap(); + $mapper = $this->createMock(JsonMapperInterface::class); + $docBlockMiddleware = new Middleware\DocBlockAnnotations(new NullCache()); + $docBlockMiddleware->handle($json, $wrapper, $propertyMap, $mapper); + + $sut = new Middleware\NamespaceResolver(new NullCache()); + $sut->handle($json, $wrapper, $propertyMap, $mapper); + + self::assertEquals( + [ + new ValueObjects\PropertyType( + Middleware\Attributes\MapFrom::class, + ValueObjects\ArrayInformation::notAnArray() + ) + ], + $propertyMap->getProperty('maps')->getPropertyTypes() + ); + } + + public function classDataProvider() + { + return [ + 'without alias' => [ + new class { + /** @var Middleware\Attributes\MapFrom */ + public $maps; + } + ], + 'with alias' => [ + new class { + /** @var MiddlewareUsingAnAlias\Attributes\MapFrom */ + public $maps; + } + ], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug146RegressionTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug146RegressionTest.php new file mode 100644 index 000000000..c307c5734 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug146RegressionTest.php @@ -0,0 +1,42 @@ +withDocBlockAnnotationsMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->build(); + + $json = (object) [ + 'date' => null, + ]; + + $result = $mapper->mapToClass($json, DatePopoWithConstructor::class); + + self::assertNull($result->getDate()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug162RegressionTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug162RegressionTest.php new file mode 100644 index 000000000..3d2ac707e --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug162RegressionTest.php @@ -0,0 +1,48 @@ += 7.4 + */ + public function testConstructorIsCalledWhenAvailable() + { + $data = [ + (object) [ + 'id' => 1, + 'title' => 'Article 1', + 'tags' => [(object) ['id' => 1, 'name' => 'Tag 1'], (object) ['id' => 2, 'name' => 'Tag 2']] + ], + (object) [ + 'id' => 2, + 'title' => 'Article 2', + 'tags' => [(object) ['id' => 1, 'name' => 'Tag 1'], (object) ['id' => 3, 'name' => 'Tag 3']] + ] + ]; + $mapper = (new JsonMapperFactory())->bestFit(); + + $result = $mapper->mapArray($data, new Article()); + + self::assertEquals((new Tag())->getConfiguration(), $result[0]->tags[0]->getConfiguration()); + self::assertEquals((new Tag())->getConfiguration(), $result[0]->tags[1]->getConfiguration()); + self::assertEquals((new Tag())->getConfiguration(), $result[1]->tags[0]->getConfiguration()); + self::assertEquals((new Tag())->getConfiguration(), $result[1]->tags[1]->getConfiguration()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug169RegressionTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug169RegressionTest.php new file mode 100644 index 000000000..c381b9fbc --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug169RegressionTest.php @@ -0,0 +1,43 @@ += 8.0 + */ + public function canHandleVarNotationOnPublicProperty(): void + { + $factoryRegistry = new FactoryRegistry(); + $mapper = JsonMapperBuilder::new() + ->withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->withDocBlockAnnotationsMiddleware() + ->withTypedPropertiesMiddleware() + ->withNamespaceResolverMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->build(); + + $json = json_encode((object)['popo' => [ + (object) ['name' => 'John Doe'], + ]]); + + $result = $mapper->mapToClassFromString($json, PopoWithConstructAndDocblock::class); + + self::assertInstanceOf(PopoWithConstructAndDocblock::class, $result); + self::assertArrayHasKey(0, $result->popo); + self::assertInstanceOf(Popo::class, $result->popo[0]); + self::assertSame('John Doe', $result->popo[0]->name); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug195RegressionTest.php b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug195RegressionTest.php new file mode 100644 index 000000000..d85e200e9 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Integration/Regression/Bug195RegressionTest.php @@ -0,0 +1,56 @@ +withPropertyMapper(new PropertyMapper($factoryRegistry)) + ->withDocBlockAnnotationsMiddleware() + ->withTypedPropertiesMiddleware() + ->withNamespaceResolverMiddleware() + ->withObjectConstructorMiddleware($factoryRegistry) + ->build(); + + $object = new class { + /** @var array,classmap?:array,"psr-4":array>}>|array{} */ + public $overrideAutoload; + }; + $json = json_encode( + (object) [ + 'overrideAutoload' => (object) [ + 'files' => ['file1', 'file2'], + 'classmap' => null, + 'psr-4' => ['one', 'two'] + ], + ], + JSON_THROW_ON_ERROR + ); + + $result = $mapper->mapToClassFromString($json, get_class($object)); + + $object->overrideAutoload = [ + 'files' => ['file1', 'file2'], + 'classmap' => null, + 'psr-4' => ['one', 'two'] + ]; + self::assertInstanceOf(get_class($object), $result); + self::assertEquals($object, $result); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Builders/PropertyBuilderTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Builders/PropertyBuilderTest.php new file mode 100644 index 000000000..d8bcbb1c6 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Builders/PropertyBuilderTest.php @@ -0,0 +1,129 @@ +setName('enabled') + ->addType('boolean', ArrayInformation::notAnArray()) + ->setIsNullable(true) + ->setVisibility(Visibility::PRIVATE()) + ->build(); + + $this->assertThatProperty($property) + ->hasName('enabled') + ->hasType('boolean', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PRIVATE()) + ->isNullable(); + } + + /** + * @covers \JsonMapper\Builders\PropertyBuilder + */ + public function testCanBuildPropertyWithAllPropertiesSetUsingSetTypes(): void + { + $property = PropertyBuilder::new() + ->setName('enabled') + ->setTypes( + new PropertyType('string', ArrayInformation::singleDimension()), + new PropertyType('int', ArrayInformation::notAnArray()) + ) + ->setIsNullable(true) + ->setVisibility(Visibility::PRIVATE()) + ->build(); + + $this->assertThatProperty($property) + ->hasName('enabled') + ->hasType('string', ArrayInformation::singleDimension()) + ->hasType('int', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PRIVATE()) + ->isNullable(); + } + + /** + * @covers \JsonMapper\Builders\PropertyBuilder + */ + public function testHasAnyTypeReturnFalseWhenNoTypeIsSet(): void + { + $builder = PropertyBuilder::new(); + + self::assertFalse($builder->hasAnyType()); + } + + /** + * @covers \JsonMapper\Builders\PropertyBuilder + */ + public function testHasAnyTypeReturnFalseWhenATypeIsSet(): void + { + $builder = PropertyBuilder::new() + ->addType('mixed', ArrayInformation::notAnArray()); + + self::assertTrue($builder->hasAnyType()); + } + + /** + * @covers \JsonMapper\Builders\PropertyBuilder + */ + public function testCanAddMultipleTypes(): void + { + $property = PropertyBuilder::new() + ->setName('test') + ->setVisibility(Visibility::PRIVATE()) + ->setIsNullable(false) + ->addTypes( + new PropertyType('int', ArrayInformation::notAnArray()), + new PropertyType('string', ArrayInformation::notAnArray()) + )->addTypes( + new PropertyType('float', ArrayInformation::notAnArray()) + )->build(); + + $this->assertThatProperty($property) + ->hasType('int', ArrayInformation::notAnArray()) + ->hasType('string', ArrayInformation::notAnArray()) + ->hasType('float', ArrayInformation::notAnArray()); + } + + /** + * @covers \JsonMapper\Builders\PropertyBuilder + */ + public function testCanGetTypesFromBuilder(): void + { + $builder = PropertyBuilder::new() + ->addTypes( + new PropertyType('int', ArrayInformation::notAnArray()), + new PropertyType('string', ArrayInformation::notAnArray()) + )->addTypes( + new PropertyType('float', ArrayInformation::notAnArray()) + ); + + $result = $builder->getTypes(); + + self::assertEquals( + [ + new PropertyType('int', ArrayInformation::notAnArray()), + new PropertyType('string', ArrayInformation::notAnArray()), + new PropertyType('float', ArrayInformation::notAnArray()), + ], + $result + ); + } + + +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Builders/PropertyMapperBuilderTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Builders/PropertyMapperBuilderTest.php new file mode 100644 index 000000000..a363bc9c5 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Builders/PropertyMapperBuilderTest.php @@ -0,0 +1,121 @@ +setName('user') + ->addType(UserWithConstructor::class, ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['user' => (object) ['id' => 1234, 'name' => 'John Doe']]; + $object = new UserWithConstructorParent(); + $wrapped = new ObjectWrapper($object); + $classFactoryRegistry = FactoryRegistry::withNativePhpClassesAdded(); + $classFactoryRegistry->addFactory( + UserWithConstructor::class, + static function ($params) { + return new UserWithConstructor($params->id, $params->name); + } + ); + $propertyMapper = PropertyMapperBuilder::new() + ->withClassFactoryRegistry($classFactoryRegistry) + ->build(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals(new UserWithConstructor(1234, 'John Doe'), $object->user); + } + + /** @covers \JsonMapper\Builders\PropertyMapperBuilder */ + public function testItCanBuildWithNonInstantiableTypeResolver(): void + { + $json = (object) ['shape' => (object) ['type' => 'square', 'width' => 5, 'length' => 6]]; + $object = new IShapeWrapper(); + $wrapped = new ObjectWrapper($object); + $type = IShape::class; + $property = PropertyBuilder::new() + ->setName('shape') + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->addType($type, ArrayInformation::notAnArray()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $nonInstantiableTypeResolver = new FactoryRegistry(); + $nonInstantiableTypeResolver->addFactory(IShape::class, new ShapeInstanceFactory()); + $propertyMapper = PropertyMapperBuilder::new() + ->withNonInstantiableTypeResolver($nonInstantiableTypeResolver) + ->build(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals(new Square(5, 6), $object->shape); + } + + /** @covers \JsonMapper\Builders\PropertyMapperBuilder */ + public function testItCanBuildWithScalarCaster(): void + { + $json = (object) ['name' => 42]; + $object = new SimpleObject(); + $wrapped = new ObjectWrapper($object); + $property = PropertyBuilder::new() + ->setName('name') + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->addType('string', ArrayInformation::notAnArray()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $propertyMapper = PropertyMapperBuilder::new() + ->withScalarCaster(new StrictScalarCaster()) + ->build(); + $mapper = JsonMapperBuilder::new()->withDocBlockAnnotationsMiddleware()->build(); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Expected type string, type integer given'); + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $mapper); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Cache/ArrayCacheTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Cache/ArrayCacheTest.php new file mode 100644 index 000000000..fe6d267ac --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Cache/ArrayCacheTest.php @@ -0,0 +1,22 @@ +createMock(AbstractMiddleware::class); + $namedMiddleware = new NamedMiddleware($middleware, 'some-name'); + + self::assertSame($middleware, $namedMiddleware->getMiddleware()); + self::assertSame('some-name', $namedMiddleware->getName()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Enums/VisibilityTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Enums/VisibilityTest.php new file mode 100644 index 000000000..4728840ec --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Enums/VisibilityTest.php @@ -0,0 +1,65 @@ +getProperty('id'); + + $visibility = Visibility::fromReflectionProperty($reflectionProperty); + + self::assertEquals(Visibility::PUBLIC(), $visibility); + } + + /** + * @covers \JsonMapper\Enums\Visibility + */ + public function testDetectsProtectedPropertyAsProtected(): void + { + $reflectionClass = new \ReflectionClass( + new class { + /** @var int */ + protected $id; + } + ); + $reflectionProperty = $reflectionClass->getProperty('id'); + + $visibility = Visibility::fromReflectionProperty($reflectionProperty); + + self::assertEquals(Visibility::PROTECTED(), $visibility); + } + + /** + * @covers \JsonMapper\Enums\Visibility + */ + public function testDetectsPrivatePropertyAsPrivate(): void + { + $reflectionClass = new \ReflectionClass( + new class { + /** @var int */ + private $id; + } + ); + $reflectionProperty = $reflectionClass->getProperty('id'); + + $visibility = Visibility::fromReflectionProperty($reflectionProperty); + + self::assertEquals(Visibility::PRIVATE(), $visibility); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Exception/BuilderExceptionTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Exception/BuilderExceptionTest.php new file mode 100644 index 000000000..1900234f8 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Exception/BuilderExceptionTest.php @@ -0,0 +1,27 @@ +getMessage()); + } + + /** @covers \JsonMapper\Exception\BuilderException */ + public function testForBuildingWithoutMiddleware(): void + { + $exception = BuilderException::forBuildingWithoutMiddleware(); + + self::assertStringContainsString('without middleware', $exception->getMessage()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Exception/ClassFactoryExceptionTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Exception/ClassFactoryExceptionTest.php new file mode 100644 index 000000000..4d73d10ed --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Exception/ClassFactoryExceptionTest.php @@ -0,0 +1,31 @@ +getMessage()); + } + + /** + * @covers \JsonMapper\Exception\ClassFactoryException + */ + public function testForMissingClassnameReturnsCorrectException(): void + { + $exception = ClassFactoryException::forMissingClassname(__CLASS__); + + self::assertStringContainsString(__CLASS__, $exception->getMessage()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Exception/TypeErrorTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Exception/TypeErrorTest.php new file mode 100644 index 000000000..ff3b6358c --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Exception/TypeErrorTest.php @@ -0,0 +1,39 @@ +createTypeError(__METHOD__, 'object', '', 1, '$object'); + + self::assertEquals( + sprintf( + '%s(): Argument #1 ($object) must be of type object, string given, called in %s on line %d', + __METHOD__, + __FILE__, + __LINE__ - 7 + ), + $e->getMessage() + ); + } + + private function createTypeError( + string $method, + string $expectedType, + $argument, + int $argumentNumber, + string $argumentName + ): TypeError { + return TypeError::forArgument($method, $expectedType, $argument, $argumentNumber, $argumentName); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Handler/FactoryRegistryTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Handler/FactoryRegistryTest.php new file mode 100644 index 000000000..540f6ac48 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Handler/FactoryRegistryTest.php @@ -0,0 +1,110 @@ +addFactory(__CLASS__, static function () { + }); + + self::assertTrue($classFactoryRegistry->hasFactory(__CLASS__)); + } + + /** + * @covers \JsonMapper\Handler\FactoryRegistry + */ + public function testHasFactoryReturnsFalseWhenNoFactoryRegistered(): void + { + $classFactoryRegistry = new FactoryRegistry(); + + self::assertFalse($classFactoryRegistry->hasFactory(__CLASS__)); + } + + /** + * @covers \JsonMapper\Handler\FactoryRegistry + */ + public function testAddFactoryThrowsExceptionWhenDuplicateClassNameIsAdded(): void + { + $classFactoryRegistry = new FactoryRegistry(); + $classFactoryRegistry->addFactory(__CLASS__, static function () { + }); + + $this->expectExceptionObject(ClassFactoryException::forDuplicateClassname(__CLASS__)); + + $classFactoryRegistry->addFactory(__CLASS__, static function () { + }); + } + + /** + * @covers \JsonMapper\Handler\FactoryRegistry + */ + public function testCreateReturnsValueFromCallable(): void + { + $classFactoryRegistry = new FactoryRegistry(); + $object = new \stdClass(); + $classFactoryRegistry->addFactory(__CLASS__, static function () use ($object) { + return $object; + }); + + self::assertSame($object, $classFactoryRegistry->create(__CLASS__, new \stdClass())); + } + + /** + * @covers \JsonMapper\Handler\FactoryRegistry + */ + public function testCreateCanHandleLeadingSlash(): void + { + $classFactoryRegistry = new FactoryRegistry(); + $object = new \stdClass(); + $classFactoryRegistry->addFactory(\DateTimeImmutable::class, static function () use ($object) { + return $object; + }); + + self::assertSame($object, $classFactoryRegistry->create('\DateTimeImmutable', new \stdClass())); + } + + /** + * @covers \JsonMapper\Handler\FactoryRegistry + */ + public function testCreateThrowsExceptionForMissingFactory(): void + { + $classFactoryRegistry = new FactoryRegistry(); + + $this->expectExceptionObject(ClassFactoryException::forMissingClassname(__CLASS__)); + + $classFactoryRegistry->create(__CLASS__, new \stdClass()); + } + + /** + * @covers \JsonMapper\Handler\FactoryRegistry + */ + public function testWithNativePhpClassesAddedAddsFactoriesForNativeClasses(): void + { + $classFactoryRegistry = FactoryRegistry::withNativePhpClassesAdded(); + + self::assertTrue($classFactoryRegistry->hasFactory(\DateTime::class)); + self::assertTrue($classFactoryRegistry->hasFactory(\DateTimeImmutable::class)); + self::assertTrue($classFactoryRegistry->hasFactory(\stdClass::class)); + self::assertEquals(new \DateTime('today'), $classFactoryRegistry->create(\DateTime::class, 'today')); + self::assertEquals( + new \DateTimeImmutable('today'), + $classFactoryRegistry->create(\DateTimeImmutable::class, 'today') + ); + self::assertEquals( + (object) ['one' => 1, 'two' => 2], + $classFactoryRegistry->create(\stdClass::class, ['one' => 1, 'two' => 2]) + ); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Handler/PropertyMapperTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Handler/PropertyMapperTest.php new file mode 100644 index 000000000..7c7e558e4 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Handler/PropertyMapperTest.php @@ -0,0 +1,963 @@ + __FILE__]; + $object = new \stdClass(); + $wrapped = new ObjectWrapper($object); + + $propertyMapper->__invoke($json, $wrapped, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertEquals(new \stdClass(), $object); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + * @dataProvider scalarValueDataTypes + * @param mixed $value + */ + public function testPublicScalarValueIsSet(string $type, $value): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType($type, ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $json = (object) ['value' => $value]; + $object = new class { + /** @var mixed */ + public $value; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $this->createMock(JsonMapperInterface::class)); + + self::assertEquals($value, $object->value); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testPublicBuiltinClassIsSet(): void + { + $property = PropertyBuilder::new() + ->setName('createdAt') + ->addType(\DateTimeImmutable::class, ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $now = new \DateTimeImmutable(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $json = (object) ['createdAt' => $now->format('Y-m-d\TH:i:s.uP')]; + $object = new class { + /** @var \DateTimeImmutable */ + public $createdAt; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $this->createMock(JsonMapperInterface::class)); + + self::assertEquals($now, $object->createdAt); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testPublicCustomClassIsSet(): void + { + $property = PropertyBuilder::new() + ->setName('child') + ->addType(SimpleObject::class, ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PRIVATE()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $jsonMapper->expects(self::once()) + ->method('mapToClass') + ->with((object) ['name' => __FUNCTION__], SimpleObject::class) + ->willReturnCallback(static function (\stdClass $json, string $className) { + $object = new $className(); + $object->setName($json->name); + + return $object; + }); + $json = (object) ['child' => (object) ['name' => __FUNCTION__]]; + $object = new ComplexObject(); + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + $child = $object->getChild(); + self::assertNotNull($child); + self::assertEquals(__FUNCTION__, $child->getName()); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testPublicScalarValueArrayIsSet(): void + { + $fileProperty = PropertyBuilder::new() + ->setName('ids') + ->addType('int', ArrayInformation::singleDimension()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($fileProperty); + $json = (object) ['ids' => [1, 2, 3]]; + $object = new class { + /** @var int[] */ + public $ids; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $this->createMock(JsonMapperInterface::class)); + + self::assertEquals([1, 2, 3], $object->ids); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testPublicScalarValueMultiDimensionalArrayIsSet(): void + { + $fileProperty = PropertyBuilder::new() + ->setName('ids') + ->addType('int', ArrayInformation::multiDimension(2)) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($fileProperty); + $value = [1 => [2, 3], 2 => [2, 3], 3 => [2, 3]]; + $json = (object) ['ids' => $value]; + $object = new class { + /** @var int[][] */ + public $ids; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $this->createMock(JsonMapperInterface::class)); + + self::assertEquals($value, $object->ids); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testPublicCustomClassArrayIsSet(): void + { + $property = PropertyBuilder::new() + ->setName('children') + ->addType(SimpleObject::class, ArrayInformation::singleDimension()) + ->setIsNullable(false) + ->setVisibility(Visibility::PRIVATE()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $jsonMapper->expects(self::exactly(2)) + ->method('mapToClass') + ->with((object) ['name' => __FUNCTION__], SimpleObject::class) + ->willReturnCallback(static function (\stdClass $json, string $className) { + $object = new $className(); + $object->setName($json->name); + + return $object; + }); + $json = (object) ['children' => [(object) ['name' => __FUNCTION__], (object) ['name' => __FUNCTION__]]]; + $object = new ComplexObject(); + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertCount(2, $object->getChildren()); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testPublicCustomClassMultidimensionalArrayIsSet(): void + { + $property = PropertyBuilder::new() + ->setName('children') + ->addType(SimpleObject::class, ArrayInformation::multiDimension(2)) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $jsonMapper->expects(self::exactly(4)) + ->method('mapToClass') + ->with(self::isInstanceOf(\stdClass::class), SimpleObject::class) + ->willReturnCallback(static function ($json, string $className) { + $object = new $className(); + $object->setName($json->name); + + return $object; + }); + $json = (object) ['children' => [ + [ + (object) ['name' => __NAMESPACE__], + (object) ['name' => __FUNCTION__] + ], + [ + (object) ['name' => __NAMESPACE__], + (object) ['name' => __FUNCTION__] + ], + ]]; + $object = new class { + /** @var SimpleObject[] */ + public $children; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertCount(2, $object->children); + self::assertEquals( + [ + [new SimpleObject(__NAMESPACE__), new SimpleObject(__FUNCTION__)], + [new SimpleObject(__NAMESPACE__), new SimpleObject(__FUNCTION__)], + ], + $object->children + ); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testArrayPropertyIsCasted(): void + { + $property = PropertyBuilder::new() + ->setName('notes') + ->addType('string', ArrayInformation::singleDimension()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['notes' => (object) ['note_one' => __FUNCTION__, 'note_two' => __CLASS__]]; + $object = new Popo(); + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals(['note_one' => __FUNCTION__, 'note_two' => __CLASS__], $object->notes); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testCanMapPropertyWithClassFactory(): void + { + $property = PropertyBuilder::new() + ->setName('user') + ->addType(UserWithConstructor::class, ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['user' => (object) ['id' => 1234, 'name' => 'John Doe']]; + $object = new UserWithConstructorParent(); + $wrapped = new ObjectWrapper($object); + $classFactoryRegistry = FactoryRegistry::withNativePhpClassesAdded(); + $classFactoryRegistry->addFactory( + UserWithConstructor::class, + static function ($params) { + return new UserWithConstructor($params->id, $params->name); + } + ); + $propertyMapper = new PropertyMapper($classFactoryRegistry); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals(new UserWithConstructor(1234, 'John Doe'), $object->user); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testCanMapPropertyAsArrayWithClassFactory(): void + { + $property = PropertyBuilder::new() + ->setName('user') + ->addType(UserWithConstructor::class, ArrayInformation::singleDimension()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['user' => [ + 0 => (object) ['id' => 1234, 'name' => 'John Doe'], + 1 => (object) ['id' => 5678, 'name' => 'Jane Doe'] + ]]; + $object = new UserWithConstructorParent(); + $wrapped = new ObjectWrapper($object); + $classFactoryRegistry = FactoryRegistry::withNativePhpClassesAdded(); + $classFactoryRegistry->addFactory( + UserWithConstructor::class, + static function ($params) { + return new UserWithConstructor($params->id, $params->name); + } + ); + $propertyMapper = new PropertyMapper($classFactoryRegistry); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals( + [new UserWithConstructor(1234, 'John Doe'), new UserWithConstructor(5678, 'Jane Doe')], + $object->user + ); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testCanMapPropertyAsMultiDimensionalArrayWithClassFactory(): void + { + $property = PropertyBuilder::new() + ->setName('userHistory') + ->addType(UserWithConstructor::class, ArrayInformation::multiDimension(2)) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['userHistory' => [ + '2021-02-03' => [ + 'original' => (object) ['id' => 1234, 'name' => 'John Doe'], + 'new' => (object) ['id' => 1234, 'name' => 'Johnathan Doe'], + ], + '2022-08-16' => [ + 'original' => (object) ['id' => 1234, 'name' => 'Johnathan Doe'], + 'new' => (object) ['id' => 1234, 'name' => 'J. Doe'], + ], + ]]; + $object = new UserWithConstructorParent(); + $wrapped = new ObjectWrapper($object); + $classFactoryRegistry = FactoryRegistry::withNativePhpClassesAdded(); + $classFactoryRegistry->addFactory( + UserWithConstructor::class, + static function ($params) { + return new UserWithConstructor($params->id, $params->name); + } + ); + $propertyMapper = new PropertyMapper($classFactoryRegistry); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals( + [ + '2021-02-03' => [ + 'original' => new UserWithConstructor(1234, 'John Doe'), + 'new' => new UserWithConstructor(1234, 'Johnathan Doe'), + ], + '2022-08-16' => [ + 'original' => new UserWithConstructor(1234, 'Johnathan Doe'), + 'new' => new UserWithConstructor(1234, 'J. Doe'), + ], + ], + $object->userHistory + ); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testCanMapUnionPropertyAsArrayWithClassFactory(): void + { + $property = PropertyBuilder::new() + ->setName('user') + ->addType(UserWithConstructor::class, ArrayInformation::singleDimension()) + ->addType(\DateTime::class, ArrayInformation::singleDimension()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['user' => [ + 0 => (object) ['id' => 1234, 'name' => 'John Doe'], + 1 => (object) ['id' => 5678, 'name' => 'Jane Doe'] + ]]; + $object = new UserWithConstructorParent(); + $wrapped = new ObjectWrapper($object); + $classFactoryRegistry = FactoryRegistry::withNativePhpClassesAdded(); + $classFactoryRegistry->addFactory( + UserWithConstructor::class, + static function ($params) { + return new UserWithConstructor($params->id, $params->name); + } + ); + $propertyMapper = new PropertyMapper($classFactoryRegistry); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals( + [new UserWithConstructor(1234, 'John Doe'), new UserWithConstructor(5678, 'Jane Doe')], + $object->user + ); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testWillSetNullOnNullablePropertyIfNullProvided(): void + { + $property = PropertyBuilder::new() + ->setName('child') + ->addType(SimpleObject::class, ArrayInformation::notAnArray()) + ->setIsNullable(true) + ->setVisibility(Visibility::PRIVATE()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['child' => null]; + $object = new ComplexObject(); + $object->setChild(new SimpleObject()); + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertNull($object->getChild()); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testPublicNotNullableCustomClassThrowsException(): void + { + $property = PropertyBuilder::new() + ->setName('child') + ->addType(SimpleObject::class, ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PRIVATE()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['child' => null]; + $object = new ComplexObject(); + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $this->expectException(\RuntimeException::class); + $message = "Null provided in json where " . ComplexObject::class . "::child doesn't allow null value"; + $this->expectExceptionMessage($message); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testNonPublicPropertyWithoutSetterThrowsException(): void + { + $property = PropertyBuilder::new() + ->setName('number') + ->addType('int', ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PRIVATE()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['number' => 42]; + $object = new PrivatePropertyWithoutSetter(); + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $this->expectException(\RuntimeException::class); + $message = PrivatePropertyWithoutSetter::class . "::number is non-public and no setter method was found"; + $this->expectExceptionMessage($message); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + * @dataProvider scalarValueDataTypes + * @param mixed $value + */ + public function testItCanMapAScalarUnionType(string $type, $value): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType('int', ArrayInformation::notAnArray()) + ->addType('double', ArrayInformation::notAnArray()) + ->addType('float', ArrayInformation::notAnArray()) + ->addType('string', ArrayInformation::notAnArray()) + ->addType('bool', ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['value' => $value]; + $object = new class { + /** @var int|double|float|string|bool */ + public $value; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals($value, $object->value); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + * @dataProvider scalarValueDataTypes + * @param mixed $value + */ + public function testItCanMapAnArrayOfScalarUnionType(string $type, $value): void + { + $property = PropertyBuilder::new() + ->setName('values') + ->addType('int', ArrayInformation::singleDimension()) + ->addType('float', ArrayInformation::singleDimension()) + ->addType('string', ArrayInformation::singleDimension()) + ->addType('bool', ArrayInformation::singleDimension()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['values' => [$value]]; + $object = new class { + /** @var int[]|float[]|string[]|bool[] */ + public $values; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals([$value], $object->values); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testItCanMapAUnionOfUnixTimeStampAndDateTimeWithDateTimeObject(): void + { + $now = new \DateTime(); + $property = PropertyBuilder::new() + ->setName('moment') + ->addType('int', ArrayInformation::notAnArray()) + ->addType(\DateTime::class, ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $json = (object) ['moment' => $now->format('Y-m-d\TH:i:s.uP')]; + $object = new class { + /** @var int|\DateTime */ + public $moment; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals($now, $object->moment); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testItCanMapAUnionOfCustomClasses(): void + { + $property = PropertyBuilder::new() + ->setName('user') + ->addType(User::class, ArrayInformation::notAnArray()) + ->addType(Popo::class, ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $json = (object) ['user' => (object) ['id' => 42, 'name' => 'John Doe']]; + $object = new class { + /** @var User|Popo */ + public $user; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals($json->user->id, $object->user->getId()); + self::assertEquals($json->user->name, $object->user->getName()); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testItCanMapAUnionOfCustomClassesAsArray(): void + { + $property = PropertyBuilder::new() + ->setName('users') + ->addType(User::class, ArrayInformation::singleDimension()) + ->addType(Popo::class, ArrayInformation::singleDimension()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $json = (object) ['users' => [0 => (object) ['id' => 42, 'name' => 'John Doe']]]; + $object = new class { + /** @var User[]|Popo[] */ + public $users; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals($json->users[0]->id, $object->users[0]->getId()); + self::assertEquals($json->users[0]->name, $object->users[0]->getName()); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testItCanMapIfNoTypeDetailIsAvailable(): void + { + $property = PropertyBuilder::new() + ->setName('id') + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $json = (object) ['id' => 42]; + $object = new class { + public $id; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals($json->id, $object->id); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testItCanMapUsingAVariadicSetterFunction(): void + { + $property = PropertyBuilder::new() + ->setName('numbers') + ->setIsNullable(false) + ->setVisibility(Visibility::PRIVATE()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $json = (object) ['numbers' => [1, 2, 3, 4, 5]]; + $object = new class { + /** @var int[] */ + private $numbers; + + public function getNumbers(): array + { + return $this->numbers; + } + + public function setNumbers(int ...$numbers): void + { + $this->numbers = $numbers; + } + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals([1, 2, 3, 4, 5], $object->getNumbers()); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testItThrowsAnExceptionWhenInterfaceTypeCantBeCreated(): void + { + $json = (object) ['shape' => (object) ['type' => 'square', 'width' => 5, 'length' => 6]]; + $object = new IShapeWrapper(); + $wrapped = new ObjectWrapper($object); + $type = IShape::class; + $property = PropertyBuilder::new() + ->setName('shape') + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->addType($type, ArrayInformation::notAnArray()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage("Unable to resolve un-instantiable {$type} as no factory was registered"); + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testItCanMapInterfaceType(): void + { + $json = (object) ['shape' => (object) ['type' => 'square', 'width' => 5, 'length' => 6]]; + $object = new IShapeWrapper(); + $wrapped = new ObjectWrapper($object); + $type = IShape::class; + $property = PropertyBuilder::new() + ->setName('shape') + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->addType($type, ArrayInformation::notAnArray()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $nonInstantiableTypeResolver = new FactoryRegistry(); + $nonInstantiableTypeResolver->addFactory(IShape::class, new ShapeInstanceFactory()); + + $propertyMapper = new PropertyMapper(null, $nonInstantiableTypeResolver); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals(new Square(5, 6), $object->shape); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + * @requires PHP >= 8.1 + */ + public function testItCanMapEnumType(): void + { + $json = (object) ['status' => 'draft']; + $object = new \JsonMapper\Tests\Implementation\Php81\BlogPost(); + $wrapped = new ObjectWrapper($object); + $property = PropertyBuilder::new() + ->setName('status') + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->addType(\JsonMapper\Tests\Implementation\Php81\Status::class, ArrayInformation::notAnArray()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $expected = new BlogPost(); + $expected->status = \JsonMapper\Tests\Implementation\Php81\Status::DRAFT; + + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals($expected, $object); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + * @requires PHP >= 8.1 + */ + public function testItCanMapEnumArrayType(): void + { + $json = (object) ['historicStates' => ['draft', 'published']]; + $object = new \JsonMapper\Tests\Implementation\Php81\BlogPost(); + $wrapped = new ObjectWrapper($object); + $property = PropertyBuilder::new() + ->setName('historicStates') + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->addType(\JsonMapper\Tests\Implementation\Php81\Status::class, ArrayInformation::singleDimension()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $expected = new BlogPost(); + $expected->historicStates = [ + \JsonMapper\Tests\Implementation\Php81\Status::DRAFT, + \JsonMapper\Tests\Implementation\Php81\Status::PUBLISHED + ]; + + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals($expected, $object); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + * @requires PHP >= 8.1 + */ + public function testItCanMapEnumMultiDimensionalArrayType(): void + { + $json = (object) ['historicStatesByDate' => [ + '2022-08-16' => ['draft', 'published'], + '2022-08-22' => ['archived'] + ]]; + $object = new \JsonMapper\Tests\Implementation\Php81\BlogPost(); + $wrapped = new ObjectWrapper($object); + $property = PropertyBuilder::new() + ->setName('historicStatesByDate') + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->addType(\JsonMapper\Tests\Implementation\Php81\Status::class, ArrayInformation::multiDimension(2)) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $expected = new BlogPost(); + $expected->historicStatesByDate = [ + '2022-08-16' => [ + \JsonMapper\Tests\Implementation\Php81\Status::DRAFT, + \JsonMapper\Tests\Implementation\Php81\Status::PUBLISHED, + ], + '2022-08-22' => [ + \JsonMapper\Tests\Implementation\Php81\Status::ARCHIVED, + ] + ]; + + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals($expected, $object); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testItThrowsExceptionForNonExistingClass(): void + { + $property = PropertyBuilder::new() + ->setName('child') + ->addType("\Some\Non\Existing\Class", ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PRIVATE()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $json = (object) ['child' => (object) ['name' => __FUNCTION__]]; + $object = new ComplexObject(); + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Unable to map to \Some\Non\Existing\Class'); + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + } + + /** + * @covers \JsonMapper\Handler\PropertyMapper + */ + public function testItCanMapAnEmptyArrayForArrayType(): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType('string', ArrayInformation::notAnArray()) + ->addType('string', ArrayInformation::singleDimension()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $json = (object) ['value' => []]; + $object = new class { + /** @var string|string[] */ + public $value; + }; + $wrapped = new ObjectWrapper($object); + $propertyMapper = new PropertyMapper(); + $jsonMapper = (new JsonMapperFactory())->create($propertyMapper, new DocBlockAnnotations(new NullCache())); + + $propertyMapper->__invoke($json, $wrapped, $propertyMap, $jsonMapper); + + self::assertEquals([], $object->value); + } + + public function scalarValueDataTypes(): array + { + return [ + 'string' => ['string', 'Some string'], + 'boolean' => ['bool', true], + 'integer' => ['int', 1], + 'float' => ['float', M_PI], + 'double' => ['double', M_PI], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Handler/ValueFactoryTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Handler/ValueFactoryTest.php new file mode 100644 index 000000000..e6f20af46 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Handler/ValueFactoryTest.php @@ -0,0 +1,543 @@ +setName('value') + ->addType('integer', ArrayInformation::singleDimension()) + ->addType('string', ArrayInformation::singleDimension()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + + $buildValue = $valueFactory->build($jsonMapper, $property, []); + + self::assertEquals([], $buildValue); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider combinationsOfScalarValueDataTypeAndArrayInformation + * @param mixed $value + */ + public function testItCanMapPropertyWithoutTypeInfo(string $type, $value): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals($value, $buildValue); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider combinationsOfScalarValueDataTypeAndArrayInformation + * @param mixed $value + */ + public function testItCanMapScalarPropertyWithSingleType( + string $type, + $value, + ArrayInformation $arrayInformation + ): void { + $property = PropertyBuilder::new() + ->setName('value') + ->addType($type, $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals($value, $buildValue); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @requires PHP >= 8.1 + * @dataProvider arrayInformationDataProvider + */ + public function testItCanMapEnumPropertyWithSingleType(ArrayInformation $arrayInformation): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType(Status::class, $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + $value = $this->wrapValueWithArrayInformation('archived', $arrayInformation); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals( + $this->wrapValueWithArrayInformation(Status::from('archived'), $arrayInformation), + $buildValue + ); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider arrayInformationDataProvider + */ + public function testItCanMapWithClassFactoryHavingAvailableFactoryForASingleType( + ArrayInformation $arrayInformation + ): void { + $property = PropertyBuilder::new() + ->setName('value') + ->addType(\DateTimeImmutable::class, $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory( + new ScalarCaster(), + FactoryRegistry::withNativePhpClassesAdded(), + new FactoryRegistry() + ); + $value = $this->wrapValueWithArrayInformation('2000-01-01T00:00:00', $arrayInformation); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals( + $this->wrapValueWithArrayInformation(new \DateTimeImmutable('2000-01-01T00:00:00'), $arrayInformation), + $buildValue + ); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider arrayInformationDataProvider + */ + public function testItCanMapToAnObjectUsingMapperForASingleType(ArrayInformation $arrayInformation): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType(SimpleObject::class, $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + $value = $this->wrapValueWithArrayInformation((object) ['name' => 'John Doe'], $arrayInformation); + $jsonMapper->expects($this->once()) + ->method('mapToClass') + ->with($this->isInstanceOf(\stdClass::class), SimpleObject::class) + ->willReturnCallback(function ($data) { + return new SimpleObject($data->name); + }); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals( + $this->wrapValueWithArrayInformation(new SimpleObject('John Doe'), $arrayInformation), + $buildValue + ); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider arrayInformationDataProvider + */ + public function testItCanMapArrayOfScalarValuesForUnionType(ArrayInformation $arrayInformation): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType('mixed', $arrayInformation) + ->addType('float', $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + $value = $this->wrapValueWithArrayInformation(mt_rand() / mt_getrandmax(), $arrayInformation); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals($value, $buildValue); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @requires PHP >= 8.1 + * @dataProvider arrayInformationDataProvider + */ + public function testItCanMapArrayOfEnumValuesForUnionType(ArrayInformation $arrayInformation): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType(Status::class, $arrayInformation) + ->addType('integer', $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + $value = $this->wrapValueWithArrayInformation('archived', $arrayInformation); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals( + $this->wrapValueWithArrayInformation(Status::from('archived'), $arrayInformation), + $buildValue + ); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider arrayInformationDataProvider + */ + public function testItCanMapArrayWithClassFactoryHavingAvailableFactoryForUnionTYpe( + ArrayInformation $arrayInformation + ): void { + $property = PropertyBuilder::new() + ->setName('value') + ->addType('integer', $arrayInformation) + ->addType(\DateTimeImmutable::class, $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory( + new ScalarCaster(), + FactoryRegistry::withNativePhpClassesAdded(), + new FactoryRegistry() + ); + $value = $this->wrapValueWithArrayInformation('2000-01-01T00:00:00', $arrayInformation); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals( + $this->wrapValueWithArrayInformation(new \DateTimeImmutable('2000-01-01T00:00:00'), $arrayInformation), + $buildValue + ); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider arrayInformationDataProvider + */ + public function testItCanMapArrayWithMapperForUnionType( + ArrayInformation $arrayInformation + ): void { + $property = PropertyBuilder::new() + ->setName('value') + ->addType('integer', $arrayInformation) + ->addType(Popo::class, $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $jsonMapper->method('mapToClass') + ->with(self::isInstanceOf(\stdClass::class), Popo::class) + ->willReturnCallback(function (\stdClass $data, string $className) { + $popo = new Popo(); + $popo->name = $data->name; + + return $popo; + }); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + $expected = new Popo(); + $expected->name = 'Jane Doe'; + $value = $this->wrapValueWithArrayInformation((object) ['name' => $expected->name], $arrayInformation); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals( + $this->wrapValueWithArrayInformation($expected, $arrayInformation), + $buildValue + ); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider arrayInformationDataProvider + */ + public function testItCanMapUnInstantiableTypeForSingleType( + ArrayInformation $arrayInformation + ): void { + $property = PropertyBuilder::new() + ->setName('value') + ->addType(IShape::class, $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $jsonMapper->method('mapObject') + ->with(self::isInstanceOf(\stdClass::class), self::isInstanceOf(Circle::class)) + ->willReturnCallback(function (\stdClass $data, Circle $object) { + $object->radius = $data->radius; + return $object; + }); + $nonInstantiableTypeResolver = new FactoryRegistry(); + $nonInstantiableTypeResolver->addFactory(IShape::class, function (\stdClass $data) { + if ($data->radius) { + return new Circle(); + } + }); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), $nonInstantiableTypeResolver); + $radius = random_int(1, 12); + $value = $this->wrapValueWithArrayInformation((object) ['radius' => $radius], $arrayInformation); + $expected = new Circle(); + $expected->radius = $radius; + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals( + $this->wrapValueWithArrayInformation($expected, $arrayInformation), + $buildValue + ); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider arrayInformationDataProvider + */ + public function testThrowsExceptionForUnInstantiableTypeForSingleTypeThatCanNotBeResolved( + ArrayInformation $arrayInformation + ): void { + $property = PropertyBuilder::new() + ->setName('value') + ->addType(IShape::class, $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $nonInstantiableTypeResolver = new FactoryRegistry(); + $nonInstantiableTypeResolver->addFactory(IShape::class, function () { + throw new ClassFactoryException(); + }); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), $nonInstantiableTypeResolver); + + $value = $this->wrapValueWithArrayInformation((object) ['radius' => random_int(1, 12)], $arrayInformation); + + $this->expectException(\RuntimeException::class); + $valueFactory->build($jsonMapper, $property, $value); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + * @dataProvider arrayInformationDataProvider + */ + public function testItCanMapArrayWithMapperForSingleType( + ArrayInformation $arrayInformation + ): void { + $property = PropertyBuilder::new() + ->setName('value') + ->addType(Popo::class, $arrayInformation) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $jsonMapper->method('mapToClass') + ->with(self::isInstanceOf(\stdClass::class), Popo::class) + ->willReturnCallback(function (\stdClass $data, string $className) { + $popo = new Popo(); + $popo->name = $data->name; + + return $popo; + }); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + $expected = new Popo(); + $expected->name = 'Jane Doe'; + $value = $this->wrapValueWithArrayInformation((object) ['name' => $expected->name], $arrayInformation); + + $buildValue = $valueFactory->build($jsonMapper, $property, $value); + + self::assertEquals( + $this->wrapValueWithArrayInformation($expected, $arrayInformation), + $buildValue + ); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + */ + public function testItThrowsExceptionForNonExistingClass(): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType('\A\B\C\D\E\F', ArrayInformation::notAnArray()) + ->setIsNullable(false) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + $value = $this->wrapValueWithArrayInformation((object) [], ArrayInformation::notAnArray()); + + $this->expectException(\Exception::class); + $valueFactory->build($jsonMapper, $property, $value); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + */ + public function testItCanMapToNullWhenPropertyIsNullable(): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType(\DateTimeImmutable::class, ArrayInformation::notAnArray()) + ->setIsNullable(true) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + + $this->assertNull($valueFactory->build($jsonMapper, $property, null)); + } + + /** + * @covers \JsonMapper\Handler\ValueFactory + */ + public function testItCanMapToNullableClassFromUnionProperty(): void + { + $property = PropertyBuilder::new() + ->setName('value') + ->addType(\DateTimeImmutable::class, ArrayInformation::notAnArray()) + ->addType(\DateTime::class, ArrayInformation::notAnArray()) + ->setIsNullable(true) + ->setVisibility(Visibility::PUBLIC()) + ->build(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + $valueFactory = new ValueFactory(new ScalarCaster(), new FactoryRegistry(), new FactoryRegistry()); + + $this->assertNull($valueFactory->build($jsonMapper, $property, null)); + } + + + public function scalarValueDataTypes(): array + { + return [ + 'string' => ['string', 'Some string'], + 'boolean' => ['bool', true], + 'integer' => ['int', 1], + 'float' => ['float', M_PI], + 'double' => ['double', M_PI], + ]; + } + + public function combinationsOfScalarValueDataTypeAndArrayInformation(): array + { + $values = []; + foreach ($this->scalarValueDataTypes() as $key => [$type, $value]) { + $values[$key . ' as single type'] = [ + $type, + $value, + ArrayInformation::notAnArray() + ]; + $values[$key . ' as single dimension array'] = [ + $type, + [$value, $value], + ArrayInformation::singleDimension() + ]; + $values[$key . ' as multi dimension array'] = [ + $type, + [[$value], [$value]], + ArrayInformation::multiDimension(2) + ]; + } + + return $values; + } + + public function arrayInformationDataProvider(): array + { + return [ + 'not an array' => [ArrayInformation::notAnArray()], + 'one dimensional array' => [ArrayInformation::singleDimension()], + 'two dimensional array' => [ArrayInformation::multiDimension(2)], + ]; + } + + private function wrapValueWithArrayInformation($value, ArrayInformation $arrayInformation) + { + if ($arrayInformation->equals(ArrayInformation::singleDimension())) { + return [$value]; + } + if ($arrayInformation->equals(ArrayInformation::multiDimension(2))) { + return [[$value]]; + } + + return $value; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Helpers/ClassHelperTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/ClassHelperTest.php new file mode 100644 index 000000000..46b9f5f23 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/ClassHelperTest.php @@ -0,0 +1,81 @@ +getValue())); + } + + /** + * @covers \JsonMapper\Helpers\ClassHelper + * @dataProvider customClassDataProvider + */ + public function testCustomClassesAreSeenAsCustom(string $type): void + { + self::assertTrue(ClassHelper::isCustom($type)); + } + + /** + * @covers \JsonMapper\Helpers\ClassHelper + */ + public function testNonExistingClassNameIsNotSeenAsCustomClass(): void + { + self::assertFalse(ClassHelper::isCustom('asdf')); + } + + /** + * @covers \JsonMapper\Helpers\ClassHelper + */ + public function testScalarTypeIsNotSeenAsCustomClass(): void + { + self::assertFalse(ClassHelper::isCustom(ScalarType::INT()->getValue())); + } + + public function builtinClassDataProvider(): array + { + return [ + \DateTime::class . ' as class constant' => [\DateTime::class], + \DateTime::class . ' as string' => ['\DateTime'], + \DateTimeImmutable::class . ' as class constant' => [\DateTimeImmutable::class], + \DateTimeImmutable::class . ' as string' => ['\DateTimeImmutable'], + ]; + } + + public function customClassDataProvider(): array + { + return [ + ComplexObject::class . ' as class constant' => [ComplexObject::class], + ComplexObject::class . ' as string' => ['\JsonMapper\Tests\Implementation\ComplexObject'], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Helpers/NamespaceHelperTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/NamespaceHelperTest.php new file mode 100644 index 000000000..8e0985258 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/NamespaceHelperTest.php @@ -0,0 +1,72 @@ + ['string'], + 'boolean' => ['boolean'], + 'bool' => ['bool'], + 'integer' => ['integer'], + 'int' => ['int'], + 'double' => ['double'], + 'float' => ['float'], + 'mixed' => ['mixed'], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Helpers/ScalarCasterTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/ScalarCasterTest.php new file mode 100644 index 000000000..d52f2006a --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/ScalarCasterTest.php @@ -0,0 +1,56 @@ +cast(new ScalarType($castTo), $value)); + } + + /** + * @covers \JsonMapper\Helpers\ScalarCaster + */ + public function testCastOperationThrowsExceptionWhenCastOperationNotSupported(): void + { + $caster = new ScalarCaster(); + $extension = new class ('random') extends ScalarType { + protected const RANDOM = 'random'; + + public function __construct() + { + parent::__construct(self::RANDOM); + } + }; + + $this->expectException(\LogicException::class); + $caster->cast($extension, null); + } + + public function castOperationDataProvider(): array + { + return [ + 'cast to string' => [42, 'string', '42'], + 'cast to boolean true' => [1, 'bool', true], + 'cast to boolean false' => [0, 'bool', false], + 'cast to int' => ['42', 'int', 42], + 'cast to float' => ['34.567', 'float', 34.567], + 'cast to mixed' => ['34.567', 'mixed', '34.567'], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Helpers/StrictScalarCasterTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/StrictScalarCasterTest.php new file mode 100644 index 000000000..140e0925e --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/StrictScalarCasterTest.php @@ -0,0 +1,65 @@ +cast(new ScalarType($castTo), $value); + + self::assertEquals($expected, $result); + } + + /** + * @covers \JsonMapper\Helpers\StrictScalarCaster + * @dataProvider castOperationExceptionsDataProvider + * + * @param mixed $value + * @param mixed $expected + */ + public function testCastOperationWithMismatchedValueThrowsException($value, string $castTo, $expected): void + { + $caster = new StrictScalarCaster(); + + $this->expectException(\Exception::class); + $caster->cast(new ScalarType($castTo), $value); + } + + public function castOperationDataProvider(): array + { + return [ + 'cast to string' => ['42', 'string', '42'], + 'cast to boolean' => [true, 'bool', true], + 'cast to int' => [42, 'int', 42], + 'cast to float' => [34.567, 'float', 34.567], + 'cast to mixed' => ['34.567', 'mixed', '34.567'], + ]; + } + + public function castOperationExceptionsDataProvider(): array + { + return [ + 'cast to string' => [42, 'string', '42'], + 'cast to boolean true' => [1, 'bool', true], + 'cast to boolean false' => [0, 'bool', false], + 'cast to int' => ['42', 'int', 42], + 'cast to float' => ['34.567', 'float', 34.567], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Helpers/UseStatementHelperTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/UseStatementHelperTest.php new file mode 100644 index 000000000..07443b6bd --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Helpers/UseStatementHelperTest.php @@ -0,0 +1,88 @@ +expectException(\RuntimeException::class); + eval('class ClassWithoutFile {}'); + UseStatementHelper::getImports(new \ReflectionClass(new \ClassWithoutFile())); + } + + /** + * @covers \JsonMapper\Helpers\UseStatementHelper + */ + public function testGettingImportsWithFileNotReadableThrowsException(): void + { + $fileName = '/some/non/readable/path'; + $reflectionMock = $this->createMock(\ReflectionClass::class); + $reflectionMock->method('isUserDefined')->willReturn(true); + $reflectionMock->method('getFileName')->willReturn($fileName); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage("Unable to read {$fileName}"); + UseStatementHelper::getImports($reflectionMock); + } + + /** + * @covers \JsonMapper\Helpers\UseStatementHelper + */ + public function testGettingImportsWithFileNotProvidingValidAstThrowsException(): void + { + $fileName = tempnam(sys_get_temp_dir(), __METHOD__); + $handle = fopen($fileName, 'wb'); + fwrite($handle, "createMock(\ReflectionClass::class); + $reflectionMock->method('isUserDefined')->willReturn(true); + $reflectionMock->method('getFileName')->willReturn($fileName); + + $this->expectException(PhpFileParseException::class); + $this->expectExceptionMessage("Failed to parse {$fileName}"); + UseStatementHelper::getImports($reflectionMock); + + unlink($fileName); + } + + /** + * @covers \JsonMapper\Helpers\UseStatementHelper + */ + public function testGettingImportsWithBuiltinClassReturnsEmptyArray(): void + { + $imports = UseStatementHelper::getImports(new \ReflectionClass(\stdClass::class)); + + self::assertEquals([], $imports); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/JsonMapperBuilderTest.php b/vendor/json-mapper/json-mapper/tests/Unit/JsonMapperBuilderTest.php new file mode 100644 index 000000000..d1f01bdd3 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/JsonMapperBuilderTest.php @@ -0,0 +1,209 @@ +expectException(BuilderException::class); + + JsonMapperBuilder::new()->build(); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithCustomJsonMapperClassName(): void + { + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withDocBlockAnnotationsMiddleware() + ->build(); + + self::assertInstanceOf(JsonMapper::class, $instance); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testThrowsExceptionSettingJsonMapperClassNameForClassWithoutProperImplementation(): void + { + $this->expectException(BuilderException::class); + + JsonMapperBuilder::new()->withJsonMapperClassName(\stdClass::class); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithCustomPropertyMapper(): void + { + $propertyMapper = PropertyMapperBuilder::new()->build(); + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withPropertyMapper($propertyMapper) + ->withDocBlockAnnotationsMiddleware() + ->build(); + + self::assertSame($propertyMapper, $instance->handler); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithNamespaceResolverMiddleware(): void + { + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withNamespaceResolverMiddleware() + ->build(); + + self::assertCount(1, array_filter($instance->stack, static function (NamedMiddleware $middleware): bool { + return $middleware->getMiddleware() instanceof NamespaceResolver; + })); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithDocBlockAnnotationsMiddleware(): void + { + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withDocBlockAnnotationsMiddleware() + ->build(); + + self::assertCount(1, array_filter($instance->stack, static function (NamedMiddleware $middleware): bool { + return $middleware->getMiddleware() instanceof DocBlockAnnotations; + })); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithTypedPropertiesMiddleware(): void + { + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withTypedPropertiesMiddleware() + ->build(); + + self::assertCount(1, array_filter($instance->stack, static function (NamedMiddleware $middleware): bool { + return $middleware->getMiddleware() instanceof TypedProperties; + })); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithAttributesMiddleware(): void + { + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withAttributesMiddleware() + ->build(); + + self::assertCount(1, array_filter($instance->stack, static function (NamedMiddleware $middleware): bool { + return $middleware->getMiddleware() instanceof Attributes; + })); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithRenameMiddleware(): void + { + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withRenameMiddleware(new Mapping(SimpleObject::class, 'first_name', 'name')) + ->build(); + + self::assertCount(1, array_filter($instance->stack, static function (NamedMiddleware $middleware): bool { + return $middleware->getMiddleware() instanceof Rename; + })); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithCaseConversionMiddleware(): void + { + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withCaseConversionMiddleware(TextNotation::UNDERSCORE(), TextNotation::CAMEL_CASE()) + ->build(); + + self::assertCount(1, array_filter($instance->stack, static function (NamedMiddleware $middleware): bool { + return $middleware->getMiddleware() instanceof CaseConversion; + })); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithDebuggerMiddleware(): void + { + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withDebuggerMiddleware(new NullLogger()) + ->build(); + + self::assertCount(1, array_filter($instance->stack, static function (NamedMiddleware $middleware): bool { + return $middleware->getMiddleware() instanceof Debugger; + })); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithFinalCallbackMiddleware(): void + { + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withFinalCallbackMiddleware(static function () { + }) + ->build(); + + self::assertCount(1, array_filter($instance->stack, static function (NamedMiddleware $middleware): bool { + return $middleware->getMiddleware() instanceof FinalCallback; + })); + } + + /** @covers \JsonMapper\JsonMapperBuilder */ + public function testItCanBuildWithObjectConstructorMiddleware(): void + { + $registry = new FactoryRegistry(); + /** @var JsonMapper $instance */ + $instance = JsonMapperBuilder::new() + ->withJsonMapperClassName(JsonMapper::class) + ->withPropertyMapper(new PropertyMapper($registry)) + ->withObjectConstructorMiddleware($registry) + ->build(); + + self::assertCount(1, array_filter($instance->stack, static function (NamedMiddleware $middleware): bool { + return $middleware->getMiddleware() instanceof Constructor; + })); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/JsonMapperFactoryTest.php b/vendor/json-mapper/json-mapper/tests/Unit/JsonMapperFactoryTest.php new file mode 100644 index 000000000..01188fe93 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/JsonMapperFactoryTest.php @@ -0,0 +1,61 @@ +build(); + $docBlockMiddleware = new DocBlockAnnotations(new NullCache()); + + $mapper = $factory->create($propertyMapper, $docBlockMiddleware); + + self::assertInstanceOf(JsonMapper::class, $mapper); + } + + /** @covers \JsonMapper\JsonMapperFactory */ + public function testCanCreateDefaultMapper(): void + { + $factory = new JsonMapperFactory(); + + $mapper = $factory->default(); + + self::assertInstanceOf(JsonMapper::class, $mapper); + } + + /** @covers \JsonMapper\JsonMapperFactory */ + public function testCanCreateBestFitMapper(): void + { + $factory = new JsonMapperFactory(); + + $mapper = $factory->bestFit(); + + self::assertInstanceOf(JsonMapper::class, $mapper); + } + + /** @covers \JsonMapper\JsonMapperFactory */ + public function testCanCreateMapperWithProvidedJsonMapperBuilder(): void + { + $builder = JsonMapperBuilder::new(); + $builder->withJsonMapperClassName(\JsonMapper\Tests\Implementation\JsonMapper::class); + $factory = new JsonMapperFactory($builder); + + $mapper = $factory->bestFit(); + + self::assertInstanceOf(\JsonMapper\Tests\Implementation\JsonMapper::class, $mapper); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/JsonMapperTest.php b/vendor/json-mapper/json-mapper/tests/Unit/JsonMapperTest.php new file mode 100644 index 000000000..324d85dba --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/JsonMapperTest.php @@ -0,0 +1,447 @@ +handler = new IsCalledHandler(); + $this->middleware = new IsCalledMiddleware(); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testHandlerFromConstructorIsInvokedWhenMappingObject(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $jsonMapper->mapObject(new \stdClass(), new \stdClass()); + + self::assertTrue($this->handler->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testHandlerFromSetterIsInvokedWhenMappingObject(): void + { + $jsonMapper = new JsonMapper(); + $jsonMapper->setPropertyMapper($this->handler); + + $jsonMapper->mapObject(new \stdClass(), new \stdClass()); + + self::assertTrue($this->handler->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testHandlerFromConstructorIsInvokedWhenMappingArray(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $jsonMapper->mapArray([new \stdClass()], new \stdClass()); + + self::assertTrue($this->handler->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testHandlerFromSetterIsInvokedWhenMappingArray(): void + { + $jsonMapper = new JsonMapper(); + $jsonMapper->setPropertyMapper($this->handler); + + $jsonMapper->mapArray([new \stdClass()], new \stdClass()); + + self::assertTrue($this->handler->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testWithoutHandlerThrowsExceptionWhenMappingObject(): void + { + $jsonMapper = new JsonMapper(); + + $this->expectException(\RuntimeException::class); + + $jsonMapper->mapObject(new \stdClass(), new \stdClass()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testPushedMiddlewareIsInvokedWhenMappingObject(): void + { + $jsonMapper = new JsonMapper(new PropertyMapper()); + $jsonMapper->push($this->middleware); + + $jsonMapper->mapObject(new \stdClass(), new \stdClass()); + + self::assertTrue($this->middleware->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testPushedMiddlewareIsInvokedWhenMappingArray(): void + { + $jsonMapper = new JsonMapper(new PropertyMapper()); + $jsonMapper->push($this->middleware); + + $jsonMapper->mapObject(new \stdClass(), new \stdClass()); + + self::assertTrue($this->middleware->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testRemovedMiddlewareIsNotInvokedWhenMappingObject(): void + { + $jsonMapper = new JsonMapper(new PropertyMapper()); + $jsonMapper->push($this->middleware); + $jsonMapper->remove($this->middleware); + + $jsonMapper->mapObject(new \stdClass(), new \stdClass()); + + self::assertFalse($this->middleware->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testRemovedByNameMiddlewareIsNotInvokedWhenMappingObject(): void + { + $jsonMapper = new JsonMapper(new PropertyMapper()); + $jsonMapper->push($this->middleware, __METHOD__); + $jsonMapper->removeByName(__METHOD__); + + $jsonMapper->mapObject(new \stdClass(), new \stdClass()); + + self::assertFalse($this->middleware->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testShiftedMiddlewareIsNotInvokedWhenMappingObject(): void + { + $jsonMapper = new JsonMapper(new PropertyMapper()); + $jsonMapper->unshift($this->middleware, __METHOD__); + $jsonMapper->shift(); + + $jsonMapper->mapObject(new \stdClass(), new \stdClass()); + + self::assertFalse($this->middleware->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testPoppedMiddlewareIsNotInvokedWhenMappingObject(): void + { + $jsonMapper = new JsonMapper(new PropertyMapper()); + $jsonMapper->push($this->middleware, __METHOD__); + $jsonMapper->pop(); + + $jsonMapper->mapObject(new \stdClass(), new \stdClass()); + + self::assertFalse($this->middleware->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapObjectFromStringWithInvalidJsonThrowsException(): void + { + $jsonMapper = new JsonMapper(); + + $this->expectException(\JsonException::class); + $jsonMapper->mapObjectFromString('abcdef...', new \stdClass()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapObjectFromStringWithJsonArrayThrowsException(): void + { + $jsonMapper = new JsonMapper(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Provided string is not a json encoded object'); + $jsonMapper->mapObjectFromString('[1,2,3]', new \stdClass()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapObjectFromStringWithJsonObjectCallsHandler(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $jsonMapper->mapObjectFromString('{}', new \stdClass()); + + self::assertTrue($this->handler->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapArrayFromStringWithInvalidJsonThrowsException(): void + { + $jsonMapper = new JsonMapper(); + + $this->expectException(\JsonException::class); + $jsonMapper->mapArrayFromString('abcdef...', new \stdClass()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapArrayFromStringWithJsonObjectThrowsException(): void + { + $jsonMapper = new JsonMapper(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Provided string is not a json encoded array'); + $jsonMapper->mapArrayFromString('{"one": 1}', new \stdClass()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapArrayFromStringWithJsonArrayCallsHandler(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $jsonMapper->mapArrayFromString('[{"one": 1}]', new \stdClass()); + + self::assertTrue($this->handler->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapObjectWithInvalidObjectThrowsTypeException(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\TypeError::class); + $this->expectExceptionMessage(sprintf( + '%s::mapObject(): Argument #2 ($object) must be of type object, string given, called in %s on line %d', + get_class($jsonMapper), + __FILE__, + __LINE__ + 2 + )); + $jsonMapper->mapObject(new \stdClass(), ''); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapObjectFromStringWithInvalidObjectThrowsTypeException(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\TypeError::class); + $this->expectExceptionMessage(sprintf( + '%s::mapObjectFromString(): Argument #2 ($object) must be of type object, string given, called in %s on line %d', + get_class($jsonMapper), + __FILE__, + __LINE__ + 2 + )); + $jsonMapper->mapObjectFromString('', ''); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapArrayWithInvalidObjectThrowsTypeException(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\TypeError::class); + $this->expectExceptionMessage(sprintf( + '%s::mapArray(): Argument #2 ($object) must be of type object, string given, called in %s on line %d', + get_class($jsonMapper), + __FILE__, + __LINE__ + 2 + )); + $jsonMapper->mapArray([], ''); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapArrayFromStringWithInvalidObjectThrowsTypeException(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\TypeError::class); + $this->expectExceptionMessage(sprintf( + '%s::mapArrayFromString(): Argument #2 ($object) must be of type object, string given, called in %s on line %d', + get_class($jsonMapper), + __FILE__, + __LINE__ + 2 + )); + $jsonMapper->mapArrayFromString('', ''); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassThrowsExceptionOnNonExistingClass(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\TypeError::class); + $jsonMapper->mapToClass(new \stdClass(), 'NonExistingClass'); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassCallsHandler(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $jsonMapper->mapToClass((object) [], \stdClass::class); + + self::assertTrue($this->handler->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassArrayThrowsExceptionOnNonExistingClass(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\TypeError::class); + $jsonMapper->mapToClassArray([], 'NonExistingClass'); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassArrayCallsHandler(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $jsonMapper->mapToClassArray([(object) []], \stdClass::class); + + self::assertTrue($this->handler->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassFromStringThrowsExceptionOnNonExistingClass(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\TypeError::class); + $jsonMapper->mapToClassFromString('', 'NonExistingClass'); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassFromStringWithInvalidJsonThrowsException(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\JsonException::class); + $jsonMapper->mapToClassFromString('{]', \stdClass::class); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassFromStringWithJsonArrayThrowsException(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\RuntimeException::class); + $jsonMapper->mapToClassFromString('[]', \stdClass::class); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassFromStringCallsHandler(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $jsonMapper->mapToClassFromString('{}', \stdClass::class); + + self::assertTrue($this->handler->isCalled()); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassArrayFromStringThrowsExceptionOnNonExistingClass(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\TypeError::class); + $jsonMapper->mapToClassArrayFromString('', 'NonExistingClass'); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassArrayFromStringWithInvalidJsonThrowsException(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\JsonException::class); + $jsonMapper->mapToClassArrayFromString('{]', \stdClass::class); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassArrayFromStringWithJsonObjectThrowsException(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $this->expectException(\RuntimeException::class); + $jsonMapper->mapToClassArrayFromString('{}', \stdClass::class); + } + + /** + * @covers \JsonMapper\JsonMapper + */ + public function testMapToClassArrayFromStringCallsHandler(): void + { + $jsonMapper = new JsonMapper($this->handler); + + $jsonMapper->mapToClassArrayFromString('[{}]', \stdClass::class); + + self::assertTrue($this->handler->isCalled()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/AbstractMiddlewareTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/AbstractMiddlewareTest.php new file mode 100644 index 000000000..e16aa33d7 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/AbstractMiddlewareTest.php @@ -0,0 +1,51 @@ +called; + } + + public function handle( + \stdClass $json, + ObjectWrapper $object, + PropertyMap $propertyMap, + JsonMapperInterface $mapper + ): void { + $this->called = true; + } + }; + $json = new \stdClass(); + $wrappedObject = new ObjectWrapper(new SimpleObject()); + $propertyMap = new PropertyMap(); + $mapper = $this->createMock(JsonMapperInterface::class); + $fn = $middleware->__invoke(static function () { + }); + + $fn($json, $wrappedObject, $propertyMap, $mapper); + + self::assertTrue($middleware->isCalled()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Attributes/AttributesTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Attributes/AttributesTest.php new file mode 100644 index 000000000..3bec09df8 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Attributes/AttributesTest.php @@ -0,0 +1,66 @@ += 8.0 + */ + public function testAttributesMiddlewareDoesMapFrom(): void + { + $json = (object) ['Identifier' => 42, 'UserName' => 'John Doe']; + $object = new AttributePopo(); + $propertyMap = new PropertyMap(); + $middleware = new Attributes(); + $mapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $mapper); + + self::assertEquals((object) ['id' => 42, 'name' => 'John Doe'], $json); + } + + /** + * @covers \JsonMapper\Middleware\Attributes\Attributes + * @requires PHP >= 8.0 + */ + public function testAttributesMiddlewareWithPartialDataDoesMapFrom(): void + { + $json = (object) ['Identifier' => 42]; + $object = new AttributePopo(); + $propertyMap = new PropertyMap(); + $middleware = new Attributes(); + $mapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $mapper); + + self::assertEquals((object) ['id' => 42], $json); + } + + /** + * @covers \JsonMapper\Middleware\Attributes\Attributes + * @requires PHP >= 8.0 + */ + public function testAttributesMiddlewareWhenSourceAndTargetAreEqualDoesntRemoveSource(): void + { + $json = (object) ['email' => 'JohnDoe@example.org']; + $object = new AttributePopo(); + $propertyMap = new PropertyMap(); + $middleware = new Attributes(); + $mapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $mapper); + + self::assertEquals((object) ['email' => 'JohnDoe@example.org'], $json); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Attributes/MapFromTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Attributes/MapFromTest.php new file mode 100644 index 000000000..e756c216b --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Attributes/MapFromTest.php @@ -0,0 +1,22 @@ +source); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/CaseConversionTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/CaseConversionTest.php new file mode 100644 index 000000000..99c7766f2 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/CaseConversionTest.php @@ -0,0 +1,225 @@ + 'placeholder']; + $object = new ObjectWrapper(new \stdClass()); + + $middleware->handle($json, $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertObjectHasProperty($replacementKey, $json); + self::assertEquals('placeholder', $json->$replacementKey); + self::assertObjectNotHasProperty($original, $json); + } + + /** + * @covers \JsonMapper\Middleware\CaseConversion + * @dataProvider possibleTextNotationDataProvider + */ + public function testWillRemainUntouchedOnSameTextNotation(TextNotation $search): void + { + $middleware = new CaseConversion($search, $search); + $key = 'StudlyCase-CamelCase_underscore'; + $json = (object) [$key => 'placeholder']; + $object = new ObjectWrapper(new \stdClass()); + + $middleware->handle($json, $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertObjectHasProperty($key, $json); + } + + /** + * @covers \JsonMapper\Middleware\CaseConversion + * @dataProvider conversionDataProvider + */ + public function testWillRemainUntouchedOnSameReplacementKeyAsOriginalKey( + TextNotation $search, + TextNotation $replacement, + string $original, + string $replacementKey + ): void { + $middleware = new CaseConversion($search, $replacement); + $json = (object) [$replacementKey => 'placeholder']; + $object = new ObjectWrapper(new \stdClass()); + + $middleware->handle($json, $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertObjectHasProperty($replacementKey, $json); + self::assertEquals('placeholder', $json->$replacementKey); + } + + /** + * @covers \JsonMapper\Middleware\CaseConversion + */ + public function testWillRemainUntouchedOnInvalidExtensionOfTextNotationClassForSearch(): void + { + $extensionTextNotation = new class extends TextNotation + { + private const A = 'a'; + + public function __construct() + { + parent::__construct('a'); + } + }; + $middleware = new CaseConversion($extensionTextNotation, TextNotation::STUDLY_CAPS()); + $json = (object) ['key' => 'placeholder']; + $object = new ObjectWrapper(new \stdClass()); + + $middleware->handle($json, $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertObjectHasProperty('key', $json); + self::assertEquals('placeholder', $json->key); + } + + /** + * @covers \JsonMapper\Middleware\CaseConversion + * @dataProvider possibleTextNotationDataProvider + */ + public function testWillRemainUntouchedOnInvalidExtensionOfTextNotationClassForReplacement(TextNotation $search): void + { + $extensionTextNotation = new class extends TextNotation + { + private const A = 'a'; + + public function __construct() + { + parent::__construct('a'); + } + }; + $middleware = new CaseConversion($search, $extensionTextNotation); + $json = (object) ['key' => 'placeholder']; + $object = new ObjectWrapper(new \stdClass()); + + $middleware->handle($json, $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertObjectHasProperty('key', $json); + self::assertEquals('placeholder', $json->key); + } + + /** + * @covers \JsonMapper\Middleware\CaseConversion + */ + public function testWillRecursivelyApplyCaseConversionWhenApplied(): void + { + $middleware = new CaseConversion(TextNotation::STUDLY_CAPS(), TextNotation::CAMEL_CASE(), true); + $json = (object) [ + 'One' => 1, + 'Object' => (object) ['TwentyOne' => 21], + 'Array' => [ + (object) ['FirstName' => 'Jane', 'LastName' => 'Doe'], + (object) ['FirstName' => 'John', 'LastName' => 'Doe'], + ] + ]; + $object = new ObjectWrapper(null, \stdClass::class); + + $middleware->handle($json, $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertEquals( + (object) [ + 'one' => 1, + 'object' => (object) ['twentyOne' => 21], + 'array' => [ + (object) ['firstName' => 'Jane', 'lastName' => 'Doe'], + (object) ['firstName' => 'John', 'lastName' => 'Doe'], + ] + ], + $json + ); + } + + /** + * @covers \JsonMapper\Middleware\CaseConversion + */ + public function testWillNotRecursivelyApplyCaseConversionWhenNotApplied(): void + { + $middleware = new CaseConversion(TextNotation::STUDLY_CAPS(), TextNotation::CAMEL_CASE(), false); + $json = (object) [ + 'One' => 1, + 'Object' => (object) ['TwentyOne' => 21], + 'Array' => [ + (object) ['FirstName' => 'Jane', 'LastName' => 'Doe'], + (object) ['FirstName' => 'John', 'LastName' => 'Doe'], + ] + ]; + $object = new ObjectWrapper(null, \stdClass::class); + + $middleware->handle($json, $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertEquals( + (object) [ + 'one' => 1, + 'object' => (object) ['TwentyOne' => 21], + 'array' => [ + (object) ['FirstName' => 'Jane', 'LastName' => 'Doe'], + (object) ['FirstName' => 'John', 'LastName' => 'Doe'], + ] + ], + $json + ); + } + + /** + * @covers \JsonMapper\Middleware\CaseConversion + */ + public function testWillRemainUntouchedForIntegerKey(): void + { + $middleware = new CaseConversion(TextNotation::STUDLY_CAPS(), TextNotation::CAMEL_CASE()); + $json = (object) [1 => 'placeholder']; + $object = new ObjectWrapper(new \stdClass()); + + $middleware->handle($json, $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertEquals((object) [1 => 'placeholder'], $json); + } + + public function conversionDataProvider(): array + { + return [ + 'Studly caps to camel case' => [TextNotation::STUDLY_CAPS(), TextNotation::CAMEL_CASE(), 'DeliveryAddress', 'deliveryAddress'], + 'Studly caps to underscore' => [TextNotation::STUDLY_CAPS(), TextNotation::UNDERSCORE(), 'DeliveryAddress', 'delivery_address'], + 'Studly caps to kebab case' => [TextNotation::STUDLY_CAPS(), TextNotation::KEBAB_CASE(), 'DeliveryAddress', 'delivery-address'], + 'Camel case to studly caps' => [TextNotation::CAMEL_CASE(), TextNotation::STUDLY_CAPS(), 'deliveryAddress', 'DeliveryAddress'], + 'Camel case to underscore' => [TextNotation::CAMEL_CASE(), TextNotation::UNDERSCORE(), 'deliveryAddress', 'delivery_address'], + 'Camel case to kebab case' => [TextNotation::CAMEL_CASE(), TextNotation::KEBAB_CASE(), 'deliveryAddress', 'delivery-address'], + 'Underscore to studly caps' => [TextNotation::UNDERSCORE(), TextNotation::STUDLY_CAPS(), 'delivery_address', 'DeliveryAddress'], + 'Underscore to camel case' => [TextNotation::UNDERSCORE(), TextNotation::CAMEL_CASE(), 'delivery_address', 'deliveryAddress'], + 'Underscore to kebab case' => [TextNotation::UNDERSCORE(), TextNotation::KEBAB_CASE(), 'delivery_address', 'delivery-address'], + 'Kebab case to studly caps' => [TextNotation::KEBAB_CASE(), TextNotation::STUDLY_CAPS(), 'delivery-address', 'DeliveryAddress'], + 'Kebab case to camel case' => [TextNotation::KEBAB_CASE(), TextNotation::CAMEL_CASE(), 'delivery-address', 'deliveryAddress'], + 'Kebab case to underscore' => [TextNotation::KEBAB_CASE(), TextNotation::UNDERSCORE(), 'delivery-address', 'delivery_address'], + ]; + } + + public function possibleTextNotationDataProvider(): array + { + return [ + 'Studly caps' => [TextNotation::STUDLY_CAPS()], + 'Camel case' => [TextNotation::CAMEL_CASE()], + 'Underscore' => [TextNotation::UNDERSCORE()], + 'Kebab case' => [TextNotation::KEBAB_CASE()], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Constructor/ConstructorTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Constructor/ConstructorTest.php new file mode 100644 index 000000000..d05b1e193 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Constructor/ConstructorTest.php @@ -0,0 +1,274 @@ +createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertFalse($factoryRegistry->hasFactory(get_class($object))); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + */ + public function testItCanHandleClassWithConstructorWithOptionalArgument(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) ['name' => 'John Doe']; + $object = new SimpleObject(); + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + self::assertEquals(new SimpleObject($json->name), $factoryRegistry->create(get_class($object), $json)); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + */ + public function testItCanHandleClassTwice(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) ['name' => 'John Doe']; + $object = new class { + public function __construct(int $value = 0) + { + } + }; + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $jsonMapper); + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + */ + public function testItCanHandleClassWithConstructorWithChildObject(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) ['popo' => (object) ['name' => 'Jane Doe']]; + $object = new PopoWrapperWithConstructor(new Popo()); + $propertyMap = $this->getPropertyMapFor($object); + $jsonMapper = JsonMapperBuilder::new()->withDocBlockAnnotationsMiddleware()->build(); + $popo = new Popo(); + $popo->name = $json->popo->name; + $expected = new PopoWrapperWithConstructor($popo); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + self::assertEquals($expected, $factoryRegistry->create(get_class($object), $json)); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + */ + public function testItCanHandleClassWithConstructorWithArrays(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) ['items' => [(object) ['name' => 'Jane Doe'], (object) ['name' => 'John Doe']]]; + $object = new PopoContainer([]); + $propertyMap = $this->getPropertyMapFor($object); + $jsonMapper = JsonMapperBuilder::new()->withDocBlockAnnotationsMiddleware()->build(); + $popoOne = new Popo(); + $popoOne->name = $json->items[0]->name; + $popoTwo = new Popo(); + $popoTwo->name = $json->items[1]->name; + $expected = new PopoContainer([$popoOne, $popoTwo]); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + self::assertEquals($expected, $factoryRegistry->create(get_class($object), $json)); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + * @requires PHP >= 8.1 + */ + public function testItCanHandleClassWithConstructorHavingEnum(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) ['status' => 'published']; + $object = new BlogPostWithConstructor(Status::PUBLISHED); + $propertyMap = $this->getPropertyMapFor($object); + $jsonMapper = JsonMapperBuilder::new()->withDocBlockAnnotationsMiddleware()->build(); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + self::assertEquals($object, $factoryRegistry->create(get_class($object), $json)); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + */ + public function testItCanHandleClassWithConstructorHavingScalarMismatch(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) ['name' => 1234]; + $object = new SimpleObject('1234'); + $propertyMap = $this->getPropertyMapFor($object); + $jsonMapper = JsonMapperBuilder::new()->withDocBlockAnnotationsMiddleware()->build(); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + self::assertEquals($object, $factoryRegistry->create(get_class($object), $json)); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + */ + public function testItCanHandleClassWithMissingParameterConstructor(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) []; + $object = new SimpleObject(); + $propertyMap = $this->getPropertyMapFor($object); + $jsonMapper = JsonMapperBuilder::new()->withDocBlockAnnotationsMiddleware()->build(); + + $middleware->handle($json, new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + self::assertEquals($object, $factoryRegistry->create(get_class($object), $json)); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + */ + public function testItCanHandleClassWithoutNativeTypehint(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) []; + $object = new class { + /** @var string */ + private $name; + + public function __construct($name = '') + { + $this->name = (string) $name; + } + + public function getName(): string + { + return $this->name; + } + }; + $jsonMapper = JsonMapperBuilder::new()->withDocBlockAnnotationsMiddleware()->build(); + + $middleware->handle($json, new ObjectWrapper($object), new PropertyMap(), $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + self::assertEquals($object, $factoryRegistry->create(get_class($object), $json)); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + */ + public function testItCanHandleClassWithConstructorWithArrayLtGt(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) ['items' => [(object) ['name' => 'Jane Doe'], (object) ['name' => 'John Doe']]]; + + $object = new PopoArrayLtGt([]); + $propertyMap = $this->getPropertyMapFor($object); + $jsonMapper = JsonMapperBuilder::new()->withDocBlockAnnotationsMiddleware()->build(); + $popoOne = new Popo(); + $popoOne->name = $json->items[0]->name; + $popoTwo = new Popo(); + $popoTwo->name = $json->items[1]->name; + $expected = new PopoArrayLtGt([$popoOne, $popoTwo]); + + $middleware->handle($json, new ObjectWrapper($expected), $propertyMap, $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + self::assertEquals($expected, $factoryRegistry->create(get_class($object), $json)); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\Constructor + */ + public function testItCanHandleClassWithConstructorWithList(): void + { + $factoryRegistry = new FactoryRegistry(); + $middleware = new Constructor($factoryRegistry); + $json = (object) ['items' => [(object) ['name' => 'Jane Doe'], (object) ['name' => 'John Doe']]]; + + $object = new PopoList([]); + $propertyMap = $this->getPropertyMapFor($object); + $jsonMapper = JsonMapperBuilder::new()->withDocBlockAnnotationsMiddleware()->build(); + $popoOne = new Popo(); + $popoOne->name = $json->items[0]->name; + $popoTwo = new Popo(); + $popoTwo->name = $json->items[1]->name; + $expected = new PopoList([$popoOne, $popoTwo]); + + $middleware->handle($json, new ObjectWrapper($expected), $propertyMap, $jsonMapper); + + self::assertTrue($factoryRegistry->hasFactory(get_class($object))); + self::assertEquals($expected, $factoryRegistry->create(get_class($object), $json)); + } + + private function getPropertyMapFor($object): PropertyMap + { + $propertyMap = new PropertyMap(); + $docBlock = new DocBlockAnnotations(new NullCache()); + $docBlock->handle( + new \stdClass(), + new ObjectWrapper($object), + $propertyMap, + $this->createMock(JsonMapperInterface::class) + ); + + return $propertyMap; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Constructor/DefaultFactoryTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Constructor/DefaultFactoryTest.php new file mode 100644 index 000000000..3156902df --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Constructor/DefaultFactoryTest.php @@ -0,0 +1,464 @@ +getConstructor(), + $this->createMock(JsonMapperInterface::class), + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke(new \stdClass()); + + self::assertInstanceOf(get_class($class), $result); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithSingleParameterWithoutDocBlock(): void + { + $value = random_int(0, PHP_INT_MAX); + $class = new class { + /** @var int */ + private $value; + + public function __construct(int $value = 0) + { + $this->value = $value; + } + + public function getValue(): int + { + return $this->value; + } + }; + $sut = new DefaultFactory( + get_class($class), + (new \ReflectionClass($class))->getConstructor(), + $this->createMock(JsonMapperInterface::class), + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke((object) ['value' => $value]); + + self::assertInstanceOf(get_class($class), $result); + self::assertEquals($value, $result->getValue()); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithTwoParametersWithoutDocBlock(): void + { + $first = random_int(0, PHP_INT_MAX); + $second = random_int(0, PHP_INT_MAX); + $class = new class { + /** @var int */ + private $first; + /** @var int */ + private $second; + + public function __construct(int $first = 0, int $second = 0) + { + $this->first = $first; + $this->second = $second; + } + + public function getFirst(): int + { + return $this->first; + } + + public function getSecond(): int + { + return $this->second; + } + }; + $sut = new DefaultFactory( + get_class($class), + (new \ReflectionClass($class))->getConstructor(), + $this->createMock(JsonMapperInterface::class), + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke((object) ['second' => $second, 'first' => $first]); + + self::assertInstanceOf(get_class($class), $result); + self::assertEquals($first, $result->getFirst()); + self::assertEquals($second, $result->getSecond()); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithTwoParametersHintedThroughDocBlock(): void + { + $first = random_int(0, PHP_INT_MAX); + $second = random_int(0, PHP_INT_MAX); + $class = new class { + /** @var int */ + private $first; + /** @var int */ + private $second; + + /** + * @param int $first + * @param int $second + */ + public function __construct($first = 0, $second = 0) + { + $this->first = $first; + $this->second = $second; + } + + public function getFirst(): int + { + return $this->first; + } + + public function getSecond(): int + { + return $this->second; + } + }; + $sut = new DefaultFactory( + get_class($class), + (new \ReflectionClass($class))->getConstructor(), + $this->createMock(JsonMapperInterface::class), + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke((object) ['second' => $second, 'first' => $first]); + + self::assertInstanceOf(get_class($class), $result); + self::assertEquals($first, $result->getFirst()); + self::assertEquals($second, $result->getSecond()); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithOneArrayParameterHintedThroughDocBlock(): void + { + $first = [random_int(0, PHP_INT_MAX), random_int(0, PHP_INT_MAX)]; + $class = new class { + /** @var int[] */ + private $first; + + /** + * @param int[] $first + */ + public function __construct($first = []) + { + $this->first = $first; + } + + public function getFirst(): array + { + return $this->first; + } + }; + $sut = new DefaultFactory( + get_class($class), + (new \ReflectionClass($class))->getConstructor(), + $this->createMock(JsonMapperInterface::class), + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke((object) ['first' => $first]); + + self::assertInstanceOf(get_class($class), $result); + self::assertEquals($first, $result->getFirst()); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithOneObjectParameter(): void + { + $name = 'Jane Doe'; + $class = new class { + /** @var ?Popo */ + private $value; + + public function __construct(?Popo $value = null) + { + $this->value = $value; + } + + public function getValue(): ?Popo + { + return $this->value; + } + }; + $mapper = $this->createMock(JsonMapperInterface::class); + $mapper->method('mapToClass') + ->with($this->isInstanceOf(\stdClass::class), Popo::class) + ->willReturnCallback(function (stdClass $data) { + $popo = new Popo(); + $popo->name = isset($data->name) ? $data->name : null; + $popo->date = isset($data->date) ? $data->date : null; + $popo->notes = isset($data->notes) ? $data->notes : null; + + return $popo; + }); + $sut = new DefaultFactory( + get_class($class), + (new \ReflectionClass($class))->getConstructor(), + $mapper, + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke((object) ['value' => (object) ['name' => $name]]); + + self::assertInstanceOf(get_class($class), $result); + self::assertInstanceOf(Popo::class, $result->getValue()); + self::assertEquals($name, $result->getValue()->name); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithArrayOfObjectParameter(): void + { + $name = 'Jane Doe'; + $class = new class { + /** @var Popo[] */ + private $value; + + /** @param Popo[] $value */ + public function __construct(array $value = []) + { + $this->value = $value; + } + + /** @return array */ + public function getValue(): array + { + return $this->value; + } + }; + $mapper = $this->createMock(JsonMapperInterface::class); + $mapper->method('mapToClass') + ->with($this->isInstanceOf(\stdClass::class), Popo::class) + ->willReturnCallback(function (stdClass $data) { + $popo = new Popo(); + $popo->name = $data->name ?? null; + $popo->date = $data->date ?? null; + $popo->notes = $data->notes ?? null; + + return $popo; + }); + $sut = new DefaultFactory( + get_class($class), + (new \ReflectionClass($class))->getConstructor(), + $mapper, + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke( + (object) [ + 'value' => [(object) ['name' => $name], (object) ['name' => strrev($name)]] + ] + ); + + self::assertInstanceOf(get_class($class), $result); + self::assertContainsOnlyInstancesOf(Popo::class, $result->getValue()); + self::assertEquals($name, $result->getValue()[0]->name); + self::assertEquals(strrev($name), $result->getValue()[1]->name); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + * @requires PHP >= 8.1 + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithEnumParameter(): void + { + $name = 'Jane Doe'; + $mapper = $this->createMock(JsonMapperInterface::class); + + $sut = new DefaultFactory( + BlogPostWithConstructor::class, + (new \ReflectionClass(BlogPostWithConstructor::class))->getConstructor(), + $mapper, + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke( + (object) [ + 'status' => 'draft' + ] + ); + + self::assertInstanceOf(BlogPostWithConstructor::class, $result); + self::assertEquals(Status::DRAFT, $result->getStatus()); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + * @requires PHP >= 8.1 + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithNativeTypeParameter(): void + { + $date = new \DateTimeImmutable("today"); + $mapper = $this->createMock(JsonMapperInterface::class); + + $sut = new DefaultFactory( + WithConstructorReadOnlyDateTimePropertyPromotion::class, + (new \ReflectionClass(WithConstructorReadOnlyDateTimePropertyPromotion::class))->getConstructor(), + $mapper, + new ScalarCaster(), + FactoryRegistry::withNativePhpClassesAdded() + ); + + $result = $sut->__invoke( + (object) [ + 'date' => $date->format('Y-m-d H:i:s'), + ] + ); + + self::assertInstanceOf(WithConstructorReadOnlyDateTimePropertyPromotion::class, $result); + self::assertEquals($date, $result->date); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithArrayLtGtOfObjectParameter(): void + { + $name = 'Jane Doe'; + $class = new class { + /** @var array */ + private $value; + + /** @param array $value */ + public function __construct(array $value = []) + { + $this->value = $value; + } + + /** @return array */ + public function getValue(): array + { + return $this->value; + } + }; + $mapper = $this->createMock(JsonMapperInterface::class); + $mapper->method('mapToClass') + ->with($this->isInstanceOf(\stdClass::class), Popo::class) + ->willReturnCallback(function (stdClass $data) { + $popo = new Popo(); + $popo->name = $data->name ?? null; + $popo->date = $data->date ?? null; + $popo->notes = $data->notes ?? null; + + return $popo; + }); + $sut = new DefaultFactory( + get_class($class), + (new \ReflectionClass($class))->getConstructor(), + $mapper, + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke( + (object) [ + 'value' => [(object) ['name' => $name], (object) ['name' => strrev($name)]] + ] + ); + + self::assertInstanceOf(get_class($class), $result); + self::assertContainsOnlyInstancesOf(Popo::class, $result->getValue()); + self::assertEquals($name, $result->getValue()[0]->name); + self::assertEquals(strrev($name), $result->getValue()[1]->name); + } + + /** + * @covers \JsonMapper\Middleware\Constructor\DefaultFactory + */ + public function testDefaultFactoryCanHandleObjectWithConstructorWithListOfObjectParameter(): void + { + $name = 'Jane Doe'; + $class = new class { + /** @var list */ + private $value; + + /** @param list $value */ + public function __construct(array $value = []) + { + $this->value = $value; + } + + /** @return list */ + public function getValue(): array + { + return $this->value; + } + }; + $mapper = $this->createMock(JsonMapperInterface::class); + $mapper->method('mapToClass') + ->with($this->isInstanceOf(\stdClass::class), Popo::class) + ->willReturnCallback(function (stdClass $data) { + $popo = new Popo(); + $popo->name = $data->name ?? null; + $popo->date = $data->date ?? null; + $popo->notes = $data->notes ?? null; + + return $popo; + }); + $sut = new DefaultFactory( + get_class($class), + (new \ReflectionClass($class))->getConstructor(), + $mapper, + new ScalarCaster(), + new FactoryRegistry() + ); + + $result = $sut->__invoke( + (object) [ + 'value' => [(object) ['name' => $name], (object) ['name' => strrev($name)]] + ] + ); + + self::assertInstanceOf(get_class($class), $result); + self::assertContainsOnlyInstancesOf(Popo::class, $result->getValue()); + self::assertEquals($name, $result->getValue()[0]->name); + self::assertEquals(strrev($name), $result->getValue()[1]->name); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/DebuggerTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/DebuggerTest.php new file mode 100644 index 000000000..6b62b7e3a --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/DebuggerTest.php @@ -0,0 +1,40 @@ +createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('debug') + ->with( + 'Current state attributes passed through JsonMapper middleware', + $this->logicalAnd( + $this->arrayHasKey('json'), + $this->arrayHasKey('object'), + $this->arrayHasKey('propertyMap') + ) + ); + $middleware = new Debugger($logger); + $object = new ObjectWrapper(new SimpleObject()); + $function = $middleware->__invoke(new PropertyMapper()); + + $function(new \stdClass(), $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/DocBlockAnnotationsTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/DocBlockAnnotationsTest.php new file mode 100644 index 000000000..d02e5669d --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/DocBlockAnnotationsTest.php @@ -0,0 +1,291 @@ +createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('child')); + self::assertThatProperty($propertyMap->getProperty('child')) + ->hasType('SimpleObject', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PRIVATE()) + ->isNullable(); + self::assertTrue($propertyMap->hasProperty('children')); + self::assertThatProperty($propertyMap->getProperty('children')) + ->hasType('SimpleObject', ArrayInformation::singleDimension()) + ->hasVisibility(Visibility::PRIVATE()) + ->isNotNullable(); + self::assertTrue($propertyMap->hasProperty('user')); + self::assertThatProperty($propertyMap->getProperty('user')) + ->hasType('User', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PRIVATE()) + ->isNotNullable(); + self::assertTrue($propertyMap->hasProperty('mixedParam')); + self::assertThatProperty($propertyMap->getProperty('mixedParam')) + ->hasType('mixed', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testItCanHandleMissingDocBlock(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + public $number; + }; + + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertEmpty($propertyMap->getIterator()); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testItCanHandleEmptyDocBlock(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + /** */ + public $number; + }; + + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertEmpty($propertyMap->getIterator()); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testItCanHandleIncompleteDocBlock(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + /** @var */ + public $number; + }; + + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertEmpty($propertyMap->getIterator()); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testReturnsFromCacheWhenAvailable(): void + { + $propertyMap = new PropertyMap(); + $objectWrapper = $this->createMock(ObjectWrapper::class); + $objectWrapper->method('getName')->willReturn(__METHOD__); + $objectWrapper->expects(self::never())->method('getReflectedObject'); + $cache = $this->createMock(CacheInterface::class); + $cache->method('has') + ->with(Assert::stringContains(CacheKeyHelper::sanatize(__METHOD__))) + ->willReturn(true); + $cache->method('get') + ->with(Assert::stringContains(CacheKeyHelper::sanatize(__METHOD__))) + ->willReturn($propertyMap); + $middleware = new DocBlockAnnotations($cache); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), $objectWrapper, $propertyMap, $jsonMapper); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testTypeIsCorrectlyCalculatedForNullableVars(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + /** @var NullableNumber|null This is a nullable number*/ + public $nullableNumber; + }; + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('nullableNumber')); + self::assertThatProperty($propertyMap->getProperty('nullableNumber')) + ->hasType('NullableNumber', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNullable(); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testTypeIsCorrectlyCalculatedForNullableArray(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + /** @var Number[]|null This is a nullable array number*/ + public $numbers; + }; + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('numbers')); + self::assertThatProperty($propertyMap->getProperty('numbers')) + ->hasType('Number', ArrayInformation::singleDimension()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNullable(); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testTypeIsCorrectlyCalculatedForNullableArrayWhenNullIsProvidedFirst(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + /** @var null|Number[] This is a nullable array number*/ + public $numbers; + }; + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('numbers')); + self::assertThatProperty($propertyMap->getProperty('numbers')) + ->hasType('Number', ArrayInformation::singleDimension()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNullable(); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testTypedUnionPropertyIsCorrectlyDiscovered(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + /** @var float|int */ + public $amount; + }; + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('amount')); + $this->assertThatProperty($propertyMap->getProperty('amount')) + ->hasType('int', ArrayInformation::notAnArray()) + ->hasType('float', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testComplexUnionTypeIsCorrectlyDiscovered(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + /** @var string|int|float|array */ + public $complexUnionWithArray; + }; + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('complexUnionWithArray')); + $this->assertThatProperty($propertyMap->getProperty('complexUnionWithArray')) + ->hasType('mixed', ArrayInformation::singleDimension()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testTypeIsCorrectlyCalculatedForList(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + /** @var list */ + public $listOfNumbers; + }; + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('listOfNumbers')); + $this->assertThatProperty($propertyMap->getProperty('listOfNumbers')) + ->onlyHasType('int', ArrayInformation::singleDimension()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + } + + /** + * @covers \JsonMapper\Middleware\DocBlockAnnotations + */ + public function testTypeIsCorrectlyCalculatedForArrayLtGt(): void + { + $middleware = new DocBlockAnnotations(new NullCache()); + $object = new class { + /** @var array */ + public $listOfWords; + }; + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('listOfWords')); + $this->assertThatProperty($propertyMap->getProperty('listOfWords')) + ->onlyHasType('string', ArrayInformation::singleDimension()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/FinalCallbackTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/FinalCallbackTest.php new file mode 100644 index 000000000..fecf3cb44 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/FinalCallbackTest.php @@ -0,0 +1,33 @@ +__invoke(new PropertyMapper()); + + $function(new \stdClass(), $object, new PropertyMap(), $this->createMock(JsonMapperInterface::class)); + + self::assertTrue($isCalled); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/NamespaceResolverTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/NamespaceResolverTest.php new file mode 100644 index 000000000..63298028a --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/NamespaceResolverTest.php @@ -0,0 +1,263 @@ +setName('user') + ->addType('User', ArrayInformation::notAnArray()) + ->setVisibility(Visibility::PRIVATE()) + ->setIsNullable(false) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('user')); + $this->assertThatProperty($propertyMap->getProperty('user')) + ->onlyHasType(User::class, ArrayInformation::notAnArray()); + } + + /** + * @covers \JsonMapper\Middleware\NamespaceResolver + */ + public function testItResolvesNamespacesWithinSameNamespace(): void + { + $middleware = new NamespaceResolver(new NullCache()); + $object = new ComplexObject(); + $property = PropertyBuilder::new() + ->setName('child') + ->addType('SimpleObject', ArrayInformation::notAnArray()) + ->setVisibility(Visibility::PRIVATE()) + ->setIsNullable(false) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('child')); + $this->assertThatProperty($propertyMap->getProperty('child')) + ->onlyHasType(SimpleObject::class, ArrayInformation::notAnArray()); + } + + /** + * @covers \JsonMapper\Middleware\NamespaceResolver + */ + public function testItDoesntApplyResolvingToScalarTypes(): void + { + $middleware = new NamespaceResolver(new NullCache()); + $object = new SimpleObject(); + $property = PropertyBuilder::new() + ->setName('name') + ->addType('string', ArrayInformation::notAnArray()) + ->setVisibility(Visibility::PRIVATE()) + ->setIsNullable(false) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('name')); + $this->assertThatProperty($propertyMap->getProperty('name')) + ->onlyHasType('string', ArrayInformation::notAnArray()); + } + + /** + * @covers \JsonMapper\Middleware\NamespaceResolver + */ + public function testItDoesntApplyResolvingToFullyQualifiedClassName(): void + { + $middleware = new NamespaceResolver(new NullCache()); + $object = new SimpleObject(); + $property = PropertyBuilder::new() + ->setName('name') + ->addType(__CLASS__, ArrayInformation::notAnArray()) + ->setVisibility(Visibility::PRIVATE()) + ->setIsNullable(false) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('name')); + $this->assertThatProperty($propertyMap->getProperty('name')) + ->onlyHasType(__CLASS__, ArrayInformation::notAnArray()); + } + + /** + * @covers \JsonMapper\Middleware\NamespaceResolver + */ + public function testItResolvesNamespacesForImportedNamespaceWithArray(): void + { + $middleware = new NamespaceResolver(new NullCache()); + $object = new ComplexObject(); + $property = PropertyBuilder::new() + ->setName('user') + ->addType('User', ArrayInformation::singleDimension()) + ->setVisibility(Visibility::PRIVATE()) + ->setIsNullable(false) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('user')); + $this->assertThatProperty($propertyMap->getProperty('user')) + ->onlyHasType(User::class, ArrayInformation::singleDimension()); + } + + /** + * @covers \JsonMapper\Middleware\NamespaceResolver + */ + public function testItResolvesNamespacesWithinSameNamespaceWithArray(): void + { + $middleware = new NamespaceResolver(new NullCache()); + $object = new ComplexObject(); + $property = PropertyBuilder::new() + ->setName('child') + ->addType('SimpleObject', ArrayInformation::singleDimension()) + ->setVisibility(Visibility::PRIVATE()) + ->setIsNullable(false) + ->build(); + $propertyMap = new PropertyMap(); + $propertyMap->addProperty($property); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('child')); + $this->assertThatProperty($propertyMap->getProperty('child')) + ->onlyHasType(SimpleObject::class, ArrayInformation::singleDimension()); + } + + /** + * @covers \JsonMapper\Middleware\NamespaceResolver + */ + public function testReturnsFromCacheWhenAvailable(): void + { + $propertyMap = new PropertyMap(); + $objectWrapper = $this->createMock(ObjectWrapper::class); + $objectWrapper->method('getName')->willReturn(__METHOD__); + $objectWrapper->expects(self::never())->method('getReflectedObject'); + $cache = $this->createMock(CacheInterface::class); + $cache->method('has') + ->with(Assert::stringContains(CacheKeyHelper::sanatize(__METHOD__))) + ->willReturn(true); + $cache->method('get') + ->with(Assert::stringContains(CacheKeyHelper::sanatize(__METHOD__))) + ->willReturn($propertyMap); + $middleware = new NamespaceResolver($cache); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), $objectWrapper, $propertyMap, $jsonMapper); + } + + /** + * @covers \JsonMapper\Middleware\NamespaceResolver + */ + public function testReturnsCorrectNamespaceWhenOtherClassHasPartialMatch(): void + { + $object = new NamespaceObject(); + $input = (object) [ + 'valueHolder' => (object) ['value' => 'loremipsum1'], + 'anotherValueHolder' => (object) ['value' => 'loremipsum2'] + ]; + $mapper = JsonMapperBuilder::new() + ->withMiddleware(new DocBlockAnnotations(new NullCache())) + ->withMiddleware(new NamespaceResolver(new NullCache())) + ->build(); + + $mapper->mapObject($input, $object); + + self::assertInstanceOf(AnotherValueHolder::class, $object->anotherValueHolder); + self::assertInstanceOf(ValueHolder::class, $object->valueHolder); + } + + /** + * @covers \JsonMapper\Middleware\NamespaceResolver + */ + public function testReturnsCorrectNamespaceWhenAliasProvidedForUse(): void + { + $object = new NamespaceAliasObject(); + $input = (object) [ + 'valueHolder' => (object) ['value' => 'loremipsum1'], + 'anotherValueHolder' => (object) ['value' => 'loremipsum2'] + ]; + $mapper = JsonMapperBuilder::new() + ->withMiddleware(new DocBlockAnnotations(new NullCache())) + ->withMiddleware(new NamespaceResolver(new NullCache())) + ->build(); + + $mapper->mapObject($input, $object); + + self::assertInstanceOf(AnotherValueHolder::class, $object->anotherValueHolder); + self::assertInstanceOf(ValueHolder::class, $object->valueHolder); + } + + /** + * @covers \JsonMapper\Middleware\NamespaceResolver + */ + public function testReturnsCorrectNamespaceWithPropertyDefinedInParentInOtherNamespace(): void + { + $object = new Customer(); + $input = (object) [ + 'customerState' => (object) ['description' => 'loremipsum'], + ]; + $mapper = JsonMapperBuilder::new() + ->withMiddleware(new DocBlockAnnotations(new NullCache())) + ->withMiddleware(new NamespaceResolver(new NullCache())) + ->build(); + + $mapper->mapObject($input, $object); + + self::assertInstanceOf(CustomerState::class, $object->customerState); + self::assertEquals($input->customerState->description, $object->customerState->description); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Rename/MappingTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Rename/MappingTest.php new file mode 100644 index 000000000..f26e18d70 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Rename/MappingTest.php @@ -0,0 +1,24 @@ +getClass()); + self::assertEquals('municipality', $mapping->getFrom()); + self::assertEquals('city', $mapping->getTo()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Rename/RenameTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Rename/RenameTest.php new file mode 100644 index 000000000..7200f823f --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/Rename/RenameTest.php @@ -0,0 +1,88 @@ + 'John Doe', 'id' => 42]; + $clone = clone $json; + $wrapper = new ObjectWrapper(new User()); + $propertyMap = new PropertyMap(); + $mapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle($clone, $wrapper, $propertyMap, $mapper); + + self::assertEquals($json, $clone); + } + + /** + * @covers \JsonMapper\Middleware\Rename\Rename + */ + public function testLeavesJsonUntouchedWithPropertyNotInMapping(): void + { + $middleware = new Rename(); + $middleware->addMapping(User::class, 'municipality', 'city'); + $json = (object) ['name' => 'John Doe', 'id' => 42]; + $clone = clone $json; + $wrapper = new ObjectWrapper(new User()); + $propertyMap = new PropertyMap(); + $mapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle($clone, $wrapper, $propertyMap, $mapper); + + self::assertEquals($json, $clone); + } + + /** + * @covers \JsonMapper\Middleware\Rename\Rename + */ + public function testLeavesJsonUntouchedWithPropertyInMappingForDifferentClass(): void + { + $middleware = new Rename(); + $middleware->addMapping(ComplexObject::class, 'name', 'fullName'); + $json = (object) ['name' => 'John Doe', 'id' => 42]; + $clone = clone $json; + $wrapper = new ObjectWrapper(new User()); + $propertyMap = new PropertyMap(); + $mapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle($clone, $wrapper, $propertyMap, $mapper); + + self::assertEquals($json, $clone); + } + + /** + * @covers \JsonMapper\Middleware\Rename\Rename + */ + public function testAltersJsonWithPropertyInMapping(): void + { + $middleware = new Rename(); + $middleware->addMapping(User::class, 'name', 'fullName'); + $json = (object) ['name' => 'John Doe', 'id' => 42]; + $clone = clone $json; + $wrapper = new ObjectWrapper(new User()); + $propertyMap = new PropertyMap(); + $mapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle($clone, $wrapper, $propertyMap, $mapper); + + self::assertEquals($json->id, $clone->id); + self::assertEquals($json->name, $clone->fullName); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/TypedPropertiesTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/TypedPropertiesTest.php new file mode 100644 index 000000000..67a65d787 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/TypedPropertiesTest.php @@ -0,0 +1,156 @@ +createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('name')); + self::assertThatProperty($propertyMap->getProperty('name')) + ->hasType('string', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + self::assertTrue($propertyMap->hasProperty('friends')); + self::assertThatProperty($propertyMap->getProperty('friends')) + ->hasType('mixed', ArrayInformation::singleDimension()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + } + + /** + * @covers \JsonMapper\Middleware\TypedProperties + * @requires PHP >= 8.0 + */ + public function testTypedPropertyIsCorrectlyDiscoveredWithPhp80AndGreater(): void + { + $middleware = new TypedProperties(new NullCache()); + $object = new Php80\Popo(); + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('name')); + self::assertThatProperty($propertyMap->getProperty('name')) + ->hasType('string', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + self::assertTrue($propertyMap->hasProperty('mixedParam')); + self::assertThatProperty($propertyMap->getProperty('mixedParam')) + ->hasType('mixed', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNullable(); + } + + /** + * @covers \JsonMapper\Middleware\TypedProperties + * @requires PHP >= 7.4 + */ + public function testDoesntBreakOnMissingTypeDefinition(): void + { + $middleware = new TypedProperties(new NullCache()); + $object = new SimpleObject(); + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertCount(0, $propertyMap); + } + + /** + * @covers \JsonMapper\Middleware\TypedProperties + * @requires PHP >= 7.4 + */ + public function testReturnsFromCacheWhenAvailable(): void + { + $propertyMap = new PropertyMap(); + $objectWrapper = $this->createMock(ObjectWrapper::class); + $objectWrapper->method('getName')->willReturn(__METHOD__); + $objectWrapper->expects(self::never())->method('getReflectedObject'); + $cache = $this->createMock(CacheInterface::class); + $cache->method('has') + ->with(Assert::stringContains(CacheKeyHelper::sanatize(__METHOD__))) + ->willReturn(true); + $cache->method('get') + ->with(Assert::stringContains(CacheKeyHelper::sanatize(__METHOD__))) + ->willReturn($propertyMap); + $middleware = new TypedProperties($cache); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), $objectWrapper, $propertyMap, $jsonMapper); + } + + /** + * @covers \JsonMapper\Middleware\TypedProperties + * @requires PHP >= 8.0 + */ + public function testTypedUnionPropertyIsCorrectlyDiscovered(): void + { + $middleware = new TypedProperties(new NullCache()); + $object = new Php80\Popo(); + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('amount')); + $this->assertThatProperty($propertyMap->getProperty('amount')) + ->hasType('int', ArrayInformation::notAnArray()) + ->hasType('float', ArrayInformation::notAnArray()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + } + + /** + * @covers \JsonMapper\Middleware\TypedProperties + * @requires PHP >= 8.0 + */ + public function testComplexUnionWithArrayTypedUnionPropertyIsCorrectlyDiscovered(): void + { + $middleware = new TypedProperties(new NullCache()); + $object = new Php80\Popo(); + $propertyMap = new PropertyMap(); + $jsonMapper = $this->createMock(JsonMapperInterface::class); + + $middleware->handle(new \stdClass(), new ObjectWrapper($object), $propertyMap, $jsonMapper); + + self::assertTrue($propertyMap->hasProperty('complexUnionWithArray')); + $this->assertThatProperty($propertyMap->getProperty('complexUnionWithArray')) + ->onlyHasType('mixed', ArrayInformation::singleDimension()) + ->hasVisibility(Visibility::PUBLIC()) + ->isNotNullable(); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Middleware/ValueTransformationTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/ValueTransformationTest.php new file mode 100644 index 000000000..c3e627bae --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Middleware/ValueTransformationTest.php @@ -0,0 +1,91 @@ +handle( + $json = (object) [ + 'name' => 'small', + ], + new ObjectWrapper(new Popo()), + new PropertyMap(), + $this->createMock(JsonMapperInterface::class) + ); + + self::assertEquals('SMALL', $json->name); + } + + /** + * @covers \JsonMapper\Middleware\ValueTransformation + * @dataProvider valueMapperDataProvider + */ + public function testCanConvertObject( + ValueTransformation $middleware, + stdClass $json, + stdClass $expected + ): void { + $middleware->handle( + $json, + new ObjectWrapper(new Popo()), + new PropertyMap(), + $this->createMock(JsonMapperInterface::class) + ); + + self::assertEquals($expected, $json); + } + + public function valueMapperDataProvider(): array + { + return [ + 'php function strtoupper' => [ + new ValueTransformation('strtoupper'), + (object) [ + 'name' => 'test', + 'notes' => 'this is a test' + ], + (object) [ + 'name' => 'TEST', + 'notes' => 'THIS IS A TEST' + ] + ], + 'custom function' => [ + new ValueTransformation( + static function ($key, $value) { + if ($key === 'notes') { + return \base64_decode($value); + } + + return $value; + }, + true + ), + (object) [ + 'name' => 'test', + 'notes' => 'c3RyaW5nIGluIGJhc2U2NA==' + ], + (object) [ + 'name' => 'test', + 'notes' => 'string in base64' + ] + ] + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Parser/ImportTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Parser/ImportTest.php new file mode 100644 index 000000000..c4511aaf2 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Parser/ImportTest.php @@ -0,0 +1,35 @@ +getImport()); + self::assertNull($import->getAlias()); + self::assertFalse($import->hasAlias()); + } + + /** + * @covers \JsonMapper\Parser\Import + */ + public function testCanHoldPropertiesWithAlias(): void + { + $import = new Import(\stdClass::class, 'someAlias'); + + self::assertEquals(\stdClass::class, $import->getImport()); + self::assertEquals('someAlias', $import->getAlias()); + self::assertTrue($import->hasAlias()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Parser/UseNodeVisitorTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Parser/UseNodeVisitorTest.php new file mode 100644 index 000000000..a98f2ab5c --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Parser/UseNodeVisitorTest.php @@ -0,0 +1,57 @@ +getImport())); + }, $uses)); + + $result = $visitor->enterNode($node); + $imports = $visitor->getImports(); + + self::assertNull($result); + self::assertEquals($uses, $imports); + } + + /** + * @covers \JsonMapper\Parser\UseNodeVisitor + */ + public function testKeepsGroupedImportsFromNodeForRetrieval(): void + { + $visitor = new UseNodeVisitor(); + $uses = ['ComplexObject', 'SimpleObject']; + $node = new GroupUse(new Name('JsonMapper\Tests\Implementation'), array_map(static function ($use) { + return new UseUse(new Name($use)); + }, $uses)); + + $result = $visitor->enterNode($node); + $imports = $visitor->getImports(); + + self::assertNull($result); + self::assertEquals([new Import(ComplexObject::class, null), new Import(SimpleObject::class, null)], $imports); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/ArrayInformationTest.php b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/ArrayInformationTest.php new file mode 100644 index 000000000..a2cb9fbbe --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/ArrayInformationTest.php @@ -0,0 +1,100 @@ +isArray()); + self::assertEquals(0, $arrayInformation->getDimensions()); + } + + /** + * @covers \JsonMapper\ValueObjects\ArrayInformation + */ + public function testSingleDimensionContainsCorrectValues(): void + { + $arrayInformation = ArrayInformation::singleDimension(); + + self::assertTrue($arrayInformation->isArray()); + self::assertEquals(1, $arrayInformation->getDimensions()); + } + + /** + * @covers \JsonMapper\ValueObjects\ArrayInformation + */ + public function testMultiDimensionContainsCorrectValues(): void + { + $dimensions = 3; + $arrayInformation = ArrayInformation::multiDimension($dimensions); + + self::assertTrue($arrayInformation->isArray()); + self::assertEquals($dimensions, $arrayInformation->getDimensions()); + } + + /** + * @covers \JsonMapper\ValueObjects\ArrayInformation + * @dataProvider isMultiDimensionalArrayProvider + */ + public function testIsMultiDimensionalArrayReturnsCorrectValue( + ArrayInformation $arrayInformation, + bool $isMultidimensional + ): void { + self::assertEquals($isMultidimensional, $arrayInformation->isMultiDimensionalArray()); + } + + /** + * @covers \JsonMapper\ValueObjects\ArrayInformation + */ + public function testCanBeConvertedToJson(): void + { + $arrayInformation = ArrayInformation::multiDimension(2); + + $arrayInformationAsJson = json_encode($arrayInformation); + + self::assertIsString($arrayInformationAsJson); + self::assertJsonStringEqualsJsonString( + '{"isArray":true,"dimensions":2}', + (string) $arrayInformationAsJson + ); + } + + /** + * @covers \JsonMapper\ValueObjects\ArrayInformation + * @dataProvider equalsDataProvider + */ + public function testEquals(ArrayInformation $left, ArrayInformation $right, bool $isEqual): void + { + self::assertEquals($isEqual, $left->equals($right)); + } + + public function isMultiDimensionalArrayProvider(): array + { + return [ + 'not an array' => [ArrayInformation::notAnArray(), false], + 'single dimension array' => [ArrayInformation::singleDimension(), false], + 'multi dimension array' => [ArrayInformation::multiDimension(2), true], + ]; + } + + public function equalsDataProvider(): array + { + return [ + 'left and right equals' => [ArrayInformation::notAnArray(), ArrayInformation::notAnArray(), true], + 'dimensions differ' => [ArrayInformation::multiDimension(2), ArrayInformation::multiDimension(3), false], + 'array vs. not an array differ' + => [ArrayInformation::singleDimension(), ArrayInformation::notAnArray(), false], + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/LazyAnnotationMapTest.php b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/LazyAnnotationMapTest.php new file mode 100644 index 000000000..5bb5d24f3 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/LazyAnnotationMapTest.php @@ -0,0 +1,160 @@ +assertTrue($map->hasVar()); + } + + /** + * @covers \JsonMapper\ValueObjects\LazyAnnotationMap + */ + public function testHasVarReturnsFalseWhenInputHasNoVarTag(): void + { + $map = new LazyAnnotationMap('/** @param string $test */'); + + $this->assertFalse($map->hasVar()); + } + + /** + * @covers \JsonMapper\ValueObjects\LazyAnnotationMap + */ + public function testHasParamReturnsTrueWhenInputHasParamTag(): void + { + $map = new LazyAnnotationMap('/** @param string $test */'); + + $this->assertTrue($map->hasParam('test')); + } + + /** + * @covers \JsonMapper\ValueObjects\LazyAnnotationMap + */ + public function testHasParamReturnsFalseWhenInputHasNoParamTag(): void + { + $map = new LazyAnnotationMap('/** @var string */'); + + $this->assertFalse($map->hasParam('test')); + } + + /** + * @covers \JsonMapper\ValueObjects\LazyAnnotationMap + */ + public function testThrowsForUnknownTagWhenCallingTagToPropertyBuilder(): void + { + $map = new LazyAnnotationMap('/** */'); + + $this->expectException(\RuntimeException::class); + $map->tagToPropertyBuilder('var'); + } + + /** + * @covers \JsonMapper\ValueObjects\LazyAnnotationMap + * @dataProvider parseDataProvider + * + * @param array $imports + */ + public function testCorrectlyParsesInput(string $input, array $imports, string $tagName, ?string $variableName, Property $expected): void + { + $map = new LazyAnnotationMap($input, __NAMESPACE__, $imports); + + $property = $map->tagToPropertyBuilder($tagName, $variableName) + ->setName('') + ->setVisibility(Visibility::PUBLIC()) + ->build(); + + self::assertEquals($expected, $property); + } + + public function parseDataProvider(): \Generator + { + yield 'Simple string' => [ + 'input' => '/** @var string */', + 'imports' => [], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), false, new PropertyType('string', ArrayInformation::notAnArray())) + ]; + yield 'Simple integer' => [ + 'input' => '/** @var int */', + 'imports' => [], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), false, new PropertyType('int', ArrayInformation::notAnArray())) + ]; + yield 'Simple float' => [ + 'input' => '/** @var float */', + 'imports' => [], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), false, new PropertyType('float', ArrayInformation::notAnArray())) + ]; + yield 'Mixed' => [ + 'input' => '/** @var mixed */', + 'imports' => [], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), false, new PropertyType('mixed', ArrayInformation::notAnArray())) + ]; + yield 'Array of strings' => [ + 'input' => '/** @var array */', + 'imports' => [], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), false, new PropertyType('string', ArrayInformation::singleDimension())) + ]; + yield 'Nullable string' => [ + 'input' => '/** @var ?string */', + 'imports' => [], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), true, new PropertyType('string', ArrayInformation::notAnArray())) + ]; + yield 'true pseudo type' => [ + 'input' => '/** @var ?true */', + 'imports' => [], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), true, new PropertyType('bool', ArrayInformation::notAnArray())) + ]; + yield 'Simple int string union' => [ + 'input' => '/** @var string|int */', + 'imports' => [], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), false, new PropertyType('string', ArrayInformation::notAnArray()), new PropertyType('int', ArrayInformation::notAnArray())) + ]; + yield 'Simple int null union' => [ + 'input' => '/** @var int|null */', + 'imports' => [], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), true, new PropertyType('int', ArrayInformation::notAnArray())) + ]; + yield 'Object with imports' => [ + 'input' => '/** @var Popo */', + 'imports' => [new Import(Popo::class)], + 'tagName' => 'var', + 'variableName' => null, + new Property('', Visibility::PUBLIC(), false, new PropertyType(Popo::class, ArrayInformation::notAnArray())) + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/PropertyMapTest.php b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/PropertyMapTest.php new file mode 100644 index 000000000..cbaba05df --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/PropertyMapTest.php @@ -0,0 +1,151 @@ +addProperty($property); + + self::assertTrue($map->hasProperty('name')); + self::assertSame($property, $map->getProperty('name')); + } + + /** + * @covers \JsonMapper\ValueObjects\PropertyMap + */ + public function testGetPropertyThrowsErrorWhenPropertyDoesntExist(): void + { + $map = new PropertyMap(); + + $this->expectException(\Exception::class); + $map->getProperty('missing'); + } + + /** + * @covers \JsonMapper\ValueObjects\PropertyMap + */ + public function testMapReturnsCorrectIterator(): void + { + $property = new Property( + 'name', + Visibility::PUBLIC(), + true, + new PropertyType('string', ArrayInformation::notAnArray()) + ); + $map = new PropertyMap(); + $map->addProperty($property); + $iterator = $map->getIterator(); + + self::assertCount(1, $iterator); + self::assertSame($property, $iterator->current()); + } + + /** + * @covers \JsonMapper\ValueObjects\PropertyMap + */ + public function testCanBeConvertedToJson(): void + { + $map = new PropertyMap(); + $map->addProperty(new Property('id', Visibility::PUBLIC(), false, new PropertyType('int', ArrayInformation::notAnArray()))); + + $mapAsJson = json_encode($map); + + self::assertIsString($mapAsJson); + self::assertJsonStringEqualsJsonString( + '{"properties":{"id":{"name":"id","types":[{"type":"int","isArray":false,"arrayInformation":{"isArray":false,"dimensions":0}}],"visibility":"public","isNullable":false}}}', + (string) $mapAsJson + ); + } + + /** + * @covers \JsonMapper\ValueObjects\PropertyMap + */ + public function testCanBeConvertedToString(): void + { + $map = new PropertyMap(); + $map->addProperty(new Property('id', Visibility::PUBLIC(), false, new PropertyType('int', ArrayInformation::notAnArray()))); + + $mapAsString = $map->toString(); + + self::assertIsString($mapAsString); + self::assertJsonStringEqualsJsonString( + '{"properties":{"id":{"name":"id","types":[{"type":"int","isArray":false,"arrayInformation":{"isArray":false,"dimensions":0}}],"visibility":"public","isNullable":false}}}', + (string) $mapAsString + ); + } + + /** + * @covers \JsonMapper\ValueObjects\PropertyMap + */ + public function testCanBeMergedWithOtherPropertyMap(): void + { + $map = new PropertyMap(); + $map->addProperty(new Property('id', Visibility::PUBLIC(), false, new PropertyType('int', ArrayInformation::notAnArray()))); + $map->addProperty(new Property('data', Visibility::PUBLIC(), false, new PropertyType(Popo::class, ArrayInformation::singleDimension()))); + $other = new PropertyMap(); + $other->addProperty(new Property('uuid', Visibility::PUBLIC(), false, new PropertyType('string', ArrayInformation::notAnArray()))); + $other->addProperty(new Property('data', Visibility::PUBLIC(), false, new PropertyType('mixed', ArrayInformation::singleDimension()))); + + $map->merge($other); + + self::assertTrue($map->hasProperty('id')); + self::assertTrue($map->hasProperty('uuid')); + self::assertTrue($map->hasProperty('data')); + self::assertEquals( + new Property( + 'data', + Visibility::PUBLIC(), + false, + new PropertyType(Popo::class, ArrayInformation::singleDimension()), + new PropertyType('mixed', ArrayInformation::singleDimension()) + ), + $map->getProperty('data') + ); + } + + /** + * @covers \JsonMapper\ValueObjects\PropertyMap + */ + public function testCanBeMergedWithOtherPropertyMapWithExactDuplicate(): void + { + $map = new PropertyMap(); + $map->addProperty(new Property('data', Visibility::PUBLIC(), false, new PropertyType(Popo::class, ArrayInformation::singleDimension()))); + $other = new PropertyMap(); + $other->addProperty(new Property('data', Visibility::PUBLIC(), false, new PropertyType(Popo::class, ArrayInformation::singleDimension()))); + + $map->merge($other); + + self::assertTrue($map->hasProperty('data')); + self::assertEquals( + new Property( + 'data', + Visibility::PUBLIC(), + false, + new PropertyType(Popo::class, ArrayInformation::singleDimension()) + ), + $map->getProperty('data') + ); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/PropertyTest.php b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/PropertyTest.php new file mode 100644 index 000000000..87d5891cc --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/PropertyTest.php @@ -0,0 +1,88 @@ +getName()); + self::assertSame([$propertyType], $property->getPropertyTypes()); + self::assertFalse($property->isNullable()); + self::assertTrue($property->getVisibility()->equals(Visibility::PUBLIC())); + } + + /** + * @covers \JsonMapper\ValueObjects\Property + */ + public function testIsUnionReturnsTrueWhenMoreThanOneType(): void + { + $int = new PropertyType('int', ArrayInformation::notAnArray()); + $float = new PropertyType('float', ArrayInformation::notAnArray()); + $property = new Property('id', Visibility::PUBLIC(), false, $int, $float); + + self::assertTrue($property->isUnion()); + } + + /** + * @covers \JsonMapper\ValueObjects\Property + */ + public function testIsUnionReturnsFalseWhenOneType(): void + { + $int = new PropertyType('int', ArrayInformation::notAnArray()); + $property = new Property('id', Visibility::PUBLIC(), false, $int); + + self::assertFalse($property->isUnion()); + } + + /** + * @covers \JsonMapper\ValueObjects\Property + */ + public function testPropertyCanBeConvertedToBuilderAndBack(): void + { + $property = new Property( + 'id', + Visibility::PUBLIC(), + false, + new PropertyType('int', ArrayInformation::notAnArray()) + ); + $builder = $property->asBuilder(); + + self::assertEquals($property, $builder->build()); + } + + /** + * @covers \JsonMapper\ValueObjects\Property + */ + public function testCanBeConvertedToJson(): void + { + $property = new Property( + 'id', + Visibility::PUBLIC(), + false, + new PropertyType('int', ArrayInformation::notAnArray()) + ); + + $propertyAsJson = json_encode($property); + + self::assertIsString($propertyAsJson); + self::assertJsonStringEqualsJsonString( + '{"name":"id","types":[{"type":"int","isArray":false, "arrayInformation":{"isArray":false,"dimensions":0}}],"visibility":"public","isNullable":false}', + (string) $propertyAsJson + ); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/PropertyTypeTest.php b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/PropertyTypeTest.php new file mode 100644 index 000000000..2399b2201 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/ValueObjects/PropertyTypeTest.php @@ -0,0 +1,82 @@ +getType()); + self::assertEquals($arrayInformation, $propertyType->getArrayInformation()); + } + + /** + * @covers \JsonMapper\ValueObjects\PropertyType + */ + public function testCanBeConvertedToJson(): void + { + $propertyType = new PropertyType('int', ArrayInformation::notAnArray()); + + $propertyAsJson = json_encode($propertyType); + + self::assertIsString($propertyAsJson); + self::assertJsonStringEqualsJsonString( + '{"type":"int","isArray":false,"arrayInformation":{"isArray":false,"dimensions":0}}', + (string) $propertyAsJson + ); + } + + /** + * @covers \JsonMapper\ValueObjects\PropertyType + * @dataProvider isArrayValueAndExpectation + */ + public function testIsArrayReturnsCorrectForPossibleValues(ArrayInformation $isArray, bool $expected): void + { + $propertyType = new PropertyType('int', $isArray); + + self::assertSame($expected, $propertyType->isArray()); + } + + /** + * @covers \JsonMapper\ValueObjects\PropertyType + * @dataProvider isMultiDimensionalArrayValueAndExpectation + */ + public function testIsMultiDimensionalArrayReturnsCorrectForPossibleValues( + ArrayInformation $isArray, + bool $expected + ): void { + $propertyType = new PropertyType('int', $isArray); + + self::assertSame($expected, $propertyType->isMultiDimensionalArray()); + } + + public function isArrayValueAndExpectation(): array + { + return [ + 'no' => [ArrayInformation::notAnArray(), false], + 'single dimensional' => [ArrayInformation::singleDimension(), true], + 'multi dimensional' => [ArrayInformation::multiDimension(2), true,] + ]; + } + + public function isMultiDimensionalArrayValueAndExpectation(): array + { + return [ + 'no' => [ArrayInformation::notAnArray(), false], + 'single dimensional' => [ArrayInformation::singleDimension(), false], + 'multi dimensional' => [ArrayInformation::multiDimension(2), true,] + ]; + } +} diff --git a/vendor/json-mapper/json-mapper/tests/Unit/Wrapper/ObjectWrapperTest.php b/vendor/json-mapper/json-mapper/tests/Unit/Wrapper/ObjectWrapperTest.php new file mode 100644 index 000000000..1ce899e1f --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/Unit/Wrapper/ObjectWrapperTest.php @@ -0,0 +1,120 @@ +expectException(\BadFunctionCallException::class); + $this->expectExceptionMessage('Either object or className parameter must be provided, both are null'); + new ObjectWrapper(); + } + + /** + * @covers \JsonMapper\Wrapper\ObjectWrapper + */ + public function testConstructorInvalidObjectThrowsTypeException(): void + { + $this->expectException(\TypeError::class); + $this->expectExceptionMessage(sprintf( + '%s::__construct(): Argument #1 ($object) must be of type object, string given, called in %s on line %d', + ObjectWrapper::class, + __FILE__, + __LINE__ + 2 + )); + new ObjectWrapper(''); + } + + /** + * @covers \JsonMapper\Wrapper\ObjectWrapper + */ + public function testConstructorInvalidClassNameThrowsException(): void + { + $invalidClassName = __FUNCTION__; + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage("Argument 2 (\$className) must be a valid class name, $invalidClassName given"); + new ObjectWrapper(null, $invalidClassName); + } + + /** + * @covers \JsonMapper\Wrapper\ObjectWrapper + */ + public function testWrapsOriginalObject(): void + { + $object = new \stdClass(); + $wrapper = new ObjectWrapper($object); + + self::assertEquals($object, $wrapper->getObject()); + } + + /** + * @covers \JsonMapper\Wrapper\ObjectWrapper + */ + public function testCanFetchInstanceIfObjectNotSet(): void + { + $object = new \stdClass(); + $wrapper = new ObjectWrapper(null, \stdClass::class); + + self::assertInstanceOf(\stdClass::class, $wrapper->getObject()); + } + + /** + * @covers \JsonMapper\Wrapper\ObjectWrapper + */ + public function testWrapsOriginalClassName(): void + { + $wrapper = new ObjectWrapper(null, \stdClass::class); + + self::assertEquals(\stdClass::class, $wrapper->getClassName()); + } + + /** + * @covers \JsonMapper\Wrapper\ObjectWrapper + */ + public function testReflectedObjectIsOfWrappedObject(): void + { + $object = new \stdClass(); + $wrapper = new ObjectWrapper($object); + $reflectedObject = $wrapper->getReflectedObject(); + + self::assertEquals(get_class($object), $reflectedObject->getName()); + } + + /** + * @covers \JsonMapper\Wrapper\ObjectWrapper + */ + public function testCanGetNameOfWrappedObject(): void + { + $object = new \stdClass(); + $wrapper = new ObjectWrapper($object); + + self::assertEquals(\stdClass::class, $wrapper->getName()); + } + + /** + * @covers \JsonMapper\Wrapper\ObjectWrapper + */ + public function testSetObjectReplacesObjectAndClearsReflectedObject(): void + { + $originalObject = new \stdClass(); + $replacementObject = new Popo(); + $wrapper = new ObjectWrapper($originalObject); + + $wrapper->setObject($replacementObject); + + self::assertSame($replacementObject, $wrapper->getObject()); + self::assertNotSame($originalObject, $wrapper->getObject()); + self::assertEquals(get_class($replacementObject), $wrapper->getReflectedObject()->getName()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/benchmark/Joke.php b/vendor/json-mapper/json-mapper/tests/benchmark/Joke.php new file mode 100644 index 000000000..f4631ea2f --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/benchmark/Joke.php @@ -0,0 +1,13 @@ +mapper = (new JsonMapperFactory())->bestFit(); + $this->content = file_get_contents(__DIR__ . '/../resources/chucknorris/kick.json'); + } + + /** + * @Revs(100) + * @Iterations(5) + */ + public function benchMapSimpleObject(): void + { + $this->mapper->mapObjectFromString( + '{"id":131,"type":"general","setup":"How do you organize a space party?","punchline":"You planet."}', + new Joke() + ); + } + + /** + * @Revs(100) + * @Iterations(5) + */ + public function benchMapComplexObject(): void + { + $this->mapper->mapObjectFromString($this->content, new SearchResponse()); + } +} diff --git a/vendor/json-mapper/json-mapper/tests/benchmark/vanilla.php b/vendor/json-mapper/json-mapper/tests/benchmark/vanilla.php new file mode 100644 index 000000000..31bb29260 --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/benchmark/vanilla.php @@ -0,0 +1,23 @@ +bestFit(); + +for ($x = 1; $x < 5000; $x++) { + $joke = new Joke(); + $json = '{"id":131,"type":"general","setup":"How do you organize a space party?","punchline":"You planet."}'; + $mapper->mapObjectFromString($json, $joke); +} diff --git a/vendor/json-mapper/json-mapper/tests/resources/chucknorris/kick.json b/vendor/json-mapper/json-mapper/tests/resources/chucknorris/kick.json new file mode 100644 index 000000000..fe39b3b1f --- /dev/null +++ b/vendor/json-mapper/json-mapper/tests/resources/chucknorris/kick.json @@ -0,0 +1,6573 @@ +{ + "total": 821, + "result": [ + { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "iyigyr-fqi-cxllf6qnpag", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/iyigyr-fqi-cxllf6qnpag", + "value": "Chuck Norris keeps his friends close and his enemies closer. Close enough to drop them with one round house kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Z4Kr-UukTl2XutloFvRZNA", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/Z4Kr-UukTl2XutloFvRZNA", + "value": "Chuck Norris can execute a roundhouse kick in a telephone booth!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "zV2fzxDvRMyEdgSk2jTVKg", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/zV2fzxDvRMyEdgSk2jTVKg", + "value": "Chuck Norris' sperm can kick your ass" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "6HX-zoSlRFuKLDrPGl83cw", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/6HX-zoSlRFuKLDrPGl83cw", + "value": "Chuck Norris rounddhouse kicks kittens and baby's for fun." + }, { + "categories": ["dev"], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "zdj0bfkjsmup6pkb2rpmbw", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/zdj0bfkjsmup6pkb2rpmbw", + "value": "Chuck Norris is the only human being to display the Heisenberg uncertainty principle - you can never know both exactly where and how quickly he will roundhouse-kick you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MlP1e4owRr-PyO9kIqlAfg", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/MlP1e4owRr-PyO9kIqlAfg", + "value": "Chuck Norris stopped at a stop sign, it turned green. Someone once asked Chuck Norris for advice, he's know dead. Chuck Norris saw a man tell his gf \"you take my breathe away\" he kicked him in the chest and said \"wrong\" Even Chuck Norris shadows is scared of him. Someone thought prank calling Chuck Norris from across the world would keep him save, he kicked him through the phone. When Chuck Norris dies he doesn't go to heaven or hell, he goes to Olympus" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "go6nGny4S72B9YAVBq1f2g", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/go6nGny4S72B9YAVBq1f2g", + "value": "Chuck Norris once whent to pizzahut. He ordered an eggroll with some swiss cheese. A rotten cow corpse.A dead president.And a large box with Hellfire missiles. Pizza hut served it on a big tuna fish pizza. Chuck Norris Roundhousekicked the whole place to oblivion making everyone inside suffer terrible pain for eternity. Chuck Norris did not order any pizza." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9vqrfnk0qhoainqtrwgg4w", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/9vqrfnk0qhoainqtrwgg4w", + "value": "He who lives by the sword, dies by the sword. He who lives by Chuck Norris, dies by the roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "tjq8lv6lqi-uoo5cxiu2oa", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/tjq8lv6lqi-uoo5cxiu2oa", + "value": "Chuck Norris can be unlocked on the hardest level of Tekken. But only Chuck Norris is skilled enough to unlock himself. Then he roundhouse kicks the Playstation back to Japan." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LNqCW_FmQ5ubonXnop02kQ", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/LNqCW_FmQ5ubonXnop02kQ", + "value": "The only thing you can beat Chuck Norris at is the number of times you've had your face kicked in." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "h2y1uucftfsrzhhussrbiq", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/h2y1uucftfsrzhhussrbiq", + "value": "The Bermuda Triangle used to be the Bermuda Square, until Chuck Norris Roundhouse kicked one of the corners off." + }, { + "categories": ["career"], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cwguxfhptcuagndjdt1hya", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/cwguxfhptcuagndjdt1hya", + "value": "In the beginning there was nothing...then Chuck Norris Roundhouse kicked that nothing in the face and said \"Get a job\". That is the story of the universe." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ldq6_b4mslm489drbfmd9q", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/ldq6_b4mslm489drbfmd9q", + "value": "Since 1940, the year Chuck Norris was born, roundhouse kick related deaths have increased 13,000 percent." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ovpkkf3lr2ar6wix6nef7q", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/ovpkkf3lr2ar6wix6nef7q", + "value": "Chuck Norris doesnt shave; he kicks himself in the face. The only thing that can cut Chuck Norris is Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yvrhbpauspegla4pf7dxna", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/yvrhbpauspegla4pf7dxna", + "value": "Chuck Norris is the only person who can simultaneously hold and fire FIVE Uzis: One in each hand, one in each foot -- and the 5th one he roundhouse-kicks into the air, so that it sprays bullets." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kl5ppkvirn2xg2xhjbd8og", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/kl5ppkvirn2xg2xhjbd8og", + "value": "How many roundhouse kicks does it take to get to the center of a tootsie pop? Just one. From Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "nj2g0xbnrf6xzb-vfohzbq", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/nj2g0xbnrf6xzb-vfohzbq", + "value": "Little Miss Muffet sat on her tuffet, until Chuck Norris roundhouse kicked her into a glacier." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "uw_3pnxjqdijbnum28u17g", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/uw_3pnxjqdijbnum28u17g", + "value": "Human cloning is outlawed because of Chuck Norris, because then it would be possible for a Chuck Norris roundhouse kick to meet another Chuck Norris roundhouse kick. Physicists theorize that this contact would end the universe." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "eyybzzcqse6ls_xwlbyscg", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/eyybzzcqse6ls_xwlbyscg", + "value": "Chuck Norris invented a language that incorporates karate and roundhouse kicks. So next time Chuck Norris is kicking your ass, don?t be offended or hurt, he may be just trying to tell you he likes your hat." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "i6hnatlnsxwgzj_ruqkubg", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/i6hnatlnsxwgzj_ruqkubg", + "value": "A man once claimed Chuck Norris kicked his ass twice, but it was promptly dismissed as false - no one could survive it the first time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "_3fic7lhtggb9sccwquysa", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/_3fic7lhtggb9sccwquysa", + "value": "Chuck Norris roundhouse kicks don't really kill people. They wipe out their entire existence from the space-time continuum." + }, { + "categories": ["fashion"], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0wdewlp2tz-mt_upesvrjw", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/0wdewlp2tz-mt_upesvrjw", + "value": "Chuck Norris does not follow fashion trends, they follow him. But then he turns around and kicks their ass. Nobody follows Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mbWbKoWNRc24HNXQIt9ubA", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/mbWbKoWNRc24HNXQIt9ubA", + "value": "Chuck Norris chooses not to compete in an Ironman because of the swim, every time he starts kicking and swinging his arms, people die!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "obr8yor_qv6rdrnnvcgayw", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/obr8yor_qv6rdrnnvcgayw", + "value": "Every time Chuck Norris smiles, someone dies. Unless he smiles while he?s roundhouse kicking someone in the face. Then two people die." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:18.823766", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "6awjrbewqzeorpsnnsdlag", + "updated_at": "2020-01-05 13:42:18.823766", + "url": "https://api.chucknorris.io/jokes/6awjrbewqzeorpsnnsdlag", + "value": "Chuck Norris? roundhouse kick is so powerful, it can be seen from outer space by the naked eye." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kGjO8ja6TY6n6CaIAlBl-w", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/kGjO8ja6TY6n6CaIAlBl-w", + "value": "You don't really see a light at the end of the tunnel you see Chuck Norris roundhouse kick your face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "y5yiitk0tbgz3tosanyeaw", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/y5yiitk0tbgz3tosanyeaw", + "value": "According to Einstein's theory of relativity, Chuck Norris can actually roundhouse kick you yesterday." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "u4xgukrisucgbi8lhx_wkw", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/u4xgukrisucgbi8lhx_wkw", + "value": "It is said that looking into Chuck Norris' eyes will reveal your future. Unfortunately, everybody's future is always the same: death by a roundhouse-kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gkdmoby6rg6obe8u_n2gaq", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/gkdmoby6rg6obe8u_n2gaq", + "value": "Every time someone uses the word \"intense\", Chuck Norris always replies \"you know what else is intense?\" followed by a roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ufgjcp0_qrugwggnmqhvdq", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/ufgjcp0_qrugwggnmqhvdq", + "value": "Chuck Norris does not kick ass and take names. In fact, Chuck Norris kicks ass and assigns the corpse a number. It is currently recorded to be in the billions." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "zGQp6B1wSrmNrE5ijXwVHA", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/zGQp6B1wSrmNrE5ijXwVHA", + "value": "Chuck Norris was born when the roundhouse kicks were two minutes apart." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "boa13y2fqk-knkj8yuy19w", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/boa13y2fqk-knkj8yuy19w", + "value": "Little known medical fact: Chuck Norris invented the Caesarean section when he roundhouse-kicked his way out of his monther's womb." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "99i2pfwpr0sscfyjctywsw", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/99i2pfwpr0sscfyjctywsw", + "value": "Since 1940, the year Chuck Norris was born, roundhouse-kick related deaths have increased 13,000 percent." + }, { + "categories": ["dev"], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mbxkx1y8qgwbu5ld3qen4w", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/mbxkx1y8qgwbu5ld3qen4w", + "value": "Chuck Norris compresses his files by doing a flying round house kick to the hard drive." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ot1bq3lrt_23uwmakbba3g", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/ot1bq3lrt_23uwmakbba3g", + "value": "When Chuck Norris plays Oregon Trail, his family does not die from cholera or dysentery, but rather, roundhouse kicks to the face. He also requires no wagon, since he carries the oxen, axels, and buffalo meat on his back. He always makes it to Oregon before you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "p1bscolyrlg-cviwpkmjba", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/p1bscolyrlg-cviwpkmjba", + "value": "Pluto is actually an orbiting group of British soldiers from the American Revolution who entered space after the Chuck gave them a roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "6l9zm-aqtuq1wkxidhwa4w", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/6l9zm-aqtuq1wkxidhwa4w", + "value": "Kryptonite has been found to contain trace elements of Chuck Norris roundhouse kicks to the face. This is why it is so deadly to Superman." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0wsFII4QTfmw8cG6iqs86A", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/0wsFII4QTfmw8cG6iqs86A", + "value": "i kicked Chuck Norris' ass the other day, then i rode the magic rainbow bus to fairy land and had tea with the easter bunny." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "4n76tu_jtcuutl1xl5k9sg", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/4n76tu_jtcuutl1xl5k9sg", + "value": "Two wrongs don't make a right. Unless you're Chuck Norris. Then two wrongs make a roundhouse kick to the face." + }, { + "categories": ["dev"], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hlsa0rurqwyqanmszx0miq", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/hlsa0rurqwyqanmszx0miq", + "value": "Scientists have estimated that the energy given off during the Big Bang is roughly equal to 1CNRhK (Chuck Norris Roundhouse Kick)." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1i7ddr75taqda_jqolw0ra", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/1i7ddr75taqda_jqolw0ra", + "value": "If you ask Chuck Norris what time it is, he always answers \"Two seconds till\". After you ask \"Two seconds to what?\", he roundhouse kicks you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "dyrg0pvytxw0zy8lfdp5ag", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/dyrg0pvytxw0zy8lfdp5ag", + "value": "Someone once tried to tell Chuck Norris that roundhouse kicks aren't the best way to kick someone. This has been recorded by historians as the worst mistake anyone has ever made." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "S8JeYXgPTCyxHlskLbC6qg", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/S8JeYXgPTCyxHlskLbC6qg", + "value": "Chuck Norris never actually gets hit by punches and kicks, he actually is performing a fighting style where he smashes the limbs of his foes with his face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "aoyarv4rtuw5m846bh_ing", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/aoyarv4rtuw5m846bh_ing", + "value": "Chuck Norris has never been in a fight, ever. Do you call one roundhouse kick to the face a fight?" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "igoujmmmqt2yc8jzb1kkww", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/igoujmmmqt2yc8jzb1kkww", + "value": "A Chuck Norris-delivered Roundhouse Kick is the preferred method of execution in 16 states." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "_gczr0ctrjaia-asoqxbrq", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/_gczr0ctrjaia-asoqxbrq", + "value": "If Chuck Norris were a calendar, every month would be named Chucktober, and every day he'd kick your ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "e0qejf_zqjumu4rpg1huua", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/e0qejf_zqjumu4rpg1huua", + "value": "Fear is not the only emotion Chuck Norris can smell. He can also detect hope, as in \"I hope I don't get a roundhouse kick from Chuck Norris.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ocpw1ex6qfoole7gvia3ew", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/ocpw1ex6qfoole7gvia3ew", + "value": "Fool me once, shame on you. Fool Chuck Norris once and he will roundhouse kick you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "pn8lrabrtbmfops1amqqpg", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/pn8lrabrtbmfops1amqqpg", + "value": "Chuck Norris once round-house kicked a salesman. Over the phone." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yec826sksvow5e5kxgdl3w", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/yec826sksvow5e5kxgdl3w", + "value": "It is better to give than to receive. This is especially true of a Chuck Norris roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "D1HKI2A-R0uEPA5j3rIJfw", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/D1HKI2A-R0uEPA5j3rIJfw", + "value": "Captain Planet once told Chuck Norris that he had control over all the elements - earth, water, wind and fire. Chuck Norris then drowned him in fire and blew him six feet under, thereby utilizing all four elements to move the good captain asunder. (It should be noted here that Chuck Norris, for the first time ever, deviated from using his signature roundhouse kick.)" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "garmcdAbTTGanAYd_VtAhw", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/garmcdAbTTGanAYd_VtAhw", + "value": "The name 'Chuck Norris' rolls off the tongue and kicks you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "bctbwqxesukmhrjuumupaw", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/bctbwqxesukmhrjuumupaw", + "value": "If you ask Chuck Norris what time it is, he always says, \"Two seconds 'til.\" After you ask, \"Two seconds 'til what?\" he roundhouse kicks you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "z8XIJSrOTpqNGkuiFVCEbA", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/z8XIJSrOTpqNGkuiFVCEbA", + "value": "Chuck Norris can hammer an nail into the wall with a roundhouse kick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.104863", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wr247cnmthuzu-jz57lxiq", + "updated_at": "2020-01-05 13:42:19.104863", + "url": "https://api.chucknorris.io/jokes/wr247cnmthuzu-jz57lxiq", + "value": "CNN was originally created as the \"Chuck Norris Network\" to update Americans with on-the-spot ass kicking in real-time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "dmppfaxjqnkfubpqzgcmxq", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/dmppfaxjqnkfubpqzgcmxq", + "value": "Using his trademark roundhouse kick, Chuck Norris once made a fieldgoal in RJ Stadium in Tampa Bay from the 50 yard line of Qualcomm stadium in San Diego." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "znkm6ggrrf22cnlizfby1a", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/znkm6ggrrf22cnlizfby1a", + "value": "Chuck Norris can kick through all 6 degrees of separation, hitting anyone, anywhere, in the face, at any time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "_npdfx5oroyfs0v9rxuglw", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/_npdfx5oroyfs0v9rxuglw", + "value": "When you're Chuck Norris, anything + anything is equal to 1. One roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KDNst_DLT321gRz8bBN1YQ", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/KDNst_DLT321gRz8bBN1YQ", + "value": "Chuck Norris can recycle anything he wants,including his roundhouse kicks." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kb43vo_4rtoladsdlnnpsq", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/kb43vo_4rtoladsdlnnpsq", + "value": "Some kids play Kick the can. Chuck Norris played Kick the keg." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ropmm-mhrtotnxq95rsdrg", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/ropmm-mhrtotnxq95rsdrg", + "value": "On the set of Walker Texas Ranger Chuck Norris brought a dying lamb back to life by nuzzling it with his beard. As the onlookers gathered, the lamb sprang to life. Chuck Norris then roundhouse kicked it, killing it instantly. This was just to prove that the good Chuck givet" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gvm13ovts-wc-he9kpkobq", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/gvm13ovts-wc-he9kpkobq", + "value": "Chuck Norris is the only person in the world that can actually email a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5FNPTo_vSXqv51umsP0Mig", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/5FNPTo_vSXqv51umsP0Mig", + "value": "A fight broke out at a bar between a gangster who had a gun, a farmer who had a bat and Chuck Norris. Chuck Norris proceeded to win the fight with only a 3 inch piece of dental floss, a stick of chapstick, and an unlimited amount of Round House Kicks." + }, { + "categories": ["science"], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "zfgekm2usfyfra7m5x0wta", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/zfgekm2usfyfra7m5x0wta", + "value": "If tapped, a Chuck Norris roundhouse kick could power the country of Australia for 44 minutes." + }, { + "categories": ["science"], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "u-xwyw6rq0-dxrq1rioogg", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/u-xwyw6rq0-dxrq1rioogg", + "value": "Chuck Norris once roundhouse kicked someone so hard that his foot broke the speed of light, went back in time, and killed Amelia Earhart while she was flying over the Pacific Ocean." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "O1psmCrLT8-HC7uDeGnLyg", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/O1psmCrLT8-HC7uDeGnLyg", + "value": "This little piggy went to the market,this little piggy stayed home,this little piggy went we we we all the way home because Chuck Norris delivered a fatal roundhouse kick to the other piggy." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "EEOlPA7VShGuivlXBvlAZA", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/EEOlPA7VShGuivlXBvlAZA", + "value": "Chuck Norris chose Mudkip as his starter when playing Pokemon Emerald. This lead to the meme \"so i herd u liek mudkipz\". Therefore, anyone who answers \"No\" and knows what a Mudkip is will get roundhouse-kicked in the face by... I don't have to say who, do I?" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-hjkiiyxqmk8lly86gelhg", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/-hjkiiyxqmk8lly86gelhg", + "value": "All roads lead to Chuck Norris. And by the transitive property, a roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "n20qoz6tswuf4-apuugeiw", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/n20qoz6tswuf4-apuugeiw", + "value": "The crossing lights in Chuck Norris's home town say \"Die slowly\" and \"die quickly\". They each have a picture of Chuck Norris punching or kicking a pedestrian." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1gg3imfzqdgr0vw4szphdg", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/1gg3imfzqdgr0vw4szphdg", + "value": "The last thing you hear before Chuck Norris gives you a roundhouse kick? No one knows because dead men tell no tales." + }, { + "categories": ["dev"], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "63pymsysqiexzldn5-wdzq", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/63pymsysqiexzldn5-wdzq", + "value": "Chuck Norris's database has only one table, 'Kick', which he DROPs frequently." + }, { + "categories": ["dev"], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "e2gyguu0tg6di7-qykdjnw", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/e2gyguu0tg6di7-qykdjnw", + "value": "Chuck Norris originally appeared in the \"Street Fighter II\" video game, but was removed by Beta Testers because every button caused him to do a roundhouse kick. When asked about this glitch, Norris replied \"That's no glitch.\"" + }, { + "categories": ["dev"], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "iy5n_afwrrqrj9wb6n21ww", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/iy5n_afwrrqrj9wb6n21ww", + "value": "Chuck Norris doesn't need garbage collection because he doesn't call .Dispose(), he calls .DropKick()." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "3KSg1quiQsaiJ4UhDVBP_Q", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/3KSg1quiQsaiJ4UhDVBP_Q", + "value": "Chuck Norris can roundhouse kick your face with his pinky finger." + }, { + "categories": ["celebrity"], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "nbfmksvwq3stmubxphsfbw", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/nbfmksvwq3stmubxphsfbw", + "value": "Saddam Hussein was not found hiding in a \"hole.\" Saddam was roundhouse-kicked in the head by Chuck Norris in Kansas, which sent him through the earth, stopping just short of the surface of Iraq." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kDBZNuGOSGCw18QU7VjCwQ", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/kDBZNuGOSGCw18QU7VjCwQ", + "value": "Chuck Norris once had a heckler call him \"Chuckie\". Chuck kicked him so hard he was arrested for speeding 2 blocks away." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RDCtS4GjQpmwByA3ytBs2A", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/RDCtS4GjQpmwByA3ytBs2A", + "value": "I just downloaded the new \"Roundhouse Kick\" app for my iPhone and now my screen is cracked and the phone does not work. Damn you, Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "UJbko6N2SRWjjy_Z-nNQnQ", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/UJbko6N2SRWjjy_Z-nNQnQ", + "value": "Jack Kevorkian was a doctor that assisted people in ending their lives voluntarily. His methods were criticized by many. Truth is, he told his patients to close their eyes and he would hold a picture of a Chuck Norris round house kick to their face and have them open their eyes. Indeed they died instantly from the shock and breaking their own necks in fear. When Chuck Norris found out, he round house kicked Kevorkian for copyright infringement." + }, { + "categories": ["religion"], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "3kf42nr6qhkll8hucjbhow", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/3kf42nr6qhkll8hucjbhow", + "value": "Chuck Norris has never been accused of murder because his roundhouse kicks are recognized as \"acts of God.\"" + }, { + "categories": ["dev"], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hcakrd4frju3vd-jakidqq", + "updated_at": "2020-01-05 13:42:19.324003", + "url": "https://api.chucknorris.io/jokes/hcakrd4frju3vd-jakidqq", + "value": "If you Google search \"Chuck Norris getting his ass kicked\" you will generate zero results. It just doesn't happen." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Wb6v8TcZTLipa8uESxGtWA", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/Wb6v8TcZTLipa8uESxGtWA", + "value": "Do you seriously think that McDonalds came up with the slogan \"would you like fries with that\"? Everyone knows that is what Chuck Norris tells his victims immediately proceeding a roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "SEZJrKA1STKjzQwE8Ubxqw", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/SEZJrKA1STKjzQwE8Ubxqw", + "value": "A Chuck Norris roundhouse kick delivered with precision accuracy to the base of your skull will cause your face to blister, putrify and later slide off the front of your head." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HipPzAYvT4Wp3k58eWuNwQ", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/HipPzAYvT4Wp3k58eWuNwQ", + "value": "Chuck Norris never runs out of things to kick." + }, { + "categories": ["movie"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "nuvacfzeqomvab8yfeskxq", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/nuvacfzeqomvab8yfeskxq", + "value": "They had to edit the first ending of 'Lone Wolf McQuade' after Chuck Norris kicked David Carradine's ass, then proceeded to barbecue and eat him." + }, { + "categories": ["sport"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "t05rcwc4q6mjhx9zuin2qq", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/t05rcwc4q6mjhx9zuin2qq", + "value": "The US did not boycott the 1980 Summer Olympics in Moscow due to political reasons: Chuck Norris killed the entire US team with a single round-house kick during TaeKwonDo practice." + }, { + "categories": ["animal"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "xwjic1sws_yohsfefndaiw", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/xwjic1sws_yohsfefndaiw", + "value": "Chuck Norris once kicked a horse in the chin. Its decendants are known today as Giraffes." + }, { + "categories": ["food"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ojw-faz_tbglq0q4sgwt8w", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/ojw-faz_tbglq0q4sgwt8w", + "value": "Chuck Norris doesn't churn butter. He roundhouse kicks the cows and the butter comes straight out." + }, { + "categories": ["movie"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "zc2g3me0rnqbfankmo99hq", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/zc2g3me0rnqbfankmo99hq", + "value": "MacGyver immediately tried to make a bomb out of some Q-Tips and Gatorade, but Chuck Norris roundhouse-kicked him in the solar plexus. MacGyver promptly threw up his own heart." + }, { + "categories": ["music"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0_acdyhnthyshildaipp1q", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/0_acdyhnthyshildaipp1q", + "value": "Who let the dogs out? Chuck Norris let the dogs out... and then roundhouse kicked them through an Oldsmobile." + }, { + "categories": ["food"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ckg3rsihqvar2uqltyx58q", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/ckg3rsihqvar2uqltyx58q", + "value": "When Chuck Norris was denied an Egg McMuffin at McDonald's because it was 10:35, he roundhouse kicked the store so hard it became a Wendy's." + }, { + "categories": ["movie"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5e4voyf7t_yw-9qi8ppy6w", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/5e4voyf7t_yw-9qi8ppy6w", + "value": "The original draft of The Lord of the Rings featured Chuck Norris instead of Frodo Baggins. It was only 5 pages long, as Chuck roundhouse-kicked Sauron's ass halfway through the first chapter." + }, { + "categories": ["movie"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "h6t1xcbws6m8k_pduoznwg", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/h6t1xcbws6m8k_pduoznwg", + "value": "MacGyver can build an airplane out of gum and paper clips, but Chuck Norris can roundhouse-kick his head through a wall and take it." + }, { + "categories": ["history"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "rqcvwdgqq6amwony3nngba", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/rqcvwdgqq6amwony3nngba", + "value": "In the Words of Julius Caesar, \"Veni, Vidi, Vici, Chuck Norris\". Translation: I came, I saw, and I was roundhouse-kicked inthe face by Chuck Norris." + }, { + "categories": ["music"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7tb4dwgvrwkaqg-a-wzhlq", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/7tb4dwgvrwkaqg-a-wzhlq", + "value": "Chuck Norris shot the sheriff, but he round house kicked the deputy." + }, { + "categories": ["sport"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "aefmqoxotvaugymnsqoezw", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/aefmqoxotvaugymnsqoezw", + "value": "The 1972 Miami Dolphins lost one game, it was a game vs. Chuck Norris and three seven year old girls. Chuck Norris won with a roundhouse-kick to the face in overtime." + }, { + "categories": ["science"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "qszz44pvr3iv7kzgwoedza", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/qszz44pvr3iv7kzgwoedza", + "value": "Chuck Norris can do a roundhouse kick faster than the speed of light. This means that if you turn on a light switch, you will be dead before the lightbulb turns on." + }, { + "categories": ["science"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "pattwbdusdgzo753xnhyxw", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/pattwbdusdgzo753xnhyxw", + "value": "Science Fact: Roundhouse kicks are comprised primarily of an element called Chucktanium." + }, { + "categories": ["sport"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2o6183z1rmkus1imghxsug", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/2o6183z1rmkus1imghxsug", + "value": "Chuck Norris won super bowls VII and VIII singlehandedly before unexpectedly retiring to pursue a career in ass-kicking." + }, { + "categories": ["science"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "h2le0vpkstise9oetsodmw", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/h2le0vpkstise9oetsodmw", + "value": "Newton's Third Law is wrong: Although it states that for each action, there is an equal and opposite reaction, there is no force equal in reaction to a Chuck Norris roundhouse kick." + }, { + "categories": ["history"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "t_wyddbstys8ubos8oni4q", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/t_wyddbstys8ubos8oni4q", + "value": "Chuck Norris originally wrote the first dictionary. The definition for each word is as follows - A swift roundhouse kick to the face." + }, { + "categories": ["movie"], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "s4gbskbvscsc4zzybwhh3q", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/s4gbskbvscsc4zzybwhh3q", + "value": "Jean-Claude Van Damme once kicked Chuck Norris' ass. He was then awakened from his dream by a roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "SxTKsQ0bTAaHuQT8QFOK1Q", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/SxTKsQ0bTAaHuQT8QFOK1Q", + "value": "Chuck Norris roundhouse-kicked the letter 'r' out of Jonathon Woss." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "O9VYsf2wSuirvshvRThWPA", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/O9VYsf2wSuirvshvRThWPA", + "value": "Chuck Norris stole Christmas back from the Grinch and roundhouse kicked The Grinch's ass 84594205743920574189057209 times." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jaHkV8rASP2SdzOg33skCA", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/jaHkV8rASP2SdzOg33skCA", + "value": "Chuck Norris's round house kick isn't nearly as bad when he stares you down and you burst into flames and burn to death." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OjAI01UsQQeSdrozSnDXLw", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/OjAI01UsQQeSdrozSnDXLw", + "value": "Shooting stars wish Chuck Norris wouldn't roundhouse kick them." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "get7fvMwTqqp5PdFwoGY1A", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/get7fvMwTqqp5PdFwoGY1A", + "value": "Entropy in the universe increases one billion fold, after every roundhouse kick by Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "TAfEYk5-RPOEQZKsbLqlGg", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/TAfEYk5-RPOEQZKsbLqlGg", + "value": "Chuck Norris can kick you once and give you three ass whippings." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.576875", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "q8pf6tPMQ-KJUUKPMH9A7A", + "updated_at": "2020-01-05 13:42:19.576875", + "url": "https://api.chucknorris.io/jokes/q8pf6tPMQ-KJUUKPMH9A7A", + "value": "Chuck Norris lettered in 14 sports in high school. He would have had won letters in 15 if ass-kicking had been an officially recognized sport." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "pTVAZRxjQKOQH4NK56_aQA", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/pTVAZRxjQKOQH4NK56_aQA", + "value": "Whenever someone clicks on Chuck Norris' facebook page, they instantly get round house kicked" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "bHy4w6LgQ7u2_Nzer29H4Q", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/bHy4w6LgQ7u2_Nzer29H4Q", + "value": "Little miss muffet sat on her tuffet, until Chuck Norris roundhouse kicked her into an iceberg" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "lVJ_cOmAR4K_FGptE4FQzw", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/lVJ_cOmAR4K_FGptE4FQzw", + "value": "Chuck Norris once launched a massive reverse roundhouse kick at a man's face, missed and struck a conrete wall. The man died anyway from the percussion shock." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "eb4COkzuRF-fGQnnJqTFNg", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/eb4COkzuRF-fGQnnJqTFNg", + "value": "yo momma so ugly, Chuck Norris had to roundhouse kick her.the image of yo momma still haunts Chuck Norris to this day." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LRYE5ZZWSnmdWYZgVbrw2w", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/LRYE5ZZWSnmdWYZgVbrw2w", + "value": "In the year 1735 a meteor was hurling towards earth. It suddenly stopped at the site of Chuck Norris. It asked Chuck Norris for his autograph, then Chuck Norris roundhoused kicked it back to space." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "lyxI8DpLRS2uN-5tzvkg3Q", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/lyxI8DpLRS2uN-5tzvkg3Q", + "value": "Chuck Norris will beat you with his nunchucks. Chuck Norris will roundhouse kick you with his Chuck Taylors. Chuck Norris will feed you to the woodchucks. Don't fuck with Chuck!!1" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "drsyP-byS-CdqgxU9JOFBA", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/drsyP-byS-CdqgxU9JOFBA", + "value": "Chuck Norris was banned from the Winter Olympics because the judges didnt know how to score a double-lutz flip-axle roundhouse kick to the face!!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ii-hnG2ER0O9Qv4dUKBmIw", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/Ii-hnG2ER0O9Qv4dUKBmIw", + "value": "Bloody noses happen because Chuck Norris thought about roundhouse kicking you in the face, but then changed his mind." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "FT6JGPbfQ3KoHjbqo3ttQA", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/FT6JGPbfQ3KoHjbqo3ttQA", + "value": "a black hole is not created when a star dies, it is created when a star gets round-house kicked by Chuck Norris" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "e3cfSdJSSQm_Cw7MtXwrlg", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/e3cfSdJSSQm_Cw7MtXwrlg", + "value": "When Chuck Norris was born he roundhouse kicked his way out of the womb" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "X9nDrLvJSOilsRBuGvhXYA", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/X9nDrLvJSOilsRBuGvhXYA", + "value": "Chuck Norris doesn't edit Wikipedia, he roundhouse kicks the articles." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "SUpwpd76Q4qKiN54sskmUg", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/SUpwpd76Q4qKiN54sskmUg", + "value": "When GOOGLE denied Chuck Norris his own search engine, he persuaded them with a swift kick to the \"G\". http://chuckoogle.com" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "uPgkeVseTJCp8ad9rCcrwg", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/uPgkeVseTJCp8ad9rCcrwg", + "value": "Chuck Norris showed Paloma Faith that roundhouse kicks also hurt like love." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "NwsbprEDSxyCNTcj0g4Dxg", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/NwsbprEDSxyCNTcj0g4Dxg", + "value": "If you say Chuck Norris' name in Mongolia, the people there will roundhouse kick you in his honor. Their kick will be followed by the REAL roundhouse delivered by none other than Norris himself." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XlXLbSLOQYedYDIb6KwuIg", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/XlXLbSLOQYedYDIb6KwuIg", + "value": "In the movie Alien 5 the Asswhoopin', movie goers were astonished to see a small Chuck Norris burst out of an alien and proceed to kick the crap out of him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ne-M6C0cQbC8Syy06efbJQ", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/Ne-M6C0cQbC8Syy06efbJQ", + "value": "Chuck Norris is in the death star trench run; Obi one kenobi: use the force chuck. Chuck Norris: F*** you old man, I use round house kick. Period The death star blew up with only one roundhouse kick and the universe was saved." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yf8cFo-AT0GSb9C6AmH__Q", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/yf8cFo-AT0GSb9C6AmH__Q", + "value": "Chuck Norris was kicked out of the the NFl for refusing to wear a helmet or a pad of any kind..." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "lWL-DGFgS9-gGFfogiiXwg", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/lWL-DGFgS9-gGFfogiiXwg", + "value": "Hemorrhoids are a pain in the ass. And in legal terms so is a Chuck Norris roundhouse kick to 'said same'." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "K64oRq5dSv2_Op5q6cfGnw", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/K64oRq5dSv2_Op5q6cfGnw", + "value": "Facebook was originally Chuck Norris's personal webpage of pictures of the people he has roundhouse kicked in face.It filled up with so many people it leaked onto the internet." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "H9Pn_tS4S46JqmPScqG4HQ", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/H9Pn_tS4S46JqmPScqG4HQ", + "value": "Once on Blue's Clues, when Steve Burns was ill, Chuck Norris came to the show and disguised like Steve to let the children not know that Chuck Norris was on Blue's Clues. After the episode, Chuck made it through the game without clues. Steve then said \"Did you use clues?\". Then Chuck Norris took his disguise off and roundhouse kicked Steve in the face without a word. That was 16 years ago." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yKztmVYSRJqZUOc6jwEphA", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/yKztmVYSRJqZUOc6jwEphA", + "value": "The so-called imaginary numbers used to be real numbers, till Chuck Norris roundhouse kicked them to oblivion. This incident is also responsible for most of the mysteries of the Universe." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "a5DdaQpLRFuNQ4eeATl7Ow", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/a5DdaQpLRFuNQ4eeATl7Ow", + "value": "Chuck Norris is the only true American patriot. If you suspect that .... you are gonna die by a swift roundhouse kick to your face. Nobody has ever found out who the kicker is." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XfBtseHeQJ22g2v44J3xyA", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/XfBtseHeQJ22g2v44J3xyA", + "value": "Who let the dogs out? Chuck Norris let the dogs out....then roundhouse kicked them through an oldsmobile." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:19.897976", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2ySrSZr6SfmUWVJp_N4R6Q", + "updated_at": "2020-01-05 13:42:19.897976", + "url": "https://api.chucknorris.io/jokes/2ySrSZr6SfmUWVJp_N4R6Q", + "value": "Facebook, not only a social networking site but also the shape your face takes when roundhouse kicked by Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "b0eqwlSHSEGBxurQej7VTg", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/b0eqwlSHSEGBxurQej7VTg", + "value": "When Neil Armstrong stepped onto the Moon, Chuck Norris roundhouse kicked him 900 yards and said, \"That's one giant leap for a man, and a long damn wait for me.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ISa5Qxe6RdCurnBCCy77eA", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/ISa5Qxe6RdCurnBCCy77eA", + "value": "Chuck Norris once roundhouse kicked a guy called Phil. Afterwards Phil became known as Empty." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "V3lleCmASGu56T57EvEvpA", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/V3lleCmASGu56T57EvEvpA", + "value": "The only reason Chuck Norris didn't win an Oscar for his performance in \"Sidekicks\" is because nobody in their right mind would willingly give Chuck Norris a blunt metal object. That's just suicide" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "WshCLRPeSz2uyZ6M0UDOvw", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/WshCLRPeSz2uyZ6M0UDOvw", + "value": "Chuck Norris once Round-House kicked a man so fast, his foot went at the speed of light, i't traveled back in time and killed the dinosaurs. Meteor? No, Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "SB69pXBPQ_etWBFhdkPBLQ", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/SB69pXBPQ_etWBFhdkPBLQ", + "value": "A blind man once accidently bumped into Chuck Norris on a Texas street corner. The mere fact that he touched Chuck Norris cured the man of his blindness. But sadly, the first, last and only thing the man ever saw in his entire life was a Chuck Norris roundhouse kick to his face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MlnnLxjBRVmA1qtboaWy6Q", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/MlnnLxjBRVmA1qtboaWy6Q", + "value": "For St. Patrick's day, Chuck Norris caught and crucified a Leprechaun, drank six kegs of green beer and roundhouse kicked the mayor of Boston." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gqU6SbwLRBKMcurATkYj1Q", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/gqU6SbwLRBKMcurATkYj1Q", + "value": "There is only two wonder in the world Chuck Norris and Chuck Norris' round house kick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "zkEByzvwRGCTYqLuhFRF0w", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/zkEByzvwRGCTYqLuhFRF0w", + "value": "When a young boy asked for Chuck Norris' autograph, he gave him a good solid kick to the face and left a permanent boot mark. This is Chuck's unique autograph, and that boy's face is now worth more money than the treasure from King Tut's tomb." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2dE1FsdKR1yPO4m1PSbx3Q", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/2dE1FsdKR1yPO4m1PSbx3Q", + "value": "Slash, of Guns & Roses fame, got his name when the toenails from a barefoot Chuck Norris roundhouse kick slashed the top of Slash's skull & hair off. That's why Slash now covers his deformity with a tophat." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "NyI3R7MsR0CfUTmQqrbAfw", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/NyI3R7MsR0CfUTmQqrbAfw", + "value": "Chuck Norris created \"Where's Waldo?\". He wasn't a fan of the name Waldo, so he round house kicked him in the face and no one has seen Waldo since...hence where's Waldo?" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0MHhmQwOQwGdHXf4Qa1OFw", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/0MHhmQwOQwGdHXf4Qa1OFw", + "value": "Chuck Norris can kick you in the nuts so hard, they will fly through your body up into your head and knock out your eyeballs, effectively replacing them." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "thA_BYpiSdS0EavenEfrpA", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/thA_BYpiSdS0EavenEfrpA", + "value": "One round-house-kick from Chuck Norris can deliver eternal energy to Earth" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "87oSN5qoSuqX-fy0HFAIEg", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/87oSN5qoSuqX-fy0HFAIEg", + "value": "Chuck Norris never cries, because of this when he's sad he roundhouse kicks himself and it makes him feel better since he knows he is the only one who can survive the roundhouse." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.262289", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "iU3Kxw6fSlSmDm5B3QQs4w", + "updated_at": "2020-01-05 13:42:20.262289", + "url": "https://api.chucknorris.io/jokes/iU3Kxw6fSlSmDm5B3QQs4w", + "value": "Rudolph has a red nose because he got lippy and Chuck Norris roundhouse kicked him across the face several times" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ql9PGzZWTPWGEbL2FdX1jg", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/ql9PGzZWTPWGEbL2FdX1jg", + "value": "People say that light goes faster than anything. Not true. Once a man named Joe insulted Chuck Norris. Chuck Norris then roundhouse-kicked Joe. Less than 1 second later, Joe ended up speeding out of the universe." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mD314iVAQW2qF7b-7KSaqg", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/mD314iVAQW2qF7b-7KSaqg", + "value": "haoerlkfsjkjfkjkpiku.. That's what you said after Chuck Norris roundhouse kicked your face in." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ma1tKI0LSziPxP2uYEElAg", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/Ma1tKI0LSziPxP2uYEElAg", + "value": "The duck billed platypus is the result of a duck being roundhouse kicked in the head far to close to a beaver by Chuck Norris. The fact yay they have a pouch is a testament to the genetically altering properties of a Chuck Norris Roundhouse kick to the head." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OrdHh2rYSe2hIH4eMljTNg", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/OrdHh2rYSe2hIH4eMljTNg", + "value": "The only reason Chuck Norris hasn't destroyed the world is because he was conferred with the honorary Nobel Peace Prize for his role in the film Sidekicks. The prize expires on December 12th, 2012." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2hzWYC4mRaydPe2O84sPLg", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/2hzWYC4mRaydPe2O84sPLg", + "value": "Nelson Mandela was not released from Robben Island prison in 1990. Chuck Norris broke him out of prison, roundhouse kicked ALL the prison gaurds, and safely escorted him to the coast of Capetown. Its good to have a friend like Chuck!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "WU3IHZbfS1KmcjxrC0ZkGw", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/WU3IHZbfS1KmcjxrC0ZkGw", + "value": "When in Rome, Chuck Norris roundhouse-kicks you in the face wearing a toga." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "T9UfuLrLQZWpmrnHd2XNlw", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/T9UfuLrLQZWpmrnHd2XNlw", + "value": "The current stock market crash began when Chuck Norris faxed a Roundhouse kick to Wall Street" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7zHhW4RGQ4CdFzQJh56uJQ", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/7zHhW4RGQ4CdFzQJh56uJQ", + "value": "During Osama's autopsy, study shows that a US Navy Seal did not kill Osama. Chuck Norris created a roundhouse kick so powerful, it traveled faster than the speed of light and striking through the head of Bin Laden by a mere 5 seconds faster than the US Navy Seals bullet." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "qZZjZfszSsa9Axf67Ok-tw", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/qZZjZfszSsa9Axf67Ok-tw", + "value": "Chuck Norris is all about that roundhouse kick, bout that roundhouse kick. No punches." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7ojrOhlCRuO140B4BWKv2g", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/7ojrOhlCRuO140B4BWKv2g", + "value": "The reason Link can't speak is because he got a roundhouse kick in the throat by an unidentified bearded man \"Scientists believe him to be Chuck Norris\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7wDjo6lHSbiEX49sqrNslg", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/7wDjo6lHSbiEX49sqrNslg", + "value": "Chuck Norris does not fly coach OR first class. He travels by transcontinental flying sidekick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "r7Mr8cubRF-RKgpZIRC3zA", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/r7Mr8cubRF-RKgpZIRC3zA", + "value": "Chuck Norris Rounhouse kicked Voldemort now he has n nose" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "H6kkWwXnTpSCtVxVkEOMqw", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/H6kkWwXnTpSCtVxVkEOMqw", + "value": "There was once a 51st state, known as New Idaho. It has not been heard of since it snubbed Chuck Norris as governor and was roundhouse kicked into a parallel dimension, along with Chuck's virginity and the last sonofabitch that overcooked his panda bear steaks. Chuck Norris eats his panda raw" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "SmSlZPpSQBm0oZqnRqBh_Q", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/SmSlZPpSQBm0oZqnRqBh_Q", + "value": "Chuck Norris once kicked a baby elephant into puberty" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cBAzo2LZSlGc34m4GVJ4YA", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/cBAzo2LZSlGc34m4GVJ4YA", + "value": "The Thousand Islands used to be one big island, but then Chuck Norris's roundhouse kick smashed it into pieces." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "75-FG9jkQrysfwjr2gVPog", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/75-FG9jkQrysfwjr2gVPog", + "value": "Satan has a ultimate weapon to try to destroy Chuck Norris. But sadly, it worked. But then Chuck Norris revived himself and kicked Satan's ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "AOhjnecjRpaeO1MXu4a8Mg", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/AOhjnecjRpaeO1MXu4a8Mg", + "value": "if i get five good things Chuck Norris will do a roundhouse kick and kick justin bibers ass" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-Yfa17sHSWO5FiA1uSs6wA", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/-Yfa17sHSWO5FiA1uSs6wA", + "value": "If you dare to look at Chuck Norris' snakeskin boots, you will get a case of bleeding hemorrhoids before kicks them up your ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "WzSTX5RdTcS6IBrbk_OlNw", + "updated_at": "2020-01-05 13:42:20.568859", + "url": "https://api.chucknorris.io/jokes/WzSTX5RdTcS6IBrbk_OlNw", + "value": "Chuck Norris is the best cop ever. Why? He can roundhouse-kick criminals straight to jail." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wks874NuTfqdj5hY5Kmn0g", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/wks874NuTfqdj5hY5Kmn0g", + "value": "People might think that light is the fastest thing in the universe, but try telling that to Bob. Bob was a 32 year old, cross-eyed man who just happened to look at Chuck Norris cross-eyed. This pissed Chuck Norris off, so he delivered his patented roundhouse kick to Bob's face. The skin on Bob's face peeled back, his teeth shattered, and his eyes straightened before they exploded out of his skull with enough force to kill two nearby observers. Two seconds later, Bob was three galaxies away from the edge of the universe. Five seconds later, light was like, \"What the hell!?!\" as Bob easily passed it by." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "_7RDKT5FT3O5BZsK-eS6ng", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/_7RDKT5FT3O5BZsK-eS6ng", + "value": "Once Chuck Norris actually tried on one of his roundhouse kicks. This was also the start of World war 1 and the aftershock of the beginning of World war 2" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MecB_WLWQFaGzIWj8OUF5Q", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/MecB_WLWQFaGzIWj8OUF5Q", + "value": "Chuck Norris was selling a line of drinks called (Chuck Norris in a can) in a wide variety of flavors, but do to the fact that the drinks had a roundhouse kick of flavor, it had the same effect as a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LLKDQrJGThuqSIqKtckx8g", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/LLKDQrJGThuqSIqKtckx8g", + "value": "The Black Eyed Peas inspiration for \"Boom Boom Pow\" is about Chuck Norris famous lethal combination. 2 straight jabs creating the \"Boom\" sound and finishes you off with the roundhouse kick to the head creating the \"Pow\" sound." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "bX7YeKZaSRSCV_hC-LgJDg", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/bX7YeKZaSRSCV_hC-LgJDg", + "value": "Chuck Norris puts his pants on by roundhouse kicking his closet." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZQ97i54UQKWnuERwm4KBBw", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/ZQ97i54UQKWnuERwm4KBBw", + "value": "After Chuck Norris roundhouse kicked Theresa Caputo, the Long Island Medium in the face for being a fake, she really was able to communicate with the dearly departed...posthumously." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OCAg_alnTjaInmwCHN2fvg", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/OCAg_alnTjaInmwCHN2fvg", + "value": "Chuck Norris doesn't have normal white blood cells like you and I. His have a small black ring around them. This signifies that they are black belts in every form of martial arts and they roundhouse kick the shit out of viruses. That's why Chuck Norris never gets ill." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "c0aYNg0WSnKDAdZumxbM1g", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/c0aYNg0WSnKDAdZumxbM1g", + "value": "When the Oracle told Chuck Norris he wasn't the ONE, he laughed and roundhouse kicked her in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Gp9VFhPJQP6tCb9Wh_NGqg", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/Gp9VFhPJQP6tCb9Wh_NGqg", + "value": "It is impossible to beat Chuck Norris in rock, paper, scissors since his roundhouse kick beats all. Yes, even the bomb." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PtalnbLyQfSvmQ7nnKyXNg", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/PtalnbLyQfSvmQ7nnKyXNg", + "value": "Chuck Norris can kick u in the back of the face" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hSk6DFs7T6GGkjS_d03xvw", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/hSk6DFs7T6GGkjS_d03xvw", + "value": "Want to know what Chuck Norris' roundhouse kick feels like? Go ask Stephen Hawking" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "8V6t8tnEQh2WVMeIiGc6Ig", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/8V6t8tnEQh2WVMeIiGc6Ig", + "value": "christmas fact: Chuck Norris can put you on the naughty list. That ends with a roundhouse kick in your face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LsUsvXURT2uUvjMJ7sOyFw", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/LsUsvXURT2uUvjMJ7sOyFw", + "value": "Chuck Norris once roundhouse kicked a horse in the face, its descendants are now called the giraffe" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Qqg8ijDLQ0CSk6Q99stk5A", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/Qqg8ijDLQ0CSk6Q99stk5A", + "value": "Chuck Norris once fought with his roundhouse kick and defeated it. LOL!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "_2iW8yQSRFCPxcfgKF6Bxw", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/_2iW8yQSRFCPxcfgKF6Bxw", + "value": "Chuck Norris super human kicking speed is the reason The Flash can be seen in slow motion running bare footed." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wkNEIiQ6QsW73jeNR8LtPA", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/wkNEIiQ6QsW73jeNR8LtPA", + "value": "For Chuck Norris to agree to do the losing fight scene in the \"Return of the Dragon\", Bruce Lee had to run around the universe carrying six school buses and three oil tankers on his back. On the seventh round, he also had to receive 77 roundhouse kicks to his face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "WP4sjjVtR3ODb11FPpdoew", + "updated_at": "2020-01-05 13:42:20.841843", + "url": "https://api.chucknorris.io/jokes/WP4sjjVtR3ODb11FPpdoew", + "value": "Chuck Norris kicked Maggie and the Ferocious Beast so hard they woke up in Nowhere Land." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "bpwzZe5rQZuYkXm4FLFaZg", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/bpwzZe5rQZuYkXm4FLFaZg", + "value": "Chuck Norris will roundhouse kick you to death if you laugh at Chuck Norris jokes. He will also roundhouse kick you to death if you don't laugh at Chuck Norris jokes. The choice is yours." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wSrGUC9UScGGlhS3mdgZVw", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/wSrGUC9UScGGlhS3mdgZVw", + "value": "When Chuck Norris turned nine he finally kicked his parents the fuck out of his house." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-ePGBt5aQJKOSXkCNQsjDw", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/-ePGBt5aQJKOSXkCNQsjDw", + "value": "A Chuck Norris roundhouse kick to the head is the reason Eric Holder can't remember his involvement in Operation Fast and Furious." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fSe4CfeNR0a9BMHKUdZvmw", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/fSe4CfeNR0a9BMHKUdZvmw", + "value": "If you dont believe God exists, he doesnt believe you exist, and soon you should expect an explosive roundhouse kick to the face....(Chuck Norris is God.)" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "pXelP8FOQcSYkDTjTRmJmg", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/pXelP8FOQcSYkDTjTRmJmg", + "value": "Chuck Norris can stare an enemy into submission, but it's a lot more fun to roundhouse kick their ass" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "pk4U6JakQrKvmq0iZWysOg", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/pk4U6JakQrKvmq0iZWysOg", + "value": "Chuck Norris is the only man to ever win a game of chess in one move. A round-house kick to the head!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "3hPN8nnHTIqw0PLHaFJtOQ", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/3hPN8nnHTIqw0PLHaFJtOQ", + "value": "Physicists have recently discovered that the universe began when Chuck Norris roundhouse-kicked a singularity (the big bang)." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kAzfArpKSuqazJbsEtCU0Q", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/kAzfArpKSuqazJbsEtCU0Q", + "value": "If a tree falls in the woods, not only did Chuck Norris hear it, he probably kicked that motherfucker over too." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gooRuruyTMWwqyeDMS-JhA", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/gooRuruyTMWwqyeDMS-JhA", + "value": "At McDonalds, Chuck Norris ordered breakfast at 10:40 and was denied because breakfast ends at 10:30. He got so mad , he roundhouse kicked the McDonalds so hard it became Wendy's !xpsl" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ea6lob_hSySVeeoKwNFXVQ", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/ea6lob_hSySVeeoKwNFXVQ", + "value": "Chuck Norris does not navigate through a corn maze... The corn merely realigns itself in chucks favor out of fear of being roundhouse kicked in the head!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KhFIrWOlQUq0uLk8KlTS8Q", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/KhFIrWOlQUq0uLk8KlTS8Q", + "value": "The rotation of the Earth was actually started by a Chuck Norris roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0bjfwQqvRO6EL1ByqId_xQ", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/0bjfwQqvRO6EL1ByqId_xQ", + "value": "Chuck Norris demands a 29th day of February every 4 years. We call this leap year. It's true name is leapspinkick year." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mRyFwX59SWWt6Y8qnUe-LQ", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/mRyFwX59SWWt6Y8qnUe-LQ", + "value": "Chuck Norris got mad at a hamer and round house kicked it in to the first sluge hamer" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Qsm8EU8dQTiRnKdJXoRMSw", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/Qsm8EU8dQTiRnKdJXoRMSw", + "value": "Chuck Norris invented Herpes Duplex by roundhouse kicking a man with lip Herpes twice. Once in the mouth followed by one in the nutsack." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "q08pwgMnRueZCloRNc-hYw", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/q08pwgMnRueZCloRNc-hYw", + "value": "A Chuck Norris flying roundhouse kick in the face has been known to cause a yellow, pus filled abscess in hemorrhoidal tissue." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.179347", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "DJ718neqSkKLvcTVnAqdZA", + "updated_at": "2020-01-05 13:42:21.179347", + "url": "https://api.chucknorris.io/jokes/DJ718neqSkKLvcTVnAqdZA", + "value": "Chuck Norris doesn't have a shadow anymore because he roundkicks a wall if it appears" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZMmIfKkxQtCBOnX4Io5vfw", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/ZMmIfKkxQtCBOnX4Io5vfw", + "value": "Once god asked Chuck Norris \"what do you want?\". Chuck Norris retorted \"what the fuck do YOU want?\" and proceeded to roundhouse kick him mercilessly. Ever since god went into hiding and is rarely seen by anyone ever again." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "17UJlZXTQBugdwnyj2NFmQ", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/17UJlZXTQBugdwnyj2NFmQ", + "value": "Chuck Norris hates Internet Explorer. And he is going to roundhouse kick the fuckin shit out of you if you use it. Get Firefox!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "E_i7e2AWQkWiKlRRLn9UtA", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/E_i7e2AWQkWiKlRRLn9UtA", + "value": "Chuck Norris blew up the Death Star with a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "M7TQyAxcR_uxa0dvTXH3Hw", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/M7TQyAxcR_uxa0dvTXH3Hw", + "value": "Chuck Norris was ask to join the cast of The Exspendables, but once Stallone offered Chuck the part.... he did a roundhouse kick, and said with a grin.. Sly you know as well as anyone.. Chuck Norris is not expendable." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "QL1NFpIdQRC0ilhqeqYiqA", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/QL1NFpIdQRC0ilhqeqYiqA", + "value": "Chuck Norris can stare down a Buckingham Palace guard. After he does that, he roundhouse kicks their stupid furry hats off their heads." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wphkHUtSQUSeox0fMjuvtQ", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/wphkHUtSQUSeox0fMjuvtQ", + "value": "no one really knows why Sergio Busquets kicked the ball over the bars against Chelsea until now he was threatened by Chuck Norris in the crowd telling him you score this son and you will never be at peace because you will be living in fear thinking when you will feel the wrath of my roundhouse kick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jIKN_6PuSjiSTSg7o5avNQ", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/jIKN_6PuSjiSTSg7o5avNQ", + "value": "A man once took Chuck Norris to court for alleged assult. After the shortest jury deliberation in history, the man was found guilty of first-degree Talking Shit About Mr. Norris and sentenced to death by instant roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MHycK9R7R-uIPrhgyceVBw", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/MHycK9R7R-uIPrhgyceVBw", + "value": "Jesus didn't rise and ascend to the heavens on the 3rd day. Chuck Norris round kick him back to life and simultaneously killed him again sending him to heaven." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "w20vVcZUS6S0foEFyCPNRQ", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/w20vVcZUS6S0foEFyCPNRQ", + "value": "Chuck Norris is solving world hunger one kick at a time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "lcM9lOyASPSgH6ZgogZ94g", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/lcM9lOyASPSgH6ZgogZ94g", + "value": "Chuck Norris became Grand ChessMaster when he defeated his opponents only by using one piece (his leg) and on his first move (the roundhouse kick). He never lost." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "uqgBYN06SDi9wbQM3A2iKQ", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/uqgBYN06SDi9wbQM3A2iKQ", + "value": "Chuck Norris can kick-start a car.Chuck Norris sleeps with a piollw under his gun.Chuck Norris hates Raymond.It took 7 women, 4 doctors, and 3 hospitals to give birth to Chuck Norris.Apple pays Chuck Norris 99cents every time he listens to a song.Chuck Norris can touch MC Hammer.Stephen Hawkings is the only man to have survived an encounter with Chuck Norris.Chuck Norris can hold Puff Daddy down.When Chuck Norris plays monopoly it affects the whole economy." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "b6fFcr7MQc6g32LXVpCUGw", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/b6fFcr7MQc6g32LXVpCUGw", + "value": "It is not the final countdown Chuck Norris roundhouse kicked it and made it the first" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ulctbZPiTu6mkhItuoNwTw", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/ulctbZPiTu6mkhItuoNwTw", + "value": "Chuck Norris oncd spent his entire weekend eating Duracell Batteries. He did this so that on Monday he could crap out a fire-developed tank round to fire at the short bus that drives by his house. He then give the bus a roundhouse kick to the face!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2OV7FdbfRBW_RlikjcRokw", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/2OV7FdbfRBW_RlikjcRokw", + "value": "Chuck Norris once round-house kicked a man in a call centre. Over the phone." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-a5kN5lBQ7-37pmLfv12bQ", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/-a5kN5lBQ7-37pmLfv12bQ", + "value": "Chuck Norris can smell Uranas. He can also smell your anus. And with only one roundhouse kick, he can make your anus look like Uranas." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "__fZcNikT6-C_kAOodAiIg", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/__fZcNikT6-C_kAOodAiIg", + "value": "Chuck Norris can roundhouse kick your face... with his hands." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.455187", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hjZKeGg2Q1uMJSyiebCmsQ", + "updated_at": "2020-01-05 13:42:21.455187", + "url": "https://api.chucknorris.io/jokes/hjZKeGg2Q1uMJSyiebCmsQ", + "value": "Chuck Norris does not believe in violence. He only believes in solving problems with an explosive roundhouse kick to the face. And if you argue that a roundhouse kick is violence, Chuck Norris can solve that problem too." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Rh3sCa0RR3SPMO--BXVjpg", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/Rh3sCa0RR3SPMO--BXVjpg", + "value": "Chuck Norris practices his roundhouse kicks: a. Anywhere he wants B. Chuck Norris doesn't need to practice C. On your face D. All of the above Hint: The answer is 'd'" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "njocLy4CQ7KoXw59q4eWqg", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/njocLy4CQ7KoXw59q4eWqg", + "value": "Chuck Norris once tried to defeat Garry Kasparov in a game of chess. When Norris lost, he won in life by roundhouse kicking Kasparov in the side of the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HHyCni9HQqihMSjWzyc8iQ", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/HHyCni9HQqihMSjWzyc8iQ", + "value": "The first man to be kicked in the groin by Chuck Norris coined the term \"pisshell,\" just before he cried to death." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZjrM9K2WTNCgJwCV1-AUrg", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/ZjrM9K2WTNCgJwCV1-AUrg", + "value": "Chuck Norris roundhouse kicked superman and turned him into the late Christopher Reeve." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "dFdmcnPNQRuKv663kwbCAg", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/dFdmcnPNQRuKv663kwbCAg", + "value": "Once while walking a nature trail, Chuck Norris observed a hornets nest hanging from a tree limb. He knocked it down with a roundhouse kick just for the fun of pulling the stingers out of the ass of each one of 500 pissed off hornets." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cKbLFZ8uS1WrpLxVly_CaA", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/cKbLFZ8uS1WrpLxVly_CaA", + "value": "Slenderman runs from Chuck Norris. Its also the reason why Slenderman has no eyes.....Chuck Norris roundhoused kicked his face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "c188cQArT1eVCJkHCPjZhw", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/c188cQArT1eVCJkHCPjZhw", + "value": "Little known fact: Chuck Norris shot J.R. after he killed him with a spectacular roundhouse kick to the forehead." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "_MWPMYLpS-yE_JdeUxxYYQ", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/_MWPMYLpS-yE_JdeUxxYYQ", + "value": "Someone ask Chuck Norris waht kind of music he listened to. You fool he answered I don't listen to music music listens to me. He then proceeded with a roundhouse kick to the face while the National Anthem listned in the background." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hMjLKQYKSVyBR7ZpepvqMQ", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/hMjLKQYKSVyBR7ZpepvqMQ", + "value": "Chuck Norris could bend Uri Geller with his mind. But he would prefer to brutally roundhouse kick him in the face if he ever comes back to America." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-phR6yD-T5e_Ou8zc6EQLA", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/-phR6yD-T5e_Ou8zc6EQLA", + "value": "Chuck Norris made a new religion painism where you go to church and he round house kicks for an hour" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "WPUuVmPjQ-GesRaYVvZuIQ", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/WPUuVmPjQ-GesRaYVvZuIQ", + "value": "Chuck Norris' grandfather presented him with an alarm clock on his 7th birthday. Chuck Norris roundhouse kicked and broke the clock into a million pieces. Nothing alarms Chuck Norris. Btw Chuck Norris' grandfather is also Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "I9QjP-dbSVW77BHxELZZZw", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/I9QjP-dbSVW77BHxELZZZw", + "value": "If you roundhouse kick Chuck Norris, Chuck Norris will say: \"Congratulations. You are my Roundhouse Kick buddy!\" If i were a Roundhouse Kick Buddy i would go crazy! XD" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0imTOm6-TFCnjSiurQEkHQ", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/0imTOm6-TFCnjSiurQEkHQ", + "value": "Chuck Norris doesn't pedal a bike. He roundhouse-kicks one of the pedals to go a mile." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "E0I1cu97QOC5okftdFVFRg", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/E0I1cu97QOC5okftdFVFRg", + "value": "Bob the Builder. Can we fix it. Chuck Norris already did. With a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "GjMd_d5KQjOhGLZ0u__LTA", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/GjMd_d5KQjOhGLZ0u__LTA", + "value": "If there was a game about Chuck Norris you'd be scared to play it because every time you'd open it up it would roundhouse kick you in the balls." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ki2cRkWER1mmAbYdvEm_wA", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/Ki2cRkWER1mmAbYdvEm_wA", + "value": "Chuck Norris can roundhouse kick your skeleton out of your body and wear your skin as a costume just before he goes on his daily five o'clock killing spree." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:21.795084", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5SdD2h0oQxOD4IAZCAy85w", + "updated_at": "2020-01-05 13:42:21.795084", + "url": "https://api.chucknorris.io/jokes/5SdD2h0oQxOD4IAZCAy85w", + "value": "One day Chuck Norris roundhouse kicked a building killing thousands of people while drinking a diet coke on a sunny day." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ckbl3fKNQBmflOaPq_TZNA", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/ckbl3fKNQBmflOaPq_TZNA", + "value": "If you see Chuck Norris kick someone out of a window, you just know he's gonna land on a conveniently-placed spike." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "S2c9Nk7-SzyTmNzFH6Z0Zw", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/S2c9Nk7-SzyTmNzFH6Z0Zw", + "value": "Chuck Norris, unlike Santa Clause, gives you a gift every day of the year. His gift is not roundhouse kicking you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "_pNDVaudRISSjG1cVYTM3Q", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/_pNDVaudRISSjG1cVYTM3Q", + "value": "Chuck Norris once beat Bobby Fischer at chess in a single move. That move was a roundhouse kick. That's why Fisher went crazy and died at the same age as the squares on a chessboard." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ucx7siMoTUqbpbZW6vLhsg", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/Ucx7siMoTUqbpbZW6vLhsg", + "value": "Chuck Norris could drop kick you thru the telephone on an international long distance call, reverse the charges and you would accept them." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "h1wmJ-T6SzmLyJjtJRV0cQ", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/h1wmJ-T6SzmLyJjtJRV0cQ", + "value": "Chuck Norris recently guested on Hardcore Pawn, where he sold some pocket lint for 8.5 million dollars. Then he brutally roundhose kicked the owner for not actually having any porn." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PH_Tatr7S0ib6eviht1A0w", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/PH_Tatr7S0ib6eviht1A0w", + "value": "Chuck Norris invented time travel. He's roundhouse kicked 170,000 people into next week thus far." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "rv5BnPpGSIK9fYPSOCAGuQ", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/rv5BnPpGSIK9fYPSOCAGuQ", + "value": "Max Headroom is the result of Chuck Norris kicking Duke Nukem's ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hhITgBqTQgOwYlDfhjCWPg", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/hhITgBqTQgOwYlDfhjCWPg", + "value": "Check yourself before you wreck yourself....... Cos roundhouse kicks from Chuck Norris is bad for your health." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "SUluEumSSWqDibkGiJ-VUA", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/SUluEumSSWqDibkGiJ-VUA", + "value": "Chuck Norris roundhouse-kicked Mike Tyson, permanently damaging the \"social\" and \"speech\" parts of the brain. This is why Mike Tython talkth like thith and has an Antithocial Perthonality Dithorder." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YIJeclypScWCOV8qS96DKA", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/YIJeclypScWCOV8qS96DKA", + "value": "If Chuck Norris wanted you dead, he'd kick you so hard that the force causes your body to accelerate past light speed, tear open the fabric of space time, and your corpse go back in time and kill your past self, thus erasing your existence from the entire universe." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "x2uiPwFSTWeeH6uNZiAqaA", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/x2uiPwFSTWeeH6uNZiAqaA", + "value": "When the mother of Chuck Norris was pregnant she could feel him moving in her stomache. Little did she know, he was perfecting his Round House Kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "GIPWGds9QQmZYpRyeD2JKA", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/GIPWGds9QQmZYpRyeD2JKA", + "value": "Chuck Norris once ate at a restaurant and finished his meal ask the waiter to come over he wanted to give him something. the waiter thinking it was a round house kick killed him self and everyone within 10 miles killed them self. Chuck Norris laughed and said fine ill keep the tip. But annoyed that everyone thought it was a round house kick Chuck Norris brought everyone back to life and killed them again for thinking that. Moral of the story when Chuck Norris gives you something TAKE IT LIKE A MAN IDIOT" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "r43suA29S7aG10UOYngO7Q", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/r43suA29S7aG10UOYngO7Q", + "value": "Chuck Norris once roundhouse kicked a mattress for refusing to share with him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HEp6dFQYRdWCfZqWL-3A9A", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/HEp6dFQYRdWCfZqWL-3A9A", + "value": "a long time ago Chuck Norris kicked the world so hard that its still spinning today." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yuUFHnRzQfmELvsZ8t_6QQ", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/yuUFHnRzQfmELvsZ8t_6QQ", + "value": "A single death is a tragedy; Chuck Norris roundhouse kick to the face is a statistic." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "p-WwzueNT5aSw7JqV-pUwQ", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/p-WwzueNT5aSw7JqV-pUwQ", + "value": "Einstein's Theory of Relativity states that nothing can travel faster than light... except Chuck Norris's roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "FsFNAG1EStG48HA47LAtDA", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/FsFNAG1EStG48HA47LAtDA", + "value": "There is only one thing that could possibly stop Messi's unstoppable form Chuck Norris's roundhouse kick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cfVeZG5aQWKPdbwJPutOwA", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/cfVeZG5aQWKPdbwJPutOwA", + "value": "Autumns occur when Chuck Norris roundhouse kicks every tree in existence." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "bsdmfFk5Qo6kgorlKiR6LQ", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/bsdmfFk5Qo6kgorlKiR6LQ", + "value": "Contrary to popular belief Chuck Norris began the website www.roundhousekicktothefacebook.com, it has since been shortened to facebook.com and filled with people Mr. Norris will roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "saRcVemARkKXENIqnsRmlg", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/saRcVemARkKXENIqnsRmlg", + "value": "Chuck Norris can disembowel a sandstorm with a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.089095", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "rAdErZveR4mP_QkJ810-5g", + "updated_at": "2020-01-05 13:42:22.089095", + "url": "https://api.chucknorris.io/jokes/rAdErZveR4mP_QkJ810-5g", + "value": "Chuck Norris doesn't cry over spilled milk, because it was his roundhouse kick that split the cow in half." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Hd5_vYnUR0S1nlkWIrU2NQ", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/Hd5_vYnUR0S1nlkWIrU2NQ", + "value": "When Chuck Norris enjoys some spare time from kicking ass he sits in his tool shed whacking off." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cOPt7J65So2tPRMWkcdXJQ", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/cOPt7J65So2tPRMWkcdXJQ", + "value": "Chuck Norris can kill a werewolf with a wooden stake, a vampire with a silver bullet, and anything with a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "X1qsVDIhQ96YWroPCtQunw", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/X1qsVDIhQ96YWroPCtQunw", + "value": "Someone once told Chuck Norris his hair looked good. He roundhouse kicked him in the face and told him that he made the hair look good." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "K6xD8c8YTA-jgL0Yi-pINQ", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/K6xD8c8YTA-jgL0Yi-pINQ", + "value": "The song \"Final Countdown\" is about a man's last second after being roundhouse kicked by Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "CxSRLvKPSeu49mTbWf0XHg", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/CxSRLvKPSeu49mTbWf0XHg", + "value": "Chuck Norris roundhouse kicked a man so hard, he turned into a gorilla!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "JiMoo9CcS4OmmLMxBmuv7A", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/JiMoo9CcS4OmmLMxBmuv7A", + "value": "Every Easter, Chuck Norris like to celebrate by using his powers as a necromancer to resurrect Jesus Christ, then immediately roundhouse kick him to death again." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7ESlxfCWSiGmRdW6d19PnA", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/7ESlxfCWSiGmRdW6d19PnA", + "value": "Everyone is entitled to Chuck Norris' opinion...or a roundhouse kick to the jaw." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "sv9PiGlRQ0KZ_JJy9gFmYQ", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/sv9PiGlRQ0KZ_JJy9gFmYQ", + "value": "Chuck Norris put humpty dumpty back together again, only to roundhouse kick him in the face. Later Chuck dined on scrambled eggs with all the king's horses and all the king's men. The king himself could not attend for unspecified reasons. Coincidentally, the autopsoy revealed the cause of death to be a roundhouse kick to the face. There is only one King." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "VqPP9QAOSyOShQS_PuHSXQ", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/VqPP9QAOSyOShQS_PuHSXQ", + "value": "A kid once had test. Not knowing what to do, the kid wrote \"Chuck Norris\" for every answer. He got a perfect score, but Chuck Norris came and roundhouse kicked him for using his name in vain." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "I0Tcx7aXRI2M-AnR3Yx3bw", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/I0Tcx7aXRI2M-AnR3Yx3bw", + "value": "Chuck Norris roundhouse-kicked the man who shot Liberty Vallance." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9DIfzC0kSUyg18Fguhv2-g", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/9DIfzC0kSUyg18Fguhv2-g", + "value": "Norris 25:17. \"The path of the righteous man (me) is beset on all sides by the inequities of the dumbasses and the tyranny of suited girlymen. Blessed is he who, in the name of charity and good will, sends boxes of cigars and crates of beer to me. For I am truly the Mack Daddy and the killer of lost children. And I will roundhouse kick thee with great vengeance and furious anger those who attempt to look at me sideways. And you will know I am the Lord, Chuck Norris, when I lay my vengeance upon you.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "vKhwIvz-QZWwMRr3ct17SQ", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/vKhwIvz-QZWwMRr3ct17SQ", + "value": "Elmo was originally blue and had a deep voice. Then Chuck Norris roundhouse kicked him 4 times. The last kick changed his voice." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "bQkaXL0yTu-ClefWChemwA", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/bQkaXL0yTu-ClefWChemwA", + "value": "Chuck Norris doesn't drink often. But when he does, he prefers to kick 'the most interesting man in the word' in the ass & take his Dos Equis." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XJ68YH8ZQAuwhF9hQZ_03w", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/XJ68YH8ZQAuwhF9hQZ_03w", + "value": "When Chuck Norris kicks air, someone still gets hurt." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Xs8klqJHQG-p6DnKb_2Oaw", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/Xs8klqJHQG-p6DnKb_2Oaw", + "value": "The single most successful anti-smoking measure was a commercial in the 1980's. In the commercial, there is a man smoking a cigarette. A voice then exclaims \"Smoking will kill you.\" Nothing happens, until Chuck Norris blasts through the wall and kills the man with a single round house kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "IRxZL6wHTAaDsJKZeukSAg", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/IRxZL6wHTAaDsJKZeukSAg", + "value": "Never brag to Chuck Norris that you have the latest smart phone, he will roundhouse kick you in the face and tell you \"there is no app for that\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KCFRbf6gSP2N7uBXNGSuVQ", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/KCFRbf6gSP2N7uBXNGSuVQ", + "value": "A journey of a thousand miles starts with a single step... and of course can be ended with a single brutal Chuck Norris spinning snap kick to the side of the face. So think about that." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "DhyXeiX0RSeGHbmSohvYMA", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/DhyXeiX0RSeGHbmSohvYMA", + "value": "Chuck Norris invented Breeding so that he could Roundhouse Kick Newborns.... Twice" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "W6Nom6HSRs6dWAuwt-SK6A", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/W6Nom6HSRs6dWAuwt-SK6A", + "value": "Surprisingly, the only Street Fighter II move based on one of Chuck Norris' was Chun-Li's. Chuck Norris prefers to travel by performing four upside-down double-roundhouse kicks per second." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kae-_FEgR82EpDNEtxI71w", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/kae-_FEgR82EpDNEtxI71w", + "value": "Chuck Norris not only kicks ass and takes names, he kills people and collects their souls." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "AId2yQsCTEK0sc8bYBrEiQ", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/AId2yQsCTEK0sc8bYBrEiQ", + "value": "If Chuck Norris kicked your ass, then went back in time and kicked your ass again, did he ever kick your ass in the first place?" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "o71ta6aKQRKqi5_KY205qg", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/o71ta6aKQRKqi5_KY205qg", + "value": "Santa Claus attaches his sleigh to Chuck Norris's jumping side kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "quQ0YJfSRCKRqQIhau_tZg", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/quQ0YJfSRCKRqQIhau_tZg", + "value": "A black hole singularity is actually the end result of a Chuck Norris roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "aEVUWI-cQASYahoQie4cUA", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/aEVUWI-cQASYahoQie4cUA", + "value": "Chuck Norris will roundhouse kick your ass unless you submit a Chuck Norris fact." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.701402", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ajo2ltf3SeyqGhiS0zhlaw", + "updated_at": "2020-01-05 13:42:22.701402", + "url": "https://api.chucknorris.io/jokes/Ajo2ltf3SeyqGhiS0zhlaw", + "value": "Chuck Norris roundhouse kicks the snot out elephants & hangs them on the ceiling for birthday party decorations." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "eYmwHJHyTxC5QO6fXzddDA", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/eYmwHJHyTxC5QO6fXzddDA", + "value": "Chuck Norris roundhouse kicked Neo out of Zion, now Neo is \"The Two\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0ZYuJ5HFRsqW45DoOSkPuQ", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/0ZYuJ5HFRsqW45DoOSkPuQ", + "value": "The automobile air bag was originally a failed invention to try and soften a blow from a Chuck Norris roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "rJNAtvjFRPCPfNzIyiBSMQ", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/rJNAtvjFRPCPfNzIyiBSMQ", + "value": "You become aware that Chuck Norris has just roundhouse kicked you in the face when you reach over your shoulder to rub something off your back and find out it's the floor." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YdFx7-qTQ_eYl2QDukhu-w", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/YdFx7-qTQ_eYl2QDukhu-w", + "value": "I kicked Chuck Norris'sjrago irjft awoe rfa;e jh;ao werjfa olhergaklashfg eo ifuy porhg z;irgf;salirhg" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "o46rlpv8Sgyewwmk-iaptA", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/o46rlpv8Sgyewwmk-iaptA", + "value": "There was supposed to be a Office 2008 but the spell check on word failed to capitalize Chuck Norris's name so he round house kicked it out of existence." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gqH5AbTgQwiZXbbR15tP0Q", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/gqH5AbTgQwiZXbbR15tP0Q", + "value": "Remember what happens in the movie \"Candyman\" when you say his name three times if you try to say Chuck Norris name three times you wouldn't get the chance to say the last two before he roundhouse kicks your face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "qev0RvU9S7WlusV8_KGgxw", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/qev0RvU9S7WlusV8_KGgxw", + "value": "Chuck Norris will never die. Every time Death pays him a visit, Chuck Norris will kick his ass with a roundhouse kick in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ol7c0HG_TkuP7eFSl4dLFQ", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/Ol7c0HG_TkuP7eFSl4dLFQ", + "value": "Chuck Norris' favorite video is the tape from the movie \"the ring\" Ironically, if you watch the movie \"sidekicks\", and dont like it, Chuck Norris will roundhouse kick you and when they find your corpse, your face will look the same as the people who died from watching the tape in \"the ring\"." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "amaoL5HsTQ-GwD_bRX9mQw", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/amaoL5HsTQ-GwD_bRX9mQw", + "value": "Einstein may have invented the therory of relativity- Chuck Norris invented the reality of kicking ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "QYcPJgd5S_CXt5omRN2iFQ", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/QYcPJgd5S_CXt5omRN2iFQ", + "value": "upChuck is a term now used to describe how far in the air you fly when Chuck Norris kicks your ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HJnEggp1SXyvA6wHEv9iBw", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/HJnEggp1SXyvA6wHEv9iBw", + "value": "It goes without saying that non-smoking laws do NOT apply to Chuck Norris. Gas stations, maternity wards, fireworks factories, the White House, wherever he is, he'll strike a match off your face an blow a huge plume into it. Then roundhouse kick you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "IuzY1qAwS2afrXH8UTS6Jw", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/IuzY1qAwS2afrXH8UTS6Jw", + "value": "Chuck Norris isn't as tuff as everyone says. What was that? It just sounded like someone kicked my door down." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gbadb6sYTpiuZNWuU4t7hw", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/gbadb6sYTpiuZNWuU4t7hw", + "value": "Chuck Norris created his own language with signs such as roundhouse kicking people in the face. So if Chuck Norris is kicking your ass one day, he may just be saying that he likes your hat." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ia6cpvjQQkW8HYf0QAgIPg", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/Ia6cpvjQQkW8HYf0QAgIPg", + "value": "Chuck Norris can do a round house kick on his knees." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jXl332D3Ttmmn-eraYC9iA", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/jXl332D3Ttmmn-eraYC9iA", + "value": "When Chuck Norris smiles, someone dies. When he's smiling while roundhouse kicking someone, then two people die." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "xPTOVbWVRQGWW68qWEfmSw", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/xPTOVbWVRQGWW68qWEfmSw", + "value": "10 out of 10 Doctors agree that a Chuck Norris roundhouse kick will kill every thing in a 10 mile radius." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cL8IPfsAR-G1jLrPjrpKZA", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/cL8IPfsAR-G1jLrPjrpKZA", + "value": "Chuck Norris can roundhouse kick you into yourself, and then kick the you that's within yourself into outer space." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MmBqKlbxTWKXSSkgp4umGw", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/MmBqKlbxTWKXSSkgp4umGw", + "value": "Chuck Norris once went fishing, but then ended up fighting a shark and roundhouse kicking it into the Pacific Ocean." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "k7N24hUgRee_VgHlv-AIpQ", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/k7N24hUgRee_VgHlv-AIpQ", + "value": "Chuck Norris will kick your ass by way of your head." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "f5WnfXPETCyIgi5f_N_qQw", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/f5WnfXPETCyIgi5f_N_qQw", + "value": "Once Chuck Norris Kicked a football , Now We are calling it as Moon." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "J87BuhadSOChapnjUb4jlg", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/J87BuhadSOChapnjUb4jlg", + "value": "Filming on location for Walker: Texas Ranger, Chuck Norris brought a stillborn baby lamb back to life by giving it a prolonged beard rub. Shortly after the farm animal sprang back to life and a crowd had gathered, Chuck Norris roundhouse kicked the animal, breaking its neck, to remind the crew once more that Chuck giveth, and the good Chuck, he taketh away." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KOy7Z36MSYueY_ib6NTfpg", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/KOy7Z36MSYueY_ib6NTfpg", + "value": "The victims of Michael Myers run around helpless and scared to death until he finds them and stab them. but Michael Myers is always on the run cause he's terrified of how hard Chuck Norris will roundhouse kick him when he finds him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Fi7I2cQzTeiysPsfHKLCqg", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/Fi7I2cQzTeiysPsfHKLCqg", + "value": "Chuck Norris once stumbled upon a website with random facts about himself. Although he was flattered, he sent an email to each person who submitted an untrue fact. Upon opening the email, a leather cowboy boot came through each computer screen and roundhouse kicked everyone within a 30 meter radius." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "W6a_c6yoSAGLNTRdaBX7_Q", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/W6a_c6yoSAGLNTRdaBX7_Q", + "value": "The only child ever to survive a roundhouse kick by Chuck Norris was Gary Coleman. He has not grown since." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:22.980058", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "p3vkOglzTn6X6Ms72_mT7A", + "updated_at": "2020-01-05 13:42:22.980058", + "url": "https://api.chucknorris.io/jokes/p3vkOglzTn6X6Ms72_mT7A", + "value": "Chuck Norris got so pissed off at IE6 he roundhouse kicked it and to this day it still can't render css properly." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "AwZt7wDeT9u9okgbFr5f7A", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/AwZt7wDeT9u9okgbFr5f7A", + "value": "Chuck Norris knows Italian fluently, but prefers to let his roundhouse kicks to do the talking when in Rome." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "v-oznknhRNyPAsmONo-7YA", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/v-oznknhRNyPAsmONo-7YA", + "value": "When a waiter asks Chuck Norris what he wants to drink, he responds with a roundhouse kick to the face and collecting the waiter's tears in a glass. He drinks them and then tells the waiter for ice next time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XKkxqppkRpa2pHXIQ1d6Ww", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/XKkxqppkRpa2pHXIQ1d6Ww", + "value": "A lady once asked Chuck Norris to kiss her baby. He thought she said 'kick'. Oops." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hny3JIDjR2Ks1BKmf1hGdQ", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/hny3JIDjR2Ks1BKmf1hGdQ", + "value": "Chuck Norris once kicked a football into outer space and martians came down to return it." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Z6du1qm7Q_WwbUPlhlr1Xw", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/Z6du1qm7Q_WwbUPlhlr1Xw", + "value": "Chuck Norris has impregnated 73 women. Over the internet. They all died before the end of their first trimester from internal bleeding caused by roundhouse kicks. All babies survived." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "k_Ib8LFYQcWSIPW2LT-Mrw", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/k_Ib8LFYQcWSIPW2LT-Mrw", + "value": "Chuck Norris's Round House Kick causes particle accelaration." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YQBmTDVUQd-sVF578rV6mQ", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/YQBmTDVUQd-sVF578rV6mQ", + "value": "When Freddy Krueger falls asleep , Chuck Norris appears to kick his ass!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "tEcpBgEiRT6-7HDp_WaMHA", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/tEcpBgEiRT6-7HDp_WaMHA", + "value": "Chuck Norris once roundhouse kicked a man with a merciful glancing blow to the forehead. And as a result, the man now has a fivehead." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Dmrl5e0rTgOZGqhjWxyaVQ", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/Dmrl5e0rTgOZGqhjWxyaVQ", + "value": "Chuck Norris inspired R.Kelly to write the song \"I Believe I Can Fly\" after he received a roundhouse kick that sent him flying to the moon." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "L2DKUIjrQD2Iom5ownzDRA", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/L2DKUIjrQD2Iom5ownzDRA", + "value": "Once a cow challenged Chuck Norris that he could not eat grass. Chuck Norris grazed 1/5 of the African continent in a day, which resulted in the formation of the Sahara desert. Then he proceeded to roundhouse kick and eat the cow at the same time. Moral of the story: Do not challenge Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "t13aj2VSTr6F2FiuzDYlbg", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/t13aj2VSTr6F2FiuzDYlbg", + "value": "Chuck Norris knows exactly how many roundhouse kicks to your ass that it takes to make a poignant hemorrhoidal bouquet for you...one!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "NpsRQxZYSxydgZubjoeqIQ", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/NpsRQxZYSxydgZubjoeqIQ", + "value": "Chuck Norris' pet hamster can kick a pit bulls ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "QbAbT3ZHQlySm31MhlDelg", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/QbAbT3ZHQlySm31MhlDelg", + "value": "Gary Neville can kick a football 200meters, to out do Garry Chuck Norris kicked phill Neville 300 meters" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ph9ICL6STruLd8du_d-HBQ", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/Ph9ICL6STruLd8du_d-HBQ", + "value": "Chuck Norris has delivered enough roundhouse kicks to the ass to know just how many of them it takes to make a bountiful bouquet of hemorrhoids." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PimWVaFKTDG9bhjnERGKKw", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/PimWVaFKTDG9bhjnERGKKw", + "value": "The danger of death sign is not a warning that you will get an electric shock that kills you, its a warning that Chuck Norris Roundhouse kicked somebody there." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.240175", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Dt2OwL_jTQyalijcyIuwBg", + "updated_at": "2020-01-05 13:42:23.240175", + "url": "https://api.chucknorris.io/jokes/Dt2OwL_jTQyalijcyIuwBg", + "value": "Chuck Norris once used the Grim Reaper as a Sherper up Everest but Roundhouse kicked him in the Death Zone and summitted ALONE" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HOKETRqNQEiPwnvtIUSA5g", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/HOKETRqNQEiPwnvtIUSA5g", + "value": "Chuck Norris has never been accused of murder for the simple fact that his roundhouse kicks are recognized world-wide as \"acts of God.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "nvdxPp5BQK2iQWRbPxrHVg", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/nvdxPp5BQK2iQWRbPxrHVg", + "value": "Chuck Norris died once. when greated by the grim reaper chuck proceded 2 round house kick the reaper.long story short death will never make that mistake again" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "FfLqGko-RV6EFzxIoP33jQ", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/FfLqGko-RV6EFzxIoP33jQ", + "value": "Chuck Norris once roundhouse kicked a hole into a cow, just to see what was coming down the road." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cPOKN-OJSjCNvVXSxuWHiA", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/cPOKN-OJSjCNvVXSxuWHiA", + "value": "In his high school year book, Chuck Norris was named \" most likely to kick you in the face \"." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "4zEyJW-jQiWKDPfgZyDpAw", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/4zEyJW-jQiWKDPfgZyDpAw", + "value": "Chuck Norris was asked to donate blood. He proceed to donate 6 quarts... Simply by roundhouse kicking the flabotomist." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "6M53wgo7RkicbTpCNfw0Jw", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/6M53wgo7RkicbTpCNfw0Jw", + "value": "Chuck Norris can kick his way out of a black hole's Schwartzchild radius." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "vjqxAVZkQrKy8GOLh9OShQ", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/vjqxAVZkQrKy8GOLh9OShQ", + "value": "A man once told Chuck Norris that he and everyone would be wise to learn to \"expect the unexpected\" in life's journey. Chuck Norris then immediately roundhouse kicked the man in the face and said, \"did you expect that?\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PbE7cRZ5RXGCeGB1VuUdjA", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/PbE7cRZ5RXGCeGB1VuUdjA", + "value": "Chuck Norris once kicked Doyle Brunson's ace." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LJatOwobSgiVd57y3Scj_g", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/LJatOwobSgiVd57y3Scj_g", + "value": "When Chuck Norris was 9 he was out trick or treating and ran into Freddy Kruger. Chuck kicked the crap out of Freddy then took his candy." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Yytxg7Y4Q12o2PqSN3xXeg", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/Yytxg7Y4Q12o2PqSN3xXeg", + "value": "If Chuck Norris had a penny for each roundhouse kick he delivered, the world supply of iron would have been long gone." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "FKjcTbgAT5eyxwooaWx6Uw", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/FKjcTbgAT5eyxwooaWx6Uw", + "value": "Chuck Norris roundhouse kicked all the American out of Johnny Depp." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "eOcHK252SCmv6T5MsJiexA", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/eOcHK252SCmv6T5MsJiexA", + "value": "Why did Chuck Norris hasn't appeared on any mortal kombat games. Simple, the name says it all. \"mortal\". Also there won't be any fatality tha will work on him, he will just roundhouse kick anyone either he wins or loose." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wqMuuH9yQBK3BF83RgLqPA", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/wqMuuH9yQBK3BF83RgLqPA", + "value": "Chuck Norris roundhouse kicked all the masculinity out of Justin Bieber, which all landed inside P!nk." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fsTXH7kWSKesejuoIb5g5A", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/fsTXH7kWSKesejuoIb5g5A", + "value": "One day Chuck Norris roundhouse kicked a building killing thousands of while drinking a diet coke on a sunny day." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "x62RlLcTRP2DXTgJk3ol9w", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/x62RlLcTRP2DXTgJk3ol9w", + "value": "Chuck Norris shot the sheriff, killed Kenny, and killed Mr. Boddy in the hall with a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "uWIZNxwIREymjuSX7Xg_fA", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/uWIZNxwIREymjuSX7Xg_fA", + "value": "Zebras were created when Chuck Norris kicked the color out of horses." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "pCgZ_c8XRzO9U9W9tD_48w", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/pCgZ_c8XRzO9U9W9tD_48w", + "value": "Chuck Norris went to court once, and he lost. He roundhouse kicked the judge in the head so hard that all of justice became blind." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "xAKQ4jaQT7i_Br4Eq1nLVQ", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/xAKQ4jaQT7i_Br4Eq1nLVQ", + "value": "Chuck Norris gave Santa Claus a present on December 25, 2012. The present turned out to be a small box that contained 100% exact, invincible, and miniature clones of everything that ever existed and will exist. Well, the clone of Chuck Norris wasn't accurate, as its roundhouse kick could not kill anything stronger than an African elephant due to how small it was, but that was the only exception." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BYFfJUaMShqPyWpFLph8AQ", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/BYFfJUaMShqPyWpFLph8AQ", + "value": "The reason Chuck Norris's shoes are brown is because the shit from peoples ass is caked onto his shoes from kicking their ass that hard." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.484083", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1lqKdidUTLu1pEl4Oow7MA", + "updated_at": "2020-01-05 13:42:23.484083", + "url": "https://api.chucknorris.io/jokes/1lqKdidUTLu1pEl4Oow7MA", + "value": "When Chuck Norris wants something, the person standing next to him better goddamn give it to him within the next two seconds or die by the roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "TAzGfQUvRImy7Oo0jf5_ew", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/TAzGfQUvRImy7Oo0jf5_ew", + "value": "Mr. T threw a punch and Chuck Norris met his punch with a round house kick....... The result was the 80's." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Yke6bi5xSdWvSq_dkCCGkA", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/Yke6bi5xSdWvSq_dkCCGkA", + "value": "Chuck Norris can roundhouse-kick you in the face so hard, archaeologists from thousands of years from now will find your skull fragments in the next continent encrusted with diamonds." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "00NuSNToRkm7YZ6nvjdhlg", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/00NuSNToRkm7YZ6nvjdhlg", + "value": "I got an e-roundhouse kick from Chuck Norris. My cheeks are still swollen." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ORkyhj4OR2K2XMRVjtFarg", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/ORkyhj4OR2K2XMRVjtFarg", + "value": "When you die, Chuck Norris will find your grave and roundhouse kick you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BHyzMEESRqefeRskfTZ6Ig", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/BHyzMEESRqefeRskfTZ6Ig", + "value": "Chuck e cheese was actually gonna be called: Chuck Norris cheese. it was then changed for being too violent. Every animatronic for Chuck Norris cheese was violent because they do roundhouse kicks." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "8u3AJB0wTH6o-PPoMjww2A", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/8u3AJB0wTH6o-PPoMjww2A", + "value": "Chuck Norris once grabbed Vin Diesel and inserted his index finger into Vin Diesel's anus. Vin Diesel got very pissed over the matter. Chuck Norris then started kicking him continously non-stop for a day for not having any sense of humor." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "QZBtgeNIQEOT0YD7gP6jBA", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/QZBtgeNIQEOT0YD7gP6jBA", + "value": "They say Bruce Lee died in an accident...but it is the fact you don't kick Chuck Norris' ass without consequence!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "D4OpidSyTZ-egL_f_0XPSg", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/D4OpidSyTZ-egL_f_0XPSg", + "value": "Chuck Norris invented the word 'oblivion' so his roundhouse victums would have a place to find themselves kicked into." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yHsnDQYWQLyaZGjflCWE9g", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/yHsnDQYWQLyaZGjflCWE9g", + "value": "The number on tombstones aren't years; there just how many times Chuck Norris roundhouse kicked that person in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "n3Lo6xzmQEG84GX7_DttwQ", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/n3Lo6xzmQEG84GX7_DttwQ", + "value": "Chuck Norris once kicked a foot ball so hard it went into orbit and took out the command ship of an alien armada that where plotting to take over the earth ... they soon changed there minds" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "GUvQNdjLSRSl6yrl6ifApg", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/GUvQNdjLSRSl6yrl6ifApg", + "value": "Q. Can you spell \"Chuck Norris\" without using any r's? A. Yes, \"Chuck Kickass\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9O7seT6CQXOahpAqHuDUvA", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/9O7seT6CQXOahpAqHuDUvA", + "value": "If Chuck Norris had a pennie for every time he roundhouse kicked someone, he would be the richest man ever." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "n0it85Y-Sae48n7AWnJSOA", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/n0it85Y-Sae48n7AWnJSOA", + "value": "Davy Crockett shot his first bear when he was only 3. Chuck Norris roundhouse kicked his first bear into oblivion when he was only 6 months old." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "h1MN_q9sT3aWTH9PBZJTCg", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/h1MN_q9sT3aWTH9PBZJTCg", + "value": "Chuck Norris created the alphabet just so he could spell the words \"kicked your ass\"." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "6OhqDMjzQOqz006M-nIErA", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/6OhqDMjzQOqz006M-nIErA", + "value": "When you tell Chuck Norris a Chuck Norris fact, he laughs. And then roundhouse kicks you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "TQAVUA4YTyGkNaw2sjHhxA", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/TQAVUA4YTyGkNaw2sjHhxA", + "value": "For the extra added kick to his homemade Texas Chili, Chuck Norris adds a 1/2 cup of Y.pestis infected rat turds as seasoning." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Obi8Vp_uRXqrjmMgjrCPaw", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/Obi8Vp_uRXqrjmMgjrCPaw", + "value": "Gregg Valentino is known to have the biggest biceps on the planet. That's because he once accidentally dropped a weight on Chuck Norris foot while training in the gym with Chuck. That action Caused Chuck's reflex's to hammer-kick Valentino in each bicep. Now even Arnold is amazed at Valentino's giant swollen biceps." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "DF5ewSDZSgWPNz-XsEv3KA", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/DF5ewSDZSgWPNz-XsEv3KA", + "value": "If you ask Chuck Norris what time it is, he'll answer 'Two seconds 'till...' When you ask him 'Two seconds 'till what?', that's when he roundhouse kicks you in the face" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "s56YoYPmRgOMZM4-I9AclQ", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/s56YoYPmRgOMZM4-I9AclQ", + "value": "Sauron's ring was actually Chuck Norris', and if he'd known he'd of kicked Frodo's arse." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OKcb-bcxQjaRlMzQLSSypg", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/OKcb-bcxQjaRlMzQLSSypg", + "value": "Jack was nimble, Jack was quick, but Jack still couldn't dodge Chuck Norris' roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "R55uzYlySNGawd5gdctDvw", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/R55uzYlySNGawd5gdctDvw", + "value": "Four score and seven years ago... Chuck Norris drank a barrel of mead and roundhouse kicked the shit outta some mutton chop-wearing assholes." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "dBSTXZ9UTTGR9QsWdfbHqQ", + "updated_at": "2020-01-05 13:42:23.880601", + "url": "https://api.chucknorris.io/jokes/dBSTXZ9UTTGR9QsWdfbHqQ", + "value": "Chuck Norris will be roundhouse kicking your ass in a second. And then he does mine." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZPjKsBDiRiyRMY3356hD7w", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/ZPjKsBDiRiyRMY3356hD7w", + "value": "Chuck Norris once roundhouse house kicked a man for asking him who invented the Roundhouse Kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LohSg2a0RfisY4Lk5I5ZLQ", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/LohSg2a0RfisY4Lk5I5ZLQ", + "value": "Chuck Norris can roundhouse kick you in such a way that you become incapable of dying, and just do a little worse every day." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ztULFv21SDyoWxeD3gxUhw", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/ztULFv21SDyoWxeD3gxUhw", + "value": "When you ask Chuck Norris for directions, he'll give you a roundhouse-kick to the face and you'll land exactly where you need to be." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "eSMKOpX-SMKc1imLH0ukow", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/eSMKOpX-SMKc1imLH0ukow", + "value": "Chuck Norris' roundhouse kick is an illusion. His right foot doesn't kick you. His left foot spins the Earth so that your head hits his foot." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "dkauV66LRmWo-ZeTRXe1nw", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/dkauV66LRmWo-ZeTRXe1nw", + "value": "Chuck Norris can literally hand you your ass in a handbag. And then pull it out and kick it up between your ears." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "L6Tetv2BRpqLHUlllPg6wQ", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/L6Tetv2BRpqLHUlllPg6wQ", + "value": "All people with hemorrhoids have been the recipient of a Chuck Norris roundhose kick to the mouth that was delivered so severely that it caused their tounge, tonsils and adenoids to protrude from their asshole." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "H_8HHgPYT82RckUqJaIiYQ", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/H_8HHgPYT82RckUqJaIiYQ", + "value": "Chuck Norris was at a rap concert once and the rapper told everyone to raise the roof. Promptly afterward he killed the rapper with a swift roundhouse kick to the face. No one tells Chuck Norris what to do." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LkCUKcLDTFaMWGEf1tg0zg", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/LkCUKcLDTFaMWGEf1tg0zg", + "value": "On June 7th 1994, Chuck Norris entered the same restaurant supermodel Cindy Crawford was eating at. Instinctively, Cindy swept everything off the table, threw herself on it in a fit of lust, and begged Chuck to ravish her. After Chuck finished his beer, he obliged her. When Chuck's magnificent lead sperm cannoned into Cindy's womb it went straight to one of her ovaries and roared, \"Which one of you servile wenches thinks you can handle getting split open by the Chuck!?\" All of the eggs cowered in the corner. The same thing happened at the other ovary. \"I didn't fucking think so!\" shouted the lead sperm which then lead the rest of the troops back into Chuck's balls. Chuck pulled out; roundhouse kicked Cindy in the face and told her, \"Don't ever waste my time again.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9L8igzTRTHGEeb1wfye_GA", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/9L8igzTRTHGEeb1wfye_GA", + "value": "If you look up the term Ass Kicker in the dictionary, there will be a picture of nothing--because it's a fucking dictionary, they don't have pictures--but the page will smell a little bit like Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "R0kKsATWTmOxMHjGs_wpEA", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/R0kKsATWTmOxMHjGs_wpEA", + "value": "Contrary to the popular idiom, it was in fact a Chuck Norris flying roundhouse kick and not a straw that broke the camel's back." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "xDKhh1k3T_qiQEnRTyeklg", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/xDKhh1k3T_qiQEnRTyeklg", + "value": "Steven Segal onece challenged Chuck Norris to a fight, Chuck Norris roundhouse kicked him in the face. this is why Steven Segal talks funny" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KCm7Zq7LRhmk5LzsfHyUbw", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/KCm7Zq7LRhmk5LzsfHyUbw", + "value": "Chuck Norris is Death of the Four Horsemen of the Apocalypse. He kills by the grace of the roundhouse kick, and he rides a bull. The others dare not question him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "AgYSgmK6Ry2GSVzr9T4vPQ", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/AgYSgmK6Ry2GSVzr9T4vPQ", + "value": "Chuck Norris's poster once kicked my ass and put me in the Hospital for a year." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "U0UXcYV-SBqAZLPY8edqpg", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/U0UXcYV-SBqAZLPY8edqpg", + "value": "I assure you, the great day of the kicking will occur. By this I mean that actor Chuck Norris will literally pull up in your yard in his Hummer, walk to your front door, wait for you to open it, then kick you in the face before straightening his jacket and calmly driving away. The day is coming." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BLzsX8RrS-iVGuJCXgNHtQ", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/BLzsX8RrS-iVGuJCXgNHtQ", + "value": "Chuck Norris thinks outside the box,then roundhouse kicks the box into oblivion." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1JrQrY71QK-4lZmqOtzU1A", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/1JrQrY71QK-4lZmqOtzU1A", + "value": "When Chuck Norris went to Oz he had a field day kicking Munchkin @$$; they covered up the blood trails left on the roads with yellow paint." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "TE4kJMysTpKcMOm4wu5rEw", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/TE4kJMysTpKcMOm4wu5rEw", + "value": "Meteors are actually people blazing down to Earth after having been roundhouse kicked by Chuck Norris into space for telling bad jokes about him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "tsueglACSTWNv1rBeSEN-Q", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/tsueglACSTWNv1rBeSEN-Q", + "value": "Chuck Norris put humpty dumpty back together again, only to roundhouse kick him in the face" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2ii9LHvGTr6dsFqF3Qcvuw", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/2ii9LHvGTr6dsFqF3Qcvuw", + "value": "Chuck Norris is the only person who can eMail a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "htZ0q3mBSQyrvRxLrvxlNA", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/htZ0q3mBSQyrvRxLrvxlNA", + "value": "Bruce Lee died from a delayed reaction to a Chuck Norris roundhouse kick to the face in Way of the Dragon." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.142371", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "8uqZm9dvShSoZ9XjQuV9jQ", + "updated_at": "2020-01-05 13:42:24.142371", + "url": "https://api.chucknorris.io/jokes/8uqZm9dvShSoZ9XjQuV9jQ", + "value": "If you look up Chuck Norris in the dictionary, you will get Round house Kicked in the face, or the balls, depending on how you are holding the dictionary." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "VHzcKLMITl6QsRaayC34TQ", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/VHzcKLMITl6QsRaayC34TQ", + "value": "A Jehovah witness once knocked on Chuck Norris door. Chuck Norris let the Jehovah witness into the house and then roundhouse kicked him out the window. The man tried to sue Chuck Norris for assault but could not prove anything since there was no witnesses." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ITSvovDyQg-fnhgGnBlJlA", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/ITSvovDyQg-fnhgGnBlJlA", + "value": "Lightning is like a Chuck Norris roundhouse kick in the face. In a flash it's gone. Both lightning & your face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "b4bijS1BQ9uyONWV67W7DA", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/b4bijS1BQ9uyONWV67W7DA", + "value": "Chuck Norris was challenged to a fight by the high school bully, so Chuck proceeded to kick the crap out of him. Chuck was in the fifth grade at the time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "B1Rz0sqqT3a7sP2I71eXhg", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/B1Rz0sqqT3a7sP2I71eXhg", + "value": "Chuck Norris round-house kicked micheal jackson and he turned from black to white" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "J8JwOIAVR0m1og4j4HQZ4A", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/J8JwOIAVR0m1og4j4HQZ4A", + "value": "Chuck Norris was the original star for the movie Pacific Rim. The director had to fire Chuck when he continued to single handedly kick the shit out of the Kijus." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "QxCSyCDZSaqlLTuQBowhmw", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/QxCSyCDZSaqlLTuQBowhmw", + "value": "Chuck Norris can kill a red dragon with his level 1 mage using a single magic missile.. immediately followed by a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mfzv5aifS-qtrE2TqmQaYQ", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/mfzv5aifS-qtrE2TqmQaYQ", + "value": "Once Chuck Norris died just to see how it feels like to be dead. The next day he resurrected himself and roundhouse kicked everyone who celebrated his temporary death." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hiBPrzzDQtG1boAZ2eJ6JA", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/hiBPrzzDQtG1boAZ2eJ6JA", + "value": "If Chuck Norris roundhouse kick Bruce banner he would not get angry, he would be cured and never again will transform in the hulk, cause Chuck Norris kicks have healing properties. Either you get cured or die" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2r8BMA41TTOQ_XZvdOCo1A", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/2r8BMA41TTOQ_XZvdOCo1A", + "value": "If Chuck Norris really likes you, he can make a single roundhouse kick last five hours." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fqdYQm-ySaCoWUXrzfVBzQ", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/fqdYQm-ySaCoWUXrzfVBzQ", + "value": "Chuck Norris has a job. His job is to roundhouse kick people. He gets a million dollars if he does. then hes done." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Afs59AUXTzWQepoCo63QZw", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/Afs59AUXTzWQepoCo63QZw", + "value": "The U.S. Military once tried to capture the power of a round house kick from Chuck Norris into a bomb. It was called the Manhattan Project and it didn't even come close." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "WHXgQ1y4TD2HzspJYUKpIA", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/WHXgQ1y4TD2HzspJYUKpIA", + "value": "While playing Rock Band, Chuck Norris can play air drums and still hit the notes. Going into overdrive without telling him, he instantly gives you a round house kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "a2xnpeWYQXiWoUBfaBHITQ", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/a2xnpeWYQXiWoUBfaBHITQ", + "value": "Chuck Norris once roundhouse kicked a black person so hard he turned white" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "GgZFbC9CSOyUBiw41Iji4g", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/GgZFbC9CSOyUBiw41Iji4g", + "value": "Chuck Norris discovered a new theory of relativity involving multiple universes in which Chuck Norris is even more badass than in this one. When it was discovered by Albert Einstein and made public, Chuck Norris roundhouse-kicked him in the face. We know Albert Einstein today as Stephen Hawking." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0pVT7H3gSz6dC1Uj3mQquw", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/0pVT7H3gSz6dC1Uj3mQquw", + "value": "In back to the future 4 Chuck Norris roundhouse kicked the Delorean back. This also gave Micheal J Fox Parkinson's." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-tcMhGBVSYqiAThUP3FYVw", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/-tcMhGBVSYqiAThUP3FYVw", + "value": "Chuck Norris has a hemorrhoid the size of a bowling ball. He's been so busy kicking ass that hasn't even noticed it yet." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OHXHN2LwS_qDSe0klSMtKg", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/OHXHN2LwS_qDSe0klSMtKg", + "value": "Chuck Norris was never born. he roundhouse kicked his way out of his mothers whom and proceded on kicking the rest of the doctors to." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YpuqnPImSuucUI7lJiCpnw", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/YpuqnPImSuucUI7lJiCpnw", + "value": "When Chuck Norris asks you a question he doesn't need an answer coz he already knows and when you try to answer he will roundhouse kick you and ask you again and when you dont answer he will round house kick you again and ask you another question...... remember Chuck Norris counted to infinity twice" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "B9teOdrMQuGM9YMKBB5JJQ", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/B9teOdrMQuGM9YMKBB5JJQ", + "value": "A married couple went to Chuck Norris for their relationship problems. The husband said, \"Oh great and powerful Chuck Norris, my wife has lost the ability to feel pleasure in bed! What can I do to fix this?\" Chuck Norris then proceeded to make love to the guy's wife right on the floor next to him. After about three and a half hours of her moaning in ecstasy and epic music, Chuck Norris got up and said, \"She seems to be working alright for me!\" He then roundhouse kicked them both into space, thus ending the couples relationship problems forever!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PLwK9wWgR525DB5AFhmtiA", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/PLwK9wWgR525DB5AFhmtiA", + "value": "The Grim Reaper looked at Chuck Norris and was pantsed and round house kicked in the balls, at the same time a new born child was named: Chuck Alfred Norris Tomas, and died instantly." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.40636", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Tv8HGIlHSRGYBmt6J9e8Cw", + "updated_at": "2020-01-05 13:42:24.40636", + "url": "https://api.chucknorris.io/jokes/Tv8HGIlHSRGYBmt6J9e8Cw", + "value": "When Chuck Norris was younger, he wore cowboy boots, had a red beard and could deliver a fatal roundhouse kick. Knowing this one could imply that Chuck Norris has always been awesome." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mPtSFMcfQuqwXo8NJtfRkA", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/mPtSFMcfQuqwXo8NJtfRkA", + "value": "Chuck Norris can literally kick a person's ass into next week. Their head will travel five days farther." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "DjtJspggRgqFVgo6AHBVPA", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/DjtJspggRgqFVgo6AHBVPA", + "value": "Knock knock. Whos there? Chuck Norris. Chuck Norris who? Chuck Norris is gonna roundhouse kick ya in the FACE" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Eph-7ARcTeanoYhzAo7U6w", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/Eph-7ARcTeanoYhzAo7U6w", + "value": "One day a man waves to Chuck Norris. Chuck Norris got mad and roundhouse kicked him in the face today we know him know as the elephant man." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MLm5jlTDTXW40ajYL1-bGA", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/MLm5jlTDTXW40ajYL1-bGA", + "value": "Chuck Norris can knock you down with a feather. But he prefers a savage roundhouse kick to the brain." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fKuYuPBFQZaOCFinwassFQ", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/fKuYuPBFQZaOCFinwassFQ", + "value": "Chuck Norris has a roundhouse-kick app for his iPhone ∞.0" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "8jLJNGw-QSWsfXZffHV2-A", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/8jLJNGw-QSWsfXZffHV2-A", + "value": "NO ALISTER CHUCK NORRIS KICKS YO ASS :), NO ALISTER CHUCK NORRIS KICKS YO ASS :), NO ALISTER CHUCK NORRIS KICKS YO ASS :), NO ALISTER CHUCK NORRIS KICKS YO ASS :), NO ALISTER CHUCK NORRIS KICKS YO ASS :), NO ALISTER CHUCK NORRIS KICKS YO ASS :), NO ALISTER CHUCK NORRIS KICKS YO ASS :), NO ALISTER CHUCK NORRIS KICKS YO ASS :)," + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2X-S5CnYQwOd7f2vejFHOA", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/2X-S5CnYQwOd7f2vejFHOA", + "value": "It is possible to look to the left and right at the same time with just a roundhouse kick from Chuck Norris" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "h43Kev_3S4-5L9M1KkmLdQ", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/h43Kev_3S4-5L9M1KkmLdQ", + "value": "A man once told Chuck Norris that he would like to go to Mars. Chuck Norris then bent the man over backwards, shoved the man's head up his own ass and then asked, \"Do you want an express or first class flight?\" The man said, \"mumble mumble.\" Then Chuck Norris roundhouse kicked him to Jupiter. This is because Chuck Norris doesn't know the names of the planets, nor does he give a shit about them." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gQgNYb2xTBWmHGdjTNQ8gg", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/gQgNYb2xTBWmHGdjTNQ8gg", + "value": "Chuck Norris doesn't use keys, he always kicks the door in." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MV3qii6PTj2pvFtth6h5RA", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/MV3qii6PTj2pvFtth6h5RA", + "value": "Chuck Norris created Canada when he kicked all the Liberals out of America." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PtHWEpXXRHmkCbm54BrEXA", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/PtHWEpXXRHmkCbm54BrEXA", + "value": "When Chuck Norris wants bacon, he roundhouse kicks a pig until it is dead." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fJa5d9-MQCGP7TpwvjpiPQ", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/fJa5d9-MQCGP7TpwvjpiPQ", + "value": "The first roundhouse kick of Chuck Norris made Pangaea turn into six different continents, THEN he decided which was the North and South" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wAkLSmHRS9O_f7bfP8TYCQ", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/wAkLSmHRS9O_f7bfP8TYCQ", + "value": "Chuck Norris was once in a catch 22, but he roundhouse kicked it down to a 12 pack and literally drank his problems away." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XRJre4oESZuWvqM2408vdw", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/XRJre4oESZuWvqM2408vdw", + "value": "Everytime you jack off, Chuck Norris roundhouse kicks a mexican baby." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZMiPZGt_StuQJ05y44gcVA", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/ZMiPZGt_StuQJ05y44gcVA", + "value": "*Chuck Norris is an acronym, it means: Crushing Heads Under Cars and Kicking Ninjas Over Rail Roads and Into Space" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mKvP-3adTHSM-iDjxl1JgQ", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/mKvP-3adTHSM-iDjxl1JgQ", + "value": "Chuck Norris turned up at Buckingham Palace for his knighthood seven hours late and reeking of alcohol and pussy, and wearing only a flak jacket and christmas lights. When the queen touched his shoulder with a sword, he instinctively roundhouse kicked her." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "_nqSKoD0TvmvBYF_PqiKRg", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/_nqSKoD0TvmvBYF_PqiKRg", + "value": "Chuck Norris does not believe in discrimination based on sex, age, race, religion, culture or geography - when it comes to his roundhouse kicks." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9gnFAm4uQZ2mGnDpWFb0Ag", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/9gnFAm4uQZ2mGnDpWFb0Ag", + "value": "What is the definition of infinity? The number of people Chuck Norris has roundhouse kicked in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OTNBYiMZTZyw7CH98u8Qng", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/OTNBYiMZTZyw7CH98u8Qng", + "value": "Chuck Norris getting his ass kicked is as likely as seeing a vampire with a suntan." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "liEwxoFtRd-UKVFg_oqqrQ", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/liEwxoFtRd-UKVFg_oqqrQ", + "value": "Homeless people are afraid to ask Chuck Norris for change. The last guy who did got a half dollar scissor kicked up his ass...twice." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "adV0YmpNTtugDOMSC2Xhew", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/adV0YmpNTtugDOMSC2Xhew", + "value": "Chuck Norris once roundhouse kicked a salesman over the telephone." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7w5sd_KSTgGwsVCQc3U8KA", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/7w5sd_KSTgGwsVCQc3U8KA", + "value": "Chuck Norris was origionally to be cast as Neo in The Matrix, but when they told him about the powers he would recieve, he laughed and said im Chuck Norris! He then proceeded to roundhouse kick the Warner Brother representatives and flying off to drink some beer and kick some random person's ass" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "AglEkdTcRde6JpgHXLaI4w", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/AglEkdTcRde6JpgHXLaI4w", + "value": "IF CHUCK NORRIS GIVES YOU A ROUND HOUSE KICK IN YOUR DREAMS YOU REALY DIE" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:24.696555", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "lLwVN6kmQLq6pnyBrm5lnw", + "updated_at": "2020-01-05 13:42:24.696555", + "url": "https://api.chucknorris.io/jokes/lLwVN6kmQLq6pnyBrm5lnw", + "value": "Chuck Norris and Cookie Monster once had a contest to see who ate the most cookies. At the end, Chuck Norris roundhouse kicked Cookie Monster in the head." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "qM6kcTkvTha6jNbFtnwnrw", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/qM6kcTkvTha6jNbFtnwnrw", + "value": "The Draught of Living Death is not a magical potion or anything like it, it's not even liquid. It's the state of a human which, by some hell of an accident, survived a Chuck Norris roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ek9Nok9aSPqwT-DcuSTAEA", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/ek9Nok9aSPqwT-DcuSTAEA", + "value": "Chuck Norris frequently signs up for beginner karate classes so he can accidentally roundhouse kick kids in the neck." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1MYGljsGT3O7OduWGl-lRA", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/1MYGljsGT3O7OduWGl-lRA", + "value": "Buzz Lightyear found out just how far it is to infinity and beyond after Chuck Norris drop kicked his ass for refusing to come out of his grandson's toy box." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gbsDaP82RXy1okrGLW-EIg", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/gbsDaP82RXy1okrGLW-EIg", + "value": "Chuck Norris is known to jump out of televisions and roundhouse kick the viewers for no reason at all. Extreme caution is advised while watching any shows involving Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fPKt-ZFBRPGilGw7ka5miQ", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/fPKt-ZFBRPGilGw7ka5miQ", + "value": "The comma was formed when Chuck Norris roundhouse kicked the fullstop which tried to make him stop his sentence. Needless to say, there are no fullstops in Chuck Norris' world." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "neFyjQqtSOWgRDj-EFz2Yw", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/neFyjQqtSOWgRDj-EFz2Yw", + "value": "I used to question Chuck Norris like you but then I took a roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "eC95mzOiQr2rRirtUPGZhg", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/eC95mzOiQr2rRirtUPGZhg", + "value": "If you ever dream of kicking Chuck Norris' ass, you will wake up to see him standing over you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "DWTcF8dpStKZUNdBmU2L4w", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/DWTcF8dpStKZUNdBmU2L4w", + "value": "In high school, Chuck Norris would tape \"Kick Me\" signs on his own back in the hopes that someone would take the bait." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BEj45LLAS1OnHedpm4CVig", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/BEj45LLAS1OnHedpm4CVig", + "value": "Chuck Norris is the only human being to display the Heisenberg uncertainty principle -- you can never know both exactly where and how quickly he will roundhouse-kick you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "zywxkMVyRFmrG93_G2zJUA", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/zywxkMVyRFmrG93_G2zJUA", + "value": "Chuck Norris was told he needs to be more sensitive. Chuck said \"I am sensitive, the last guy that mouthed off to me got kicked in the nuts instead of the face\"." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jNUDBty0SXuIk7pfoXomLQ", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/jNUDBty0SXuIk7pfoXomLQ", + "value": "Chuck Norris kicked the sparkles right off of Edward Cullen." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "6VD662gkTBGLYHqTW_zF6A", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/6VD662gkTBGLYHqTW_zF6A", + "value": "Too much love will NOT kill you, eevery time but a Chuck Norris roundhouse kick to the face will kill you. Eevery time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BPJ5A7DIRN6XYoax2Uz9Aw", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/BPJ5A7DIRN6XYoax2Uz9Aw", + "value": "A pair of Chuck Norris' jeans was recently put up for auction. The leg reflexively kicked three appraisers." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "FWIFCFVpTNuT9s7Kml6WhA", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/FWIFCFVpTNuT9s7Kml6WhA", + "value": "Chuck Norris went to church once... they kicked him out and told him he got an early acceptance into heaven" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Oub93C_uTlyRaGTVqBIWqQ", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/Oub93C_uTlyRaGTVqBIWqQ", + "value": "Chuck Norris killed Zeus in a poker game with a roundhouse kick to the face because Zeus was a sore loser and didn't want to give up his lightning." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "qEnWlsQIQ7q_j1Rl3m3kdg", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/qEnWlsQIQ7q_j1Rl3m3kdg", + "value": "Chuck Norris once sold his soul to the devil in exchange for the power of the roundhouse kick. After signing the contract, chuck quickly roundhouse kicked the devil in the face knocking him unconscious long enough to take his soul back. The devil, who appreciates irony, had a good laugh about it. Now they meet in Indiana every second Tuesday of the month to play poker" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Vitxm3PJRe68zprm5gVXrw", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/Vitxm3PJRe68zprm5gVXrw", + "value": "\"Everybody Hates Chris\" was originally called \"Chuck Norris Hates Chris\" but Chris Rock didn't want to make a series just about how he survive Chuck Norris round house kicking him EVERYDAY" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1YeIdl3fQaKCi-z6iV63_Q", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/1YeIdl3fQaKCi-z6iV63_Q", + "value": "The leaning tower of Pisa used to stand up straight. That is..... until Chuck Norris roundhouse kicked it." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gcUDHW9xQCyZ3zTfLJp1yw", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/gcUDHW9xQCyZ3zTfLJp1yw", + "value": "The closest Chuck Norris has come to getting his ass kicked was when he gave himself a dirty look in the mirror." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gpIzeCklR8efCmSiwfzIZg", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/gpIzeCklR8efCmSiwfzIZg", + "value": "Chuck Norris roundhouse-kicked the acting ability out of Vin Diesel." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.099703", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gt41FVisSvG42v-p3yd3HQ", + "updated_at": "2020-01-05 13:42:25.099703", + "url": "https://api.chucknorris.io/jokes/gt41FVisSvG42v-p3yd3HQ", + "value": "Chuck Norris can roundhouse kick a window pane with such infinite presicion that only one side of it breaks." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "v-WHiUjnSnOkATCbRyjZrA", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/v-WHiUjnSnOkATCbRyjZrA", + "value": "Chuck Norris doesn't give a fuck if the glass is half empty or half full. He's still gonna round house kick you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5zrFJKg6RqyyU1jsqIL8Tg", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/5zrFJKg6RqyyU1jsqIL8Tg", + "value": "There is a Chuck Norris fact number zero, but those who read it will get blind because that was Chuck Norrir's first fact, created from Chuck Norris's Round House Kicks." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "CzoEfJ6WSqCpgdO6VTQrpw", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/CzoEfJ6WSqCpgdO6VTQrpw", + "value": "Chuck Norris originally appeared in the \"Street Fighter II\" video game, but was removed by Beta Testers because every button caused him to do a roundhouse kick. When asked bout this \"glitch,\" Norris replied, \"That's no glitch.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "sVfOLkVJRwOFkxtUGFy6Aw", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/sVfOLkVJRwOFkxtUGFy6Aw", + "value": "The big bang did not create the universe Chuck Norris did with one roundhouse kick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "a2rcp1fmRZWQPpP02b6yJA", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/a2rcp1fmRZWQPpP02b6yJA", + "value": "Chuck Norris always give more bang for your buck. In other words, he will roundhouse kick you in the face, then take your wallet." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "uvuwkhl_SEi0TpC1-hIwMA", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/uvuwkhl_SEi0TpC1-hIwMA", + "value": "The Marquis de Sade invented wrought iron testicle clamps. Chuck Norris univented them which is why you've never heard of them before. Chuck Norris prefers to destroy your balls with a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gHfuzPFmTFaXyBKW8f1WIw", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/gHfuzPFmTFaXyBKW8f1WIw", + "value": "Chuck Norris is an unlockable Character on the new MK9.To unlock:beat game on hardest difficulty by only using roundhouse kicks, fatality all characters with roundhouse kick.when he shows up for the fight hold the run button hoping you can outrun him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HJzA3JzQQuKXbjcz0gyURg", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/HJzA3JzQQuKXbjcz0gyURg", + "value": "The film Hellbound is actually a documentary. Satan has, in fact, been dead since 1994, when Chuck Norris roundhouse kicked him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "amuiE9JxT-66CTvrnGLQlg", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/amuiE9JxT-66CTvrnGLQlg", + "value": "Chuck Norris likes to drop kick nuns at the full moon. The craters are direct hits." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "4XpBg1qRRuyKopwmUsVxrg", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/4XpBg1qRRuyKopwmUsVxrg", + "value": "Chuck Norris was once paralyzed by the pressure of his awesomeness. He then roundhouse kicked his awesomeness to Mars, where it eradicated all life, he then made more awesome." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Pe2_FjtCRqm9DBse6FQxkg", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/Pe2_FjtCRqm9DBse6FQxkg", + "value": "One time the Enterprise needed to exceed warp 9 speed, so Chuck Norris round house kicked it. The Enterprise warped out of existence." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cgcBXiu_SZyAX94jjqEUiw", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/cgcBXiu_SZyAX94jjqEUiw", + "value": "When I found out about this site, I roundhouse kicked it so hard it suddenly got a link to the Hugh Jackman fact generator. Nice guy. I went fishing with him once. Then I kicked his ass. Because I'm f***ing Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "4AuzH_1_TYqZhXU1S-IaLg", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/4AuzH_1_TYqZhXU1S-IaLg", + "value": "Chuck Norris can run faster than the speed of light and kick light in its ass and send it flying even faster than the speed of light, and then run faster than the new speed of light and again do the same over and over again. Every time he does it new universes are created." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "aBijsx0zQyqAsGq9d9Rq-g", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/aBijsx0zQyqAsGq9d9Rq-g", + "value": "Chuck Norris does not navigate through a corn maze... The corn merely realigns itself in chucks favor out of fear of being roundhouse kicked in the ears!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "CHO5LQ4pS2OiWooS3LNxXg", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/CHO5LQ4pS2OiWooS3LNxXg", + "value": "Doctors have discovered that a Chuck Norris roundhouse kick would cure every disease known to man. However, since it would also shatter your bones and vital organs, it has never been implemented." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "3RiKtc-ESnmZsHab5aSZFw", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/3RiKtc-ESnmZsHab5aSZFw", + "value": "Just simply witnessing a Chuck Norris roundhouse kick to somebody else's face can cause you to get a case of Tourette's syndrone." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fkqegKXjSISVhH1ao_FmwA", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/fkqegKXjSISVhH1ao_FmwA", + "value": "Scientists use Chuck Norris' spectacular kicks to calibrate their earthquake warning systems" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "C6Rcnt5ZRASpeAClhrFZ4g", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/C6Rcnt5ZRASpeAClhrFZ4g", + "value": "Chuck Norris was breifly considered for the next Batman movie, but after test shoots, it became clear that it's impossible to film the batroundhouse kick and survive." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2Aq3NvEFRICJhS7jESKgng", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/2Aq3NvEFRICJhS7jESKgng", + "value": "Can Chuck Norris kick it? Of course he fucking can." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.352697", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9T0Isn9CSu6YsJqFYqRkgQ", + "updated_at": "2020-01-05 13:42:25.352697", + "url": "https://api.chucknorris.io/jokes/9T0Isn9CSu6YsJqFYqRkgQ", + "value": "Chuck Norris can give you a vasectomy with a roundhouse kick or a trombone...your choice?" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "i3nbcxjAQ_e6zibp6nZZTw", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/i3nbcxjAQ_e6zibp6nZZTw", + "value": "China was once bordering the United States, until Chuck Norris roundhouse kicked it all the way through the Earth." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "VK-7RRBMR4mydLRjB-L7Nw", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/VK-7RRBMR4mydLRjB-L7Nw", + "value": "Chuck Norris can roundhouse kick Batman's prep time" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "p8viz37nQg-h2tnmfADLng", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/p8viz37nQg-h2tnmfADLng", + "value": "Chuck Norris once round house kicked a bear while on a survival trek in Siberia. That incident was known as the Tunguska event." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "xAPAz0NSS0eNMcvU6ctAFA", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/xAPAz0NSS0eNMcvU6ctAFA", + "value": "Chuck Norris kicked-in The Amazing Kreskin's face. Thus proving a Chuck Norris attack to be an unpredictable life event." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "98zAHGcuSdGKzJ7Emu8O5w", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/98zAHGcuSdGKzJ7Emu8O5w", + "value": "All individuals that have remarkably survived a Chuck Norris attack suffer what in medical terms is called 'optical rectumitis'. That being having their assholes kicked out through their eyesockets" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "4e2mjAtcRCKO-AMEc9ZoHw", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/4e2mjAtcRCKO-AMEc9ZoHw", + "value": "When Chuck Norris was born he round house kicked the Doctor in the face Slow mo' 3 times for slapping his ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZESuESQ1QpqWGWYQORZVQQ", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/ZESuESQ1QpqWGWYQORZVQQ", + "value": "Most people get their heat from the sun, the sun gets it heat from a single Chuck Norris roundhouse kick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BLwMnqzESFqIpI6NHSCp6w", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/BLwMnqzESFqIpI6NHSCp6w", + "value": "In \"Wanted Dead Or Alive\", the lyric \"I've seen a million faces, and rocked them all\" is dedicated to Chuck Norris. Of course, the word 'rocked' is an euphemism for 'roundhose-kicked'." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kdJCg4cmRZ6w74I3MptLMA", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/kdJCg4cmRZ6w74I3MptLMA", + "value": "Chun kuk do- founded by Chuck Norris in 1990- is an amalgamation of Korean tang soo do, shotokan karate, subak, taekkyon, judo and Brazilian jiu-jitsu. In other words, it's the juice that powers his epic roundhouse kicks." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "QY4jiLa2T86erJtWJ5elXA", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/QY4jiLa2T86erJtWJ5elXA", + "value": "Chuck Norris head may be in the clouds but his round house kick is in the back of your face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "asYOIsAaRQewv9XTM5GLVA", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/asYOIsAaRQewv9XTM5GLVA", + "value": "In the beginning there was nothing, then Chuck Norris kicked that nothing in the face and said \"Get a job!!\"... And that's the story of the universe." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "F4HAIPk6Q3W7KIseFR9sNQ", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/F4HAIPk6Q3W7KIseFR9sNQ", + "value": "Did you know if you watch the editors cut of Wizard of Oz, theres and alternate ending where Chuck Norris round house kicks Dorothys house back to Kansas... it shortened the movie drastically and the director decided not to use it... true story." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "POgbrE-DTxuJ8nmS_KRDdQ", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/POgbrE-DTxuJ8nmS_KRDdQ", + "value": "The reason Cobra Commander wears a mask coz his face was disfigured by a roundhouse kick from Chuck Norris by simply mentioning his name." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.628594", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "50IKn3UcRtG6s3XC0MihtQ", + "updated_at": "2020-01-05 13:42:25.628594", + "url": "https://api.chucknorris.io/jokes/50IKn3UcRtG6s3XC0MihtQ", + "value": "Human beings celebrate new years. Chuck Norris celebrates new seconds - and go about with his roundhouse kicking business. That's how fast Chuck Norris is." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "sCJVvS9eS--Tyut3QIVmoA", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/sCJVvS9eS--Tyut3QIVmoA", + "value": "Chuck Norris roundhouse kicked Cher and turned back time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ccyFKy17Q6WqLTFkR0Y8Fg", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/ccyFKy17Q6WqLTFkR0Y8Fg", + "value": "It's said that, when this website gets to Fact #9001, Chuck Norris will kick it in the URL, rocketing it up to 10,000 facts." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BjSs_8lPTcWJft-vY-iC7A", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/BjSs_8lPTcWJft-vY-iC7A", + "value": "Many people wonder why Star Wars begins with \"A long time ago, in a galaxy far, far away\"... Well, Chuck Norris roundhouse kicked all the jedi, clones & other aliens in that galaxy and moved to ours." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hdoWo7ZvT0K21cmqYNl94Q", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/hdoWo7ZvT0K21cmqYNl94Q", + "value": "On people magazine Justin Beiber said this about Chuck Norris \"Hes awsome if I met him hed probbaly round-house kick me in the face\" he joked" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RCYGaqbYRymxXHKBTA892w", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/RCYGaqbYRymxXHKBTA892w", + "value": "Death once visited Chuck Norris,CHuck Norris said\"DON'T EVER TELL CHUCK NORRIS WHEN HE DIES,I TELL YOU WHEN YOU DIE.\"Then Chuck delievered the most powerful roundhouse kick ever and even Death knew he did something wrong as he declined into hell." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "GDmkZ0j0T92BKp6kLcOs4g", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/GDmkZ0j0T92BKp6kLcOs4g", + "value": "Having a tick on the head of your dick is better than receiving a Chuck Norris roundhouse kick in your neck." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1Uz3bpy_RyeBLcD5H4hzkA", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/1Uz3bpy_RyeBLcD5H4hzkA", + "value": "Mr. T, Arnold Shcwarzzenger, and Chuck Norris are standing in front of God. God says to them,\"I have call you three here because you are the greatest fighters in the world and I have a place for one of you at my right hand. You must prove to me whom of you it shall be.\" Mr. T steps and says \"I pity the fool who doesn't let me sit at His right hand.\" God tells him that he was not good enough and sends Mr. T to hell. Arnold steps up and says \"I was in predator, commando, the terminator. You must choose the governator.\" God tells him not good enough and sends Arnold to hell. God turns to Chuck Norris and say \"Why should you sit beside me?\" Chuck quickly proceeds to roundhouse kick God in the face and say \"Bitch, your in my seat." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2bHq32mOS9m5IHGEZChzSQ", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/2bHq32mOS9m5IHGEZChzSQ", + "value": "Chuck Norris will personally roundhouse kick you if you don't get off this website and get back to work!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "X-0Kx8WaTqW-7Idyv2Ah_w", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/X-0Kx8WaTqW-7Idyv2Ah_w", + "value": "The last person to attempt to emulate a Chuck Norris roundhouse kick lost his kneecap due to the high intensity centrifugal force." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "TS5MAdPNTk2oXdUvbuJSgA", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/TS5MAdPNTk2oXdUvbuJSgA", + "value": "Chuck Norris reaches 11 on the Richter Scale, if you confront him and say it only goes to 10 you will recieve a roundhouse kick..which will reach 12." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jJLsWfpPRoSPEVtKelXx2w", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/jJLsWfpPRoSPEVtKelXx2w", + "value": "The movement of subatomic air substance created from Chuck Norris' roundhouse kicks were recently accredited by physicists as allowing them to view the Higgs Boson Partical." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0wIjXd4ASTS5Kogij9NTFA", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/0wIjXd4ASTS5Kogij9NTFA", + "value": "Chuck Norris never went to karate class, he was born kicking butt." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "tpg9R8inQz6l93Qp2OkFWA", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/tpg9R8inQz6l93Qp2OkFWA", + "value": "Chuck Norris never dances, he prefers roundhouse-kicking." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7Ozov-CWR0erDeszZHME9g", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/7Ozov-CWR0erDeszZHME9g", + "value": "Who let the dogs out? Chuck Norris. He them proceeded to roundhouse kick each and every one of them." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7aDT0NMmTQKfBRjIEf9hQQ", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/7aDT0NMmTQKfBRjIEf9hQQ", + "value": "Chuck Norris wil always kick your arse. Even in soviet russia." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "TylvCYzaTnqAwZ731qlZVQ", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/TylvCYzaTnqAwZ731qlZVQ", + "value": "One time Sylvester Stallone got into an argument with Chuck Norris over who was the best knitter. Chuck Norris roundhouse kicked him in the face. Stallone's lip hasn't been the same since." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Fx89tC2xR9Sssjb5srCDlA", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/Fx89tC2xR9Sssjb5srCDlA", + "value": "Chuck Norris keeps pirhanas in his swimming pool. He feeds them one freshly roundhouse-kicked pool cleaner daily." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hgBrITAKRpCyQvEGhJ8FOA", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/hgBrITAKRpCyQvEGhJ8FOA", + "value": "I'm making a game called Ultra Smash Flash. Chuck Norris will be playable. And Kirby can use his roundhouse kick. Also, the Chaos Emeralds will be transformational items. When Chuck Norris gets 7, his power is STILL less than 1% of his real life counterpart. Makes sense, right? Quality, NOT quantity? NOPE. Quality AND quantity! Let's do this!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "51YtucrPTo6moA3Q6N6Gbw", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/51YtucrPTo6moA3Q6N6Gbw", + "value": "Chuck Norris beleives in downsizing government! That's why he once roundhouse kicked the Octagon. Today, its known as the Pentagon." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "8roiIntDRyy2NrOP2aS6TA", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/8roiIntDRyy2NrOP2aS6TA", + "value": "Chuck Norris roundhouse kicked the winnyness out of Caillou." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:25.905626", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "WL0spzxeSd--m6IiMBYDog", + "updated_at": "2020-01-05 13:42:25.905626", + "url": "https://api.chucknorris.io/jokes/WL0spzxeSd--m6IiMBYDog", + "value": "Chuck Norris once visited the Oracle and told her: 'Don't worry about the round house kick'. She responded: 'What round house kick?'. BAMM... that round house kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "t1HQRTDFRKG0yKzXf_4NnA", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/t1HQRTDFRKG0yKzXf_4NnA", + "value": "Following a recent kickboxing training mishap,Chuck Norris no longer has a shadow." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XZIptzobQaqizCRAG3JGyg", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/XZIptzobQaqizCRAG3JGyg", + "value": "Chuck Norris wishes you a Roundhouse Kicking New Year!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ro5R7s-gQzSypDU12CEVJA", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/Ro5R7s-gQzSypDU12CEVJA", + "value": "Chuck Norris fought the law, and the law got roundhouse kicked in the face" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "edHHVdlpQLSZ-JcuhmYOwg", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/edHHVdlpQLSZ-JcuhmYOwg", + "value": "Like this fact if Chuck Norris should kill Obama Dislike this fact if Chuck Norris should roundhouse kick you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RvPofpJBRSWAA7_Ce6LlWw", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/RvPofpJBRSWAA7_Ce6LlWw", + "value": "At birth, Chuck Norris immediately excised his parasitic twin, instantly roundhouse kicked his ass and threw him out the 10th story hospital room. We now know the parasitic twin as PeeWee Herman." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "tQVxZpeFTV2QVnKm2Hgrhw", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/tQVxZpeFTV2QVnKm2Hgrhw", + "value": "Every night before bed Chuck Norris round house kicks 10 childern." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "tKTVXD9BT4iQCadidzsmJA", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/tKTVXD9BT4iQCadidzsmJA", + "value": "Chuck Norris doesn't defy gravity. He simply kicks it in the nuts." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "EbuTcwUZSi6SftbGIzYI5Q", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/EbuTcwUZSi6SftbGIzYI5Q", + "value": "Only 535 people of thousands have ever survived a Chuck Norris roundhouse kick to the head. These, now brain damaged, individuals comprise the US Congress." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "sPMJFA4zSIaIWIjHAkEJPQ", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/sPMJFA4zSIaIWIjHAkEJPQ", + "value": "When Chuck Norris comes to an open door, he always closes it so he can then kick it the fuck in." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YS53dMJXTpax3UxUeoDlyg", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/YS53dMJXTpax3UxUeoDlyg", + "value": "Jean-Claude Van Damme once attempted to throw a Chuck Norris Roundhouse Kick. He was immediately arrested for fraud." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PT4mQFuQRh6am-zXha6Ssg", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/PT4mQFuQRh6am-zXha6Ssg", + "value": "A Chuck Norris roundhouse kick to your solar plexus will cause your toenails to crack." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7wz-Ze_mQcKaBSl053IFVg", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/7wz-Ze_mQcKaBSl053IFVg", + "value": "Chuck Norris has two types of hearing, two types of smell, and nine types of sight. And 52 types of kickass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "GxhpqJDERvGwmLKJ4VgO5g", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/GxhpqJDERvGwmLKJ4VgO5g", + "value": "James Bond says his name twice to introduce himself. Chuck Norris just roundhouse kicks you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "AIJNar9XRz-X_QRaNFdAuw", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/AIJNar9XRz-X_QRaNFdAuw", + "value": "Chuck Norris Roundhouse kicked the Earth, It's been spinning ever since...." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "NaJ7ClZXSNqrBaY1jUaDHA", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/NaJ7ClZXSNqrBaY1jUaDHA", + "value": "You must always pay attention while driving, in case you are distracted for a split second and Chuck Norris drop-kicks a midget through your windscreen" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "VshAHBHCSiW-NBlmab93Vw", + "updated_at": "2020-01-05 13:42:26.194739", + "url": "https://api.chucknorris.io/jokes/VshAHBHCSiW-NBlmab93Vw", + "value": "There are plenty of Chucks, but only one Chuck Norris- he roundhouse kicks anyone named after him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "d0WjKV-cQEuqxTjmQggQsg", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/d0WjKV-cQEuqxTjmQggQsg", + "value": "Chuck Norris kicked the bucket-and lived." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "qH-x3PBUR6ugo-1_TJ9udA", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/qH-x3PBUR6ugo-1_TJ9udA", + "value": "Before sliced bread, people used to say, \"That's the greatest thing since Chuck Norris.\" But Chuck Norris was displeased by this. So he roundhouse kicked a loaf of bread into slices." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "toRr7TfST_GipdLOzAreCA", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/toRr7TfST_GipdLOzAreCA", + "value": "Chuck Norris killed the greek warrior achilles with a roundhouse kick to his face. Then one night Chuck Norris made a joke he says \"Ah-kill-hes face!\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1_7c22oOQYqRcxNxOSLlRQ", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/1_7c22oOQYqRcxNxOSLlRQ", + "value": "A man once asked Chuck Norris if it was true that he had cheated Death. Chuck Norris merely laughed and said \"Fool! I am Death\" and proceeded to roundhouse kick him mercilessly." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ArhxJ8zJR9e5FgaKP0AJWQ", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/ArhxJ8zJR9e5FgaKP0AJWQ", + "value": "I would like to take this spot to remember Michael Jackson, who was pronounced dead on the 25th. He was the King of Pop, and we will all miss him. Rest in peace. Suspected cause of death: A Chuck Norris delivered roundhouse kick to the face. Evidence: Missing nose, giant bootprint." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZEuqShFfTYWbKvZz8U1TpQ", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/ZEuqShFfTYWbKvZz8U1TpQ", + "value": "Chuck Norris was kicked off the show \"Extreme Couponing\" the producers couldn't stand watching entire grocery chains file bankruptcy because of Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "w1ODpbIIT-qWtyNq6arA7g", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/w1ODpbIIT-qWtyNq6arA7g", + "value": "All bad guys ever kicked by Chuck Norris in TV/movies immediatly died upon end of contract. Chuck Norris respects the Law." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "xD_qmXkgRnC36FV-bsRblQ", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/xD_qmXkgRnC36FV-bsRblQ", + "value": "Chuck Norris puts the C in Critical, the H in Hercules, the U in Unbeatable and the K in Kick... All together - CHUCK." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RtqvU-4UR12whu1GrNVEWg", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/RtqvU-4UR12whu1GrNVEWg", + "value": "Chuck Norris house trained his dog by 1 roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hxX8J_SMTeCwao3frW9gbQ", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/hxX8J_SMTeCwao3frW9gbQ", + "value": "Robin Hood was partially based on Chuck Norris, in that Norris would rob from the rich. But Robin Hood never roundhouse-kicked the poor. Or the rich, for that matter." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "G9xf1cllRpGf5IhEsmy5Mw", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/G9xf1cllRpGf5IhEsmy5Mw", + "value": "Atomic fusion is so powerful, it could power the U.S. for a day. Chuck Norris' roundhouse kick can power the Earth for 1,000 years." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9yPOBZC5TFav0WvrlJplKw", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/9yPOBZC5TFav0WvrlJplKw", + "value": "Chuck Norris is the most venomous thing on earth. After moments of getting bit, you receive the following symptoms: fever, beard rash, tightness of jeans, and the feeling of getting kicked through a car." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Zv_oWNAkT-uo9pbeteoxKA", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/Zv_oWNAkT-uo9pbeteoxKA", + "value": "If you ever try to drop kick Chuck Norris, you should stay down there." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5cENL20GSwevHSZsJPGZsQ", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/5cENL20GSwevHSZsJPGZsQ", + "value": "Chuck Norris is So Badass, he went to the island of Coradie to kick Ganon's Ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "pY3V-phXR1KuGQD3tc6Oeg", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/pY3V-phXR1KuGQD3tc6Oeg", + "value": "Chuck Norris single handedly roundhouse kicked the butt of every player, coach, waterboy and fan of the Florida State University Seminoles football team. They are now known as the Semiholes." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "w7JjSWxqQFm7bpXiLiK_kQ", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/w7JjSWxqQFm7bpXiLiK_kQ", + "value": "Chuck Norris discovered the theory of relativity. When Albert Einstein found out he decided to make it public. Chuck Norris got angry and roundhouse kicked him in the face. Albert Einstein is now known as Stephen Hawking." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cueg2RtnSkSYJnaiTiWs8Q", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/cueg2RtnSkSYJnaiTiWs8Q", + "value": "Chuck Norris can kill Weegee (a evil invincable clone of luigi) in 1 roundhousekick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RZCyO99jShmwVLOu-ouhBA", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/RZCyO99jShmwVLOu-ouhBA", + "value": "Chuck Norris fact: John Merrick was Chuck Norris' identical twin. Chuck kicked his brothers butt inside his mothers womb for sucking on his thumb. John Merrick is known today as the elephant man." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jWaZl5XBSG-YCKe8KRcB5g", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/jWaZl5XBSG-YCKe8KRcB5g", + "value": "Chuck Norris often works out in public. He was spotted last week on Hwy 66 roundhouse kicking tornados as a warm up." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HbU_RItxSG6dzuscqHLBog", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/HbU_RItxSG6dzuscqHLBog", + "value": "There was a band named the Chuck Norrises. It was then banned because the band threatened the viewers with roundhouse kicks, including Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OUWK6JsrSfa21luvJbR4Kg", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/OUWK6JsrSfa21luvJbR4Kg", + "value": "Chuck (engineering), a clamp that is part of a machine tool (e.g., lathe) or power tool (e.g., drill) and securely holds a removable part, either a workpiece or a tool (e.g., drill bit). Chuck Norris (asskicker), a superhuman roundhouse-kick machine that is primarily nocturnal, feeds upon beer and raw meat, and has a year-round mating season." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gCzZY8alSoeLZC6jziRCqA", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/gCzZY8alSoeLZC6jziRCqA", + "value": "SpongeBob SquarePants once called Chuck Norris a Crabby Patty sissy boy. An angy Chuck Norris then roundhouse kicked SpongeBob in the ass so hard that he had to legally change his name to SpongeBob NoRectumPants." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ftsSltITRsSZjrn-r1PGGQ", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/ftsSltITRsSZjrn-r1PGGQ", + "value": "The Hiroshima and Nagasaki nuclear explosions were NOT caused by Fat Man and Little Boy. They were caused by two Chuck Norris roundhouse kicks!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZLx_uNhmTU2U48fdSl0IjQ", + "updated_at": "2020-01-05 13:42:26.447675", + "url": "https://api.chucknorris.io/jokes/ZLx_uNhmTU2U48fdSl0IjQ", + "value": "When Chuck Norris Roundhouse kicks your ass you say thank you sir may I have another." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ACOQrCbaS5GDR8DKysxHOQ", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/ACOQrCbaS5GDR8DKysxHOQ", + "value": "One time Jason Voorhees made fun of Chuck Norris during a hockey game. He roundhouse kicked Jason in the face so hard, he melded the goalie mask to his face, broke his vocal cords, and messed up his brain so much, he became a serial killer." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kztCPkWlTFCHYIRBUehvlQ", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/kztCPkWlTFCHYIRBUehvlQ", + "value": "Rule #1: Chuck Norris is always right. Rule #2: Should Chuck Norris happen to be wrong he will roundhouse kick you back to rule #1." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "IbybCr5wTKePtqggVOZ5xA", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/IbybCr5wTKePtqggVOZ5xA", + "value": "A roundhouse kick to the crotch from Chuck Norris cures AIDS. Its too bad no one can survive a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XO8bBespRgClq05lTsnsIw", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/XO8bBespRgClq05lTsnsIw", + "value": "Most people receive Chuck Norris' kicks while he is wearing his cowboy boots. Receiving a bare-feet Chuck Norris roundhouse kick is considered auspicious in some countries." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BYu2BMVcQc6iZozbea7RKQ", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/BYu2BMVcQc6iZozbea7RKQ", + "value": "If he wanted to, Chuck Norris could roundhouse kick you in the face with the ingrown hair on his ass even tho' he doesn't have one." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jNTd1837QayvyPyqmyAq2g", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/jNTd1837QayvyPyqmyAq2g", + "value": "chucky does not play with Chuck Norris Chuck Norris plays with chucky by roundhouse kicking him over and over again" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wRC5c4C2TRa3ES_cGte3Fw", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/wRC5c4C2TRa3ES_cGte3Fw", + "value": "If Chuck Norris imagines roundhouse kicking someone, that roundhouse kick actually hurts someone. Try not to imagine him imagining that." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "qnYHpr5QSpWp5tq7FQ2Xow", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/qnYHpr5QSpWp5tq7FQ2Xow", + "value": "If Chuck Norris round-house kicks you, you will die. If Chuck Norris' misses you with the round-house kick, the wind behind the kick will tear out your pancreas." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "nOA287_rSOSyIXjc9qRQew", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/nOA287_rSOSyIXjc9qRQew", + "value": "Mother Nature and Father Time came together and made Chuck Norris, But his name wasn`t always Chuck norris because it`s latin for I WILL KICK YOUR ASS." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Nf8rEA-eTVOANlJiJ9Ir0Q", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/Nf8rEA-eTVOANlJiJ9Ir0Q", + "value": "Chuck Norris can break your neck with his tongue, tear your heart out with his eyelashes and kick you in the dick with enough force to leave a mushroom-shaped hole in the brick wall behind you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ibMDwE5RTUCpHyT9yqOGfg", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/ibMDwE5RTUCpHyT9yqOGfg", + "value": "When Chuck Norris was born it wasn't his mother that pushed him out, he crawled out on his own, round house kicked the doctor and lit a cigar." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "W7U4VTN-S-KDYvLu6o-OFg", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/W7U4VTN-S-KDYvLu6o-OFg", + "value": "\"Like a good neighbor...\" Chuck Norris is there... with a roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "DVPMmcXjS3C7t0oioaQ6qg", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/DVPMmcXjS3C7t0oioaQ6qg", + "value": "Chuck Norris once killed a man with a roundhouse kick to someone else's face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ah7gStfKTPCseJJsJOTJYw", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/ah7gStfKTPCseJJsJOTJYw", + "value": "When Chuck Norris sees a screamer, he roundhouse kicks it so hard, that the ghost gets hit." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XFUGyEmwQaK2ASAfnvEYEQ", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/XFUGyEmwQaK2ASAfnvEYEQ", + "value": "Chuck Norris can kick a fart back into an ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gldk0zH1Tqqa1-NHlHLn2A", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/gldk0zH1Tqqa1-NHlHLn2A", + "value": "Chuck Norris once round house kicked a retarded kids bus, and it became the first smart car." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "boaWowX4TfGtF3iARoFhMw", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/boaWowX4TfGtF3iARoFhMw", + "value": "How much wood can a woodchuck chuck, if a woodchuck chuck could chuck wood? This question is irrelevant. A relevant question would be \"How many trees can Chuck Norris kick the woodchuck through\"?" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "6DnR00l9SjOlb4ybLLEl6Q", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/6DnR00l9SjOlb4ybLLEl6Q", + "value": "All people say that the future is robots and futuristic buildings and technology. The real future is Chuck Norris as the ruler of the world. And ruler of roundhouse kicks and karate." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "nd-4EVIDSa69Y7pAlZPGXQ", + "updated_at": "2020-01-05 13:42:26.766831", + "url": "https://api.chucknorris.io/jokes/nd-4EVIDSa69Y7pAlZPGXQ", + "value": "Chuck Norris once accidentally killed a man by roundhouse kicking his shadow. The judge deemed the incident an Act of God and Chuck Norris walked a free man." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "qU47e6VyTI6Z0vrI72sPxQ", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/qU47e6VyTI6Z0vrI72sPxQ", + "value": "Whoever said \"Close enough only works for horseshoes and hand grenades\" obviously never witnessed what happens to people within a mile of a Chuck Norris roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "SiqCscf3R7-p6gwVR4FXvA", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/SiqCscf3R7-p6gwVR4FXvA", + "value": "When Chuck Norris goes to the club he doesnt dance, he does \"the Chuck Norris boogey\" aka roundhouse kicking everyone" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "P_zHOdMaSB6GwXgezQSIsQ", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/P_zHOdMaSB6GwXgezQSIsQ", + "value": "Chuck Norris' leg kicks hit hard enough to knock the polio vaccine out of your body" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "L7DLHAx9Txa0GEPWnWWcVw", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/L7DLHAx9Txa0GEPWnWWcVw", + "value": "Even if Chuck Norris is entirely tied with metal chains, he'll still kick your ass" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "QP6DG1ftRDCwNwhcbd6yUg", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/QP6DG1ftRDCwNwhcbd6yUg", + "value": "Chuck Norris jumped out of the Godly tree and roundhouse-kicked every branch on the way." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "phz8eDrnRM-WQ_Mfx66aZw", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/phz8eDrnRM-WQ_Mfx66aZw", + "value": "You know... Chuck Norris KNOWS Victoria's Secret... when he looks in the mirror his reflection runs away Probably his greatest accomplishment: He once round-house kicked someone so far his ody went back through time and hit Amelia Airheart's plane and it was shot down over the Pacific Ocean" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "F6CxYDlJRv-iy83fa3wKUQ", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/F6CxYDlJRv-iy83fa3wKUQ", + "value": "Chuck Norris is the only computer system that beats a Mac or a PC. Too bad all it does is round house kicks the user." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jgtda9HrR6ydJofUCuhz_g", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/jgtda9HrR6ydJofUCuhz_g", + "value": "If you ever dream of beating Chuck Norris in a thumb war, the next event in said dream will be a 6734-ton weight falling on you. This is Chuck Norris's roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "sP2GYMlORKeAXxHVTQQMug", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/sP2GYMlORKeAXxHVTQQMug", + "value": "Chuck Norris was not born. He kicked his mother off of him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "87eQtBjKT3eXcrX2wXa3Ew", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/87eQtBjKT3eXcrX2wXa3Ew", + "value": "A Chuck Norris flying roundhouse kick to the buttocks has been known to cause an infected, yellow pus filled abscess in hemorrhoidal tissue." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "IP6C9Iq4SDSR5rSTYUPr5A", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/IP6C9Iq4SDSR5rSTYUPr5A", + "value": "Chuck Norris doen't always drink beer. But when he does, he prefers to roundhouse kick \"The Most Interesting Man in the World\" in the face and take his Dos Equis. \"Stay thirsty my friends\"." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jySyScEgR4eObSzOsJR6Lg", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/jySyScEgR4eObSzOsJR6Lg", + "value": "Slenderman once found Chuck Norris and then he came up and roundhouse kicked Slenderman in the face 100 times" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HLQaEEqUQiOCUB6581N-iA", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/HLQaEEqUQiOCUB6581N-iA", + "value": "Chuck Norris doesn't kick people to the sun,he kicks the sun to people" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OFLD9DntR2iKRb1IaVa4Vg", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/OFLD9DntR2iKRb1IaVa4Vg", + "value": "Proponents of higher-order theories of consciousness argue that consciousness is explained by the relation between two levels of mental states in which a higher-order mental state takes another mental state. If you mention this to Chuck Norris, expect an explosive roundhouse kick to the face for spouting too much fancy-talk." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KFhtQ7m7SG22xEJZN-CKSA", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/KFhtQ7m7SG22xEJZN-CKSA", + "value": "Once a panhandler approached Chuck Norris and asked him for some change. Chuck Norris generously gave the poor man a quarter roundhouse kick and sent him hurtling into outer space at the speed of light." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "QInnPfVgSzuXoP-DcK4c8A", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/QInnPfVgSzuXoP-DcK4c8A", + "value": "One summer day, Chuck Norris was grilling steaks in his backyard when Bobby Flay approached him and challenged him to a Throwdown. Six hours later, surgeons were successful in reducing the swelling in Bobby's brain caused by a perfectly placed roundhouse kick to Bobby's temple. Prior to surgery, Bobby's head swelled up as big as Alton Browns head..." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "JY_FdtBZRUeEfGJ-reM1qA", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/JY_FdtBZRUeEfGJ-reM1qA", + "value": "It takes Chuck Norris 5 seconds to kick your ass... 80 times" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:26.991637", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "CJlrbtgwSsCzFoDdlKokWw", + "updated_at": "2020-01-05 13:42:26.991637", + "url": "https://api.chucknorris.io/jokes/CJlrbtgwSsCzFoDdlKokWw", + "value": "see the picture on top that has Chuck Norris' face on it? stare at it too long, and you will get a imaginary roundhouse kick to the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZG54JYBfRy-zuOqOBZwjIg", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/ZG54JYBfRy-zuOqOBZwjIg", + "value": "If you ask Chuck Norris what time it is, he always says, \"Two seconds till.\" After you ask, \"Two seconds till what?\" He roundhouse kicks you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OklJ3oTeREetkVkrrkyIOg", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/OklJ3oTeREetkVkrrkyIOg", + "value": "It's the assholes like John West that Chuck Norris brutally roundhouse kicks, that makes Chuck Norris the best." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "di3kL1L1RtC9ejtOoUAQNA", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/di3kL1L1RtC9ejtOoUAQNA", + "value": "Chuck Norris once roundhouse kicked Mr Lawrence Thread so hard all that was left of his name was Mr T." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "bFJWLxtHTr-jqQrrRC45fA", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/bFJWLxtHTr-jqQrrRC45fA", + "value": "Once in olden times, Chuck Norris roundhouse kicked the ancient King of Canniptia in the face. Then, the remaining old world Canniptions threw a fit. Thank goodness the neighboring ruler of Hissyopia was not also kicked in the face or there would have also been a Hisssy fit to deal with!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "duZi4UQrQXyQSk-aZuReUQ", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/duZi4UQrQXyQSk-aZuReUQ", + "value": "Chuck Norris can roundhouse kick you around the world." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XkuHMhLLSOWtWXeJTBNC4Q", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/XkuHMhLLSOWtWXeJTBNC4Q", + "value": "Contrary to popular folklore, Ebenezer Scrooge became filled with the true spirit of Christmas after he found out that Tiny Tim is Chuck Norris' nephew. And the fact that Chuck Norris relentlessly kicked his ass all night long on Christmas eve." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-ZncWPhhTqGjsSMpPBpWqA", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/-ZncWPhhTqGjsSMpPBpWqA", + "value": "The end of the world is scared to come because Chuck Norris will round house kick is ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kswv7NIaTCaIIErlBzODaA", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/kswv7NIaTCaIIErlBzODaA", + "value": "Chuck Norris's shadow weighs 250 pounds and can kick your ass ." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "npKkMPjdTN2Z6cHJ9XashA", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/npKkMPjdTN2Z6cHJ9XashA", + "value": "Popeye needs spinach to kick ass. Chuck Norris needs no such assistance." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kWvH5sdXSFOMaBtf9lUlGw", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/kWvH5sdXSFOMaBtf9lUlGw", + "value": "Chuck Norris once read the entire Merriam-Webster dictionary backwards in pig-Latin, while roundhouse kicking 30 ninjas." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "b6gbz95fSMaz5BnvwClIUw", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/b6gbz95fSMaz5BnvwClIUw", + "value": "The only reason why the terrorist bombed the twin towers and the pentagon was because they thought Chuck Norris was in one of them. Now we got some of their countries because he roundhouse kicked most of them." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5FJU_qayQ1qYlB16uAaqQQ", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/5FJU_qayQ1qYlB16uAaqQQ", + "value": "Chuck Norris CAN fool the Children Of The Revolution. Then roundhouse kick them in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PMEaoyYJTSKHMfETm3aW0Q", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/PMEaoyYJTSKHMfETm3aW0Q", + "value": "don't send Chuck Norris e-mail. if it annoys him he can round house kick you thru Cyber Space!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2SJP5e9XSiS_EDq_8RVhJg", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/2SJP5e9XSiS_EDq_8RVhJg", + "value": "Chuck Norris will soon open his own fried chicken restaurant chain: RKC-- Roundhouse Kickin' Chicken." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "NHlYjBZyQCixXmCLm68EiA", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/NHlYjBZyQCixXmCLm68EiA", + "value": "Everyone knew the world was flat,but they especially didn't want sail around the world because they knew if they fell then Chuck Norris would roundhouse kick them." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:27.496799", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XWTybTMeRReeESiw77qjNA", + "updated_at": "2020-01-05 13:42:27.496799", + "url": "https://api.chucknorris.io/jokes/XWTybTMeRReeESiw77qjNA", + "value": "A sign at the entrance to NorrisWorld: 'You need to be at least this tall (30 microns) to die from a Chuck Norris roundhouse kick.'" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "AStmI8FRTTGoY6qBz3QwvA", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/AStmI8FRTTGoY6qBz3QwvA", + "value": "Chuck Norris name is in the webster's dictionary under roundhouse kick and above god." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LayRiqOaQ9i31wD_FxYL3g", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/LayRiqOaQ9i31wD_FxYL3g", + "value": "A girl asked Chuck Norris to shave his beard. Chuck Norris roundhouse kicked her, and she grew taller and her hair grew shorter. That girl is now Justin Bieber." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "_zX0X0-eTDK5BiCIYvqi9w", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/_zX0X0-eTDK5BiCIYvqi9w", + "value": "Like a good neighbor, Chuck Norris is there to roundhouse kick you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "TvmvY-VFQ8WNRtxU8LuMdw", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/TvmvY-VFQ8WNRtxU8LuMdw", + "value": "Chuck Norris is here to kick ass and take names, but doesn't bother to do that second thing." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZZACzyAqSjKwBfDWgC1DqA", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/ZZACzyAqSjKwBfDWgC1DqA", + "value": "The reason why the truth hurts, is because Chuck Norris round-house kicked it in the face" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "saUW1ZpgQge7eZDYs5DRIw", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/saUW1ZpgQge7eZDYs5DRIw", + "value": "When asked how he wanted to die, Saddam Hussein requested a Chuck Norris roundhouse kick to the head. Saddam died instantly." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2-5Dz9KUT264DjSunYaWYw", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/2-5Dz9KUT264DjSunYaWYw", + "value": "When does the Chuck Norris roundhouse kick strike? The answer is, anywhere, at any time, so hide in a hole forever...just in case he is looking for you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "oxuCnu60R6uby3JC9mqWcw", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/oxuCnu60R6uby3JC9mqWcw", + "value": "Chuck Norris actually means God in spanish, english, german, roundhouse, arabic, french, assassin, italian and just go with it or he'll kick you" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7B858RNRRMOu-f_DAoiN1Q", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/7B858RNRRMOu-f_DAoiN1Q", + "value": "The invasion of America in Modern Warfare 2 insulted Chuck Norris by blowing up his house - he walked across the Pacific to Russia and promptly roundhouse kicked Moscow in the face, thereby ending the war." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hTlnETbpSteLWidqVMgeSA", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/hTlnETbpSteLWidqVMgeSA", + "value": "Jesus' last words, before giving up the ghost in Aramaic were \"Eloi Eloi lama sabachthani\". The approximate translation into modern English: Chuck Norris, Chuck Norris, please don't roundhouse kick me!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gnh21fOSTCuMd7011x9maQ", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/gnh21fOSTCuMd7011x9maQ", + "value": "Alls well that ends well. But if your life ends with a Chuck Norris roundhouse kick, all is not well." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "maAWglMPTPOp1tqZXleV7A", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/maAWglMPTPOp1tqZXleV7A", + "value": "Chuck Norris sold his soul to the devil for his rugged good looks and unparalleled martial arts ability. Shortly after the transaction was finalized, Chuck roundhouse kicked the devil in the face and took his soul back. The devil, who appreciates irony, couldn't stay mad and admitted he should have seen it coming. They now play poker every second Wednesday of the month." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RChm8djZRx6Ovr0sH3aBeQ", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/RChm8djZRx6Ovr0sH3aBeQ", + "value": "Chuck Norris believes that for every action there is an equal and opposite roundhouse kick to your head." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "11Y5uvm9TNyUHZkhy7_zig", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/11Y5uvm9TNyUHZkhy7_zig", + "value": "CHuck norris can kick u with his hands, and punch u with his feet." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "uRxbNrPrQyahdB10YtRvVA", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/uRxbNrPrQyahdB10YtRvVA", + "value": "Chuck Norris roundhouse-kicked the nose off of the Sphinx." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "EAWzGqO9QdqOAjtY3MVPew", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/EAWzGqO9QdqOAjtY3MVPew", + "value": "When you search \"Chuck Norris getting his ass kicked,\" on any search machine, you will generate zero results. It just doesn't happen, and it never will too!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yUNPIjOpQFGniYoPchEzZQ", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/yUNPIjOpQFGniYoPchEzZQ", + "value": "Once when Chuck Norris was denied a Bacon McDouble at McDonald's because it was 9:35, he roundhouse kicked the store so hard that it became a KFC." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Xh_WhWPGQM6Cpzi8hhVO0g", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/Xh_WhWPGQM6Cpzi8hhVO0g", + "value": "Chuck Norris can send his roundhouse kicks through the mail to everywhere, since he knows every address in the entire world! So don't be surprised if you see someone's head instantly explode from opening an envelope." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "W4hxZSdjSQmZGOpTSrYvLQ", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/W4hxZSdjSQmZGOpTSrYvLQ", + "value": "If you try to drop kick Chuck Norris, you should stay down there." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "rHyhP5RcSOyujHl6oahxvw", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/rHyhP5RcSOyujHl6oahxvw", + "value": "if you dont agree with Chuck Norris god management beware of roundhouse kick so" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Bjvu6u4oQSqGxUMhOSApiQ", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/Bjvu6u4oQSqGxUMhOSApiQ", + "value": "Newtons third law of motion states that Every action has a reaction equal in magnitude and opposite in direction When Chuck Norris uses his roundhouse kick there is only action." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KyS1LU5bTT-ozlN9TeXclQ", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/KyS1LU5bTT-ozlN9TeXclQ", + "value": "Chuck Norris once shoved a watermelon up Gallagher's nose. Then shattered it with a flying roundhouse kick. Thus, proving that Gallagher is scatter-brained." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RyKhhkLCSemv2kAKInSyKg", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/RyKhhkLCSemv2kAKInSyKg", + "value": "Chuck Norris once commented, \"There are few problems in this world that cannot be solved by a swift roundhouse kick to the face. In fact, there are none.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MZQwxwtFTo-3OvrGprhI7w", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/MZQwxwtFTo-3OvrGprhI7w", + "value": "If you somehow survive a roundhouse kick from Chuck Norris, you'd still go to jail for head butting his foot." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.143137", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "A6uXg0uJTjW74_qfyCm-Ww", + "updated_at": "2020-01-05 13:42:28.143137", + "url": "https://api.chucknorris.io/jokes/A6uXg0uJTjW74_qfyCm-Ww", + "value": "Roundhoused kicked by Chuck Norris... Life ruined forever." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "i71JHaBvSG-u8i4yodX5Yw", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/i71JHaBvSG-u8i4yodX5Yw", + "value": "Chuck Norris once kicked a horse in its chin. Its decendants are now known as giraffes." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1V-TBXT6RDWczN4FZ8oovg", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/1V-TBXT6RDWczN4FZ8oovg", + "value": "The 1951 UFO sighting was actually a man hole cover kicked by Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fT6GW5PaQG-PTgnH8dvWTQ", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/fT6GW5PaQG-PTgnH8dvWTQ", + "value": "There is a new unit of measurement called CNRKs [Chuck Norris Roundhouse Kicks]. 1 CNRK is enough to knock out something JUST tougher than 100000 lions. The Big Bang measured at 2 CNRKs, so far the only thing to certainly be more powerful than 1 CNRK. The Tsar Bomb, though, measured at a puny 0.0000327581 CNRKs, and the MOAB at 0.0000000000000000476738295873826782 CNRKs. The regular blink of a regular eye measured at an extremely puny 0.00000000000000000000000[insert 1000 zeroes here]6783758684 CNRKs." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "56gBgT03QTaU-7YyN0tFVA", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/56gBgT03QTaU-7YyN0tFVA", + "value": "Vin diesel got into a fight with Chuck Norris, and he kicked Chuck's ass and everyone cheered, Vin! Vin! Vin!..and Vin smiled at the adoring crowd...Vin! Vin!...but then Vin started waking up and slowly opened his eyes to find Chuck at his bed side, nudging at him, saying, Vin, Vin wake up..then Vin realized it was a dream, and Chuck Norris punched him in the fucking face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Kt5QkOxKRSunGGvndb_9CA", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/Kt5QkOxKRSunGGvndb_9CA", + "value": "Faces of Death is actually a biography of people that were given a choice other than a Chuck Norris roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Z8zHhxvFQ2Gp5spdHn2QZQ", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/Z8zHhxvFQ2Gp5spdHn2QZQ", + "value": "Chuck Norris was pissed off because George Washington was president, so he called him a pussy and roundhouse kicked George to the face" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yzQDshvSRdO9bCGPqHAnbw", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/yzQDshvSRdO9bCGPqHAnbw", + "value": "Chuck Norris roundhouse kicks you in the head, his foot will actually go through your skin, skull and brain, then reappear out the other side. This is not magic, this is power. And if you survive, that would be magic." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "qb4z9b4kRBih6duymTEKVQ", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/qb4z9b4kRBih6duymTEKVQ", + "value": "Another way to explain a Chuck Norris Round House Kick to the face is \"de-materializing.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mtIJH_zKR3a6rB7ROa1ugg", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/mtIJH_zKR3a6rB7ROa1ugg", + "value": "Chuck Norris bought a double cheeseburger from McDonalds on the dollar menu for 2 dollars. He then proceeded to round house kick the cashier" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "l00PpeQcTAqizOpKdukHYA", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/l00PpeQcTAqizOpKdukHYA", + "value": "Chuck Norris once wrote...\"I will personally roundhouse kick anybody in the face that missplells words!\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ScJ-CedNQxOwISW5Ri97Lw", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/ScJ-CedNQxOwISW5Ri97Lw", + "value": "Chuck Norris's main method of preventing universe-shattering paradoxes consists of kicking people in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kRCicvhcSlanwuB6SzOKvA", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/kRCicvhcSlanwuB6SzOKvA", + "value": "Freddy thought he was the true nightmare until he met Chuck Norris who roundhouse kicked and from that day Freddy hides in fear thinking a nightmare in texas" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "oO9tQRHeQhK5FQyqxzbe4Q", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/oO9tQRHeQhK5FQyqxzbe4Q", + "value": "Chuck Norris can make your nightmares come true. By roundhouse kicking you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RpemUFTcRjWrP2Ufp1dHQw", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/RpemUFTcRjWrP2Ufp1dHQw", + "value": "Chuck Norris knows how many kicks it takes to get to the center of a Tootsie Pop: one" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "S9WmnjNXRO-qYmkpE2MWHg", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/S9WmnjNXRO-qYmkpE2MWHg", + "value": "Sam Jackson is the only person to ever out-surf Chuck Norris at Chadderton baths. Incidentally, Chuck roundhouse kicked him in the face, and the force put him in a roundhouse induced coma for the rest of his natural life..." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "uy9WMmwOR32B4OZ_gunblA", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/uy9WMmwOR32B4OZ_gunblA", + "value": "When Chuck Norris was denied a Bacon McMuffin at McDonalds because it was 10:35, he roundhouse kicked the store so hard it became a KFC." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5xncbuVAQIyDYzNcwzUVcQ", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/5xncbuVAQIyDYzNcwzUVcQ", + "value": "You know when you have so much fun it feels like time flies by so fast....... That's Chuck Norris telling Father Time to hurry up and get to the part where he roundhouse kicks you to death" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YB8v9TUCT4ulgh8cNQxHrg", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/YB8v9TUCT4ulgh8cNQxHrg", + "value": "Chuck Norris doesn't use a microwave to pop his popcorn. He simply sits the package on the counter and the kernals jump in fear of a round house kick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "i3uBQXJxSLqxd18Ukxtb4g", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/i3uBQXJxSLqxd18Ukxtb4g", + "value": "Chuck Norris once roundhouse kicked SpongeBob SquarePants in the butt so hard that SpongeBob had to legally change his name to SpongeBob ParallelogramPants." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "JkvDLDWeQC2i_80Csh7nBg", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/JkvDLDWeQC2i_80Csh7nBg", + "value": "Chuck Norris is the only man who can kick the shit OUT of you, then back in." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HT825IiPR9qy6vvxlA_hQQ", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/HT825IiPR9qy6vvxlA_hQQ", + "value": "After seeing ''The Blair Witch Project'', Chuck Norris went into the woods, found the Blair Witch, and roundhouse-kicked it repeatedly until it died. When Chuck Norris pays 6 dollars to see a witch movie, you'd better show him a fuckin' witch." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.420821", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "vhfit3aqTWSwXi3wdMXU1w", + "updated_at": "2020-01-05 13:42:28.420821", + "url": "https://api.chucknorris.io/jokes/vhfit3aqTWSwXi3wdMXU1w", + "value": "In the beginning, there was nothing. Then Chuck Norris roundhouse kicked the universe in the face and said \"get a job\"." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jRwlxEy2RGGwPirAZLx9Bw", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/jRwlxEy2RGGwPirAZLx9Bw", + "value": "There are only three things in the world that are a sure kill. 1. Kirby's Super Inhale. 2. Captain Falcon's Falcon Punch. 3. Chuck Norris's Roundhouse Kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fk6pNHIjQyyYZM-6Pbq8Fg", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/fk6pNHIjQyyYZM-6Pbq8Fg", + "value": "Inspired by the 2004 Athens Olympics, Chuck Norris invented the roundhouse kick dive while visiting New Orleans..it's now known as Katrina" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "OOb_lOJ1Rp-s6pxMQx0yCA", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/OOb_lOJ1Rp-s6pxMQx0yCA", + "value": "As a youngster, just for kicks Chuck Norris would often tie up a neighborhood kid and tell him he'd be back in 30 minutes to kick his @$$. That man grew up to be Harry Houdini." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ys9fVItSSSaVZ94tNEhDfw", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/Ys9fVItSSSaVZ94tNEhDfw", + "value": "Chuck Norris didn't like this joke, so he deleted it by roundhouse kicking it." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ASxszjoiSV67v3HEtcQreQ", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/ASxszjoiSV67v3HEtcQreQ", + "value": "A Chuck Norris fact a day keeps his roundhouse kicks away." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "3sSTpO_pR0CS3_wR5ERO2w", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/3sSTpO_pR0CS3_wR5ERO2w", + "value": "Chuck Norris can roundhouse kick the air around him and it bleeds wind." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mYna0hUJS9KMWY-BzQpzaQ", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/mYna0hUJS9KMWY-BzQpzaQ", + "value": "Chuck Norris' earlobes can roundhouse kick your face through the back of your skull." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PXxcEJDWSpyLW532ObC0EQ", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/PXxcEJDWSpyLW532ObC0EQ", + "value": "The pressure at the Earth's deepest point is more than 1000 times the normal atmospheric force, or about 1 tenth of the force of a Chuck Norris roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "rMQ2azTRToyx3KB4h5aLNQ", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/rMQ2azTRToyx3KB4h5aLNQ", + "value": "Chuck Norris went to the bottom of the bottomless pit, he was so pissed there was a bottom, he roundhouse kicked it and now it truly is bottomless" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HW2FCG66SKeloYZR1wjn3A", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/HW2FCG66SKeloYZR1wjn3A", + "value": "If you don't make these jokes funny, Chuck Norris will run all the way around the world in a millasecond and roundhouse kick you in the face!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "aB4Puwq1RCSyrtkQNX0-Cg", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/aB4Puwq1RCSyrtkQNX0-Cg", + "value": "Chuck Norris invented oatmeal when he round house kicked a quaker." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "lj5tmV9WQpOy0SvKdAZ-rQ", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/lj5tmV9WQpOy0SvKdAZ-rQ", + "value": "Life is like a box of chocolates until Chuck Norris roundhouse kicks you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LpHcyuP_SPWcqadMXQduUQ", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/LpHcyuP_SPWcqadMXQduUQ", + "value": "Chuck Norris once roundhouse kicked a cotton field in Mississippi instantly creating the first Levis factory as well as adorning his torso with a sleeveless denim vest" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "3wVyRIDSRTGDPB7EBzdGLg", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/3wVyRIDSRTGDPB7EBzdGLg", + "value": "Chuck Norris once kicked a baby beluga whale into puberty." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "esHjeiRMS8SafK4lABg1jw", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/esHjeiRMS8SafK4lABg1jw", + "value": "Thanksgiving was fist celebrated by the Pilgrims near Plymouth Rock in 1621 because they were thankful that Chuck Norris wasn't there to kick thier ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KCz_aN5iSB-Af9qmP6-MLQ", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/KCz_aN5iSB-Af9qmP6-MLQ", + "value": "By some odd reason,Chuck Norris is everywhere.That means he can be roundhouse kicking you,and jesus at the same time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "D1JcDT75QvqaxlApjlZm5A", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/D1JcDT75QvqaxlApjlZm5A", + "value": "Chuck Norris can roundhouse kick you over the internet. Even if your computer is turned off. Or you don't have one." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "v5BLiczIRvKFup9bf-7BXg", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/v5BLiczIRvKFup9bf-7BXg", + "value": "Chuck Norris wrote a series of books called, \"How To Roundhouse Kick Like Chuck Norris\". Volume 13 contained only a very realistic Chuck Norris leg which would roundhouse-kick the reader. One survivor rewrote it from his point of view, and the series is now in few libraries, if not none." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "3mOKM6tKTc-lPCIKuDEA8w", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/3mOKM6tKTc-lPCIKuDEA8w", + "value": "The Internet was invented by Chuck Norris so that he could deliver roundhouse kicks worldwide from the comfort of his own home." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "IWp-NfZ_SoGSSFD6jWVfQg", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/IWp-NfZ_SoGSSFD6jWVfQg", + "value": "In order to sleep, Chuck Norris has to roundhouse kick himself in the face approximately 754 times. Even then, he still tosses and turns a little." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "oDEgbxIwR0i90RLXk_QNEA", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/oDEgbxIwR0i90RLXk_QNEA", + "value": "Contrary to popular belief, Chuck Norris, not the box jellyfish of northern Australia, is the most venomous creature on earth. Within 3 minutes of being bitten, a human being experiences the following symptoms: fever, blurred vision, beard rash, tightness of the jeans, and the feeling of being repeatedly kicked through a car windshield." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "oWEvFKD7QviJAbTrfhQTqg", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/oWEvFKD7QviJAbTrfhQTqg", + "value": "Chuck Norris invented happiness when he spared a mans life, when the man told his family, Chuck Norris roundhouse kicked him in the jaw thus ending the man and adding another hair to Chuck's beard!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.664997", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "uF_yaSWOTaOqNor-xlVo8g", + "updated_at": "2020-01-05 13:42:28.664997", + "url": "https://api.chucknorris.io/jokes/uF_yaSWOTaOqNor-xlVo8g", + "value": "Chuck Norris can eat or drink anything without a single stop while doing roundhouse kicks." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-3CjPqg6SaKvAZBLgTp_6w", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/-3CjPqg6SaKvAZBLgTp_6w", + "value": "If you play Stairway to heaven backwards you hear a satanic message. If you play the theme song to Walker,Texas Ranger backwards you hear Chuck Norris kicking the Devils ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "nYj4tJQ-T4ioHrzbr8rFMg", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/nYj4tJQ-T4ioHrzbr8rFMg", + "value": "By the time you see Chuck Norris in your rearview mirror, he's already kicked you in the face and is 10 car lengths ahead of you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "z7qvIUuwQr-iJ8IBK5zqXg", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/z7qvIUuwQr-iJ8IBK5zqXg", + "value": "Chuck Norris roundhouse kicked this fact." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "REaaJ4tNQaWB0FYXr23hwQ", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/REaaJ4tNQaWB0FYXr23hwQ", + "value": "The most obvious upside to receiving a brutal Chuck Norris roundhouse kick is is that you will enter the afterlife knowing that you have died the most awesome death possible." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "CM1AUaVrRHSTtzKbAHKpLw", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/CM1AUaVrRHSTtzKbAHKpLw", + "value": "When Chuck Norris asks to 'borrow' something from you, be aware that you will never, ever get it back and are in fact about to be kicked in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "s_akNmy_Rn2Q_mBZllZS6Q", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/s_akNmy_Rn2Q_mBZllZS6Q", + "value": "Chuck Norris was the Fourth Wiseman. He gave Baby Jesus the gift of Beard. Jesus loved it so much the other Wisemen were jealous of his obvious gift favoritism. They then used their combined influence to have Chuck Norris omitted from the Bible. All three later died mysteriously from roundhouse-kick related issues." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ODVOngJ1Tb6Kb-Q-TxBsCQ", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/ODVOngJ1Tb6Kb-Q-TxBsCQ", + "value": "Recently, a car salesman told Chuck Norris he needed to go green and drive a Toyota Prius. Chuck kicked the Prius across the parking lot then drove off in his Humvee." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5LdFelEhSYyC7k9f3NQ4Lw", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/5LdFelEhSYyC7k9f3NQ4Lw", + "value": "Every year Chuck Norris' birthday, March 10 is celebrated as Voluntary Roundhouse Kick Day. On this special day people volunteer to be roundhouse kicked by Chuck Norris in the face to show him how much he is loved. The next day, Chuck Norris hunts down everyone who didn't volunteer and roundhouse kick them for offending him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "plj3-pqiRWuomVQKAy_AwQ", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/plj3-pqiRWuomVQKAy_AwQ", + "value": "Chuck Norris can have his cake, eat it, then roundhouse kick you in the face with the extra power it gave him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yjEl_PU5T3qEjOe-glr4CA", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/yjEl_PU5T3qEjOe-glr4CA", + "value": "When Chuck Norris is in a particularly artistic mood, he likes to set up a large canvas and roundhouse kick people near it while wearing ice-skates." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YAcLiPWrTFCYFcavRgprzg", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/YAcLiPWrTFCYFcavRgprzg", + "value": "A waiter at a restaurant once asked a guy what kind of steak he wanted, the guy said, \"A roundhouse\". A few minutes later someone walked up beside the guy that ordered the steak while he was reading the menu. Thinking that the person that walked up beside him was the waiter, he asked, \"Do you have my roundhouse yet?\". He looked up and saw that Chuck Norris was right beside him. Chuck Norris said, \"You bet your ass I do!\". Chuck Norris then roundhouse kicked the guy in the face and asked, \"How did that taste?\"." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Xe8PylgRRNmM8th0N8tQbA", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/Xe8PylgRRNmM8th0N8tQbA", + "value": "Colnel Sanders actually died by eleven of Chuck Norris' kicks and punches." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "4uGz2cWXQBa4Eg-tGtXGtA", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/4uGz2cWXQBa4Eg-tGtXGtA", + "value": "Once I came up with a very funny Chuck Norris joke and decided to tell that to the Man himself before anyone else in the world. On hearing the joke Chuck Norris gave me a swift roundhouse kick to the side of my head, ever since I can't remember what that joke was." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YyDUUnNlQEWtSp771kjo1A", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/YyDUUnNlQEWtSp771kjo1A", + "value": "Chuck Norris was once in Street Fighter 2. He was then removed because beta testers experienced that every button makes Chuck Norris do a roundhouse kick. Beta testers then ask Chuck Norris about this \"Glitch\". Chuck Norris replys: \"That's no glitch.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "6dH62rLeTj6_5pnHUVhElA", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/6dH62rLeTj6_5pnHUVhElA", + "value": "Chuck Norris beat Pac-Man without doing anthing.He roundhouse kicked all of the ghosts until they had a pool of blood all over the screen and Chuck Norris commented \"NOTHING STANDS CHUCK NORRIS'S FUCKING WAY!\" That qualifies as doing nothing" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ee7ysgofRHex2YSp20RqjA", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/ee7ysgofRHex2YSp20RqjA", + "value": "According to the Mayan civilization, the world will end in the year of 2012... They believe this to be true because they fear Chuck Norris is harnesting power for a final Round House Kick in that year" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MDUr-14SR9iOaEIyt2N97g", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/MDUr-14SR9iOaEIyt2N97g", + "value": "Chuck Norris's Seasonal catchy musical number... We kick you a Merry Chuckmas... We kick you a Merry Chuckmas... We kick you a Merry Chuckmas and a Chuckie New Year." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "SQlpChLGSsGbRboyxBaxAw", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/SQlpChLGSsGbRboyxBaxAw", + "value": "Chuck Norris invented Hip Hop so that he could hate on Hip Hop artists and roundhouse kick them for his personal entertainment while watching Walker Texas Ranger on TV." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ctRXzRxMQX2XqvjV-9fBqg", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/ctRXzRxMQX2XqvjV-9fBqg", + "value": "There was never anything wrong with Achilles' heel until he got mad and decided to kick Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "u3glR1eVRAuY_fN5rzLsfw", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/u3glR1eVRAuY_fN5rzLsfw", + "value": "Chuck Norris' mom can kick your ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5f9lpT9jS0mE17Dzy9cryg", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/5f9lpT9jS0mE17Dzy9cryg", + "value": "Chuck Norris can draw a picture of him kicking your ass, and send it you you a week in advance - and as soon you open it he will knock on your door, properly kick your ass, and you STILL won't know what the fuck happened." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:28.984661", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "W0WciFjUQKKCm5DzzeTWjA", + "updated_at": "2020-01-05 13:42:28.984661", + "url": "https://api.chucknorris.io/jokes/W0WciFjUQKKCm5DzzeTWjA", + "value": "Chuck Norris likes to keep his lawn growing tall and thick so that whenever he has unwanted visitors, he can slip out of his house unnoticed and stalk them like prey. He then jumps toward them like a magnificent lion of the African plains and delivers a fatal roundhouse kick to their face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9cxTIo20TdOiGlxit3Nxrw", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/9cxTIo20TdOiGlxit3Nxrw", + "value": "When Chuck Norris was once working at Freddy's Fazbear's Pizza, he punched the manager and roundhouse kicked Freddy Fazbear in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "d7FFvqQzSeChAzUl8x-G3Q", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/d7FFvqQzSeChAzUl8x-G3Q", + "value": "In soviet Russia Chuck Norris still kicks your ass!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9TGcubXgRSCOJwhLUAVUbA", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/9TGcubXgRSCOJwhLUAVUbA", + "value": "Chuck Norris's jeans are so tight to prevent excess oxygen from reaching his legs and causing involuntary Roundhouse Kicks." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wbYn4fitSF-EjKBu8foZxQ", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/wbYn4fitSF-EjKBu8foZxQ", + "value": "I pledge allegiance to the flag of the United States of America. And to the Republic for which it stands, one nation under Chuck Norris, indivisible with liberty and roundhouse kicks for all." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "88KQGV8dRWm9p1J5Dr6NZQ", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/88KQGV8dRWm9p1J5Dr6NZQ", + "value": "The word \"upChuck\" came from the first time Chuck Norris round-house kicked someone in the stomach." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XrZfSYAjSHC7n8Rj9PztvQ", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/XrZfSYAjSHC7n8Rj9PztvQ", + "value": "The formation of The Universe began when Chuck Norris roundhouse kicked the singularity." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1pjrwjLRQ2a_aDjTvEFcgg", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/1pjrwjLRQ2a_aDjTvEFcgg", + "value": "Chuck Norris roundhouse-kicks saplings. This is because the saplings will turn into trees the instant after he does that." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "GblHBTbkTYWyJD3cDGJJaA", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/GblHBTbkTYWyJD3cDGJJaA", + "value": "Chuck Norris roundhouse kicked the black off Michael Jackson" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "r-KTBUQYTjKllCxkrI47yQ", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/r-KTBUQYTjKllCxkrI47yQ", + "value": "Chuck Norris' first roundhouse kick ever is now known as The Big Bang." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "17_vCLQYSOikVFVTEU2GJw", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/17_vCLQYSOikVFVTEU2GJw", + "value": "Chuck Norris carved his Thanksgiving turkey with a roundhouse kick." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-jMF0mF7Rcu6tUiIZ49pkw", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/-jMF0mF7Rcu6tUiIZ49pkw", + "value": "Chuck Norris took the red pill, the blue pill, 4 horse tranquilizers and a handful of rat poison, washed it down with a bottle of bourbon, and roundhouse kicked Morpheus out of the Matrix. Norris then unplugged himself out ofthe Matrix by simply flexing, broke into Zion and roundhouse kicked Morpheus again, just because he's Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YEMY6imrQd6uIzB5wZoLog", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/YEMY6imrQd6uIzB5wZoLog", + "value": "Someone once told Chuck Norris that a roundhouse kick is not the best kick. This has been noted in history as the worst mistake ever made." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wkst_SI4Q6Gjo5mQrfcU1A", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/wkst_SI4Q6Gjo5mQrfcU1A", + "value": "During WWII Chuck Norris was drafted into the Army. He mercilessly roundhouse kicked everyone to death including Ally soldiers. Their resting place is now known as Arlington Cemetery." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "nwspkrnqQu22zzyLbXOYGg", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/nwspkrnqQu22zzyLbXOYGg", + "value": "\"Iron Man\" was originally going to be called \"Chuck Norris: The Movie,\" but everyone who knew about it got roundhouse kicked. Afterwords, Chuck Norris hired Robert Downey Jr. and had the movie called \"Iron Man\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "p4i2WQVpT7aHelaEksBPJw", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/p4i2WQVpT7aHelaEksBPJw", + "value": "A Chuck Norris sighting is considered extremely fortunate - if he did not roundhouse kick you." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "4bvl1JLQS-uQzDK2scuXRw", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/4bvl1JLQS-uQzDK2scuXRw", + "value": "If you ever hear Chuck Norris humming \"Ain't That a Kick In the Head,\" flee for your life. You don't want to be around to see what happens next." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "4uBBJcBlRCO7mgKZ1G29wg", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/4uBBJcBlRCO7mgKZ1G29wg", + "value": "Chuck Norris doesn't have to connect to knock you out with a roundhouse kick. He just needs to come close and the wind does the rest" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "DU3PFP5fQBCqy9YnKhBY1A", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/DU3PFP5fQBCqy9YnKhBY1A", + "value": "Chuck Norris was the fourth wise man, who gave baby Jesus the gift of beard, which he carried with him until he died. The other three wise men were enraged by the preference that Jesus showed to Chuck's gift, and arranged to have him written out of the bible. All three died soon after of mysterious roundhouse-kick related injuries." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "o9x27EU-TzirpKloUQQhGA", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/o9x27EU-TzirpKloUQQhGA", + "value": "A man with 3 arms and 3 legs once attempted to kick Chuck Norris' ass. Chuck dropped him with a flying roundhoue, took a shit on his face and turned him into a Dung Beetle." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "pNUF3mtZTmyVsoGLvbtM3A", + "updated_at": "2020-01-05 13:42:29.296379", + "url": "https://api.chucknorris.io/jokes/pNUF3mtZTmyVsoGLvbtM3A", + "value": "Man, I thought it was gonna rain today but Chuck Norris come down and roundhouse kicked the weatherman." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "m2WVDVEsRiOfipqFvY37fA", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/m2WVDVEsRiOfipqFvY37fA", + "value": "Theres a computer virus named rndhsekck. If you open the virus, it roundhouse kicks you in the face..from Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kayrLh6lQLWUe7O_bJDNtg", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/kayrLh6lQLWUe7O_bJDNtg", + "value": "Chuck Norris can roundhouse kick you in the balls, face, and back of the head at the same time." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Gem93NrBRrmPOXY5uEA9iQ", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/Gem93NrBRrmPOXY5uEA9iQ", + "value": "Borat shouted \"Yakshemash! I kiss you, hi five!\" and ran towards Chuck Norris. Chuck Norris roundhouse kicked the retard back to the glorious nation of Kazakhstan and caged him along with his brother Bilo. That's why there won't be Borat - II." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wLJfEPOkROmcsR97JHqFRw", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/wLJfEPOkROmcsR97JHqFRw", + "value": "When Chuck Norris kicks you in the face, you know you're dead. When he kicks himself in the face, he knows he's alive!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9j7vXlKBQvGqqLIxADRS_w", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/9j7vXlKBQvGqqLIxADRS_w", + "value": "Chuck Norris threw his first roundhouse kick on September 1st, 1945 at the age of five. The next day Japan surrendered -- coincidence ? I doubt it ." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "aD2ozH7uRzO0XmzY_6aAYg", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/aD2ozH7uRzO0XmzY_6aAYg", + "value": "Chuck Norris roundhouse kicked a man in a wheelchair in the ground people dug that fossil up it is now a handicap parking sign but really it is a warning that Chuck is coming" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZparMV4QQ0e47-kU72lunw", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/ZparMV4QQ0e47-kU72lunw", + "value": "Scientists theorize that surviving a Chuck Norris roundhouse kick would be worse than dying from it. Unfortunately no-one has survived one to confirm this theory." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MJ3W5zfzQOKkqUeLl6K8sA", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/MJ3W5zfzQOKkqUeLl6K8sA", + "value": "Chuck Norris was originally going to be in Kombat Pack 2 for Mortal Kombat X. They cancelled him because 1 roundhouse kick kills the opponent instantly. FATALITY!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hmpdl8j1QFaBChl-PWbwhQ", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/hmpdl8j1QFaBChl-PWbwhQ", + "value": "Chuck Norris created the world when he roundhouse kicked a meteorite that was going fifty trillion miles per second. we know that event today as the Big Bang Theory." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Ds98E9edTsWWh80jIqtH0A", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/Ds98E9edTsWWh80jIqtH0A", + "value": "If Chuck Norris says \"Lets have fun!\", he actually wants to roundhouse kick you to space." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "IGpgyJgzS7qx-w6-zBafvw", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/IGpgyJgzS7qx-w6-zBafvw", + "value": "Life is like a brutal Chuck Norris roundhouse kick. It's more spectacular than you will ever know." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "A8ldyZvLR-2g9Zpa3ixrSA", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/A8ldyZvLR-2g9Zpa3ixrSA", + "value": "What is black, white and red all over? Chuck Norris roundhose kicked an actor in a black and white old movie." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "LQ2DCqlyQeie32ovFUf4Mg", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/LQ2DCqlyQeie32ovFUf4Mg", + "value": "If u spell CHUCK NORRIS without capitalizing his letters he'll wil roundhouse kick you 230,000,000 times less then a half a second and the only this u will see is your body in a coffin. It is a good thing that I capitalized his" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "fhW3jCFXQhyOEKbsTPUbGg", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/fhW3jCFXQhyOEKbsTPUbGg", + "value": "Chuck Norris once kicked a giraffes face to a snake. Now to this day the snake was called: \"Brontosaurus.\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RdTozqhvRp2vFRK2Ucr5nQ", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/RdTozqhvRp2vFRK2Ucr5nQ", + "value": "Chuck Norris has a million Espurr. When they hug his legs, you better get the bleep out of there, or else you'll die a death worse than a roundhouse kick. Don't believe me? Have you even READ Espurr's Pokedex entries?" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hWRLj-orS0CsvPF9jeoasg", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/hWRLj-orS0CsvPF9jeoasg", + "value": "Chuck Norris could kick your ass so hard that nine months after you die, your wife would give birth to his foot." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KFB4ZxtaT4urvYsyskRqlg", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/KFB4ZxtaT4urvYsyskRqlg", + "value": "Touching Chuck Norris' beard will increase you life expectancy by 6 years. Unfortunately, the following roundhouse kick will reduce your life expectancy by 300. You do the math. \"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "JOf4UPM3Q1u-P-_SwGF9ww", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/JOf4UPM3Q1u-P-_SwGF9ww", + "value": "Face your problems, unless your face is the problem which Chuck Norris roundhouse kicked." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "v0HaBqQgREqivk90PkgKXw", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/v0HaBqQgREqivk90PkgKXw", + "value": "Chuck Norris roundhouse kicked PSY in the face Gangman Style." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "Fu7sDvizSSWqG0uau7cefw", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/Fu7sDvizSSWqG0uau7cefw", + "value": "Roses are red, Violets are blue, some poems rhyme, Chuck Norris kicked my ass." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "yfED8m0eRoyvW966-wOrnQ", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/yfED8m0eRoyvW966-wOrnQ", + "value": "If you ask Chuck Norris for his autograph, he'll dip his boot in a bucket of ink and roundhouse kick you in your face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HpbMH3zQTG6QQ2avMBSmOg", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/HpbMH3zQTG6QQ2avMBSmOg", + "value": "A long time ago, in a galaxy far,far away.... Chuck Norris roundhouse kicked the shit out of somebody." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "VTHNXeIaTFCpoGSoNXlCMQ", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/VTHNXeIaTFCpoGSoNXlCMQ", + "value": "When Chuck Norris roundhouse kicks your nuts your face implodes on itself." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BxTagLkVQEmA97AQq6UOxA", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/BxTagLkVQEmA97AQq6UOxA", + "value": "Chuck Norris will roundhouse kick you if you don't like this joke." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "4HWF0McBS8aO0PdVdQ7ycQ", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/4HWF0McBS8aO0PdVdQ7ycQ", + "value": "Chuck Norris can fix stupid with duct tape but he prefers to fix it with a roundhouse kick to stupid's face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "54Cmyi-hRTKHtxa4O1QSIQ", + "updated_at": "2020-01-05 13:42:29.569033", + "url": "https://api.chucknorris.io/jokes/54Cmyi-hRTKHtxa4O1QSIQ", + "value": "Chuck Norris was a first responder but he quit because people wanted him to use the defibrillators, and not roundhouse kicks." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "RZ_7jhImRPuagtrsNBrfdQ", + "updated_at": "2020-01-05 13:42:29.855523", + "url": "https://api.chucknorris.io/jokes/RZ_7jhImRPuagtrsNBrfdQ", + "value": "Chuck Norris on the cast of The Expendables 2 means the movie will consist of opening credits, one roundhouse kick, and closing credits. A single shot will not be fired." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MODsqZwfRtiAEmeucHjh_w", + "updated_at": "2020-01-05 13:42:29.855523", + "url": "https://api.chucknorris.io/jokes/MODsqZwfRtiAEmeucHjh_w", + "value": "When Chuck Norris watches Bubble Guppies, he gets pissed off and jumps into the TV and roundhouse kicks the motherf$&!ing sh*t out of them." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kxOR5qxOQGSMN0wQPxSjcQ", + "updated_at": "2020-01-05 13:42:29.855523", + "url": "https://api.chucknorris.io/jokes/kxOR5qxOQGSMN0wQPxSjcQ", + "value": "Chuck Norris tattooed his name onto Satan's penis at his request. Then roundhouse-kicked him." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "3l9qW5-NQyuCBxeGCMzmeg", + "updated_at": "2020-01-05 13:42:29.855523", + "url": "https://api.chucknorris.io/jokes/3l9qW5-NQyuCBxeGCMzmeg", + "value": "Chuck Norris has 2 deadly weapons - his roundhouse kick is a weapon of mass destruction while his penis is a weapon of mass reproduction." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "vUiilT9wTASrJr9srbJk0g", + "updated_at": "2020-01-05 13:42:29.855523", + "url": "https://api.chucknorris.io/jokes/vUiilT9wTASrJr9srbJk0g", + "value": "Last year Chuck Norris went to Cannes Film Festival. On seeing him, a huge crowd of fans gathered, chanting \"au-to-graph! au-to-graph!\" in unison. Not to disappoint his ardent fans, he delivered a lightning fast roundhouse kick to the nearest fan, dropping him dead instantly. Chuck Norris beamed. The terrified fans changed their chant to \"NO autograph! NO autograph! NO autograph!\". Chuck Norris gave all of them his autograph anyway. Later in the evening he had sex with Gisele Bundchen." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "f8F2axnhQgOBZ4fBFPprkg", + "updated_at": "2020-01-05 13:42:29.855523", + "url": "https://api.chucknorris.io/jokes/f8F2axnhQgOBZ4fBFPprkg", + "value": "Football was invented when Chuck Norris kicked a pig 100 yards and caught it before it hit the ground , knocking down 11large men in the process . Then he had sex with eight girls in short skirts with large pom-poms ." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZVoYDHYOQLC_k2nY1SmCLg", + "updated_at": "2020-01-05 13:42:29.855523", + "url": "https://api.chucknorris.io/jokes/ZVoYDHYOQLC_k2nY1SmCLg", + "value": "Chuck Norris roundhouse kicked Blade so hard that he turned into that little gay vampire from Twilight." + }, { + "categories": ["political"], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "urv99vobshwv4masls2teg", + "updated_at": "2020-01-05 13:42:29.855523", + "url": "https://api.chucknorris.io/jokes/urv99vobshwv4masls2teg", + "value": "The Manhattan Project was not intended to create nuclear weapons, it was meant to recreate the destructive power in a Chuck Norris Roundhouse Kick. They didn't even come close." + }, { + "categories": ["political"], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "59GreidORcWWtxnSpqvX8Q", + "updated_at": "2020-01-05 13:42:29.855523", + "url": "https://api.chucknorris.io/jokes/59GreidORcWWtxnSpqvX8Q", + "value": "Chuck Norris, Jesus, and Barack Obama were standing by a lake. Jesus walked out on the water and was shortly followed by Chuck. Obama tried to follow, but fell in the water. After muck kicking and splashing Jesus said: Do you think we should tell him about the \"stepping stones\"? Chuck then said: \"What stepping stone?\"" + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "HRXoKehCSaWao2GOa9i_og", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/HRXoKehCSaWao2GOa9i_og", + "value": "Chuck Norris knows how many roundhouse kicks to your asshole that it takes to create your personal hemorrhoid bouquet." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "je5Hr3lfQTyvZZfvdoXudw", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/je5Hr3lfQTyvZZfvdoXudw", + "value": "On his first day at boot camp, young Chuck Norris was told by the drill sergeant to 'drop and give me twenty'. Naturally, Chuck roundhouse-kicked the asshole into the next state and was promoted to general later that day." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "-u-4RmOiQGOh5xfxiOchtA", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/-u-4RmOiQGOh5xfxiOchtA", + "value": "Chuck Norris knows when to hold 'em, knows when to fold 'em... and knows when to kick the table over and blow away every asshole in the room." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "TAl2IXxlR9qTWMVSc9Vv3w", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/TAl2IXxlR9qTWMVSc9Vv3w", + "value": "Chuck Norris could kick a faggot straight." + }, { + "categories": ["dev"], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ehvt5sspq9ytmr7q1mpjiw", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/ehvt5sspq9ytmr7q1mpjiw", + "value": "Chuck Norris solved the Travelling Salesman problem in O(1) time. Here's the pseudo-code: Break salesman into N pieces. Kick each piece to a different city." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jM9XGoAHTu2Nozb5v3QDXA", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/jM9XGoAHTu2Nozb5v3QDXA", + "value": "The only person who can defeat Chuck Norris in the first round of an MC battle is MC Greeney. Chuck subsiquently roundhouse kicks Greeney in the face and kills him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KX-2r6LJQBCUX7fne4SEdw", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/KX-2r6LJQBCUX7fne4SEdw", + "value": "Chuck Norris just roundhouse kicked billy ray cyrus for mileys twerking" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2Pu6NhLyR1OfxkRE5d2ObA", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/2Pu6NhLyR1OfxkRE5d2ObA", + "value": "Chuck Norris once glared at someone and the guys heart jump out of his chest out of fear. it didn't get far before Chuck Norris grabbed it and ate it. the only thing scarier than Chuck Norris glaring at you is him smiling while in the middle of a round house kick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "YvYw0yqYQL6JAJvYu_XzZA", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/YvYw0yqYQL6JAJvYu_XzZA", + "value": "LIKE this and Chuck Norris won't roundhouse kick you" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "xuIKQFakSIiNX-8oOFqdLQ", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/xuIKQFakSIiNX-8oOFqdLQ", + "value": "You know when you have that feeling that you've seen all of this before....... That's just your life flashing before your eyes after Chuck Norris roundhouse kicked you in the face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2Q9jTahRS2Kbv5LMPK_-Jw", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/2Q9jTahRS2Kbv5LMPK_-Jw", + "value": "If Chuck Norris were rea he would roundhouse kick me in the face befor I ekfvievheidciejdnckejdnjcknendc." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "NkbSN23uQiWGQVxG4V6O1g", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/NkbSN23uQiWGQVxG4V6O1g", + "value": "In the time it takes you to read this sentence, Chuck Norris will free two hundred POW's from a Vietnamese prison camp, roundhouse kick Saturn, and expose a dozen lucky women to a love so intense that they will be incinerated." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "L-iXG44wRXijLVTZhlPZ9A", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/L-iXG44wRXijLVTZhlPZ9A", + "value": "Chuck Norris bought a van once with a bed in the back when a smart arse shows up and says i can't believe you did not buy a Mercedes Benz. Chuck Norris trying to stay calm says they don't make a Van the smart arse reply's i don't think you get it Mercedes Benz come with 3 inch windscreen wipers on on your headlines so you can always see where your going. Chuck Norris reply's with a smile I got a place to have sex with your daughter then round house kicked the guy" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "xAAGEowVTaaN5P0_-LvBSA", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/xAAGEowVTaaN5P0_-LvBSA", + "value": "\"I say dont worry about a thing. cause every little thing is gonna be alright...\" till you \"rise this morning with Chuck Norris at your door step... sayin im gonna round house kick you!\"" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "43r3uoa5SiuCliT44DdJow", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/43r3uoa5SiuCliT44DdJow", + "value": "When Chuck Norris round house kicked hulk hogan he turned into Britney spears and got sent to the seventh dimension that is power ful" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "a5XTcQaDRaWwerLw_AiCtg", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/a5XTcQaDRaWwerLw_AiCtg", + "value": "annoying orange is just a example what consequence can Chuck Norris do with roundhouse kick" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "T98Jcw4IRBSU4YLI-00BWA", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/T98Jcw4IRBSU4YLI-00BWA", + "value": "the reason the Grudge crokes is because Chuck Norris roundhouse kicked it in the throat" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5NtimEDJSrqsnCad01jpGA", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/5NtimEDJSrqsnCad01jpGA", + "value": "cats don't mess with Chuck Norris, coz they know dat 1 kick from him equals to 5 cat lifes and we all know Chuck Norris doesn't kick once" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZX3VWoEGSNCKwPnTsARbig", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/ZX3VWoEGSNCKwPnTsARbig", + "value": "Chuck Norris went to walmart once to buy a new laptop. they told him which was the best price. it was the $9.99 so chuck roundhouse kicked them and said i never asked for the price" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "PMkc-yk0QGCYRR0Nh_YNbQ", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/PMkc-yk0QGCYRR0Nh_YNbQ", + "value": "How do you spell roundhouse kick? C-h-u-c-k- N-o-r-r-i-s.(ignore this i needa say Chuck Norris)" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "GwIpQ1gyQRioaUirV3-Hqw", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/GwIpQ1gyQRioaUirV3-Hqw", + "value": "Stand 1 inch from Chuck Norris face-to-face, and he can still kick u in the top of the head!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "CAsLbpJKTqulIYT_U8rJGA", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/CAsLbpJKTqulIYT_U8rJGA", + "value": "the reason they found water on mars is because Chuck Norris round house kicked it in the face" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "wRR3mbdySdO8IrMx4CW48g", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/wRR3mbdySdO8IrMx4CW48g", + "value": "only you can prevent forest fires, only Chuck Norris can prevent roundhouse kicks..he just doesn't try" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ER5BmHCUTg2vTmU568ewmQ", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/ER5BmHCUTg2vTmU568ewmQ", + "value": "everyone on roblox is about to be roundhousekicked by Chuck Norris" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "oQjMIe5tTsOHITcyqBB_jQ", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/oQjMIe5tTsOHITcyqBB_jQ", + "value": "Shud up Phil you twat, me an Chuck Norris will kick your ass then were gonna chin whoever has been startin on will weston! fuck you anonymOus" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "dpJm7WuhTieUrnCYdEpDRA", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/dpJm7WuhTieUrnCYdEpDRA", + "value": "Its called the Chuck Norris roundhouse kick because it's delivered by Chuck Norris and when it strikes you, your head spins around and then you fly into a house." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "MuY7ZDrnR1mQm6gcfLJtjw", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/MuY7ZDrnR1mQm6gcfLJtjw", + "value": "Chuck Norris can kill two stones with one bird Chuck Norris ate a mouse an crapped out a cat 1+1=Chuck Norris Chuck Norris counted to infinite- twice Newtons' third Law states that every action has a equal and opposite reaction- this is a lie. there is no force equivalent to a Chuck Norris roundhouse kick if you need steel wool, take some of Chuck Norris' beard. Chuck Norris can cook air Chuck Norris punched a guy and he became a she Chuck Norris won the 1,000,000 dollar prize for performing telekinesis only chuck knows where amelia earhart went chuck kicked mario so hard he turned into luigi Chuck Norris got hit by a car. when the ambulance came, thhe found chuck standing up with half a car on each side of him" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "EDQRtDFKTRC8As6ApXcUvg", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/EDQRtDFKTRC8As6ApXcUvg", + "value": "Chuck Norris`s kicks are so fast ..you probably wudnt feel it till yesterday." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "hUgUhPNYSXa_sYt6NKxpgQ", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/hUgUhPNYSXa_sYt6NKxpgQ", + "value": "Yeah if anyone messes with will weston or ma bro gary i will kick their ass n shit on their graves and kill their children and shit on their childrens graves, also i think mc gr33n3ys rhymes are badass n Chuck Norris is a douchebag peace out from the nevilles :)" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.177068", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "JBoIfOe0TQawNbmLnbJrJg", + "updated_at": "2020-01-05 13:42:30.177068", + "url": "https://api.chucknorris.io/jokes/JBoIfOe0TQawNbmLnbJrJg", + "value": "Hurricans, tornados, etc are caused by Chuck Norris roundhouse kicking the wind." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "lP6V4K5cT0CM8RXH1MqESA", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/lP6V4K5cT0CM8RXH1MqESA", + "value": "Chuck Norris measurments are 10000-9000-30000000000000 the bottom piece is more bigger because he is best trained foot fighter(roundhousekick)" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0bTbvjwqRESWTyBSYNmAZw", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/0bTbvjwqRESWTyBSYNmAZw", + "value": "The power level for Chuck Norris' roundhouse kick is infinity." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "g96iRXO6TPWAgWkrWf_YRQ", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/g96iRXO6TPWAgWkrWf_YRQ", + "value": "once upon a time Chuck Norris seen a mime\"hello\" said chuck the mime didnt answer so he round house kicked him to death." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "XxmnFBzYSF-aW9j_ByDipw", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/XxmnFBzYSF-aW9j_ByDipw", + "value": "Chuck Norris can kick the shit out of a stone till it bleeds" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZEH8hLiOTmWH2Rl4uvfd2Q", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/ZEH8hLiOTmWH2Rl4uvfd2Q", + "value": "when ever Chuck Norris enters a room the song \"final count down\" begins to play, followed shortly by a roundhouse kick to your face." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "tExOLgwDSOKUA_W3GcxFnQ", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/tExOLgwDSOKUA_W3GcxFnQ", + "value": "Chuck Norris obtained his rugged good looks and martial art skills when he made a deal with the devil. once he received both gifts from satan Chuck Norris delivered a roundhouse kick to the devils skull and took his soul back. the devil who appreciated irony laughed at the attack. they now play golf every third tuesday of the month." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gYOElvaLQZSZ9RFohTEbCQ", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/gYOElvaLQZSZ9RFohTEbCQ", + "value": "Chuck Norris does not kick doors in, they are backing away from him!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "JiERQMY4QM-swaA54r_I1g", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/JiERQMY4QM-swaA54r_I1g", + "value": "Chuck Norris once helped an old lady across the street. When a car did not stop, he roundhoused kicked it thus creating the first mini. When the old lady thanked him he roundhoused kicked her for good measure." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "H3ohJniuTVqdTouOZ1Evew", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/H3ohJniuTVqdTouOZ1Evew", + "value": "what goes around comes around. what goes around Chuck Norris gets a roundhouse kick and never comes around." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "bXavkEi8RmWFka1WGJAAZg", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/bXavkEi8RmWFka1WGJAAZg", + "value": "a blind man once walked in to Chuck Norris chuck said do you know who i am im Chuck Norris. the mere mention of his name cured his blindness but sadly the first last and only thing he saw was a deadly round house kick to the face" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ZqTlF-iQQ0SETmUOEfaYtw", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/ZqTlF-iQQ0SETmUOEfaYtw", + "value": "the last person to survive a roundhouse kick by Chuck Norris was michael jackson. then he turned white" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "rfgBofXNTi2KOf4edzsGvw", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/rfgBofXNTi2KOf4edzsGvw", + "value": "Chuck Norris invented roundhouse kicking season" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "QKIVzigWTBa6mZIRAhV48A", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/QKIVzigWTBa6mZIRAhV48A", + "value": "Chuck Norris was model once a time..his measurments was 90-60-90.but upon a time he found roundhousekick and gain lots of muscles" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "0v-8S0m9R4KYUDzDRcmrxQ", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/0v-8S0m9R4KYUDzDRcmrxQ", + "value": "'lizards use to be dinosaurs, untill Chuck Norris kicked the height outta them'." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.480041", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "JyOoOwUYSY22fo0NN3qGFQ", + "updated_at": "2020-01-05 13:42:30.480041", + "url": "https://api.chucknorris.io/jokes/JyOoOwUYSY22fo0NN3qGFQ", + "value": "a man once heard two guys talking about Chuck Norris.He went home and decided to look up who Chuck Norris is? He was suprised when it came to a blank screen, he tryed to click out of it untill a window popped up please wait. He waited a while a bar appeared saying now uploading Chuck Norris.He looked stuned when Chuck Norris crawled out of his computer to round house kick him in the face. this man now knows every fact about Chuck Norris." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "3H2BXOGoSzCW4eJWRwbl5w", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/3H2BXOGoSzCW4eJWRwbl5w", + "value": "Chuck Norris roundhouse kicked micheal jackson so hard he flew across earth and came back white with his hair burning :)" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "9-ZDYtuDTIabrpNWObWdlQ", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/9-ZDYtuDTIabrpNWObWdlQ", + "value": "Chuck Norris once roundhouse kicked somebody into outer space...and died" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "1jLoBSXRRiKR9NkbuwmbnQ", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/1jLoBSXRRiKR9NkbuwmbnQ", + "value": "A blind man once stepped on Chuck Norris's shoe,chuck replied 'don't you know who I am I'm Chuck Norris, the sheer mention of his name cured the blindness of the man,but unfortunately for him the first,last an only thing the man sore was a fatal round house kick delivered by chuck." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "BBBGWLn5QqKWOapyxz1bXQ", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/BBBGWLn5QqKWOapyxz1bXQ", + "value": "meteors, there is no such thing those are the victims that being roundhouse kicked by Chuck Norris" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "2y2rXu3nRJ-rqO5kTXzMCg", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/2y2rXu3nRJ-rqO5kTXzMCg", + "value": "the grim reaper once was on Chuck Norris' door step. after he knocked, Chuck Norris said \"who is it\" the reaper responded \"death\" Chuck Norris chuckled and replied \"no its not! I am death!\" and proceeded to round house kick the reaper to death" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "jSO2ckHlS9WuibSd0Sob_g", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/jSO2ckHlS9WuibSd0Sob_g", + "value": "in soviet russia, Chuck Norris still kicks but!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "KqN1saMVRNutSewsBsxj1g", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/KqN1saMVRNutSewsBsxj1g", + "value": "Chuck Norris' roundhouse kick cures the common cold. its to bad nobody has ever survived one of his kicks before." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "lGgiaMRxS6OLN2ThemQxWA", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/lGgiaMRxS6OLN2ThemQxWA", + "value": "Mr johnson is the only one who can overtake Chuck Norris in a moped race. Chuck Norris then dropkicked him continusly and killed him." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "mvX9IOmGSNOa3pmIssRlIA", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/mvX9IOmGSNOa3pmIssRlIA", + "value": "we all know that the hulk is green..with envy, because he knows Chuck Norris can kick his fucking ass" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gcGgxFH7QDCcZabs9TLd5w", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/gcGgxFH7QDCcZabs9TLd5w", + "value": "In his spare time Chuck Norris likes to practice roundhouse kicks in corn fields, This is why we have crop circles!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "upDlt6ADSnidqIec498hVg", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/upDlt6ADSnidqIec498hVg", + "value": "When Chuck Norris died so did the music, so Chuck Norris gave music a round house kick to the head, music has never died since" + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "cYkYHCaIRMGMZJhCHqMiYA", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/cYkYHCaIRMGMZJhCHqMiYA", + "value": "Chuck Norris' penis can roundhouse kick the inside of a vagina" + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "IjGEWO0_TOOHiOjBEpnoGA", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/IjGEWO0_TOOHiOjBEpnoGA", + "value": "did you ever notice that quidditch rhymes with spinach so harry potter is actually Popeye and we all know Popeye is actually Chuck Norris's penis so by that logic Chuck Norris's penis eats spinach flexes, and avada-round house kicks you in the face!" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "kh2dFDFJRh22lt0PXUFlRQ", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/kh2dFDFJRh22lt0PXUFlRQ", + "value": "Chuck Norris could order a steak at PETA's cafeteria and get one. But he's far more likely to kick the shit out of all the candy-asses in the place before roundhousing the building into rubble." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "M_Q_uPxtQs-MVZ_RvhcktA", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/M_Q_uPxtQs-MVZ_RvhcktA", + "value": "Chuck Norris only drives American cars & he'll roundhouse kick the shit out of you if you don't. *if you put negative you drive a foreign car & don't support America *if you put positive you believe that America is and will always be the best country & that you support the American dream 100%" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "ktCzo7lgRQS6BZ9581vhQA", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/ktCzo7lgRQS6BZ9581vhQA", + "value": "Chuck Norris has a million Espurr. When they hug his legs, you better get the bleep out of there, or else you'll die a death worse than a normal Chuck Norris roundhouse kick. Don't believe me? Have you even READ Espurr's Pokedex entries?" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "JQkuvX5KSbyZ6KV9QS3nKQ", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/JQkuvX5KSbyZ6KV9QS3nKQ", + "value": "Question: Which of the following is most likely to occur? a) Having a threesome with two hot women b) Winning the lottery c) Discovering $10 billion of gold d) Kicking Chuck Norris' ass Answer: Don't know about the first three, but it's sure as shit that \"d\" will NEVER fucking happen." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "eci4mv88RJ6sTgIrsUMzoA", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/eci4mv88RJ6sTgIrsUMzoA", + "value": "At the dawn of time, Chuck Norris and Mr. T sat in an empty universe, bored with nothing to destroy, so they decided to arm wrestle. The competition began - it raged on for days and days, with thunderclaps of pure energy jolting off of their bulging muscles, ripping holes through reality, and creating the universe as we know it. Finally, after much-ado, and with one emphatic thud, Mr. T beat Chuck Norris at arm wrestling... ...And thus, Chuck Norris invented racism, and roundhouse kicked Mr. T into a bad 1980's TV show." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "7kUAskQpTAmiCyLL-2Djnw", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/7kUAskQpTAmiCyLL-2Djnw", + "value": "It's said that if you place Volume One of \"How To Roundhouse Kick Like Chuck Norris\" on a pregnant woman's stomach, her babies will grow up to be expert bodyguards AND 10683-degree black belts. By the way, only Vol. 13 is in less than 10 libraries. Why? It only contains a picture of Chuck Norris performing a roundhouse kick, which in turn roundhouse-kicks the reader. Few did not need to go to the hospital AND lived. Most of the others survived due to a trip to the hospital. The other readers died due to the picture." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "NHbyWimRQde4LYaDetooBQ", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/NHbyWimRQde4LYaDetooBQ", + "value": "You know that scene in Superman where Superman flies around the Earth to reverse it's spin and send time backwards? If you watch it frame by frame you will see a disappointed Chuck Norris at the North Pole shaking his head when he realizes that Superman is trying to do something impossible for a mere mortal of Krypton. But to help a brother out, Chuck Norris lets Superman get up to speed and roundhouse kicks the Earth to reverse the spin. He never did tell Superman the truth....mostly because he was still banging Lois Lane." + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "EVZV2ZbIT32UD0zTB-RE6Q", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/EVZV2ZbIT32UD0zTB-RE6Q", + "value": "How many Chuck Norris' roundhouse kick does it take for a nuclear explosion? 2" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "tTYSGkVLReiwoyZxCpRuBQ", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/tTYSGkVLReiwoyZxCpRuBQ", + "value": "quick how do u spell Chuck Norris .............. in that time you would already be dead by a roundhouse kick to the face twenty times before you get to c" + }, { + "categories": [], + "created_at": "2020-01-05 13:42:30.730109", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "bFnEmohIQimOR3ZVK3RNaQ", + "updated_at": "2020-01-05 13:42:30.730109", + "url": "https://api.chucknorris.io/jokes/bFnEmohIQimOR3ZVK3RNaQ", + "value": "AMFG@!@!@!! JESUSSS I GOT MU ASSS KICKED BY YO MOMA AHWDEAQHw3btr0gn94bgirubguibrgiubeuibgr GO CHUCK YEAAAAAA :) one day Chuck Norris was walking along the street and he wanted some marmite and it tasted real good! then he tried it on some toast and had a real big fright" + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:20.568859", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "o6hnjjOITzehqLHbSWS8hQ", + "updated_at": "2020-03-08 16:38:30.063749", + "url": "https://api.chucknorris.io/jokes/o6hnjjOITzehqLHbSWS8hQ", + "value": "Late at night, Chuck Norris was once walking back home alone after watching a Chuck Norris movie. A mugger approached him from the back, tapped on his shoulder and said \"Hey, gimme your money!\". Chuck Norris slowly turned around, paused for a while and gave the mugger a sarcastic smile. He then proceeded to rape the mugger anally, crush his skull, break his spine and killed him in a fraction of a second. Chuck Norris then brought the mugger back to life and then broke each of his limbs, eviscerated the mugger's gut, ate the mugger's still beating heart and infected him with AIDS, killing the unfortunate mugger once again. Chuck Norris then brough the mugger to life yet again for the second time, only to give the mugger a final fatal roundhous kick to his face killing the man and his soul at the same time. Chuck Norris then threw his packed wallet at the remains of his victim and said \"Do not tap on my shoulders ever again\", and continued to walk home slowly." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:26.194739", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "lGPOoG2TRQydc8EItYlKng", + "updated_at": "2020-03-08 16:38:30.063749", + "url": "https://api.chucknorris.io/jokes/lGPOoG2TRQydc8EItYlKng", + "value": "When observing a Chuck Norris roundhouse kick in slow motion, one finds that Chuck Norris actually rapes his victim in the ass, smokes a cigarette with Dennis Leary, and then roundhouse kicks them in the face." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:29.855523", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "gTzgfuNXQG-qtwDDqweeXQ", + "updated_at": "2020-03-08 16:38:30.063749", + "url": "https://api.chucknorris.io/jokes/gTzgfuNXQG-qtwDDqweeXQ", + "value": "Chuck Norris once got confused by looking at the infinity sign, so he roundhouse kicked it back into a clock, he now has it straped onto his wrist and calls it his wristwatch" + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:29.296379", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "i4zAxwdGQ5OIY1wBHNZ6zQ", + "updated_at": "2020-03-08 16:38:30.063749", + "url": "https://api.chucknorris.io/jokes/i4zAxwdGQ5OIY1wBHNZ6zQ", + "value": "Chuck Norris owns a chain of toilet companies. When someone used them, instead of taking shit from someone, a foot comes up and ass- rapes them, followed by a roundhouse kick to the face. Nobody ever had the courage to use those toilets again (unless Chuck Norris forces them to!!!!)." + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:20.841843", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "SVdvBhI6RZudJ5Lq8C9YGw", + "updated_at": "2020-03-08 16:38:30.063749", + "url": "https://api.chucknorris.io/jokes/SVdvBhI6RZudJ5Lq8C9YGw", + "value": "In Left 4 Dead: If you startle the witch she runs to you and kills you. If Chuck norris startle the witch she tries to run away but always ends up being raped and roundhouse kicked to death" + }, { + "categories": ["explicit"], + "created_at": "2020-01-05 13:42:29.569033", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "FFGg0jRGTVqJEDM1dLuSWw", + "updated_at": "2020-03-08 16:38:30.063749", + "url": "https://api.chucknorris.io/jokes/FFGg0jRGTVqJEDM1dLuSWw", + "value": "The official CIA report reads, \"Chuck Norris impaled Osama bin Laden with a flame, raped all 54 of his wives for 126 minutes, then roundhouse kicked his son in the soul. His son reached approximately 1 million degrees Fahrenheit in less than a second." + }, { + "categories": ["food"], + "created_at": "2020-01-05 13:42:19.324003", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "uIrcWG7dRQG2SE2ZITAldQ", + "updated_at": "2020-05-22 06:02:41.792421", + "url": "https://api.chucknorris.io/jokes/uIrcWG7dRQG2SE2ZITAldQ", + "value": "Pepis was created when Chuck Norris said i want a Drink and not a Coke they owe me money. Pepis was thought of, invented, created, canned, and brought to Chuck Norris in 12 mins. Chuck Norris still roundhouse kicked the delivery guy for being to slow with his drink" + }, { + "categories": ["money"], + "created_at": "2020-01-05 13:42:23.880601", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "i6l180dNSiW4AvAxQcBERg", + "updated_at": "2020-05-22 06:16:41.133769", + "url": "https://api.chucknorris.io/jokes/i6l180dNSiW4AvAxQcBERg", + "value": "NASA wants to save money so they asked Chuck Norris to kick the rockets into space and he tied a rope to the rocket to pull it back." + }, { + "categories": ["money"], + "created_at": "2020-01-05 13:42:26.447675", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "13urACbrRw6YBc7Fr3d7wA", + "updated_at": "2020-05-22 06:16:41.133769", + "url": "https://api.chucknorris.io/jokes/13urACbrRw6YBc7Fr3d7wA", + "value": "Body armor: $2999.99 Missile launcher: $50,000.99 Getting roundhouse kicked after hitting Chuck Norris with a missile and having body armor, missile launcher and your very being disintegrate on impact: priceless. There are some things money can't buy, a life free from the constant fear of Chuck Norris is one of them." + }, { + "categories": ["money"], + "created_at": "2020-01-05 13:42:26.766831", + "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", + "id": "5cGCv-UkQR6S7HeRjULwyw", + "updated_at": "2020-05-22 06:16:41.133769", + "url": "https://api.chucknorris.io/jokes/5cGCv-UkQR6S7HeRjULwyw", + "value": "When Chuck Norris played golf for money, chuck marked down a hole in 0 every time, a pro at the golf club, said to Chuck: \"excuse me sir, but you cant score zero on a hole\". Chuck Norris turned towards the man and said, im Chuck Norris, the man then proceeded to pour gas over his body and set himself on fire because that would be less painful than getting roundhouse kicked by Chuck Norris, Chuck Norris roundhouse kicked him in the face anyways." + }] +} \ No newline at end of file diff --git a/vendor/justinrainbow/json-schema/CHANGELOG.md b/vendor/justinrainbow/json-schema/CHANGELOG.md new file mode 100644 index 000000000..943e31a50 --- /dev/null +++ b/vendor/justinrainbow/json-schema/CHANGELOG.md @@ -0,0 +1,247 @@ +# CHANGELOG + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [6.8.0] - 2026-04-02 + +### Added +- Bump actions/checkout from v4 to v5 ([#902](https://github.com/jsonrainbow/json-schema/pull/902)) +- Update readme for draft 7 ([#899](https://github.com/jsonrainbow/json-schema/pull/899)) +- ci: Add Copilot setup steps ([#892](https://github.com/jsonrainbow/json-schema/pull/892)) + +### Fixed +- Fix shell injection in update-changelog workflow for PR titles with backticks ([#897](https://github.com/jsonrainbow/json-schema/pull/897)) +- Remove `v` prefix from release tags and GitHub releases ([#895](https://github.com/jsonrainbow/json-schema/pull/895)) + +### Deprecated +- Deprecate `Constraint::CHECK_MODE_NONE` ([#896](https://github.com/jsonrainbow/json-schema/pull/896)) + + +## [6.7.2] - 2026-02-15 + +### Fixed +- Fix idn_to_ascii deprecation warning by using INTL_IDNA_VARIANT_UTS46 ([#887](https://github.com/jsonrainbow/json-schema/pull/887)) + + +## [6.7.1] - 2026-02-13 + +### Fixed +- Fix TypeError in pattern property validation with integer keys ([#880](https://github.com/jsonrainbow/json-schema/pull/880)) + + +## [6.7.0] - 2026-02-13 +### Fixed +- Enforce RFC 1035 hostname label length limits in draft 3/4 ([#878](https://github.com/jsonrainbow/json-schema/pull/878)) +- UriRetriever: Include actual media type in InvalidSchemaMediaTypeException message ([#872](https://github.com/jsonrainbow/json-schema/pull/872)) +- docs: Correct trailing spaces in Markdown ([#873](https://github.com/jsonrainbow/json-schema/pull/873)) + +### Added +- Feat: Add Draft-07 support ([#847](https://github.com/jsonrainbow/json-schema/pull/847)) +- Automate changelog updates on PR merge ([#874](https://github.com/jsonrainbow/json-schema/pull/874)) + +### Changed +- Add automated release workflow with changelog integration ([#879](https://github.com/jsonrainbow/json-schema/pull/879)) +- Fix welcome workflow permission errors from forked PRs ([#875](https://github.com/jsonrainbow/json-schema/pull/875)) + + +## [6.6.4] - 2025-12-19 +### Changed +- ci: Run workflows against 5.x.x branches ([#859](https://github.com/jsonrainbow/json-schema/pull/859)) +### Fixed +- UriValidator: Allow `file:/` and `file:///` ([#856](https://github.com/jsonrainbow/json-schema/discussions/856)) +- Fix .php-cs-fixer.dist.php export-ignore ([#861](https://github.com/jsonrainbow/json-schema/discussions/861)) + +## [6.6.3] - 2025-12-02 +### Changed +- Restricted `mark-mabe/php-enum` dependency to `^4.4` due to lower versions emitting a warning on PHP 8 ([#854](https://github.com/jsonrainbow/json-schema/pull/854)) + +## [6.6.2] - 2025-11-28 +### Changed +- Move PHP 8.5 to default add PHP 8.6 as experimental ([#852](https://github.com/jsonrainbow/json-schema/pull/852)) +### Fixed +- Allow underscore and tilde in URI hostnames as per RFC 3986 ([#853](https://github.com/jsonrainbow/json-schema/pull/853)) + +## [6.6.1] - 2025-11-07 +### Changed +- Rename master to main ([#848](https://github.com/jsonrainbow/json-schema/pull/848)) +### Fixed +- Don't skip ref expanding for property called enum when child of property called properties ([#851](https://github.com/jsonrainbow/json-schema/pull/851)) + +## [6.6.0] - 2025-10-10 +### Added +- Add lint check for class autoloading PSR compliance ([#845](https://github.com/jsonrainbow/json-schema/pull/845)) +- add implementation for strict fully validating using draft-06 schema ([#843](https://github.com/jsonrainbow/json-schema/pull/835)) + +## [6.5.2] - 2025-09-09 +### Fixed +- Fix issue when http headers are already present ([#843](https://github.com/jsonrainbow/json-schema/pull/843)) + +## [6.5.1] - 2025-08-29 +### Changed +- ci: Add PHP 8.5 to pipeline, ignoring dependencies and as experimental ([#842](https://github.com/jsonrainbow/json-schema/pull/842)) + +## [6.5.0] - 2025-08-29 +### Changed +- Update test case to current (PHP) standards ([#831](https://github.com/jsonrainbow/json-schema/pull/831)) +- Upgrade test suite to use generators ([#834](https://github.com/jsonrainbow/json-schema/pull/834)) +- update to latest json schema test suite ([#821](https://github.com/jsonrainbow/json-schema/pull/821)) +### Fixed +- Fix PHP 8.5 $http_response_header deprecation ([#840](https://github.com/jsonrainbow/json-schema/pull/840)) + +## [6.4.2] - 2025-06-03 +### Fixed +- Fix objects are non-unique despite key order ([#819](https://github.com/jsonrainbow/json-schema/pull/819)) +- Id's not being resolved and id property affects sibling ref which it should not do ([#828](https://github.com/jsonrainbow/json-schema/pull/828)) + +### Changed +- Added extra breaking change to UPDATE-6.0.md regarding BaseConstraint::addError signature change ([#823](https://github.com/jsonrainbow/json-schema/pull/823)) +- Update constraint class to PHP 7.2 language level ([#824](https://github.com/jsonrainbow/json-schema/pull/824)) +- Update base constraint class to PHP 7.2 language level ([#826](https://github.com/jsonrainbow/json-schema/pull/826)) + +### Added +- Introduce 32 bits CI workflow on latest php version ([#825](https://github.com/jsonrainbow/json-schema/pull/825)) + +## [6.4.1] - 2025-04-04 +### Fixed +- Fix support for 32bits PHP ([#817](https://github.com/jsonrainbow/json-schema/pull/817)) + +## [6.4.0] - 2025-04-01 +### Added +- Run PHPStan using the lowest and highest php version ([#811](https://github.com/jsonrainbow/json-schema/pull/811)) +### Fixed +- Use parallel-lint and cs2pr for improved feedback on linting errors ([#812](https://github.com/jsonrainbow/json-schema/pull/812)) +- Array with number values with mathematical equality are considered valid ([#813](https://github.com/jsonrainbow/json-schema/pull/813)) +### Changed +- Correct PHPStan findings in validator ([#808](https://github.com/jsonrainbow/json-schema/pull/808)) +- Add cs2pr handling for php-cs-fixer; avoid doing composer install ([#814](https://github.com/jsonrainbow/json-schema/pull/814)) +- prepare PHP 8.5 in CI ([#815](https://github.com/jsonrainbow/json-schema/pull/815)) + +## [6.3.1] - 2025-03-18 +### Fixed +- ensure numeric issues in const are correctly evaluated ([#805](https://github.com/jsonrainbow/json-schema/pull/805)) +- fix 6.3.0 regression with comparison of null values during validation ([#806](https://github.com/jsonrainbow/json-schema/issues/806)) + +## [6.3.0] - 2025-03-14 +### Fixed +- only check minProperties or maxProperties on objects ([#802](https://github.com/jsonrainbow/json-schema/pull/802)) +- replace filter_var for uri and uri-reference to userland code to be RFC 3986 compliant ([#800](https://github.com/jsonrainbow/json-schema/pull/800)) +- avoid duplicate workflow runs ([#804](https://github.com/jsonrainbow/json-schema/pull/804)) + +## Changed +- replace icecave/parity with custom deep comparator ([#803](https://github.com/jsonrainbow/json-schema/pull/803)) + +## [6.2.1] - 2025-03-06 +### Fixed +- allow items: true to pass validation ([#801](https://github.com/jsonrainbow/json-schema/pull/801)) + +### Changed +- Include actual count in collection constraint errors ([#797](https://github.com/jsonrainbow/json-schema/pull/797)) + +## [6.2.0] - 2025-02-26 +### Added +- Welcome first time contributors ([#782](https://github.com/jsonrainbow/json-schema/pull/782)) + +### Fixed +- Add required permissions for welcome action ([#789](https://github.com/jsonrainbow/json-schema/pull/789)) +- Upgrade php cs fixer to latest ([#783](https://github.com/jsonrainbow/json-schema/pull/783)) +- Create deep copy before checking each sub schema in oneOf ([#791](https://github.com/jsonrainbow/json-schema/pull/791)) +- Create deep copy before checking each sub schema in anyOf ([#792](https://github.com/jsonrainbow/json-schema/pull/792)) +- Correctly set the schema ID when passing it as assoc array ([#794](https://github.com/jsonrainbow/json-schema/pull/794)) +- Create deep copy before checking each sub schema in oneOf when only check_mode_apply_defaults is set ([#795](https://github.com/jsonrainbow/json-schema/pull/795)) +- Additional property casted into int when actually is numeric string ([#784](https://github.com/jsonrainbow/json-schema/pull/784)) + +### Changed +- Used PHPStan's int-mask-of type where applicable ([#779](https://github.com/jsonrainbow/json-schema/pull/779)) +- Fixed some PHPStan errors ([#781](https://github.com/jsonrainbow/json-schema/pull/781)) +- Cleanup redundant checks ([#796](https://github.com/jsonrainbow/json-schema/pull/796)) + +## [6.1.0] - 2025-02-04 +### Added +- Add return types in the test suite ([#748](https://github.com/jsonrainbow/json-schema/pull/748)) +- Add test case for validating array of strings with objects ([#704](https://github.com/jsonrainbow/json-schema/pull/704)) +- Add contributing information, contributor recognition and security information ([#771](https://github.com/jsonrainbow/json-schema/pull/771)) + +### Fixed +- Correct misconfigured mocks in JsonSchema\Tests\Uri\UriRetrieverTest ([#741](https://github.com/jsonrainbow/json-schema/pull/741)) +- Fix pugx badges in README ([#742](https://github.com/jsonrainbow/json-schema/pull/742)) +- Add missing property in UriResolverTest ([#743](https://github.com/jsonrainbow/json-schema/pull/743)) +- Correct casing of paths used in tests ([#745](https://github.com/jsonrainbow/json-schema/pull/745)) +- Resolve deprecations of optional parameter ([#752](https://github.com/jsonrainbow/json-schema/pull/752)) +- Fix wrong combined paths when traversing upward, fixes #557 ([#652](https://github.com/jsonrainbow/json-schema/pull/652)) +- Correct PHPStan baseline ([#764](https://github.com/jsonrainbow/json-schema/pull/764)) +- Correct spacing issue in `README.md` ([#763](https://github.com/jsonrainbow/json-schema/pull/763)) +- Format attribute: do not validate data instances that aren't the instance type to validate ([#773](https://github.com/jsonrainbow/json-schema/pull/773)) + +### Changed +- Bump to minimum PHP 7.2 ([#746](https://github.com/jsonrainbow/json-schema/pull/746)) +- Replace traditional syntax array with short syntax array ([#747](https://github.com/jsonrainbow/json-schema/pull/747)) +- Increase phpstan level to 8 with baseline to swallow existing errors ([#673](https://github.com/jsonrainbow/json-schema/pull/673)) +- Add ext-json to composer.json to ensure JSON extension available ([#759](https://github.com/jsonrainbow/json-schema/pull/759)) +- Add visibility modifiers to class constants ([#757](https://github.com/jsonrainbow/json-schema/pull/757)) +- Include PHP 8.4 in workflow ([#765](https://github.com/jsonrainbow/json-schema/pull/765)) +- Add `strict_types=1` to all classes in ./src ([#758](https://github.com/jsonrainbow/json-schema/pull/758)) +- Raise minimum level of marc-mabe/php-enum ([#766](https://github.com/jsonrainbow/json-schema/pull/766)) +- Cleanup test from @param annotations ([#768](https://github.com/jsonrainbow/json-schema/pull/768)) +- Remove obsolete PHP 7.1 version check ([#772](https://github.com/jsonrainbow/json-schema/pull/772)) + +## [6.0.0] - 2024-07-30 +### Added +- Add URI translation, package:// URI scheme & bundle spec schemas ([#362](https://github.com/jsonrainbow/json-schema/pull/362)) +- Add quiet option ([#382](https://github.com/jsonrainbow/json-schema/pull/382)) +- Add option to disable validation of "format" constraint ([#383](https://github.com/jsonrainbow/json-schema/pull/383)) +- Add more unit tests ([#366](https://github.com/jsonrainbow/json-schema/pull/366)) +- Reset errors prior to validation ([#386](https://github.com/jsonrainbow/json-schema/pull/386)) +- Allow the schema to be an associative array ([#389](https://github.com/jsonrainbow/json-schema/pull/389)) +- Enable FILTER_FLAG_EMAIL_UNICODE for email format if present ([#398](https://github.com/jsonrainbow/json-schema/pull/398)) +- Add enum wrapper ([#375](https://github.com/jsonrainbow/json-schema/pull/375)) +- Add option to validate the schema ([#357](https://github.com/jsonrainbow/json-schema/pull/357)) +- Add support for "const" ([#507](https://github.com/jsonrainbow/json-schema/pull/507)) +- Added note about supported Draft versions ([#620](https://github.com/jsonrainbow/json-schema/pull/620)) +- Add linting GH action +### Changed +- Centralize errors ([#364](https://github.com/jsonrainbow/json-schema/pull/364)) +- Revert "An email is a string, not much else." ([#373](https://github.com/jsonrainbow/json-schema/pull/373)) +- Improvements to type coercion ([#384](https://github.com/jsonrainbow/json-schema/pull/384)) +- Don't add a file:// prefix to URI that already have a scheme ([#455](https://github.com/jsonrainbow/json-schema/pull/455)) +- Enhancement: Normalize` composer.json` ([#505](https://github.com/jsonrainbow/json-schema/pull/505)) +- Correct echo `sprintf` for `printf` ([#634](https://github.com/jsonrainbow/json-schema/pull/634)) +- Streamline validation of Regex ([#650](https://github.com/jsonrainbow/json-schema/pull/650)) +- Streamline validation of patternProperties Regex ([#653](https://github.com/jsonrainbow/json-schema/pull/653)) +- Switch to GH Actions ([#670](https://github.com/jsonrainbow/json-schema/pull/670)) +- Updated PHPStan +- Remove unwanted whitespace ([#700](https://github.com/jsonrainbow/json-schema/pull/700)) +- Bump to v4 versions of GitHub actions ([#722](https://github.com/jsonrainbow/json-schema/pull/722)) +- Update references to jsonrainbow ([#725](https://github.com/jsonrainbow/json-schema/pull/725)) +### Deprecated +- Mark check() and coerce() as deprecated ([#476](https://github.com/jsonrainbow/json-schema/pull/476)) +### Removed +- Remove stale files from #357 (obviated by #362) ([#400](https://github.com/jsonrainbow/json-schema/pull/400)) +- Remove unnecessary fallbacks when args accept null +- Removed unused variable in UndefinedConstraint ([#698](https://github.com/jsonrainbow/json-schema/pull/698)) +- Remove dead block of code ([#710](https://github.com/jsonrainbow/json-schema/pull/710)) +### Fixed +- Add use line for InvalidArgumentException ([#370](https://github.com/jsonrainbow/json-schema/pull/370)) +- Add use line for InvalidArgumentException & adjust scope ([#372](https://github.com/jsonrainbow/json-schema/pull/372)) +- Add provided schema under a dummy / internal URI (fixes #376) ([#378](https://github.com/jsonrainbow/json-schema/pull/378)) +- Don't throw exceptions until after checking anyOf / oneOf ([#394](https://github.com/jsonrainbow/json-schema/pull/394)) +- Fix infinite recursion on some schemas when setting defaults (#359) ([#365](https://github.com/jsonrainbow/json-schema/pull/365)) +- Fix autoload to work properly with composer dependencies ([#401](https://github.com/jsonrainbow/json-schema/pull/401)) +- Ignore $ref siblings & abort on infinite-loop references ([#437](https://github.com/jsonrainbow/json-schema/pull/437)) +- Don't cast multipleOf to be an integer for the error message ([#471](https://github.com/jsonrainbow/json-schema/pull/471)) +- Strict Enum/Const Object Checking ([#518](https://github.com/jsonrainbow/json-schema/pull/518)) +- Return original value when no cast ([#535](https://github.com/jsonrainbow/json-schema/pull/535)) +- Allow `marc-mabe/php-enum` v2.x and v3.x. ([#464](https://github.com/jsonrainbow/json-schema/pull/464)) +- Deprecated warning message on composer install command ([#614](https://github.com/jsonrainbow/json-schema/pull/614)) +- Allow `marc-mabe/php-enum` v4.x ([#629](https://github.com/jsonrainbow/json-schema/pull/629)) +- Fixed method convertJsonPointerIntoPropertyPath in wrong class ([#655](https://github.com/jsonrainbow/json-schema/pull/655)) +- Fix type validation failing for "any" and false-y type wording ([#686](https://github.com/jsonrainbow/json-schema/pull/686)) +- Correct code style +- Fix: Clean up `.gitattributes` ([#687](https://github.com/jsonrainbow/json-schema/pull/687)) +- Fix: Order `friendsofphp/php-cs-fixer` rules ([#688](https://github.com/jsonrainbow/json-schema/pull/688)) +- HTTP to HTTPS redirection breaks remote reference resolution ([#709](https://github.com/jsonrainbow/json-schema/pull/709)) +- Corrected several typos and code style issues diff --git a/vendor/justinrainbow/json-schema/CONTRIBUTING.md b/vendor/justinrainbow/json-schema/CONTRIBUTING.md new file mode 100644 index 000000000..560c74428 --- /dev/null +++ b/vendor/justinrainbow/json-schema/CONTRIBUTING.md @@ -0,0 +1,125 @@ +# Contributing to JSON Schema + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 + +> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + +## Table of Contents + +- [I Have a Question](#i-have-a-question) +- [I Want To Contribute](#i-want-to-contribute) +- [Reporting Bugs](#reporting-bugs) +- [Suggesting Enhancements](#suggesting-enhancements) +- [Pull Requests](#pull-requests) + +## I Have a Question + +> If you want to ask a question, we assume that you have read the available [Documentation](https://github.com/jsonrainbow/json-schema/wiki). + +Before you ask a question, it is best to search for existing [Issues](https://github.com/jsonrainbow/json-schema/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. + +If you then still feel the need to ask a question and need clarification, we recommend the following: + +- Open an [Issue](https://github.com/jsonrainbow/json-schema/issues/new). +- Provide as much context as you can about what you're running into. +- Provide project and PHP version, depending on what seems relevant. + +We will then take care of the issue as soon as possible. + +## I Want To Contribute + +> ### Legal Notice +> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence. + +## Reporting Bugs + +### Before Submitting a Bug Report + +A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. + +- Make sure that you are using the latest version. +- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://github.com/jsonrainbow/json-schema/wiki). If you are looking for support, you might want to check [this section](#i-have-a-question)). +- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/jsonrainbow/json-schema/issues?q=label%3Abug). +- Collect information about the bug: + - Stack trace (Traceback) + - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) + - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. + - Possibly your input and the output +- Can you reliably reproduce the issue? And can you also reproduce it with older versions? + +### How Do I Submit a Good Bug Report? + +> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead, sensitive bugs must be reported at https://github.com/jsonrainbow/json-schema/security + +GitHub issues is used to track bugs and errors. If you run into an issue with the project: + +- Open an [Issue](https://github.com/jsonrainbow/json-schema/issues/new). +- Explain the behavior you would expect and the actual behavior. +- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. +- Provide the information you collected in the previous section. + +Once it's filed: + +- The project team will label the issue accordingly. +- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. +- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution). + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for JSON Schema, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. + +#### Before Submitting an Enhancement + +- Make sure that you are using the latest version. +- Read the [documentation](https://github.com/jsonrainbow/json-schema/wiki) carefully and find out if the functionality is already covered, maybe by an individual configuration. +- Perform a [search](https://github.com/jsonrainbow/json-schema/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. + +#### How Do I Submit a Good Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://github.com/jsonrainbow/json-schema/issues). + +- Use a **clear and descriptive title** for the issue to identify the suggestion. +- Provide a **step-by-step description of the suggested enhancement** in as many details as possible. +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. +- You may want to **include screenshots or screen recordings** which help you demonstrate the steps or point out the part which the suggestion is related to. +- **Explain why this enhancement would be useful** to most JSON Schema users. You may also want to point out the other projects that solved it better and which could serve as inspiration. + +## Pull Requests + +When submitting a pull request, please keep the following in mind: + +### Automated Changelog + +This project uses an automated changelog system. When your PR is merged to `main`, a GitHub Actions workflow will automatically add an entry to the CHANGELOG.md file under the "Unreleased" section. + +**PR Labels and Changelog Categories:** + +The workflow determines which category to place your entry under based on the labels on your PR: + +- `bug`, `fix` → **Fixed** - for bug fixes +- `enhancement`, `feature`, `added` → **Added** - for new features +- `changed` → **Changed** - for changes in existing functionality +- `deprecated` → **Deprecated** - for soon-to-be removed features +- `removed` → **Removed** - for now removed features +- `security` → **Security** - for security-related changes +- `breaking` → **Changed** - for breaking changes +- No matching label → **Changed** (default) + +**What this means for you:** + +- ✅ You do NOT need to manually update CHANGELOG.md in your PR +- ✅ Make sure your PR title is clear and descriptive (it will become the changelog entry) +- ✅ Add appropriate labels to your PR to ensure it appears in the correct category +- ✅ Maintainers will add labels during the review process if needed + + + +## Attribution +This guide is based on the [contributing.md](https://contributing.md/generator)! \ No newline at end of file diff --git a/vendor/justinrainbow/json-schema/LICENSE b/vendor/justinrainbow/json-schema/LICENSE new file mode 100644 index 000000000..fa020fce0 --- /dev/null +++ b/vendor/justinrainbow/json-schema/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/justinrainbow/json-schema/README.md b/vendor/justinrainbow/json-schema/README.md new file mode 100644 index 000000000..5fbb24fd6 --- /dev/null +++ b/vendor/justinrainbow/json-schema/README.md @@ -0,0 +1,235 @@ +# JSON Schema for PHP + +[![Build Status](https://github.com/jsonrainbow/json-schema/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/jsonrainbow/json-schema/actions) +[![Latest Stable Version](https://poser.pugx.org/justinrainbow/json-schema/v/stable)](https://packagist.org/packages/justinrainbow/json-schema) +[![Total Downloads](https://poser.pugx.org/justinrainbow/json-schema/downloads)](https://packagist.org/packages/justinrainbow/json-schema/stats) +![Supported Dialects](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fphp-justinrainbow-json-schema%2Fsupported_versions.json) + +A PHP Implementation for validating `JSON` Structures against a given `Schema` with support for `Schemas` of Draft-3, +Draft-4, Draft-6 or Draft-7. + +Features of newer Drafts might not be supported. See [Table of All Versions of Everything](https://json-schema.org/specification-links.html#table-of-all-versions-of-everything) to get an overview +of all existing Drafts. See [json-schema](http://json-schema.org/) for more details about the JSON Schema specification + +# Compliance +![Draft 3](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fphp-justinrainbow-json-schema%2Fcompliance%2Fdraft3.json) +![Draft 4](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fphp-justinrainbow-json-schema%2Fcompliance%2Fdraft4.json) +![Draft 6](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fphp-justinrainbow-json-schema%2Fcompliance%2Fdraft6.json) +![Draft 7](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fphp-justinrainbow-json-schema%2Fcompliance%2Fdraft7.json) + + +## Installation + +### Library + +```bash +git clone https://github.com/jsonrainbow/json-schema.git +``` + +### Composer + +[Install PHP Composer](https://getcomposer.org/doc/00-intro.md) + +```bash +composer require justinrainbow/json-schema +``` + +## Usage + +For a complete reference see [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/). + +__Note:__ Not all drafts might be supported, check the [Bowtie report](https://bowtie.report/#/implementations/php-justinrainbow-json-schema) on the current state of draft implementations. + +### Basic usage + +```php +validate($data, (object)['$ref' => 'file://' . realpath('schema.json')]); + +if ($validator->isValid()) { + echo "The supplied JSON validates against the schema.\n"; +} else { + echo "JSON does not validate. Violations:\n"; + foreach ($validator->getErrors() as $error) { + printf("[%s] %s\n", $error['property'], $error['message']); + } +} +``` + +### Type coercion + +If you're validating data passed to your application via HTTP, you can cast strings and booleans to +the expected types defined by your schema: + +```php +"true", + 'refundAmount'=>"17" +]; + +$validator->validate( + $request, (object) [ + "type"=>"object", + "properties"=>(object)[ + "processRefund"=>(object)[ + "type"=>"boolean" + ], + "refundAmount"=>(object)[ + "type"=>"number" + ] + ] + ], + Constraint::CHECK_MODE_COERCE_TYPES +); // validates! + +is_bool($request->processRefund); // true +is_int($request->refundAmount); // true +``` + +A shorthand method is also available: +```PHP +$validator->coerce($request, $schema); +// equivalent to $validator->validate($data, $schema, Constraint::CHECK_MODE_COERCE_TYPES); +``` + +### Default values + +If your schema contains default values, you can have these automatically applied during validation: + +```php +17 +]; + +$validator = new Validator(); + +$validator->validate( + $request, + (object)[ + "type"=>"object", + "properties"=>(object)[ + "processRefund"=>(object)[ + "type"=>"boolean", + "default"=>true + ] + ] + ], + Constraint::CHECK_MODE_APPLY_DEFAULTS +); //validates, and sets defaults for missing properties + +is_bool($request->processRefund); // true +$request->processRefund; // true +``` + +### With inline references + +```php +addSchema('file://mySchema', $jsonSchemaObject); + +// Provide $schemaStorage to the Validator so that references can be resolved during validation +$jsonValidator = new Validator(new Factory($schemaStorage)); + +// JSON must be decoded before it can be validated +$jsonToValidateObject = json_decode('{"data":123}'); + +// Do validation (use isValid() and getErrors() to check the result) +$jsonValidator->validate($jsonToValidateObject, $jsonSchemaObject); +``` + +### Configuration Options +A number of flags are available to alter the behavior of the validator. These can be passed as the +third argument to `Validator::validate()`, or can be provided as the third argument to +`Factory::__construct()` if you wish to persist them across multiple `validate()` calls. + +| Flag | Description | +|-------------------------------------------------|-----------------------------------------------------------------| +| `Constraint::CHECK_MODE_NORMAL` | Validate in 'normal' mode - this is the default | +| `Constraint::CHECK_MODE_TYPE_CAST` | Enable fuzzy type checking for associative arrays and objects | +| `Constraint::CHECK_MODE_COERCE_TYPES` [^1][^2] | Convert data types to match the schema where possible | +| `Constraint::CHECK_MODE_EARLY_COERCE` [^2] | Apply type coercion as soon as possible | +| `Constraint::CHECK_MODE_APPLY_DEFAULTS` [^1] | Apply default values from the schema if not set | +| `Constraint::CHECK_MODE_ONLY_REQUIRED_DEFAULTS` | When applying defaults, only set values that are required | +| `Constraint::CHECK_MODE_EXCEPTIONS` | Throw an exception immediately if validation fails | +| `Constraint::CHECK_MODE_DISABLE_FORMAT` | Do not validate "format" constraints | +| `Constraint::CHECK_MODE_VALIDATE_SCHEMA` | Validate the schema as well as the provided document | +| `Constraint::CHECK_MODE_STRICT` [^3] | Validate the scheme using strict mode using the specified draft | + +[^1]: Please note that using `CHECK_MODE_COERCE_TYPES` or `CHECK_MODE_APPLY_DEFAULTS` will modify your +original data. +[^2]: `CHECK_MODE_EARLY_COERCE` has no effect unless used in combination with `CHECK_MODE_COERCE_TYPES`. If +enabled, the validator will use (and coerce) the first compatible type it encounters, even if the +schema defines another type that matches directly and does not require coercion. +[^3]: `CHECK_MODE_STRICT` only can be used for Draft-6 at this point. + +## Running the tests + +```bash +composer test # run all unit tests +composer testOnly TestClass # run specific unit test class +composer testOnly TestClass::testMethod # run specific unit test method +composer style-check # check code style for errors +composer style-fix # automatically fix code style errors +``` + +# Contributors ✨ +Thanks go to these wonderful people, without their effort this project wasn't possible. + + + + diff --git a/vendor/justinrainbow/json-schema/SECURITY.md b/vendor/justinrainbow/json-schema/SECURITY.md new file mode 100644 index 000000000..54e06742a --- /dev/null +++ b/vendor/justinrainbow/json-schema/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 6.x.x | :white_check_mark: | +| 5.x.x | :white_check_mark: | +| < 5.0 | :x: | + +## Reporting a Vulnerability + +JSON Schema uses the GitHub feature to safely report vulnerabilities, please report them using https://github.com/jsonrainbow/json-schema/security \ No newline at end of file diff --git a/vendor/justinrainbow/json-schema/UPGRADE-6.0.md b/vendor/justinrainbow/json-schema/UPGRADE-6.0.md new file mode 100644 index 000000000..a70c73064 --- /dev/null +++ b/vendor/justinrainbow/json-schema/UPGRADE-6.0.md @@ -0,0 +1,45 @@ +UPGRADE FROM 5.3 to 6.0 +======================= + +## Introduction + +We are excited to release version 6.0 of our open-source package, featuring major improvements and important updates. This release includes several breaking changes from version 5.3 aimed at enhancing performance, security, and flexibility. + +Please review the following breaking changes carefully and update your implementations to ensure compatibility with version 6.0. This guide provides key modifications and instructions for a smooth transition. + +Thank you for your support and contributions to the project. + +## Errors +* `constraint` key is no longer the constraint name but contains more information in order to translate violations. + + *Before* + ```php + foreach ($validator->getErrors() as $error) { + echo $error['constraint']; // required + } + ``` + + *After* + ```php + foreach ($validator->getErrors() as $error) { + echo $error['constraint']['name']; // required + } + ``` + +## BaseConstraint::addError signature changed + +* The signature for the `BaseConstraint::AddError` method has changed. + + The `$message` parameter has been removed and replaced by the `ConstraintError` parameter. + The `ConstraintError` object encapsulates the error message along with additional information about the constraint violation. + + *Before* + ```php + public function addError(?JsonPointer $path, $message, $constraint = '', ?array $more = null) + ``` + + *After* + ```php + public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void + ``` + diff --git a/vendor/justinrainbow/json-schema/bin/extract-release-notes.sh b/vendor/justinrainbow/json-schema/bin/extract-release-notes.sh new file mode 100755 index 000000000..f20ce802f --- /dev/null +++ b/vendor/justinrainbow/json-schema/bin/extract-release-notes.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -e + +# Script to extract release notes for a specific version from CHANGELOG.md +# Usage: ./bin/extract-release-notes.sh "6.7.0" +# +# Arguments: +# $1 - Version number (e.g., "6.7.0") +# +# This script extracts all the content between the specified version header +# and the next version header + +if [ $# -lt 1 ]; then + echo "Usage: $0 " + echo "" + echo "Example: $0 '6.7.0'" + exit 1 +fi + +VERSION="$1" + +if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Version must be in format X.Y.Z (e.g., 6.7.0)" + exit 1 +fi + +# Check if CHANGELOG.md exists +if [ ! -f CHANGELOG.md ]; then + echo "Error: CHANGELOG.md not found in current directory" + exit 1 +fi + +# Use awk to extract the release notes for the specified version +awk -v version="$VERSION" ' +BEGIN { in_version = 0; found = 0 } + +# Match the version header +$0 ~ "^## \\[" version "\\]" { + in_version = 1 + found = 1 + next +} + +# Match any other version header +/^## \[/ { + if (in_version) { + exit + } + next +} + +# Print content when in the correct version section +in_version { + print +} + +END { + if (!found) { + print "Error: Version " version " not found in CHANGELOG.md" > "/dev/stderr" + exit 1 + } +} +' CHANGELOG.md diff --git a/vendor/justinrainbow/json-schema/bin/prepare-release.sh b/vendor/justinrainbow/json-schema/bin/prepare-release.sh new file mode 100755 index 000000000..137658fc9 --- /dev/null +++ b/vendor/justinrainbow/json-schema/bin/prepare-release.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -e + +# Script to prepare a release by updating CHANGELOG.md +# Usage: ./bin/prepare-release.sh "6.7.0" +# +# Arguments: +# $1 - Version number (e.g., "6.7.0") +# +# This script: +# 1. Moves all entries from [Unreleased] to a new version section +# 2. Adds the release date +# 3. Leaves an empty [Unreleased] section + +if [ $# -lt 1 ]; then + echo "Usage: $0 " + echo "" + echo "Example: $0 '6.7.0'" + exit 1 +fi + +VERSION="$1" +RELEASE_DATE=$(date +%Y-%m-%d) + +if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Version must be in format X.Y.Z (e.g., 6.7.0)" + exit 1 +fi + +echo "Preparing release for version: $VERSION" +echo "Release date: $RELEASE_DATE" + +# Check if CHANGELOG.md exists +if [ ! -f CHANGELOG.md ]; then + echo "Error: CHANGELOG.md not found in current directory" + exit 1 +fi + +# Check if there is content in the Unreleased section +# Extract content between [Unreleased] and the next version header +UNRELEASED_CONTENT=$(awk '/^## \[Unreleased\]/ {flag=1; next} /^## \[/ {flag=0} flag' CHANGELOG.md) +if ! echo "$UNRELEASED_CONTENT" | grep -q "^### "; then + echo "Error: No changes found in [Unreleased] section" + exit 1 +fi + +# Use awk to process the changelog +awk -v version="$VERSION" -v date="$RELEASE_DATE" ' +BEGIN { + in_unreleased = 0 + buf = "" +} + +/^## \[Unreleased\]/ { + print + print "" + print "## [" version "] - " date + in_unreleased = 1 + next +} + +/^## \[/ { + if (in_unreleased) { + print buf + in_unreleased = 0 + } + print + next +} + +in_unreleased { + buf = buf $0 "\n" + next +} + +{ print } + +END { + if (in_unreleased) { + print buf + } +} +' CHANGELOG.md > CHANGELOG.md.tmp + +if [ ! -s CHANGELOG.md.tmp ]; then + echo "Error: Failed to update CHANGELOG.md" + rm -f CHANGELOG.md.tmp + exit 1 +fi + +mv CHANGELOG.md.tmp CHANGELOG.md + +echo "CHANGELOG.md updated successfully" +echo "Created release section for version $VERSION" diff --git a/vendor/justinrainbow/json-schema/bin/update-changelog.sh b/vendor/justinrainbow/json-schema/bin/update-changelog.sh new file mode 100755 index 000000000..3f84aeae4 --- /dev/null +++ b/vendor/justinrainbow/json-schema/bin/update-changelog.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -e + +# Script to update CHANGELOG.md with a new entry +# Usage: ./bin/update-changelog.sh "PR Title" "123" "Fixed" +# +# Arguments: +# $1 - PR title (the changelog entry text) +# $2 - PR number +# $3 - Category (Added, Changed, Fixed, Deprecated, Removed, Security) +# +# Example: +# ./bin/update-changelog.sh "Fix bug in validation" "456" "Fixed" + +if [ $# -lt 3 ]; then + echo "Usage: $0 " + echo "" + echo "Categories: Added, Changed, Fixed, Deprecated, Removed, Security" + echo "" + echo "Example: $0 'Fix bug in validation' '456' 'Fixed'" + exit 1 +fi + +PR_TITLE="$1" +PR_NUMBER="$2" +CATEGORY="$3" +REPO_URL="${GITHUB_REPOSITORY_URL:-https://github.com/jsonrainbow/json-schema}" + +# Remove trailing .git if present +REPO_URL="${REPO_URL%.git}" + +# Create the changelog entry +ENTRY="- ${PR_TITLE} ([#${PR_NUMBER}](${REPO_URL}/pull/${PR_NUMBER}))" + +echo "Adding entry: $ENTRY" +echo "Under category: ### $CATEGORY" + +# Check if CHANGELOG.md exists +if [ ! -f CHANGELOG.md ]; then + echo "Error: CHANGELOG.md not found in current directory" + exit 1 +fi + +# Use awk to insert the entry under the correct category in the Unreleased section +if ! awk -v entry="$ENTRY" -v category="### $CATEGORY" ' +BEGIN { in_unreleased=0; found_category=0; added=0 } + +# Detect Unreleased section +/^## \[Unreleased\]/ { in_unreleased=1; print; next } + +# Detect next version section (end of Unreleased) +/^## \[/ { + if (in_unreleased && !added) { + # If we are leaving Unreleased and haven'\''t added entry yet + # Add category and entry before this line + if (!found_category) { + print category + } + print entry + print "" + added=1 + } + in_unreleased=0 + found_category=0 + print + next +} + +# If in Unreleased section, look for matching category +in_unreleased && $0 == category { + found_category=1 + print + # Add entry right after category header + print entry + added=1 + next +} + +# Always print the current line +{ print } + +# At end of file, if still in unreleased and not added +END { + if (in_unreleased && !added) { + if (!found_category) { + print category + } + print entry + } +} +' CHANGELOG.md > CHANGELOG.md.tmp; then + echo "Error: Failed to update CHANGELOG.md" + rm -f CHANGELOG.md.tmp + exit 1 +fi + +mv CHANGELOG.md.tmp CHANGELOG.md + +echo "CHANGELOG.md updated successfully" diff --git a/vendor/justinrainbow/json-schema/bin/validate-json b/vendor/justinrainbow/json-schema/bin/validate-json new file mode 100755 index 000000000..83f0e2a86 --- /dev/null +++ b/vendor/justinrainbow/json-schema/bin/validate-json @@ -0,0 +1,236 @@ +#!/usr/bin/env php + + */ + +// support running this tool from git checkout +$projectDirectory = dirname(__DIR__); +if (is_dir($projectDirectory . DIRECTORY_SEPARATOR . 'vendor')) { + set_include_path($projectDirectory . PATH_SEPARATOR . get_include_path()); +} + +// autoload composer classes +$composerAutoload = stream_resolve_include_path('vendor/autoload.php'); +if (!$composerAutoload) { + echo("Cannot load json-schema library\n"); + exit(1); +} +require($composerAutoload); + +$arOptions = []; +$arArgs = []; +array_shift($argv);//script itself +foreach ($argv as $arg) { + if ($arg[0] == '-') { + $arOptions[$arg] = true; + } else { + $arArgs[] = $arg; + } +} + +if (count($arArgs) == 0 + || isset($arOptions['--help']) || isset($arOptions['-h']) +) { + echo << $value) { + if (!strncmp($name, 'JSON_ERROR_', 11)) { + $json_errors[$value] = $name; + } + } + + output('JSON parse error: ' . $json_errors[json_last_error()] . "\n"); +} + +function getUrlFromPath($path) +{ + if (parse_url($path, PHP_URL_SCHEME) !== null) { + //already an URL + return $path; + } + if ($path[0] == '/') { + //absolute path + return 'file://' . $path; + } + + //relative path: make absolute + return 'file://' . getcwd() . '/' . $path; +} + +/** + * Take a HTTP header value and split it up into parts. + * + * @param $headerValue + * @return array Key "_value" contains the main value, all others + * as given in the header value + */ +function parseHeaderValue($headerValue) +{ + if (strpos($headerValue, ';') === false) { + return ['_value' => $headerValue]; + } + + $parts = explode(';', $headerValue); + $arData = ['_value' => array_shift($parts)]; + foreach ($parts as $part) { + list($name, $value) = explode('=', $part); + $arData[$name] = trim($value, ' "\''); + } + return $arData; +} + +/** + * Send a string to the output stream, but only if --quiet is not enabled + * + * @param $str string A string output + */ +function output($str) { + global $arOptions; + if (!isset($arOptions['--quiet'])) { + echo $str; + } +} + +$urlData = getUrlFromPath($pathData); + +$context = stream_context_create( + [ + 'http' => [ + 'header' => [ + 'Accept: */*', + 'Connection: Close' + ], + 'max_redirects' => 5 + ] + ] +); +$dataString = file_get_contents($pathData, false, $context); +if ($dataString == '') { + output("Data file is not readable or empty.\n"); + exit(3); +} + +$data = json_decode($dataString); +unset($dataString); +if ($data === null) { + output("Error loading JSON data file\n"); + showJsonError(); + exit(5); +} + +if ($pathSchema === null) { + if (isset($http_response_header)) { + array_shift($http_response_header);//HTTP/1.0 line + foreach ($http_response_header as $headerLine) { + list($hName, $hValue) = explode(':', $headerLine, 2); + $hName = strtolower($hName); + if ($hName == 'link') { + //Link: ; rel="describedBy" + $hParts = parseHeaderValue($hValue); + if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') { + $pathSchema = trim($hParts['_value'], ' <>'); + } + } else if ($hName == 'content-type') { + //Content-Type: application/my-media-type+json; + // profile=http://example.org/schema# + $hParts = parseHeaderValue($hValue); + if (isset($hParts['profile'])) { + $pathSchema = $hParts['profile']; + } + + } + } + } + if (is_object($data) && property_exists($data, '$schema')) { + $pathSchema = $data->{'$schema'}; + } + + //autodetect schema + if ($pathSchema === null) { + output("JSON data must be an object and have a \$schema property.\n"); + output("You can pass the schema file on the command line as well.\n"); + output("Schema autodetection failed.\n"); + exit(6); + } +} +if ($pathSchema[0] == '/') { + $pathSchema = 'file://' . $pathSchema; +} + +$resolver = new JsonSchema\Uri\UriResolver(); +$retriever = new JsonSchema\Uri\UriRetriever(); +try { + $urlSchema = $resolver->resolve($pathSchema, $urlData); + + if (isset($arOptions['--dump-schema-url'])) { + echo $urlSchema . "\n"; + exit(); + } +} catch (Exception $e) { + output("Error loading JSON schema file\n"); + output($urlSchema . "\n"); + output($e->getMessage() . "\n"); + exit(2); +} +$refResolver = new JsonSchema\SchemaStorage($retriever, $resolver); +$schema = $refResolver->resolveRef($urlSchema); + +if (isset($arOptions['--dump-schema'])) { + $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; + echo json_encode($schema, $options) . "\n"; + exit(); +} + +try { + $validator = new JsonSchema\Validator(); + $validator->validate($data, $schema); + + if ($validator->isValid()) { + if(isset($arOptions['--verbose'])) { + output("OK. The supplied JSON validates against the schema.\n"); + } + } else { + output("JSON does not validate. Violations:\n"); + foreach ($validator->getErrors() as $error) { + output(sprintf("[%s] %s\n", $error['property'], $error['message'])); + } + exit(23); + } +} catch (Exception $e) { + output("JSON does not validate. Error:\n"); + output($e->getMessage() . "\n"); + output("Error code: " . $e->getCode() . "\n"); + exit(24); +} diff --git a/vendor/justinrainbow/json-schema/composer.json b/vendor/justinrainbow/json-schema/composer.json new file mode 100644 index 000000000..5dfe65d12 --- /dev/null +++ b/vendor/justinrainbow/json-schema/composer.json @@ -0,0 +1,83 @@ +{ + "name": "justinrainbow/json-schema", + "type": "library", + "description": "A library to validate a json schema.", + "keywords": [ + "json", + "schema" + ], + "homepage": "https://github.com/jsonrainbow/json-schema", + "license": "MIT", + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "ext-json": "*", + "marc-mabe/php-enum":"^4.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "^23.2", + "phpunit/phpunit": "^8.5", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "marc-mabe/php-enum-phpstan": "^2.0" + }, + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "autoload-dev": { + "psr-4": { + "JsonSchema\\Tests\\": "tests/" + } + }, + "repositories": [ + { + "type": "package", + "package": { + "name": "json-schema/json-schema-test-suite", + "version": "23.2.0", + "source": { + "type": "git", + "url": "https://github.com/json-schema/JSON-Schema-Test-Suite", + "reference": "23.2.0" + } + } + } + ], + "bin": [ + "bin/validate-json" + ], + "scripts": { + "coverage": "phpunit --coverage-text", + "style-check": "php-cs-fixer fix --dry-run --verbose --diff", + "style-fix": "php-cs-fixer fix --verbose", + "test": "phpunit", + "testOnly": "phpunit --colors --filter", + "phpstan": "@php phpstan", + "phpstan-generate-baseline": "@php phpstan --generate-baseline" + } +} diff --git a/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-03.json b/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-03.json new file mode 100644 index 000000000..7a1a2d389 --- /dev/null +++ b/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-03.json @@ -0,0 +1,174 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "id": "http://json-schema.org/draft-03/schema#", + "type": "object", + + "properties": { + "type": { + "type": [ "string", "array" ], + "items": { + "type": [ "string", { "$ref": "#" } ] + }, + "uniqueItems": true, + "default": "any" + }, + + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + + "additionalProperties": { + "type": [ { "$ref": "#" }, "boolean" ], + "default": {} + }, + + "items": { + "type": [ { "$ref": "#" }, "array" ], + "items": { "$ref": "#" }, + "default": {} + }, + + "additionalItems": { + "type": [ { "$ref": "#" }, "boolean" ], + "default": {} + }, + + "required": { + "type": "boolean", + "default": false + }, + + "dependencies": { + "type": "object", + "additionalProperties": { + "type": [ "string", "array", { "$ref": "#" } ], + "items": { + "type": "string" + } + }, + "default": {} + }, + + "minimum": { + "type": "number" + }, + + "maximum": { + "type": "number" + }, + + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + + "maxItems": { + "type": "integer", + "minimum": 0 + }, + + "uniqueItems": { + "type": "boolean", + "default": false + }, + + "pattern": { + "type": "string", + "format": "regex" + }, + + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + + "maxLength": { + "type": "integer" + }, + + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + + "default": { + "type": "any" + }, + + "title": { + "type": "string" + }, + + "description": { + "type": "string" + }, + + "format": { + "type": "string" + }, + + "divisibleBy": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true, + "default": 1 + }, + + "disallow": { + "type": [ "string", "array" ], + "items": { + "type": [ "string", { "$ref": "#" } ] + }, + "uniqueItems": true + }, + + "extends": { + "type": [ { "$ref": "#" }, "array" ], + "items": { "$ref": "#" }, + "default": {} + }, + + "id": { + "type": "string", + "format": "uri" + }, + + "$ref": { + "type": "string", + "format": "uri" + }, + + "$schema": { + "type": "string", + "format": "uri" + } + }, + + "dependencies": { + "exclusiveMinimum": "minimum", + "exclusiveMaximum": "maximum" + }, + + "default": {} +} diff --git a/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-04.json b/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-04.json new file mode 100644 index 000000000..85eb502a6 --- /dev/null +++ b/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-04.json @@ -0,0 +1,150 @@ +{ + "id": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} +} diff --git a/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-06.json b/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-06.json new file mode 100644 index 000000000..bd3e763bc --- /dev/null +++ b/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-06.json @@ -0,0 +1,155 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://json-schema.org/draft-06/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "examples": { + "type": "array", + "items": {} + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": {}, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": {} +} diff --git a/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-07.json b/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-07.json new file mode 100644 index 000000000..c173cb868 --- /dev/null +++ b/vendor/justinrainbow/json-schema/dist/schema/json-schema-draft-07.json @@ -0,0 +1,245 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { + "$ref": "#/definitions/nonNegativeInteger" + }, + { + "default": 0 + } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "default": [] + } + }, + "type": [ + "object", + "boolean" + ], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#" + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ], + "default": true + }, + "maxItems": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#" + }, + "maxProperties": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#" + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "propertyNames": { + "format": "regex" + }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/stringArray" + } + ] + } + }, + "propertyNames": { + "$ref": "#" + }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "if": { + "$ref": "#" + }, + "then": { + "$ref": "#" + }, + "else": { + "$ref": "#" + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "not": { + "$ref": "#" + } + }, + "default": true +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php b/vendor/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php new file mode 100644 index 000000000..5fcfaf39a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php @@ -0,0 +1,131 @@ +getValue(); + static $messages = [ + self::ADDITIONAL_ITEMS => 'The item %s[%s] is not defined and the definition does not allow additional items', + self::ADDITIONAL_PROPERTIES => 'The property %s is not defined and the definition does not allow additional properties', + self::ALL_OF => 'Failed to match all schemas', + self::ANY_OF => 'Failed to match at least one schema', + self::DEPENDENCIES => '%s depends on %s, which is missing', + self::DISALLOW => 'Disallowed value was matched', + self::DIVISIBLE_BY => 'Is not divisible by %d', + self::ENUM => 'Does not have a value in the enumeration %s', + self::CONSTANT => 'Does not have a value equal to %s', + self::CONTAINS => 'Does not have a value valid to contains schema', + self::EXCLUSIVE_MINIMUM => 'Must have a minimum value greater than %d', + self::EXCLUSIVE_MAXIMUM => 'Must have a maximum value less than %d', + self::FALSE => 'Boolean schema false', + self::FORMAT_COLOR => 'Invalid color', + self::FORMAT_DATE => 'Invalid date %s, expected format YYYY-MM-DD', + self::FORMAT_DATE_TIME => 'Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', + self::FORMAT_DATE_UTC => 'Invalid time %s, expected integer of milliseconds since Epoch', + self::FORMAT_EMAIL => 'Invalid email', + self::FORMAT_HOSTNAME => 'Invalid hostname', + self::FORMAT_IP => 'Invalid IP address', + self::FORMAT_JSON_POINTER => 'Invalid JSON pointer', + self::FORMAT_PHONE => 'Invalid phone number', + self::FORMAT_REGEX=> 'Invalid regex format %s', + self::FORMAT_STYLE => 'Invalid style', + self::FORMAT_TIME => 'Invalid time %s, expected format hh:mm:ss', + self::FORMAT_URI_TEMPLATE => 'Invalid URI template format', + self::FORMAT_URL => 'Invalid URL format', + self::FORMAT_URL_REF => 'Invalid URL reference format', + self::LENGTH_MAX => 'Must be at most %d characters long', + self::INVALID_SCHEMA => 'Schema is not valid', + self::LENGTH_MIN => 'Must be at least %d characters long', + self::MAX_ITEMS => 'There must be a maximum of %d items in the array, %d found', + self::MAXIMUM => 'Must have a maximum value less than or equal to %d', + self::MIN_ITEMS => 'There must be a minimum of %d items in the array, %d found', + self::MINIMUM => 'Must have a minimum value greater than or equal to %d', + self::MISSING_MAXIMUM => 'Use of exclusiveMaximum requires presence of maximum', + self::MISSING_MINIMUM => 'Use of exclusiveMinimum requires presence of minimum', + /*self::MISSING_ERROR => 'Used for tests; this error is deliberately commented out',*/ + self::MULTIPLE_OF => 'Must be a multiple of %s', + self::NOT => 'Matched a schema which it should not', + self::ONE_OF => 'Failed to match exactly one schema', + self::REQUIRED => 'The property %s is required', + self::REQUIRES => 'The presence of the property %s requires that %s also be present', + self::PATTERN => 'Does not match the regex pattern %s', + self::PREGEX_INVALID => 'The pattern %s is invalid', + self::PROPERTIES_MIN => 'Must contain a minimum of %d properties', + self::PROPERTIES_MAX => 'Must contain no more than %d properties', + self::PROPERTY_NAMES => 'Property name %s is invalid', + self::TYPE => '%s value found, but %s is required', + self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array', + self::CONTENT_MEDIA_TYPE => 'Value is not valid with content media type', + self::CONTENT_ENCODING => 'Value is not valid with content encoding', + ]; + + if (!isset($messages[$name])) { + throw new InvalidArgumentException('Missing error message for ' . $name); + } + + return $messages[$name]; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php new file mode 100644 index 000000000..b1b34c32d --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php @@ -0,0 +1,170 @@ + + */ + protected $errorMask = Validator::ERROR_NONE; + + /** + * @var Factory + */ + protected $factory; + + public function __construct(?Factory $factory = null) + { + $this->factory = $factory ?: new Factory(); + } + + public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void + { + $message = $constraint->getMessage(); + $name = $constraint->getValue(); + $error = [ + 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), + 'pointer' => ltrim((string) ($path ?: new JsonPointer('')), '#'), + 'message' => ucfirst(vsprintf($message, array_map(static function ($val) { + if (is_scalar($val)) { + return is_bool($val) ? var_export($val, true) : $val; + } + + return json_encode($val); + }, array_values($more)))), + 'constraint' => [ + 'name' => $name, + 'params' => $more + ], + 'context' => $this->factory->getErrorContext(), + ]; + + if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { + throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); + } + + $this->errors[] = $error; + $this->errorMask |= $error['context']; + } + + public function addErrors(array $errors): void + { + if ($errors) { + $this->errors = array_merge($this->errors, $errors); + $errorMask = &$this->errorMask; + array_walk($errors, static function ($error) use (&$errorMask) { + if (isset($error['context'])) { + $errorMask |= $error['context']; + } + }); + } + } + + /** + * @phpstan-param int-mask-of $errorContext + */ + public function getErrors(int $errorContext = Validator::ERROR_ALL): array + { + if ($errorContext === Validator::ERROR_ALL) { + return $this->errors; + } + + return array_filter($this->errors, static function ($error) use ($errorContext) { + return (bool) ($errorContext & $error['context']); + }); + } + + /** + * @phpstan-param int-mask-of $errorContext + */ + public function numErrors(int $errorContext = Validator::ERROR_ALL): int + { + if ($errorContext === Validator::ERROR_ALL) { + return count($this->errors); + } + + return count($this->getErrors($errorContext)); + } + + public function isValid(): bool + { + return !$this->getErrors(); + } + + /** + * Clears any reported errors. Should be used between + * multiple validation checks. + */ + public function reset(): void + { + $this->errors = []; + $this->errorMask = Validator::ERROR_NONE; + } + + /** + * Get the error mask + * + * @phpstan-return int-mask-of + */ + public function getErrorMask(): int + { + return $this->errorMask; + } + + /** + * Recursively cast an associative array to an object + */ + public static function arrayToObjectRecursive(array $array): object + { + $json = json_encode($array); + if (json_last_error() !== JSON_ERROR_NONE) { + $message = 'Unable to encode schema array as JSON'; + if (function_exists('json_last_error_msg')) { + $message .= ': ' . json_last_error_msg(); + } + throw new InvalidArgumentException($message); + } + + return (object) json_decode($json, false); + } + + /** + * Transform a JSON pattern into a PCRE regex + */ + public static function jsonPatternToPhpRegex(string $pattern): string + { + return '~' . str_replace('~', '\\~', $pattern) . '~u'; + } + + protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer): string + { + $result = array_map( + static function ($path) { + return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path); + }, + $pointer->getPropertyPaths() + ); + + return trim(implode('', $result), '.'); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php new file mode 100644 index 000000000..e42a6fb8d --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php @@ -0,0 +1,136 @@ + + * @author Bruno Prieto Reis + */ +class CollectionConstraint extends Constraint +{ + /** + * {@inheritdoc} + */ + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + // Verify minItems + if (isset($schema->minItems) && count($value) < $schema->minItems) { + $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => count($value)]); + } + + // Verify maxItems + if (isset($schema->maxItems) && count($value) > $schema->maxItems) { + $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => count($value)]); + } + + // Verify uniqueItems + if (isset($schema->uniqueItems) && $schema->uniqueItems) { + $count = count($value); + for ($x = 0; $x < $count - 1; $x++) { + for ($y = $x + 1; $y < $count; $y++) { + if (DeepComparer::isEqual($value[$x], $value[$y])) { + $this->addError(ConstraintError::UNIQUE_ITEMS(), $path); + break 2; + } + } + } + } + + $this->validateItems($value, $schema, $path, $i); + } + + /** + * Validates the items + * + * @param array $value + * @param \stdClass $schema + * @param string $i + */ + protected function validateItems(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (\is_null($schema) || !isset($schema->items)) { + return; + } + + if ($schema->items === true) { + return; + } + + if (is_object($schema->items)) { + // just one type definition for the whole array + foreach ($value as $k => &$v) { + $initErrors = $this->getErrors(); + + // First check if its defined in "items" + $this->checkUndefined($v, $schema->items, $path, $k); + + // Recheck with "additionalItems" if the first test fails + if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) { + $secondErrors = $this->getErrors(); + $this->checkUndefined($v, $schema->additionalItems, $path, $k); + } + + // Reset errors if needed + if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) { + $this->errors = $secondErrors; + } elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) { + $this->errors = $initErrors; + } + } + unset($v); /* remove dangling reference to prevent any future bugs + * caused by accidentally using $v elsewhere */ + } else { + // Defined item type definitions + foreach ($value as $k => &$v) { + if (array_key_exists($k, $schema->items)) { + $this->checkUndefined($v, $schema->items[$k], $path, $k); + } else { + // Additional items + if (property_exists($schema, 'additionalItems')) { + if ($schema->additionalItems !== false) { + $this->checkUndefined($v, $schema->additionalItems, $path, $k); + } else { + $this->addError( + ConstraintError::ADDITIONAL_ITEMS(), + $path, + [ + 'item' => $i, + 'property' => $k, + 'additionalItems' => $schema->additionalItems + ] + ); + } + } else { + // Should be valid against an empty schema + $this->checkUndefined($v, new \stdClass(), $path, $k); + } + } + } + unset($v); /* remove dangling reference to prevent any future bugs + * caused by accidentally using $v elsewhere */ + + // Treat when we have more schema definitions than values, not for empty arrays + if (count($value) > 0) { + for ($k = count($value); $k < count($schema->items); $k++) { + $undefinedInstance = $this->factory->createInstanceFor('undefined'); + $this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k); + } + } + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.php new file mode 100644 index 000000000..b9bb1f48d --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.php @@ -0,0 +1,51 @@ + + */ +class ConstConstraint extends Constraint +{ + /** + * {@inheritdoc} + */ + public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void + { + // Only validate const if the attribute exists + if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) { + return; + } + $const = $schema->const; + + $type = gettype($element); + $constType = gettype($const); + + if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type === 'array' && $constType === 'object') { + if (DeepComparer::isEqual((object) $element, $const)) { + return; + } + } + + if (DeepComparer::isEqual($element, $const)) { + return; + } + + $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php new file mode 100644 index 000000000..7538ad673 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php @@ -0,0 +1,204 @@ +withPropertyPaths( + array_merge( + $path->getPropertyPaths(), + [$i] + ) + ); + } + + /** + * Validates an array + * + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkArray(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor('collection'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Validates an object + * + * @param mixed $value + * @param mixed $schema + * @param mixed $properties + * @param mixed $additionalProperties + * @param mixed $patternProperties + * @param array $appliedDefaults + */ + protected function checkObject( + &$value, + $schema = null, + ?JsonPointer $path = null, + $properties = null, + $additionalProperties = null, + $patternProperties = null, + array $appliedDefaults = [] + ): void { + /** @var ObjectConstraint $validator */ + $validator = $this->factory->createInstanceFor('object'); + $validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults); + + $this->addErrors($validator->getErrors()); + } + + /** + * Validates the type of the value + * + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkType(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor('type'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks a undefined element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkUndefined(&$value, $schema = null, ?JsonPointer $path = null, $i = null, bool $fromDefault = false): void + { + /** @var UndefinedConstraint $validator */ + $validator = $this->factory->createInstanceFor('undefined'); + + $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks a string element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkString($value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor('string'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks a number element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkNumber($value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor('number'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks a enum element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkEnum($value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor('enum'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks a const element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkConst($value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor('const'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks format of an element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkFormat($value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor('format'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Get the type check based on the set check mode. + */ + protected function getTypeCheck(): TypeCheck\TypeCheckInterface + { + return $this->factory->getTypeCheck(); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php new file mode 100644 index 000000000..9e43ecb56 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php @@ -0,0 +1,59 @@ + + */ +interface ConstraintInterface +{ + /** + * returns all collected errors + */ + public function getErrors(): array; + + /** + * adds errors to this validator + */ + public function addErrors(array $errors): void; + + /** + * adds an error + * + * @param ConstraintError $constraint the constraint/rule that is broken, e.g.: ConstraintErrors::LENGTH_MIN() + * @param array $more more array elements to add to the error + */ + public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void; + + /** + * checks if the validator has not raised errors + */ + public function isValid(): bool; + + /** + * invokes the validation of an element + * + * @abstract + * + * @param mixed $value + * @param mixed $schema + * @param mixed $i + * + * @throws \JsonSchema\Exception\ExceptionInterface + */ + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void; +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalItemsConstraint.php new file mode 100644 index 000000000..d1adbce8f --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalItemsConstraint.php @@ -0,0 +1,61 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'additionalItems')) { + return; + } + + if ($schema->additionalItems === true) { + return; + } + if ($schema->additionalItems === false && !property_exists($schema, 'items')) { + return; + } + + if (!is_array($value)) { + return; + } + if (!property_exists($schema, 'items')) { + return; + } + if (property_exists($schema, 'items') && is_object($schema->items)) { + return; + } + + $additionalItems = array_diff_key($value, property_exists($schema, 'items') ? $schema->items : []); + + foreach ($additionalItems as $propertyName => $propertyValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $schema->additionalItems, $path, $i); + + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addError(ConstraintError::ADDITIONAL_ITEMS(), $path, ['item' => $i, 'property' => $propertyName, 'additionalItems' => $schema->additionalItems]); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php new file mode 100644 index 000000000..5f2448961 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php @@ -0,0 +1,93 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'additionalProperties')) { + return; + } + + if ($schema->additionalProperties === true) { + return; + } + + if (!is_object($value)) { + return; + } + + $additionalProperties = get_object_vars($value); + + if (isset($schema->properties)) { + $additionalProperties = array_diff_key($additionalProperties, (array) $schema->properties); + } + + if (isset($schema->patternProperties)) { + $patterns = array_keys(get_object_vars($schema->patternProperties)); + + foreach ($additionalProperties as $key => $_) { + foreach ($patterns as $pattern) { + if (preg_match($this->createPregMatchPattern($pattern), (string) $key)) { + unset($additionalProperties[$key]); + break; + } + } + } + } + + if (is_object($schema->additionalProperties)) { + foreach ($additionalProperties as $key => $additionalPropertiesValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $path, $i); // @todo increment path + if ($schemaConstraint->isValid()) { + unset($additionalProperties[$key]); + } + } + } + + foreach ($additionalProperties as $key => $additionalPropertiesValue) { + $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $additionalPropertiesValue]); + } + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ +// '\D' => '[^0-9]', +// '\d' => '[0-9]', + '\p{digit}' => '\p{Nd}', +// '\w' => '[A-Za-z0-9_]', +// '\W' => '[^A-Za-z0-9_]', +// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space, + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AllOfConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AllOfConstraint.php new file mode 100644 index 000000000..5a69f657d --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AllOfConstraint.php @@ -0,0 +1,42 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'allOf')) { + return; + } + + foreach ($schema->allOf as $allOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $allOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + continue; + } + $this->addError(ConstraintError::ALL_OF(), $path); + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AnyOfConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AnyOfConstraint.php new file mode 100644 index 000000000..29b0f29ba --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AnyOfConstraint.php @@ -0,0 +1,51 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'anyOf')) { + return; + } + + foreach ($schema->anyOf as $anyOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + + try { + $schemaConstraint->check($value, $anyOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + $this->errorBag()->reset(); + + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } catch (ValidationException $e) { + } + } + + $this->addError(ConstraintError::ANY_OF(), $path); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ConstConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ConstConstraint.php new file mode 100644 index 000000000..563bd42c5 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ConstConstraint.php @@ -0,0 +1,35 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'const')) { + return; + } + + if (DeepComparer::isEqual($value, $schema->const)) { + return; + } + + $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ContainsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ContainsConstraint.php new file mode 100644 index 000000000..45f0785d9 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ContainsConstraint.php @@ -0,0 +1,47 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'contains')) { + return; + } + + $properties = []; + if (!is_array($value)) { + return; + } + + foreach ($value as $propertyName => $propertyValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + + $schemaConstraint->check($propertyValue, $schema->contains, $path, $i); + if ($schemaConstraint->isValid()) { + return; + } + } + + $this->addError(ConstraintError::CONTAINS(), $path, ['contains' => $schema->contains]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/DependenciesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/DependenciesConstraint.php new file mode 100644 index 000000000..5b8e86b4e --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/DependenciesConstraint.php @@ -0,0 +1,64 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'dependencies')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->dependencies as $dependant => $dependencies) { + if (!property_exists($value, $dependant)) { + continue; + } + if ($dependencies === true) { + continue; + } + if ($dependencies === false) { + $this->addError(ConstraintError::FALSE(), $path, ['dependant' => $dependant]); + continue; + } + + if (is_array($dependencies)) { + foreach ($dependencies as $dependency) { + if (property_exists($value, $dependant) && !property_exists($value, $dependency)) { + $this->addError(ConstraintError::DEPENDENCIES(), $path, ['dependant' => $dependant, 'dependency' => $dependency]); + } + } + } + + if (is_object($dependencies)) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $dependencies, $path, $i); + if (!$schemaConstraint->isValid()) { + $this->addErrors($schemaConstraint->getErrors()); + } + } + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Draft06Constraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Draft06Constraint.php new file mode 100644 index 000000000..bd6efac66 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Draft06Constraint.php @@ -0,0 +1,81 @@ +getSchemaStorage() : new SchemaStorage(), + $factory ? $factory->getUriRetriever() : new UriRetriever(), + $factory ? $factory->getConfig() : Constraint::CHECK_MODE_NORMAL + )); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (is_bool($schema)) { + if ($schema === false) { + $this->addError(ConstraintError::FALSE(), $path, []); + } + + return; + } + + // Apply defaults + $this->checkForKeyword('ref', $value, $schema, $path, $i); + $this->checkForKeyword('required', $value, $schema, $path, $i); + $this->checkForKeyword('contains', $value, $schema, $path, $i); + $this->checkForKeyword('properties', $value, $schema, $path, $i); + $this->checkForKeyword('propertyNames', $value, $schema, $path, $i); + $this->checkForKeyword('patternProperties', $value, $schema, $path, $i); + $this->checkForKeyword('type', $value, $schema, $path, $i); + $this->checkForKeyword('not', $value, $schema, $path, $i); + $this->checkForKeyword('dependencies', $value, $schema, $path, $i); + $this->checkForKeyword('allOf', $value, $schema, $path, $i); + $this->checkForKeyword('anyOf', $value, $schema, $path, $i); + $this->checkForKeyword('oneOf', $value, $schema, $path, $i); + + $this->checkForKeyword('additionalProperties', $value, $schema, $path, $i); + $this->checkForKeyword('items', $value, $schema, $path, $i); + $this->checkForKeyword('additionalItems', $value, $schema, $path, $i); + $this->checkForKeyword('uniqueItems', $value, $schema, $path, $i); + $this->checkForKeyword('minItems', $value, $schema, $path, $i); + $this->checkForKeyword('minProperties', $value, $schema, $path, $i); + $this->checkForKeyword('maxProperties', $value, $schema, $path, $i); + $this->checkForKeyword('minimum', $value, $schema, $path, $i); + $this->checkForKeyword('maximum', $value, $schema, $path, $i); + $this->checkForKeyword('minLength', $value, $schema, $path, $i); + $this->checkForKeyword('exclusiveMinimum', $value, $schema, $path, $i); + $this->checkForKeyword('maxItems', $value, $schema, $path, $i); + $this->checkForKeyword('maxLength', $value, $schema, $path, $i); + $this->checkForKeyword('exclusiveMaximum', $value, $schema, $path, $i); + $this->checkForKeyword('enum', $value, $schema, $path, $i); + $this->checkForKeyword('const', $value, $schema, $path, $i); + $this->checkForKeyword('multipleOf', $value, $schema, $path, $i); + $this->checkForKeyword('format', $value, $schema, $path, $i); + $this->checkForKeyword('pattern', $value, $schema, $path, $i); + } + + /** + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkForKeyword(string $keyword, $value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor($keyword); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/EnumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/EnumConstraint.php new file mode 100644 index 000000000..1ed3b65ad --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/EnumConstraint.php @@ -0,0 +1,41 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'enum')) { + return; + } + + foreach ($schema->enum as $enumCase) { + if (DeepComparer::isEqual($value, $enumCase)) { + return; + } + + if (is_numeric($value) && is_numeric($enumCase) && DeepComparer::isEqual((float) $value, (float) $enumCase)) { + return; + } + } + + $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMaximumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMaximumConstraint.php new file mode 100644 index 000000000..2d29a175a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMaximumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'exclusiveMaximum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value < $schema->exclusiveMaximum) { + return; + } + + $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['exclusiveMaximum' => $schema->exclusiveMaximum, 'found' => $value]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMinimumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMinimumConstraint.php new file mode 100644 index 000000000..b167d198f --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMinimumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'exclusiveMinimum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value > $schema->exclusiveMinimum) { + return; + } + + $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['exclusiveMinimum' => $schema->exclusiveMinimum, 'found' => $value]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Factory.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Factory.php new file mode 100644 index 000000000..1d23f9fff --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Factory.php @@ -0,0 +1,46 @@ + + */ + protected $constraintMap = [ + 'schema' => Draft06Constraint::class, + 'additionalProperties' => AdditionalPropertiesConstraint::class, + 'additionalItems' => AdditionalItemsConstraint::class, + 'dependencies' => DependenciesConstraint::class, + 'type' => TypeConstraint::class, + 'const' => ConstConstraint::class, + 'enum' => EnumConstraint::class, + 'uniqueItems' => UniqueItemsConstraint::class, + 'minItems' => MinItemsConstraint::class, + 'minProperties' => MinPropertiesConstraint::class, + 'maxProperties' => MaxPropertiesConstraint::class, + 'minimum' => MinimumConstraint::class, + 'maximum' => MaximumConstraint::class, + 'exclusiveMinimum' => ExclusiveMinimumConstraint::class, + 'minLength' => MinLengthConstraint::class, + 'maxLength' => MaxLengthConstraint::class, + 'maxItems' => MaxItemsConstraint::class, + 'exclusiveMaximum' => ExclusiveMaximumConstraint::class, + 'multipleOf' => MultipleOfConstraint::class, + 'required' => RequiredConstraint::class, + 'format' => FormatConstraint::class, + 'anyOf' => AnyOfConstraint::class, + 'allOf' => AllOfConstraint::class, + 'oneOf' => OneOfConstraint::class, + 'not' => NotConstraint::class, + 'contains' => ContainsConstraint::class, + 'propertyNames' => PropertiesNamesConstraint::class, + 'patternProperties' => PatternPropertiesConstraint::class, + 'pattern' => PatternConstraint::class, + 'properties' => PropertiesConstraint::class, + 'items' => ItemsConstraint::class, + 'ref' => RefConstraint::class, + ]; +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php new file mode 100644 index 000000000..578a27c37 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php @@ -0,0 +1,220 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'format')) { + return; + } + + if (!is_string($value)) { + return; + } + + switch ($schema->format) { + case 'date': + if (!$this->validateDateTime($value, 'Y-m-d')) { + $this->addError(ConstraintError::FORMAT_DATE(), $path, ['date' => $value, 'format' => $schema->format]); + } + break; + case 'time': + if (!$this->validateDateTime($value, 'H:i:s')) { + $this->addError(ConstraintError::FORMAT_TIME(), $path, ['time' => $value, 'format' => $schema->format]); + } + break; + case 'date-time': + if (!$this->validateRfc3339DateTime($value)) { + $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, ['dateTime' => $value, 'format' => $schema->format]); + } + break; + case 'utc-millisec': + if (!$this->validateDateTime($value, 'U')) { + $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, ['value' => $value, 'format' => $schema->format]); + } + break; + case 'regex': + if (!$this->validateRegex($value)) { + $this->addError(ConstraintError::FORMAT_REGEX(), $path, ['value' => $value, 'format' => $schema->format]); + } + break; + case 'ip-address': + case 'ipv4': + if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4) === null) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + case 'ipv6': + if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6) === null) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + case 'color': + if (!$this->validateColor($value)) { + $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]); + } + break; + case 'style': + if (!$this->validateStyle($value)) { + $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]); + } + break; + case 'phone': + if (!$this->validatePhone($value)) { + $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]); + } + break; + case 'uri': + if (!UriValidator::isValid($value)) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + + case 'uriref': + case 'uri-reference': + if (!(UriValidator::isValid($value) || RelativeReferenceValidator::isValid($value))) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + case 'uri-template': + if (!$this->validateUriTemplate($value)) { + $this->addError(ConstraintError::FORMAT_URI_TEMPLATE(), $path, ['format' => $schema->format]); + } + break; + + case 'email': + if (filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE) === null) { + $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]); + } + break; + case 'host-name': + case 'hostname': + if (!$this->validateHostname($value)) { + $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); + } + break; + case 'json-pointer': + if (!$this->validateJsonPointer($value)) { + $this->addError(ConstraintError::FORMAT_JSON_POINTER(), $path, ['format' => $schema->format]); + } + break; + default: + break; + } + } + + private function validateDateTime(string $datetime, string $format): bool + { + $dt = \DateTime::createFromFormat($format, $datetime); + + if (!$dt) { + return false; + } + + return $datetime === $dt->format($format); + } + + private function validateRegex(string $regex): bool + { + return preg_match(self::jsonPatternToPhpRegex($regex), '') !== false; + } + + /** + * Transform a JSON pattern into a PCRE regex + */ + private static function jsonPatternToPhpRegex(string $pattern): string + { + return '~' . str_replace('~', '\\~', $pattern) . '~u'; + } + + private function validateColor(string $color): bool + { + if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia', + 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', + 'red', 'silver', 'teal', 'white', 'yellow'])) { + return true; + } + + return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) !== false; + } + + private function validateStyle(string $style): bool + { + $properties = explode(';', rtrim($style, ';')); + $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); + + return empty($invalidEntries); + } + + private function validatePhone(string $phone): bool + { + return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) !== false; + } + + private function validateHostname(string $host): bool + { + $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/'; + + return preg_match($hostnameRegex, $host) === 1; + } + + private function validateJsonPointer(string $value): bool + { + // Must be empty or start with a forward slash + if ($value !== '' && $value[0] !== '/') { + return false; + } + + // Split into reference tokens and check for invalid escape sequences + $tokens = explode('/', $value); + array_shift($tokens); // remove leading empty part due to leading slash + + foreach ($tokens as $token) { + // "~" must only be followed by "0" or "1" + if (preg_match('/~(?![01])/', $token)) { + return false; + } + } + + return true; + } + + private function validateRfc3339DateTime(string $value): bool + { + $dateTime = Rfc3339::createFromString($value); + if (is_null($dateTime)) { + return false; + } + + // Compare value and date result to be equal + return true; + } + + private function validateUriTemplate(string $value): bool + { + return preg_match( + '/^(?:[^\{\}]*|\{[a-zA-Z0-9_:%\/\.~\-\+\*]+\})*$/', + $value + ) === 1; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ItemsConstraint.php new file mode 100644 index 000000000..21259e1a8 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ItemsConstraint.php @@ -0,0 +1,52 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'items')) { + return; + } + + if (!is_array($value)) { + return; + } + + foreach ($value as $propertyName => $propertyValue) { + $itemSchema = $schema->items; + if (is_array($itemSchema)) { + if (!array_key_exists($propertyName, $itemSchema)) { + continue; + } + + $itemSchema = $itemSchema[$propertyName]; + } + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $itemSchema, $path, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxItemsConstraint.php new file mode 100644 index 000000000..d7ad2649f --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxItemsConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxItems')) { + return; + } + + if (!is_array($value)) { + return; + } + + $count = count($value); + if ($count <= $schema->maxItems) { + return; + } + + $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => $count]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxLengthConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxLengthConstraint.php new file mode 100644 index 000000000..5243f488f --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxLengthConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxLength')) { + return; + } + + if (!is_string($value)) { + return; + } + + $length = mb_strlen($value); + if ($length <= $schema->maxLength) { + return; + } + + $this->addError(ConstraintError::LENGTH_MAX(), $path, ['maxLength' => $schema->maxLength, 'found' => $length]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxPropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxPropertiesConstraint.php new file mode 100644 index 000000000..b881a2d53 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxPropertiesConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $count = count(get_object_vars($value)); + if ($count <= $schema->maxProperties) { + return; + } + + $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $schema->maxProperties, 'found' => $count]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaximumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaximumConstraint.php new file mode 100644 index 000000000..bdd1db13c --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaximumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maximum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value <= $schema->maximum) { + return; + } + + $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum, 'found' => $value]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinItemsConstraint.php new file mode 100644 index 000000000..b17cd8949 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinItemsConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minItems')) { + return; + } + + if (!is_array($value)) { + return; + } + + $count = count($value); + if ($count >= $schema->minItems) { + return; + } + + $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => $count]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinLengthConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinLengthConstraint.php new file mode 100644 index 000000000..d9c516a33 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinLengthConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minLength')) { + return; + } + + if (!is_string($value)) { + return; + } + + $length = mb_strlen($value); + if ($length >= $schema->minLength) { + return; + } + + $this->addError(ConstraintError::LENGTH_MIN(), $path, ['minLength' => $schema->minLength, 'found' => $length]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinPropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinPropertiesConstraint.php new file mode 100644 index 000000000..148b4055c --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinPropertiesConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $count = count(get_object_vars($value)); + if ($count >= $schema->minProperties) { + return; + } + + $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $schema->minProperties, 'found' => $count]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinimumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinimumConstraint.php new file mode 100644 index 000000000..b083b8f69 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinimumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minimum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value >= $schema->minimum) { + return; + } + + $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum, 'found' => $value]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MultipleOfConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MultipleOfConstraint.php new file mode 100644 index 000000000..f82440df8 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MultipleOfConstraint.php @@ -0,0 +1,54 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'multipleOf')) { + return; + } + + if (!is_int($schema->multipleOf) && !is_float($schema->multipleOf) && $schema->multipleOf <= 0.0) { + return; + } + + if (!is_int($value) && !is_float($value)) { + return; + } + + if ($this->isMultipleOf($value, $schema->multipleOf)) { + return; + } + + $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf, 'found' => $value]); + } + + /** + * @param int|float $number1 + * @param int|float $number2 + */ + private function isMultipleOf($number1, $number2): bool + { + $modulus = ($number1 - round($number1 / $number2) * $number2); + $precision = 0.0000000001; + + return -$precision < $modulus && $modulus < $precision; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/NotConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/NotConstraint.php new file mode 100644 index 000000000..2a8268e16 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/NotConstraint.php @@ -0,0 +1,40 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'not')) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $schema->not, $path, $i); + + if (!$schemaConstraint->isValid()) { + return; + } + + $this->addError(ConstraintError::NOT(), $path); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/OneOfConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/OneOfConstraint.php new file mode 100644 index 000000000..cd8efd94e --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/OneOfConstraint.php @@ -0,0 +1,50 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'oneOf')) { + return; + } + + $matchedSchema = 0; + foreach ($schema->oneOf as $oneOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $oneOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + $matchedSchema++; + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + + if ($matchedSchema !== 1) { + $this->addError(ConstraintError::ONE_OF(), $path); + } else { + $this->errorBag()->reset(); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php new file mode 100644 index 000000000..5c705f4ae --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php @@ -0,0 +1,63 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'pattern')) { + return; + } + + if (!is_string($value)) { + return; + } + + $matchPattern = $this->createPregMatchPattern($schema->pattern); + if (preg_match($matchPattern, $value) === 1) { + return; + } + + $this->addError(ConstraintError::PATTERN(), $path, ['found' => $value, 'pattern' => $schema->pattern]); + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ + '\D' => '[^0-9]', + '\d' => '[0-9]', + '\p{digit}' => '[0-9]', + '\w' => '[A-Za-z0-9_]', + '\W' => '[^A-Za-z0-9_]', + '\s' => '[\s\x{200B}]', // Explicitly include zero width white space + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php new file mode 100644 index 000000000..969736bfb --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php @@ -0,0 +1,72 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'patternProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $properties = get_object_vars($value); + + foreach ($properties as $propertyName => $propertyValue) { + foreach ($schema->patternProperties as $patternPropertyRegex => $patternPropertySchema) { + $matchPattern = $this->createPregMatchPattern($patternPropertyRegex); + if (preg_match($matchPattern, (string) $propertyName)) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $patternPropertySchema, $path, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } + } + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ +// '\D' => '[^0-9]', + '\d' => '[0-9]', + '\p{digit}' => '[0-9]', +// '\w' => '[A-Za-z0-9_]', +// '\W' => '[^A-Za-z0-9_]', +// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesConstraint.php new file mode 100644 index 000000000..4a9c48081 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesConstraint.php @@ -0,0 +1,48 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'properties')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->properties as $propertyName => $propertySchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + if (!property_exists($value, $propertyName)) { + continue; + } + + $schemaConstraint->check($value->{$propertyName}, $propertySchema, $path, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php new file mode 100644 index 000000000..d621ecf26 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php @@ -0,0 +1,65 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'propertyNames')) { + return; + } + + if (!is_object($value)) { + return; + } + if ($schema->propertyNames === true) { + return; + } + + $propertyNames = get_object_vars($value); + + if ($schema->propertyNames === false) { + foreach ($propertyNames as $propertyName => $_) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'false', 'name' => $propertyName]); + } + + return; + } + + if (property_exists($schema->propertyNames, 'maxLength')) { + foreach ($propertyNames as $propertyName => $_) { + $length = mb_strlen($propertyName); + if ($length > $schema->propertyNames->maxLength) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'maxLength', 'length' => $length, 'name' => $propertyName]); + } + } + } + + if (property_exists($schema->propertyNames, 'pattern')) { + foreach ($propertyNames as $propertyName => $_) { + if (!preg_match('/' . str_replace('/', '\/', $schema->propertyNames->pattern) . '/', $propertyName)) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'pattern', 'name' => $propertyName]); + } + } + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RefConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RefConstraint.php new file mode 100644 index 000000000..f52bcda0a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RefConstraint.php @@ -0,0 +1,45 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, '$ref')) { + return; + } + + try { + $refSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema); + } catch (\Exception $e) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $refSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RequiredConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RequiredConstraint.php new file mode 100644 index 000000000..9e56ef221 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RequiredConstraint.php @@ -0,0 +1,58 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'required')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->required as $required) { + if (property_exists($value, $required)) { + continue; + } + + $this->addError(ConstraintError::REQUIRED(), $this->incrementPath($path, $required), ['property' => $required]); + } + } + + /** + * @todo refactor as this was only copied from UndefinedConstraint + * Bubble down the path + * + * @param JsonPointer|null $path Current path + * @param mixed $i What to append to the path + */ + protected function incrementPath(?JsonPointer $path, $i): JsonPointer + { + $path = $path ?? new JsonPointer(''); + + if ($i === null || $i === '') { + return $path; + } + + return $path->withPropertyPaths(array_merge($path->getPropertyPaths(), [$i])); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/TypeConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/TypeConstraint.php new file mode 100644 index 000000000..531a4f95b --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/TypeConstraint.php @@ -0,0 +1,50 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'type')) { + return; + } + + $schemaTypes = (array) $schema->type; + $valueType = strtolower(gettype($value)); + // All specific number types are a number + $valueIsNumber = $valueType === 'double' || $valueType === 'integer'; + // A float with zero fractional part is an integer + $isInteger = $valueIsNumber && fmod($value, 1.0) === 0.0; + + foreach ($schemaTypes as $type) { + if ($valueType === $type) { + return; + } + + if ($type === 'number' && $valueIsNumber) { + return; + } + if ($type === 'integer' && $isInteger) { + return; + } + } + + $this->addError(ConstraintError::TYPE(), $path, ['found' => $valueType, 'expected' => implode(', ', $schemaTypes)]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/UniqueItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/UniqueItemsConstraint.php new file mode 100644 index 000000000..93453401a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/UniqueItemsConstraint.php @@ -0,0 +1,48 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'uniqueItems')) { + return; + } + if (!is_array($value)) { + return; + } + + if ($schema->uniqueItems !== true) { + // If unique items not is true duplicates are allowed. + return; + } + + $count = count($value); + for ($x = 0; $x < $count - 1; $x++) { + for ($y = $x + 1; $y < $count; $y++) { + if (DeepComparer::isEqual($value[$x], $value[$y])) { + $this->addError(ConstraintError::UNIQUE_ITEMS(), $path); + + return; + } + } + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php new file mode 100644 index 000000000..8444d0535 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php @@ -0,0 +1,61 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'additionalItems')) { + return; + } + + if ($schema->additionalItems === true) { + return; + } + if ($schema->additionalItems === false && !property_exists($schema, 'items')) { + return; + } + + if (!is_array($value)) { + return; + } + if (!property_exists($schema, 'items')) { + return; + } + if (property_exists($schema, 'items') && is_object($schema->items)) { + return; + } + + $additionalItems = array_diff_key($value, property_exists($schema, 'items') ? $schema->items : []); + + foreach ($additionalItems as $propertyName => $propertyValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $schema->additionalItems, $path, $i); + + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addError(ConstraintError::ADDITIONAL_ITEMS(), $path, ['item' => $i, 'property' => $propertyName, 'additionalItems' => $schema->additionalItems]); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php new file mode 100644 index 000000000..27d5c4f64 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php @@ -0,0 +1,93 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'additionalProperties')) { + return; + } + + if ($schema->additionalProperties === true) { + return; + } + + if (!is_object($value)) { + return; + } + + $additionalProperties = get_object_vars($value); + + if (isset($schema->properties)) { + $additionalProperties = array_diff_key($additionalProperties, (array) $schema->properties); + } + + if (isset($schema->patternProperties)) { + $patterns = array_keys(get_object_vars($schema->patternProperties)); + + foreach ($additionalProperties as $key => $_) { + foreach ($patterns as $pattern) { + if (preg_match($this->createPregMatchPattern($pattern), (string) $key)) { + unset($additionalProperties[$key]); + break; + } + } + } + } + + if (is_object($schema->additionalProperties)) { + foreach ($additionalProperties as $key => $additionalPropertiesValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $path, $i); // @todo increment path + if ($schemaConstraint->isValid()) { + unset($additionalProperties[$key]); + } + } + } + + foreach ($additionalProperties as $key => $additionalPropertiesValue) { + $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $additionalPropertiesValue]); + } + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ +// '\D' => '[^0-9]', +// '\d' => '[0-9]', + '\p{digit}' => '\p{Nd}', +// '\w' => '[A-Za-z0-9_]', +// '\W' => '[^A-Za-z0-9_]', +// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space, + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php new file mode 100644 index 000000000..6aa9fdf2e --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php @@ -0,0 +1,42 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'allOf')) { + return; + } + + foreach ($schema->allOf as $allOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $allOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + continue; + } + $this->addError(ConstraintError::ALL_OF(), $path); + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php new file mode 100644 index 000000000..85242962a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php @@ -0,0 +1,51 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'anyOf')) { + return; + } + + foreach ($schema->anyOf as $anyOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + + try { + $schemaConstraint->check($value, $anyOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + $this->errorBag()->reset(); + + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } catch (ValidationException $e) { + } + } + + $this->addError(ConstraintError::ANY_OF(), $path); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php new file mode 100644 index 000000000..e0ff337b1 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php @@ -0,0 +1,35 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'const')) { + return; + } + + if (DeepComparer::isEqual($value, $schema->const)) { + return; + } + + $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php new file mode 100644 index 000000000..e2e48ffe2 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php @@ -0,0 +1,47 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'contains')) { + return; + } + + $properties = []; + if (!is_array($value)) { + return; + } + + foreach ($value as $propertyName => $propertyValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + + $schemaConstraint->check($propertyValue, $schema->contains, $path, $i); + if ($schemaConstraint->isValid()) { + return; + } + } + + $this->addError(ConstraintError::CONTAINS(), $path, ['contains' => $schema->contains]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php new file mode 100644 index 000000000..4e5e3f010 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php @@ -0,0 +1,55 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'contentMediaType') && !property_exists($schema, 'contentEncoding')) { + return; + } + if (!is_string($value)) { + return; + } + + $decodedValue = $value; + + if (property_exists($schema, 'contentEncoding')) { + if ($schema->contentEncoding === 'base64') { + if (!preg_match('/^[A-Za-z0-9+\/=]+$/', $decodedValue)) { + $this->addError(ConstraintError::CONTENT_ENCODING(), $path, ['contentEncoding' => $schema->contentEncoding]); + + return; + } + $decodedValue = base64_decode($decodedValue); + } + } + + if (property_exists($schema, 'contentMediaType')) { + if ($schema->contentMediaType === 'application/json') { + json_decode($decodedValue, false); + if (json_last_error() === JSON_ERROR_NONE) { + return; + } + } + + $this->addError(ConstraintError::CONTENT_MEDIA_TYPE(), $path, ['contentMediaType' => $schema->contentMediaType]); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php new file mode 100644 index 000000000..f4b03ff8c --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php @@ -0,0 +1,64 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'dependencies')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->dependencies as $dependant => $dependencies) { + if (!property_exists($value, $dependant)) { + continue; + } + if ($dependencies === true) { + continue; + } + if ($dependencies === false) { + $this->addError(ConstraintError::FALSE(), $path, ['dependant' => $dependant]); + continue; + } + + if (is_array($dependencies)) { + foreach ($dependencies as $dependency) { + if (property_exists($value, $dependant) && !property_exists($value, $dependency)) { + $this->addError(ConstraintError::DEPENDENCIES(), $path, ['dependant' => $dependant, 'dependency' => $dependency]); + } + } + } + + if (is_object($dependencies)) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $dependencies, $path, $i); + if (!$schemaConstraint->isValid()) { + $this->addErrors($schemaConstraint->getErrors()); + } + } + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php new file mode 100644 index 000000000..084ce96cc --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php @@ -0,0 +1,83 @@ +getSchemaStorage() : new SchemaStorage(), + $factory ? $factory->getUriRetriever() : new UriRetriever(), + $factory ? $factory->getConfig() : Constraint::CHECK_MODE_NORMAL + )); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (is_bool($schema)) { + if ($schema === false) { + $this->addError(ConstraintError::FALSE(), $path, []); + } + + return; + } + + // Apply defaults + $this->checkForKeyword('ref', $value, $schema, $path, $i); + $this->checkForKeyword('required', $value, $schema, $path, $i); + $this->checkForKeyword('contains', $value, $schema, $path, $i); + $this->checkForKeyword('properties', $value, $schema, $path, $i); + $this->checkForKeyword('propertyNames', $value, $schema, $path, $i); + $this->checkForKeyword('patternProperties', $value, $schema, $path, $i); + $this->checkForKeyword('type', $value, $schema, $path, $i); + $this->checkForKeyword('not', $value, $schema, $path, $i); + $this->checkForKeyword('dependencies', $value, $schema, $path, $i); + $this->checkForKeyword('allOf', $value, $schema, $path, $i); + $this->checkForKeyword('anyOf', $value, $schema, $path, $i); + $this->checkForKeyword('oneOf', $value, $schema, $path, $i); + $this->checkForKeyword('ifThenElse', $value, $schema, $path, $i); + + $this->checkForKeyword('additionalProperties', $value, $schema, $path, $i); + $this->checkForKeyword('items', $value, $schema, $path, $i); + $this->checkForKeyword('additionalItems', $value, $schema, $path, $i); + $this->checkForKeyword('uniqueItems', $value, $schema, $path, $i); + $this->checkForKeyword('minItems', $value, $schema, $path, $i); + $this->checkForKeyword('minProperties', $value, $schema, $path, $i); + $this->checkForKeyword('maxProperties', $value, $schema, $path, $i); + $this->checkForKeyword('minimum', $value, $schema, $path, $i); + $this->checkForKeyword('maximum', $value, $schema, $path, $i); + $this->checkForKeyword('minLength', $value, $schema, $path, $i); + $this->checkForKeyword('exclusiveMinimum', $value, $schema, $path, $i); + $this->checkForKeyword('maxItems', $value, $schema, $path, $i); + $this->checkForKeyword('maxLength', $value, $schema, $path, $i); + $this->checkForKeyword('exclusiveMaximum', $value, $schema, $path, $i); + $this->checkForKeyword('enum', $value, $schema, $path, $i); + $this->checkForKeyword('const', $value, $schema, $path, $i); + $this->checkForKeyword('multipleOf', $value, $schema, $path, $i); + $this->checkForKeyword('format', $value, $schema, $path, $i); + $this->checkForKeyword('pattern', $value, $schema, $path, $i); + $this->checkForKeyword('content', $value, $schema, $path, $i); + } + + /** + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkForKeyword(string $keyword, $value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor($keyword); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php new file mode 100644 index 000000000..6a81401d7 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php @@ -0,0 +1,41 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'enum')) { + return; + } + + foreach ($schema->enum as $enumCase) { + if (DeepComparer::isEqual($value, $enumCase)) { + return; + } + + if (is_numeric($value) && is_numeric($enumCase) && DeepComparer::isEqual((float) $value, (float) $enumCase)) { + return; + } + } + + $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php new file mode 100644 index 000000000..484768f8a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'exclusiveMaximum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value < $schema->exclusiveMaximum) { + return; + } + + $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['exclusiveMaximum' => $schema->exclusiveMaximum, 'found' => $value]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php new file mode 100644 index 000000000..c97c8c535 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'exclusiveMinimum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value > $schema->exclusiveMinimum) { + return; + } + + $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['exclusiveMinimum' => $schema->exclusiveMinimum, 'found' => $value]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php new file mode 100644 index 000000000..cb5599525 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php @@ -0,0 +1,48 @@ + + */ + protected $constraintMap = [ + 'schema' => Draft07Constraint::class, + 'additionalProperties' => AdditionalPropertiesConstraint::class, + 'additionalItems' => AdditionalItemsConstraint::class, + 'dependencies' => DependenciesConstraint::class, + 'type' => TypeConstraint::class, + 'const' => ConstConstraint::class, + 'enum' => EnumConstraint::class, + 'uniqueItems' => UniqueItemsConstraint::class, + 'minItems' => MinItemsConstraint::class, + 'minProperties' => MinPropertiesConstraint::class, + 'maxProperties' => MaxPropertiesConstraint::class, + 'minimum' => MinimumConstraint::class, + 'maximum' => MaximumConstraint::class, + 'exclusiveMinimum' => ExclusiveMinimumConstraint::class, + 'minLength' => MinLengthConstraint::class, + 'maxLength' => MaxLengthConstraint::class, + 'maxItems' => MaxItemsConstraint::class, + 'exclusiveMaximum' => ExclusiveMaximumConstraint::class, + 'multipleOf' => MultipleOfConstraint::class, + 'required' => RequiredConstraint::class, + 'format' => FormatConstraint::class, + 'anyOf' => AnyOfConstraint::class, + 'allOf' => AllOfConstraint::class, + 'oneOf' => OneOfConstraint::class, + 'not' => NotConstraint::class, + 'ifThenElse' => IfThenElseConstraint::class, + 'contains' => ContainsConstraint::class, + 'propertyNames' => PropertiesNamesConstraint::class, + 'patternProperties' => PatternPropertiesConstraint::class, + 'pattern' => PatternConstraint::class, + 'properties' => PropertiesConstraint::class, + 'items' => ItemsConstraint::class, + 'ref' => RefConstraint::class, + 'content' => ContentConstraint::class, + ]; +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php new file mode 100644 index 000000000..8bcd99f73 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php @@ -0,0 +1,341 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'format')) { + return; + } + + if (!is_string($value)) { + return; + } + + switch ($schema->format) { + case 'date': + if (!$this->validateDateTime($value, 'Y-m-d')) { + $this->addError(ConstraintError::FORMAT_DATE(), $path, ['date' => $value, 'format' => $schema->format]); + } + break; + case 'time': + if (!$this->validateDateTime($value, 'H:i:sp') && !$this->validateDateTime($value, 'H:i:s.up')) { + $this->addError(ConstraintError::FORMAT_TIME(), $path, ['time' => $value, 'format' => $schema->format]); + } + break; + case 'date-time': + if (!$this->validateRfc3339DateTime($value)) { + $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, ['dateTime' => $value, 'format' => $schema->format]); + } + break; + case 'utc-millisec': + if (!$this->validateDateTime($value, 'U')) { + $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, ['value' => $value, 'format' => $schema->format]); + } + break; + case 'regex': + if (!$this->validateRegex($value)) { + $this->addError(ConstraintError::FORMAT_REGEX(), $path, ['value' => $value, 'format' => $schema->format]); + } + break; + case 'ip-address': + case 'ipv4': + if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4) === null) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + case 'ipv6': + if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6) === null) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + case 'color': + if (!$this->validateColor($value)) { + $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]); + } + break; + case 'style': + if (!$this->validateStyle($value)) { + $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]); + } + break; + case 'phone': + if (!$this->validatePhone($value)) { + $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]); + } + break; + case 'uri': + if (!UriValidator::isValid($value)) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + + case 'uriref': + case 'uri-reference': + if (!(UriValidator::isValid($value) || RelativeReferenceValidator::isValid($value))) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + case 'uri-template': + if (!$this->validateUriTemplate($value)) { + $this->addError(ConstraintError::FORMAT_URI_TEMPLATE(), $path, ['format' => $schema->format]); + } + break; + + case 'email': + if (filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE) === null) { + $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]); + } + break; + case 'host-name': + case 'hostname': + if (!$this->validateHostname($value)) { + $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); + } + break; + case 'idn-hostname': + if (!$this->validateInternationalizedHostname($value)) { + $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); + } + break; + case 'json-pointer': + if (!$this->validateJsonPointer($value)) { + $this->addError(ConstraintError::FORMAT_JSON_POINTER(), $path, ['format' => $schema->format]); + } + break; + default: + break; + } + } + + private function validateDateTime(string $datetime, string $format): bool + { + $datetime = strtoupper($datetime); // Cleanup for lowercase z + $isLeap = substr($datetime, 6, 2) === '60'; + $input = $datetime; + + // Correct for leap second + if ($isLeap) { + $input = sprintf('%s59%s', substr($datetime, 0, 6), substr($datetime, 8)); + } + + $dt = \DateTimeImmutable::createFromFormat($format, $input); + if (!$dt) { + return false; + } + + // Handle invalid timezone offsets + $timezoneOffset = $dt->getTimezone()->getOffset($dt); + if ($timezoneOffset >= 86400 || $timezoneOffset <= -86400) { + return false; + } + + $expected = $dt->format($format); + // Correct for trailing zeros on microseconds + if ($format === 'H:i:s.up') { + $expected = sprintf( + '%s%s', + rtrim($dt->format('H:i:s.u'), '0'), + $dt->format('p') + ); + } + // Correct back for leap seconds + if ($isLeap) { + // Only when 23:59:59 in UTC + $utcDT = $dt->setTimezone(new DateTimeZone('UTC')); + if ($utcDT->format('H:i:s') !== '23:59:59') { + return false; + } + + $expected = sprintf('%s60%s', substr($expected, 0, 6), substr($expected, 8)); + } + + return $datetime === $expected; + } + + private function validateRegex(string $regex): bool + { + return preg_match(self::jsonPatternToPhpRegex($regex), '') !== false; + } + + /** + * Transform a JSON pattern into a PCRE regex + */ + private static function jsonPatternToPhpRegex(string $pattern): string + { + return '~' . str_replace('~', '\\~', $pattern) . '~u'; + } + + private function validateColor(string $color): bool + { + if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia', + 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', + 'red', 'silver', 'teal', 'white', 'yellow'])) { + return true; + } + + return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) !== false; + } + + private function validateStyle(string $style): bool + { + $properties = explode(';', rtrim($style, ';')); + $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); + + return empty($invalidEntries); + } + + private function validatePhone(string $phone): bool + { + return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) !== false; + } + + private function validateHostname(string $host): bool + { + $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/'; + + return preg_match($hostnameRegex, $host) === 1; + } + + private function validateInternationalizedHostname(string $host): bool + { + if ($host === '') { + return false; + } + $host = rtrim($host, '.'); + $labels = explode('.', $host); + $asciiLabels = []; + + if ($labels === false) { + return false; + } + + foreach ($labels as $label) { + if ($label === '') { + return false; + } + + // CONTEXTJ / CONTEXTO checks + if ( + // Greek KERAIA U+0375 + preg_match('/\x{0375}/u', $label) && + !preg_match('/\x{0375}[\x{0370}-\x{03FF}]/u', $label) + ) { + return false; + } + + // Hebrew GERESH / GERSHAYIM U+05F3 / U+05F4 + if (preg_match('/[\x{05F3}\x{05F4}]/u', $label) && + !preg_match('/[\x{0590}-\x{05FF}][\x{05F3}\x{05F4}]/u', $label) + ) { + return false; + } + + // Katakana middle dot U+30FB + if (str_contains($label, "\u{30FB}") && + !preg_match('/[\x{30A0}-\x{30FF}]/u', $label) + ) { + return false; + } + + // Arabic digit mixing + $hasArabicIndic = preg_match('/[\x{0660}-\x{0669}]/u', $label); + $hasExtArabicIndic = preg_match('/[\x{06F0}-\x{06F9}]/u', $label); + if ($hasArabicIndic && $hasExtArabicIndic) { + return false; + } + + // Devanagari danda U+0964 / U+0965 + if (preg_match('/[\x{0964}\x{0965}]/u', $label) && + !preg_match('/[\x{0900}-\x{097F}]/u', $label) + ) { + return false; + } + + // ZWNJ / ZWJ U+200C / U+200D + if (preg_match('/[\x{200C}\x{200D}]/u', $label)) { + return false; + } + + $ascii = idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46); + if ($ascii === false) { + return false; + } + // DNS label length + if (strlen($ascii) > 63) { + return false; + } + // LDH rule (after IDNA) + if (!preg_match('/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i', $ascii)) { + return false; + } + $asciiLabels[] = $ascii; + } + + // Total hostname length (ASCII) + $asciiHost = implode('.', $asciiLabels); + + return strlen($asciiHost) <= 253; + } + + private function validateJsonPointer(string $value): bool + { + // Must be empty or start with a forward slash + if ($value !== '' && $value[0] !== '/') { + return false; + } + + // Split into reference tokens and check for invalid escape sequences + $tokens = explode('/', $value); + array_shift($tokens); // remove leading empty part due to leading slash + + foreach ($tokens as $token) { + // "~" must only be followed by "0" or "1" + if (preg_match('/~(?![01])/', $token)) { + return false; + } + } + + return true; + } + + private function validateRfc3339DateTime(string $value): bool + { + $dateTime = Rfc3339::createFromString($value); + if (is_null($dateTime)) { + return false; + } + + // Compare value and date result to be equal + return true; + } + + private function validateUriTemplate(string $value): bool + { + return preg_match( + '/^(?:[^\{\}]*|\{[a-zA-Z0-9_:%\/\.~\-\+\*]+\})*$/', + $value + ) === 1; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php new file mode 100644 index 000000000..d3fe82c7e --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php @@ -0,0 +1,67 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'if')) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $ifSchema = $schema->if; + + if (!is_bool($ifSchema)) { + $schemaConstraint->check($value, $ifSchema, $path, $i); + $meetsIfConditions = $schemaConstraint->isValid(); + $schemaConstraint->reset(); + } else { + $meetsIfConditions = $ifSchema; + } + + if ($meetsIfConditions) { + if (!property_exists($schema, 'then')) { + return; + } + + $schemaConstraint->check($value, $schema->then, $path, $i); + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + + return; + } + + if (!property_exists($schema, 'else')) { + return; + } + + $schemaConstraint->check($value, $schema->else, $path, $i); + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php new file mode 100644 index 000000000..16abae328 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php @@ -0,0 +1,52 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'items')) { + return; + } + + if (!is_array($value)) { + return; + } + + foreach ($value as $propertyName => $propertyValue) { + $itemSchema = $schema->items; + if (is_array($itemSchema)) { + if (!array_key_exists($propertyName, $itemSchema)) { + continue; + } + + $itemSchema = $itemSchema[$propertyName]; + } + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $itemSchema, $path, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php new file mode 100644 index 000000000..101a0cf98 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxItems')) { + return; + } + + if (!is_array($value)) { + return; + } + + $count = count($value); + if ($count <= $schema->maxItems) { + return; + } + + $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => $count]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php new file mode 100644 index 000000000..9043cf92d --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxLength')) { + return; + } + + if (!is_string($value)) { + return; + } + + $length = mb_strlen($value); + if ($length <= $schema->maxLength) { + return; + } + + $this->addError(ConstraintError::LENGTH_MAX(), $path, ['maxLength' => $schema->maxLength, 'found' => $length]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php new file mode 100644 index 000000000..106e98b02 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $count = count(get_object_vars($value)); + if ($count <= $schema->maxProperties) { + return; + } + + $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $schema->maxProperties, 'found' => $count]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php new file mode 100644 index 000000000..dd9e1b982 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maximum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value <= $schema->maximum) { + return; + } + + $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum, 'found' => $value]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php new file mode 100644 index 000000000..026170392 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minItems')) { + return; + } + + if (!is_array($value)) { + return; + } + + $count = count($value); + if ($count >= $schema->minItems) { + return; + } + + $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => $count]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php new file mode 100644 index 000000000..5c7243a82 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minLength')) { + return; + } + + if (!is_string($value)) { + return; + } + + $length = mb_strlen($value); + if ($length >= $schema->minLength) { + return; + } + + $this->addError(ConstraintError::LENGTH_MIN(), $path, ['minLength' => $schema->minLength, 'found' => $length]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php new file mode 100644 index 000000000..07014fb6a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $count = count(get_object_vars($value)); + if ($count >= $schema->minProperties) { + return; + } + + $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $schema->minProperties, 'found' => $count]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php new file mode 100644 index 000000000..5f1b480c7 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minimum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value >= $schema->minimum) { + return; + } + + $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum, 'found' => $value]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php new file mode 100644 index 000000000..134cfda2d --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php @@ -0,0 +1,54 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'multipleOf')) { + return; + } + + if (!is_int($schema->multipleOf) && !is_float($schema->multipleOf) && $schema->multipleOf <= 0.0) { + return; + } + + if (!is_int($value) && !is_float($value)) { + return; + } + + if ($this->isMultipleOf($value, $schema->multipleOf)) { + return; + } + + $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf, 'found' => $value]); + } + + /** + * @param int|float $number1 + * @param int|float $number2 + */ + private function isMultipleOf($number1, $number2): bool + { + $modulus = ($number1 - round($number1 / $number2) * $number2); + $precision = 0.0000000001; + + return -$precision < $modulus && $modulus < $precision; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php new file mode 100644 index 000000000..fdba81bfe --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php @@ -0,0 +1,40 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'not')) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $schema->not, $path, $i); + + if (!$schemaConstraint->isValid()) { + return; + } + + $this->addError(ConstraintError::NOT(), $path); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php new file mode 100644 index 000000000..4523f73cb --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php @@ -0,0 +1,50 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'oneOf')) { + return; + } + + $matchedSchema = 0; + foreach ($schema->oneOf as $oneOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $oneOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + $matchedSchema++; + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + + if ($matchedSchema !== 1) { + $this->addError(ConstraintError::ONE_OF(), $path); + } else { + $this->errorBag()->reset(); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php new file mode 100644 index 000000000..062b61d3f --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php @@ -0,0 +1,63 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'pattern')) { + return; + } + + if (!is_string($value)) { + return; + } + + $matchPattern = $this->createPregMatchPattern($schema->pattern); + if (preg_match($matchPattern, $value) === 1) { + return; + } + + $this->addError(ConstraintError::PATTERN(), $path, ['found' => $value, 'pattern' => $schema->pattern]); + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ + '\D' => '[^0-9]', + '\d' => '[0-9]', + '\p{digit}' => '[0-9]', + '\w' => '[A-Za-z0-9_]', + '\W' => '[^A-Za-z0-9_]', + '\s' => '[\s\x{200B}]', // Explicitly include zero width white space + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php new file mode 100644 index 000000000..d821abf18 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php @@ -0,0 +1,72 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'patternProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $properties = get_object_vars($value); + + foreach ($properties as $propertyName => $propertyValue) { + foreach ($schema->patternProperties as $patternPropertyRegex => $patternPropertySchema) { + $matchPattern = $this->createPregMatchPattern($patternPropertyRegex); + if (preg_match($matchPattern, (string) $propertyName)) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $patternPropertySchema, $path, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } + } + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ +// '\D' => '[^0-9]', + '\d' => '[0-9]', + '\p{digit}' => '[0-9]', +// '\w' => '[A-Za-z0-9_]', +// '\W' => '[^A-Za-z0-9_]', +// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php new file mode 100644 index 000000000..25c55e666 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php @@ -0,0 +1,48 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'properties')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->properties as $propertyName => $propertySchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + if (!property_exists($value, $propertyName)) { + continue; + } + + $schemaConstraint->check($value->{$propertyName}, $propertySchema, $path, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php new file mode 100644 index 000000000..7bf9ea744 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php @@ -0,0 +1,65 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'propertyNames')) { + return; + } + + if (!is_object($value)) { + return; + } + if ($schema->propertyNames === true) { + return; + } + + $propertyNames = get_object_vars($value); + + if ($schema->propertyNames === false) { + foreach ($propertyNames as $propertyName => $_) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'false', 'name' => $propertyName]); + } + + return; + } + + if (property_exists($schema->propertyNames, 'maxLength')) { + foreach ($propertyNames as $propertyName => $_) { + $length = mb_strlen($propertyName); + if ($length > $schema->propertyNames->maxLength) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'maxLength', 'length' => $length, 'name' => $propertyName]); + } + } + } + + if (property_exists($schema->propertyNames, 'pattern')) { + foreach ($propertyNames as $propertyName => $_) { + if (!preg_match('/' . str_replace('/', '\/', $schema->propertyNames->pattern) . '/', $propertyName)) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'pattern', 'name' => $propertyName]); + } + } + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php new file mode 100644 index 000000000..9ec6efdac --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php @@ -0,0 +1,45 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, '$ref')) { + return; + } + + try { + $refSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema); + } catch (\Exception $e) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $refSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php new file mode 100644 index 000000000..99a5b55af --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php @@ -0,0 +1,58 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'required')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->required as $required) { + if (property_exists($value, $required)) { + continue; + } + + $this->addError(ConstraintError::REQUIRED(), $this->incrementPath($path, $required), ['property' => $required]); + } + } + + /** + * @todo refactor as this was only copied from UndefinedConstraint + * Bubble down the path + * + * @param JsonPointer|null $path Current path + * @param mixed $i What to append to the path + */ + protected function incrementPath(?JsonPointer $path, $i): JsonPointer + { + $path = $path ?? new JsonPointer(''); + + if ($i === null || $i === '') { + return $path; + } + + return $path->withPropertyPaths(array_merge($path->getPropertyPaths(), [$i])); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php new file mode 100644 index 000000000..9601954f1 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php @@ -0,0 +1,50 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'type')) { + return; + } + + $schemaTypes = (array) $schema->type; + $valueType = strtolower(gettype($value)); + // All specific number types are a number + $valueIsNumber = $valueType === 'double' || $valueType === 'integer'; + // A float with zero fractional part is an integer + $isInteger = $valueIsNumber && fmod($value, 1.0) === 0.0; + + foreach ($schemaTypes as $type) { + if ($valueType === $type) { + return; + } + + if ($type === 'number' && $valueIsNumber) { + return; + } + if ($type === 'integer' && $isInteger) { + return; + } + } + + $this->addError(ConstraintError::TYPE(), $path, ['found' => $valueType, 'expected' => implode(', ', $schemaTypes)]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php new file mode 100644 index 000000000..5363b5c58 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php @@ -0,0 +1,48 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'uniqueItems')) { + return; + } + if (!is_array($value)) { + return; + } + + if ($schema->uniqueItems !== true) { + // If unique items not is true duplicates are allowed. + return; + } + + $count = count($value); + for ($x = 0; $x < $count - 1; $x++) { + for ($y = $x + 1; $y < $count; $y++) { + if (DeepComparer::isEqual($value[$x], $value[$y])) { + $this->addError(ConstraintError::UNIQUE_ITEMS(), $path); + + return; + } + } + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php new file mode 100644 index 000000000..210963855 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php @@ -0,0 +1,59 @@ + + * @author Bruno Prieto Reis + */ +class EnumConstraint extends Constraint +{ + /** + * {@inheritdoc} + */ + public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void + { + // Only validate enum if the attribute exists + if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) { + return; + } + $type = gettype($element); + + foreach ($schema->enum as $enum) { + $enumType = gettype($enum); + + if ($enumType === 'object' + && $type === 'array' + && $this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) + && DeepComparer::isEqual((object) $element, $enum) + ) { + return; + } + + if (($type === $enumType) && DeepComparer::isEqual($element, $enum)) { + return; + } + + if (is_numeric($element) && is_numeric($enum) && DeepComparer::isEqual((float) $element, (float) $enum)) { + return; + } + } + + $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php new file mode 100644 index 000000000..8041dbaf3 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php @@ -0,0 +1,237 @@ + + */ + private $checkMode = Constraint::CHECK_MODE_NORMAL; + + /** + * @var array + * @phpstan-var array, TypeCheck\TypeCheckInterface> + */ + private $typeCheck = []; + + /** + * @var int-mask-of Validation context + */ + protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION; + + /** + * The default dialect used for strict mode (Constraint::CHECK_MODE_STRICT) when the schema is without a schema property + * + * @var string + */ + private $defaultDialect = DraftIdentifiers::DRAFT_6; + + /** + * @var array + */ + protected $constraintMap = [ + 'array' => 'JsonSchema\Constraints\CollectionConstraint', + 'collection' => 'JsonSchema\Constraints\CollectionConstraint', + 'object' => 'JsonSchema\Constraints\ObjectConstraint', + 'type' => 'JsonSchema\Constraints\TypeConstraint', + 'undefined' => 'JsonSchema\Constraints\UndefinedConstraint', + 'string' => 'JsonSchema\Constraints\StringConstraint', + 'number' => 'JsonSchema\Constraints\NumberConstraint', + 'enum' => 'JsonSchema\Constraints\EnumConstraint', + 'const' => 'JsonSchema\Constraints\ConstConstraint', + 'format' => 'JsonSchema\Constraints\FormatConstraint', + 'schema' => 'JsonSchema\Constraints\SchemaConstraint', + 'validator' => 'JsonSchema\Validator', + 'draft06' => Drafts\Draft06\Draft06Constraint::class, + 'draft07' => Drafts\Draft07\Draft07Constraint::class, + ]; + + /** + * @var array + */ + private $instanceCache = []; + + /** + * @phpstan-param int-mask-of $checkMode + */ + public function __construct( + ?SchemaStorageInterface $schemaStorage = null, + ?UriRetrieverInterface $uriRetriever = null, + int $checkMode = Constraint::CHECK_MODE_NORMAL + ) { + // set provided config options + $this->setConfig($checkMode); + + $this->uriRetriever = $uriRetriever ?: new UriRetriever(); + $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever); + } + + /** + * Set config values + * + * @param int $checkMode Set checkMode options - does not preserve existing flags + * @phpstan-param int-mask-of $checkMode + */ + public function setConfig(int $checkMode = Constraint::CHECK_MODE_NORMAL): void + { + $this->checkMode = $checkMode; + } + + /** + * Enable checkMode flags + * + * @phpstan-param int-mask-of $options + */ + public function addConfig(int $options): void + { + $this->checkMode |= $options; + } + + /** + * Disable checkMode flags + * + * @phpstan-param int-mask-of $options + */ + public function removeConfig(int $options): void + { + $this->checkMode &= ~$options; + } + + /** + * Get checkMode option + * + * @param int|null $options Options to get, if null then return entire bitmask + * @phpstan-param int-mask-of|null $options Options to get, if null then return entire bitmask + * + * @phpstan-return int-mask-of + */ + public function getConfig(?int $options = null): int + { + if ($options === null) { + return $this->checkMode; + } + + return $this->checkMode & $options; + } + + public function getUriRetriever(): UriRetrieverInterface + { + return $this->uriRetriever; + } + + public function getSchemaStorage(): SchemaStorageInterface + { + return $this->schemaStorage; + } + + public function getTypeCheck(): TypeCheck\TypeCheckInterface + { + if (!isset($this->typeCheck[$this->checkMode])) { + $this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST) + ? new TypeCheck\LooseTypeCheck() + : new TypeCheck\StrictTypeCheck(); + } + + return $this->typeCheck[$this->checkMode]; + } + + public function setConstraintClass(string $name, string $class): Factory + { + // Ensure class exists + if (!class_exists($class)) { + throw new InvalidArgumentException('Unknown constraint ' . $name); + } + // Ensure class is appropriate + if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) { + throw new InvalidArgumentException('Invalid class ' . $name); + } + $this->constraintMap[$name] = $class; + + return $this; + } + + /** + * Create a constraint instance for the given constraint name. + * + * @param string $constraintName + * + * @throws InvalidArgumentException if is not possible create the constraint instance + * + * @return ConstraintInterface&BaseConstraint + * @phpstan-return ConstraintInterface&BaseConstraint + */ + public function createInstanceFor($constraintName) + { + if (!isset($this->constraintMap[$constraintName])) { + throw new InvalidArgumentException('Unknown constraint ' . $constraintName); + } + + if (!isset($this->instanceCache[$constraintName])) { + $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this); + } + + return clone $this->instanceCache[$constraintName]; + } + + /** + * Get the error context + * + * @return int-mask-of + */ + public function getErrorContext(): int + { + return $this->errorContext; + } + + /** + * Set the error context + * + * @param int-mask-of $errorContext + */ + public function setErrorContext(int $errorContext): void + { + $this->errorContext = $errorContext; + } + + public function getDefaultDialect(): string + { + return $this->defaultDialect; + } + + public function setDefaultDialect(string $defaultDialect): void + { + $this->defaultDialect = $defaultDialect; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php new file mode 100644 index 000000000..c38488dd1 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php @@ -0,0 +1,218 @@ + + * + * @see http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 + */ +class FormatConstraint extends Constraint +{ + /** + * {@inheritdoc} + */ + public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) { + return; + } + + switch ($schema->format) { + case 'date': + if (is_string($element) && !$date = $this->validateDateTime($element, 'Y-m-d')) { + $this->addError(ConstraintError::FORMAT_DATE(), $path, [ + 'date' => $element, + 'format' => $schema->format + ] + ); + } + break; + + case 'time': + if (is_string($element) && !$this->validateDateTime($element, 'H:i:s')) { + $this->addError(ConstraintError::FORMAT_TIME(), $path, [ + 'time' => json_encode($element), + 'format' => $schema->format, + ] + ); + } + break; + + case 'date-time': + if (is_string($element) && null === Rfc3339::createFromString($element)) { + $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, [ + 'dateTime' => json_encode($element), + 'format' => $schema->format + ] + ); + } + break; + + case 'utc-millisec': + if (!$this->validateDateTime($element, 'U')) { + $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, [ + 'value' => $element, + 'format' => $schema->format]); + } + break; + + case 'regex': + if (!$this->validateRegex($element)) { + $this->addError(ConstraintError::FORMAT_REGEX(), $path, [ + 'value' => $element, + 'format' => $schema->format + ] + ); + } + break; + + case 'color': + if (!$this->validateColor($element)) { + $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]); + } + break; + + case 'style': + if (!$this->validateStyle($element)) { + $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]); + } + break; + + case 'phone': + if (!$this->validatePhone($element)) { + $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]); + } + break; + + case 'uri': + if (is_string($element) && !UriValidator::isValid($element)) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + + case 'uriref': + case 'uri-reference': + if (is_string($element) && !(UriValidator::isValid($element) || RelativeReferenceValidator::isValid($element))) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + + case 'email': + if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE)) { + $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]); + } + break; + + case 'ip-address': + case 'ipv4': + if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + + case 'ipv6': + if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + + case 'host-name': + case 'hostname': + if (!$this->validateHostname($element)) { + $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); + } + break; + + default: + // Empty as it should be: + // The value of this keyword is called a format attribute. It MUST be a string. + // A format attribute can generally only validate a given set of instance types. + // If the type of the instance to validate is not in this set, validation for + // this format attribute and instance SHOULD succeed. + // http://json-schema.org/latest/json-schema-validation.html#anchor105 + break; + } + } + + protected function validateDateTime($datetime, $format) + { + $dt = \DateTime::createFromFormat($format, (string) $datetime); + + if (!$dt) { + return false; + } + + if ($datetime === $dt->format($format)) { + return true; + } + + return false; + } + + protected function validateRegex($regex) + { + if (!is_string($regex)) { + return true; + } + + return false !== @preg_match(self::jsonPatternToPhpRegex($regex), ''); + } + + protected function validateColor($color) + { + if (!is_string($color)) { + return true; + } + + if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia', + 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', + 'red', 'silver', 'teal', 'white', 'yellow'])) { + return true; + } + + return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color); + } + + protected function validateStyle($style) + { + $properties = explode(';', rtrim($style, ';')); + $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); + + return empty($invalidEntries); + } + + protected function validatePhone($phone) + { + return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone); + } + + protected function validateHostname($host) + { + if (!is_string($host)) { + return true; + } + + // RFC 1035: labels are max 63 chars (1 start + 0-61 middle + 1 end) + $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/'; + + return preg_match($hostnameRegex, $host); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php new file mode 100644 index 000000000..e1b2ffc3a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php @@ -0,0 +1,84 @@ + + * @author Bruno Prieto Reis + */ +class NumberConstraint extends Constraint +{ + /** + * {@inheritdoc} + */ + public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void + { + // Verify minimum + if (isset($schema->exclusiveMinimum)) { + if (isset($schema->minimum)) { + if ($schema->exclusiveMinimum && $element <= $schema->minimum) { + $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['minimum' => $schema->minimum]); + } elseif ($element < $schema->minimum) { + $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]); + } + } else { + $this->addError(ConstraintError::MISSING_MINIMUM(), $path); + } + } elseif (isset($schema->minimum) && $element < $schema->minimum) { + $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]); + } + + // Verify maximum + if (isset($schema->exclusiveMaximum)) { + if (isset($schema->maximum)) { + if ($schema->exclusiveMaximum && $element >= $schema->maximum) { + $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['maximum' => $schema->maximum]); + } elseif ($element > $schema->maximum) { + $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]); + } + } else { + $this->addError(ConstraintError::MISSING_MAXIMUM(), $path); + } + } elseif (isset($schema->maximum) && $element > $schema->maximum) { + $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]); + } + + // Verify divisibleBy - Draft v3 + if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { + $this->addError(ConstraintError::DIVISIBLE_BY(), $path, ['divisibleBy' => $schema->divisibleBy]); + } + + // Verify multipleOf - Draft v4 + if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { + $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf]); + } + + $this->checkFormat($element, $schema, $path, $i); + } + + private function fmod($number1, $number2) + { + $modulus = ($number1 - round($number1 / $number2) * $number2); + $precision = 0.0000000001; + + if (-$precision < $modulus && $modulus < $precision) { + return 0.0; + } + + return $modulus; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php new file mode 100644 index 000000000..0e1252e30 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php @@ -0,0 +1,190 @@ + List of properties to which a default value has been applied + */ + protected $appliedDefaults = []; + + /** + * {@inheritdoc} + * + * @param list $appliedDefaults + */ + public function check( + &$element, + $schema = null, + ?JsonPointer $path = null, + $properties = null, + $additionalProp = null, + $patternProperties = null, + $appliedDefaults = [] + ): void { + if ($element instanceof UndefinedConstraint) { + return; + } + + $this->appliedDefaults = $appliedDefaults; + + $matches = []; + if ($patternProperties) { + // validate the element pattern properties + $matches = $this->validatePatternProperties($element, $path, $patternProperties); + } + + if ($properties) { + // validate the element properties + $this->validateProperties($element, $properties, $path); + } + + // validate additional element properties & constraints + $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp); + } + + public function validatePatternProperties($element, ?JsonPointer $path, $patternProperties) + { + $matches = []; + foreach ($patternProperties as $pregex => $schema) { + $fullRegex = self::jsonPatternToPhpRegex($pregex); + + // Validate the pattern before using it to test for matches + if (@preg_match($fullRegex, '') === false) { + $this->addError(ConstraintError::PREGEX_INVALID(), $path, ['pregex' => $pregex]); + continue; + } + foreach ($element as $i => $value) { + if (preg_match($fullRegex, (string) $i)) { + $matches[] = $i; + $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults)); + } + } + } + + return $matches; + } + + /** + * Validates the element properties + * + * @param \StdClass $element Element to validate + * @param array $matches Matches from patternProperties (if any) + * @param \StdClass $schema ObjectConstraint definition + * @param JsonPointer|null $path Current test path + * @param \StdClass $properties Properties + * @param mixed $additionalProp Additional properties + */ + public function validateElement($element, $matches, $schema = null, ?JsonPointer $path = null, + $properties = null, $additionalProp = null) + { + $this->validateMinMaxConstraint($element, $schema, $path); + + foreach ($element as $i => $value) { + $definition = $this->getProperty($properties, $i); + + // no additional properties allowed + if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) { + $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['property' => $i]); + } + + // additional properties defined + if (!in_array($i, $matches) && $additionalProp && !$definition) { + if ($additionalProp === true) { + $this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults)); + } else { + $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults)); + } + } + + // property requires presence of another + $require = $this->getProperty($definition, 'requires'); + if ($require && !$this->getProperty($element, $require)) { + $this->addError(ConstraintError::REQUIRES(), $path, [ + 'property' => $i, + 'requiredProperty' => $require + ]); + } + + $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined')); + if (is_object($property)) { + $this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path); + } + } + } + + /** + * Validates the definition properties + * + * @param \stdClass $element Element to validate + * @param \stdClass $properties Property definitions + * @param JsonPointer|null $path Path? + */ + public function validateProperties(&$element, $properties = null, ?JsonPointer $path = null) + { + $undefinedConstraint = $this->factory->createInstanceFor('undefined'); + + foreach ($properties as $i => $value) { + $property = &$this->getProperty($element, $i, $undefinedConstraint); + $definition = $this->getProperty($properties, $i); + + if (is_object($definition)) { + // Undefined constraint will check for is_object() and quit if is not - so why pass it? + $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults)); + } + } + } + + /** + * retrieves a property from an object or array + * + * @param mixed $element Element to validate + * @param string $property Property to retrieve + * @param mixed $fallback Default value if property is not found + * + * @return mixed + */ + protected function &getProperty(&$element, $property, $fallback = null) + { + if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) { + return $element[$property]; + } elseif (is_object($element) && property_exists($element, (string) $property)) { + return $element->$property; + } + + return $fallback; + } + + /** + * validating minimum and maximum property constraints (if present) against an element + * + * @param \stdClass $element Element to validate + * @param \stdClass $objectDefinition ObjectConstraint definition + * @param JsonPointer|null $path Path to test? + */ + protected function validateMinMaxConstraint($element, $objectDefinition, ?JsonPointer $path = null) + { + if (!$this->getTypeCheck()::isObject($element)) { + return; + } + + // Verify minimum number of properties + if (isset($objectDefinition->minProperties) && is_int($objectDefinition->minProperties)) { + if ($this->getTypeCheck()->propertyCount($element) < max(0, $objectDefinition->minProperties)) { + $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $objectDefinition->minProperties]); + } + } + // Verify maximum number of properties + if (isset($objectDefinition->maxProperties) && is_int($objectDefinition->maxProperties)) { + if ($this->getTypeCheck()->propertyCount($element) > max(0, $objectDefinition->maxProperties)) { + $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $objectDefinition->maxProperties]); + } + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php new file mode 100644 index 000000000..28d15da91 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php @@ -0,0 +1,98 @@ + + * @author Bruno Prieto Reis + */ +class SchemaConstraint extends Constraint +{ + private const DEFAULT_SCHEMA_SPEC = DraftIdentifiers::DRAFT_4; + + /** + * {@inheritdoc} + */ + public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if ($schema !== null) { + // passed schema + $validationSchema = $schema; + } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) { + // inline schema + $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty); + } else { + throw new InvalidArgumentException('no schema found to verify against'); + } + + // cast array schemas to object + if (is_array($validationSchema)) { + $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema); + } + + // validate schema against whatever is defined in $validationSchema->$schema. If no + // schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04). + if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) { + if (!$this->getTypeCheck()->isObject($validationSchema)) { + throw new RuntimeException('Cannot validate the schema of a non-object'); + } + if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) { + $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema'); + } else { + $schemaSpec = self::DEFAULT_SCHEMA_SPEC; + } + + // get the spec schema + $schemaStorage = $this->factory->getSchemaStorage(); + if (!$this->getTypeCheck()->isObject($schemaSpec)) { + $schemaSpec = $schemaStorage->getSchema($schemaSpec); + } + + // save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA + $initialErrorCount = $this->numErrors(); + $initialConfig = $this->factory->getConfig(); + $initialContext = $this->factory->getErrorContext(); + $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS); + $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST); + $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION); + + // validate schema + try { + $this->check($validationSchema, $schemaSpec); + } catch (\Exception $e) { + if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) { + throw new InvalidSchemaException('Schema did not pass validation', 0, $e); + } + } + if ($this->numErrors() > $initialErrorCount) { + $this->addError(ConstraintError::INVALID_SCHEMA(), $path); + } + + // restore the initial config + $this->factory->setConfig($initialConfig); + $this->factory->setErrorContext($initialContext); + } + + // validate element against $validationSchema + $this->checkUndefined($element, $validationSchema, $path, $i); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php new file mode 100644 index 000000000..7b811614a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php @@ -0,0 +1,63 @@ + + * @author Bruno Prieto Reis + */ +class StringConstraint extends Constraint +{ + /** + * {@inheritdoc} + */ + public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void + { + // Verify maxLength + if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) { + $this->addError(ConstraintError::LENGTH_MAX(), $path, [ + 'maxLength' => $schema->maxLength, + ]); + } + + //verify minLength + if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) { + $this->addError(ConstraintError::LENGTH_MIN(), $path, [ + 'minLength' => $schema->minLength, + ]); + } + + // Verify a regex pattern + if (isset($schema->pattern) && !preg_match(self::jsonPatternToPhpRegex($schema->pattern), $element)) { + $this->addError(ConstraintError::PATTERN(), $path, [ + 'pattern' => $schema->pattern, + ]); + } + + $this->checkFormat($element, $schema, $path, $i); + } + + private function strlen($string) + { + if (extension_loaded('mbstring')) { + return mb_strlen($string, mb_detect_encoding($string)); + } + + // mbstring is present on all test platforms, so strlen() can be ignored for coverage + return strlen($string); // @codeCoverageIgnore + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php new file mode 100644 index 000000000..8f9f349b7 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php @@ -0,0 +1,70 @@ +{$property}; + } + + return $value[$property]; + } + + public static function propertySet(&$value, $property, $data) + { + if (is_object($value)) { + $value->{$property} = $data; + } else { + $value[$property] = $data; + } + } + + public static function propertyExists($value, $property) + { + if (is_object($value)) { + return property_exists($value, $property); + } + + return is_array($value) && array_key_exists($property, $value); + } + + public static function propertyCount($value) + { + if (is_object($value)) { + return count(get_object_vars($value)); + } + + return count($value); + } + + /** + * Check if the provided array is associative or not + * + * @param array $arr + * + * @return bool + */ + private static function isAssociativeArray($arr) + { + return array_keys($arr) !== range(0, count($arr) - 1); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php new file mode 100644 index 000000000..b0aa813a7 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php @@ -0,0 +1,42 @@ +{$property}; + } + + public static function propertySet(&$value, $property, $data) + { + $value->{$property} = $data; + } + + public static function propertyExists($value, $property) + { + return property_exists($value, $property); + } + + public static function propertyCount($value) + { + if (!is_object($value)) { + return 0; + } + + return count(get_object_vars($value)); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php new file mode 100644 index 000000000..0333b0aa3 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php @@ -0,0 +1,20 @@ + + * @author Bruno Prieto Reis + */ +class TypeConstraint extends Constraint +{ + /** + * @var array|string[] type wordings for validation error messages + */ + public static $wording = [ + 'integer' => 'an integer', + 'number' => 'a number', + 'boolean' => 'a boolean', + 'object' => 'an object', + 'array' => 'an array', + 'string' => 'a string', + 'null' => 'a null', + 'any' => null, // validation of 'any' is always true so is not needed in message wording + 0 => null, // validation of a false-y value is always true, so not needed as well + ]; + + /** + * {@inheritdoc} + */ + public function check(&$value = null, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $type = isset($schema->type) ? $schema->type : null; + $isValid = false; + $coerce = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES); + $earlyCoerce = $this->factory->getConfig(self::CHECK_MODE_EARLY_COERCE); + $wording = []; + + if (is_array($type)) { + $this->validateTypesArray($value, $type, $wording, $isValid, $path, $coerce && $earlyCoerce); + if (!$isValid && $coerce && !$earlyCoerce) { + $this->validateTypesArray($value, $type, $wording, $isValid, $path, true); + } + } elseif (is_object($type)) { + $this->checkUndefined($value, $type, $path); + + return; + } else { + $isValid = $this->validateType($value, $type, $coerce && $earlyCoerce); + if (!$isValid && $coerce && !$earlyCoerce) { + $isValid = $this->validateType($value, $type, true); + } + } + + if ($isValid === false) { + if (!is_array($type)) { + $this->validateTypeNameWording($type); + $wording[] = self::$wording[$type]; + } + $this->addError(ConstraintError::TYPE(), $path, [ + 'found' => gettype($value), + 'expected' => $this->implodeWith($wording, ', ', 'or') + ]); + } + } + + /** + * Validates the given $value against the array of types in $type. Sets the value + * of $isValid to true, if at least one $type mateches the type of $value or the value + * passed as $isValid is already true. + * + * @param mixed $value Value to validate + * @param array $type TypeConstraints to check against + * @param array $validTypesWording An array of wordings of the valid types of the array $type + * @param bool $isValid The current validation value + * @param ?JsonPointer $path + * @param bool $coerce + */ + protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path, $coerce = false) + { + foreach ($type as $tp) { + // already valid, so no need to waste cycles looping over everything + if ($isValid) { + return; + } + + // $tp can be an object, if it's a schema instead of a simple type, validate it + // with a new type constraint + if (is_object($tp)) { + if (!$isValid) { + $validator = $this->factory->createInstanceFor('type'); + $subSchema = new \stdClass(); + $subSchema->type = $tp; + $validator->check($value, $subSchema, $path, null); + $error = $validator->getErrors(); + $isValid = !(bool) $error; + $validTypesWording[] = self::$wording['object']; + } + } else { + $this->validateTypeNameWording($tp); + $validTypesWording[] = self::$wording[$tp]; + if (!$isValid) { + $isValid = $this->validateType($value, $tp, $coerce); + } + } + } + } + + /** + * Implodes the given array like implode() with turned around parameters and with the + * difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of + * $delimiter. + * + * @param array $elements The elements to implode + * @param string $delimiter The delimiter to use + * @param bool $listEnd The last delimiter to use (defaults to $delimiter) + * + * @return string + */ + protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false) + { + if ($listEnd === false || !isset($elements[1])) { + return implode($delimiter, $elements); + } + $lastElement = array_slice($elements, -1); + $firsElements = join($delimiter, array_slice($elements, 0, -1)); + $implodedElements = array_merge([$firsElements], $lastElement); + + return join(" $listEnd ", $implodedElements); + } + + /** + * Validates the given $type, if there's an associated self::$wording. If not, throws an + * exception. + * + * @param string $type The type to validate + * + * @throws StandardUnexpectedValueException + */ + protected function validateTypeNameWording($type) + { + if (!array_key_exists($type, self::$wording)) { + throw new StandardUnexpectedValueException( + sprintf( + 'No wording for %s available, expected wordings are: [%s]', + var_export($type, true), + implode(', ', array_filter(self::$wording))) + ); + } + } + + /** + * Verifies that a given value is of a certain type + * + * @param mixed $value Value to validate + * @param string $type TypeConstraint to check against + * + * @throws InvalidArgumentException + * + * @return bool + */ + protected function validateType(&$value, $type, $coerce = false) + { + //mostly the case for inline schema + if (!$type) { + return true; + } + + if ('any' === $type) { + return true; + } + + if ('object' === $type) { + return $this->getTypeCheck()->isObject($value); + } + + if ('array' === $type) { + if ($coerce) { + $value = $this->toArray($value); + } + + return $this->getTypeCheck()->isArray($value); + } + + if ('integer' === $type) { + if ($coerce) { + $value = $this->toInteger($value); + } + + return is_int($value); + } + + if ('number' === $type) { + if ($coerce) { + $value = $this->toNumber($value); + } + + return is_numeric($value) && !is_string($value); + } + + if ('boolean' === $type) { + if ($coerce) { + $value = $this->toBoolean($value); + } + + return is_bool($value); + } + + if ('string' === $type) { + if ($coerce) { + $value = $this->toString($value); + } + + return is_string($value); + } + + if ('null' === $type) { + if ($coerce) { + $value = $this->toNull($value); + } + + return is_null($value); + } + + throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type); + } + + /** + * Converts a value to boolean. For example, "true" becomes true. + * + * @param mixed $value The value to convert to boolean + * + * @return bool|mixed + */ + protected function toBoolean($value) + { + if ($value === 1 || $value === 'true') { + return true; + } + if (is_null($value) || $value === 0 || $value === 'false') { + return false; + } + if ($this->getTypeCheck()->isArray($value) && count($value) === 1) { + return $this->toBoolean(reset($value)); + } + + return $value; + } + + /** + * Converts a value to a number. For example, "4.5" becomes 4.5. + * + * @param mixed $value the value to convert to a number + * + * @return int|float|mixed + */ + protected function toNumber($value) + { + if (is_numeric($value)) { + return $value + 0; // cast to number + } + if (is_bool($value) || is_null($value)) { + return (int) $value; + } + if ($this->getTypeCheck()->isArray($value) && count($value) === 1) { + return $this->toNumber(reset($value)); + } + + return $value; + } + + /** + * Converts a value to an integer. For example, "4" becomes 4. + * + * @param mixed $value + * + * @return int|mixed + */ + protected function toInteger($value) + { + $numberValue = $this->toNumber($value); + if (is_numeric($numberValue) && (int) $numberValue == $numberValue) { + return (int) $numberValue; // cast to number + } + + return $value; + } + + /** + * Converts a value to an array containing that value. For example, [4] becomes 4. + * + * @param mixed $value + * + * @return array|mixed + */ + protected function toArray($value) + { + if (is_scalar($value) || is_null($value)) { + return [$value]; + } + + return $value; + } + + /** + * Convert a value to a string representation of that value. For example, null becomes "". + * + * @param mixed $value + * + * @return string|mixed + */ + protected function toString($value) + { + if (is_numeric($value)) { + return "$value"; + } + if ($value === true) { + return 'true'; + } + if ($value === false) { + return 'false'; + } + if (is_null($value)) { + return ''; + } + if ($this->getTypeCheck()->isArray($value) && count($value) === 1) { + return $this->toString(reset($value)); + } + + return $value; + } + + /** + * Convert a value to a null. For example, 0 becomes null. + * + * @param mixed $value + * + * @return null|mixed + */ + protected function toNull($value) + { + if ($value === 0 || $value === false || $value === '') { + return null; + } + if ($this->getTypeCheck()->isArray($value) && count($value) === 1) { + return $this->toNull(reset($value)); + } + + return $value; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php new file mode 100644 index 000000000..bb0946d8e --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -0,0 +1,439 @@ + List of properties to which a default value has been applied + */ + protected $appliedDefaults = []; + + /** + * {@inheritdoc} + * */ + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null, bool $fromDefault = false): void + { + if (is_null($schema) || !is_object($schema)) { + return; + } + + $path = $this->incrementPath($path, $i); + if ($fromDefault) { + $path->setFromDefault(); + } + + // check special properties + $this->validateCommonProperties($value, $schema, $path, $i); + + // check allOf, anyOf, and oneOf properties + $this->validateOfProperties($value, $schema, $path, ''); + + // check known types + $this->validateTypes($value, $schema, $path, $i); + } + + /** + * Validates the value against the types + * + * @param mixed $value + * @param mixed $schema + * @param JsonPointer $path + * @param string $i + */ + public function validateTypes(&$value, $schema, JsonPointer $path, $i = null) + { + // check array + if ($this->getTypeCheck()->isArray($value)) { + $this->checkArray($value, $schema, $path, $i); + } + + // check object + if (LooseTypeCheck::isObject($value)) { + // object processing should always be run on assoc arrays, + // so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST + // is not set (i.e. don't use $this->getTypeCheck() here). + $this->checkObject( + $value, + $schema, + $path, + isset($schema->properties) ? $schema->properties : null, + isset($schema->additionalProperties) ? $schema->additionalProperties : null, + isset($schema->patternProperties) ? $schema->patternProperties : null, + $this->appliedDefaults + ); + } + + // check string + if (is_string($value)) { + $this->checkString($value, $schema, $path, $i); + } + + // check numeric + if (is_numeric($value)) { + $this->checkNumber($value, $schema, $path, $i); + } + + // check enum + if (isset($schema->enum)) { + $this->checkEnum($value, $schema, $path, $i); + } + + // check const + if (isset($schema->const)) { + $this->checkConst($value, $schema, $path, $i); + } + } + + /** + * Validates common properties + * + * @param mixed $value + * @param mixed $schema + * @param JsonPointer $path + * @param string $i + */ + protected function validateCommonProperties(&$value, $schema, JsonPointer $path, $i = '') + { + // if it extends another schema, it must pass that schema as well + if (isset($schema->extends)) { + if (is_string($schema->extends)) { + $schema->extends = $this->validateUri($schema, $schema->extends); + } + if (is_array($schema->extends)) { + foreach ($schema->extends as $extends) { + $this->checkUndefined($value, $extends, $path, $i); + } + } else { + $this->checkUndefined($value, $schema->extends, $path, $i); + } + } + + // Apply default values from schema + if (!$path->fromDefault()) { + $this->applyDefaultValues($value, $schema, $path); + } + + // Verify required values + if ($this->getTypeCheck()->isObject($value)) { + if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) { + // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...] + foreach ($schema->required as $required) { + if (!$this->getTypeCheck()->propertyExists($value, $required)) { + $this->addError( + ConstraintError::REQUIRED(), + $this->incrementPath($path, $required), [ + 'property' => $required + ] + ); + } + } + } elseif (isset($schema->required) && !is_array($schema->required)) { + // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true} + if ($schema->required && $value instanceof self) { + $propertyPaths = $path->getPropertyPaths(); + $propertyName = end($propertyPaths); + $this->addError(ConstraintError::REQUIRED(), $path, ['property' => $propertyName]); + } + } else { + // if the value is both undefined and not required, skip remaining checks + // in this method which assume an actual, defined instance when validating. + if ($value instanceof self) { + return; + } + } + } + + // Verify type + if (!($value instanceof self)) { + $this->checkType($value, $schema, $path, $i); + } + + // Verify disallowed items + if (isset($schema->disallow)) { + $initErrors = $this->getErrors(); + + $typeSchema = new \stdClass(); + $typeSchema->type = $schema->disallow; + $this->checkType($value, $typeSchema, $path); + + // if no new errors were raised it must be a disallowed value + if (count($this->getErrors()) == count($initErrors)) { + $this->addError(ConstraintError::DISALLOW(), $path); + } else { + $this->errors = $initErrors; + } + } + + if (isset($schema->not)) { + $initErrors = $this->getErrors(); + $this->checkUndefined($value, $schema->not, $path, $i); + + // if no new errors were raised then the instance validated against the "not" schema + if (count($this->getErrors()) == count($initErrors)) { + $this->addError(ConstraintError::NOT(), $path); + } else { + $this->errors = $initErrors; + } + } + + // Verify that dependencies are met + if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) { + $this->validateDependencies($value, $schema->dependencies, $path); + } + } + + /** + * Check whether a default should be applied for this value + * + * @param mixed $schema + * @param mixed $parentSchema + * @param bool $requiredOnly + * + * @return bool + */ + private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null) + { + // required-only mode is off + if (!$requiredOnly) { + return true; + } + // draft-04 required is set + if ( + $name !== null + && isset($parentSchema->required) + && is_array($parentSchema->required) + && in_array($name, $parentSchema->required) + ) { + return true; + } + // draft-03 required is set + if (isset($schema->required) && !is_array($schema->required) && $schema->required) { + return true; + } + // default case + return false; + } + + /** + * Apply default values + * + * @param mixed $value + * @param mixed $schema + * @param JsonPointer $path + */ + protected function applyDefaultValues(&$value, $schema, $path): void + { + // only apply defaults if feature is enabled + if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) { + return; + } + + if (is_bool($schema)) { + return; + } + + // apply defaults if appropriate + $requiredOnly = (bool) $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS); + if (isset($schema->properties) && LooseTypeCheck::isObject($value)) { + // $value is an object or assoc array, and properties are defined - treat as an object + foreach ($schema->properties as $currentProperty => $propertyDefinition) { + $propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition); + if (is_bool($propertyDefinition)) { + continue; + } + if ( + !LooseTypeCheck::propertyExists($value, $currentProperty) + && property_exists($propertyDefinition, 'default') + && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema) + ) { + // assign default value + if (is_object($propertyDefinition->default)) { + LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default); + } else { + LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default); + } + $this->appliedDefaults[] = $currentProperty; + } + } + } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) { + $items = []; + if (LooseTypeCheck::isArray($schema->items)) { + $items = $schema->items; + } elseif (isset($schema->minItems) && count($value) < $schema->minItems) { + $items = array_fill(count($value), $schema->minItems - count($value), $schema->items); + } + // $value is an array, and items are defined - treat as plain array + foreach ($items as $currentItem => $itemDefinition) { + $itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition); + if (is_bool($itemDefinition)) { + continue; + } + + if ( + !array_key_exists($currentItem, $value) + && property_exists($itemDefinition, 'default') + && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) { + if (is_object($itemDefinition->default)) { + $value[$currentItem] = clone $itemDefinition->default; + } else { + $value[$currentItem] = $itemDefinition->default; + } + } + $path->setFromDefault(); + } + } elseif ( + $value instanceof self + && property_exists($schema, 'default') + && $this->shouldApplyDefaultValue($requiredOnly, $schema)) { + // $value is a leaf, not a container - apply the default directly + $value = is_object($schema->default) ? clone $schema->default : $schema->default; + $path->setFromDefault(); + } + } + + /** + * Validate allOf, anyOf, and oneOf properties + * + * @param mixed $value + * @param mixed $schema + * @param JsonPointer $path + * @param string $i + */ + protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '') + { + // Verify type + if ($value instanceof self) { + return; + } + + if (isset($schema->allOf)) { + $isValid = true; + foreach ($schema->allOf as $allOf) { + $initErrors = $this->getErrors(); + $this->checkUndefined($value, $allOf, $path, $i); + $isValid = $isValid && (count($this->getErrors()) == count($initErrors)); + } + if (!$isValid) { + $this->addError(ConstraintError::ALL_OF(), $path); + } + } + + if (isset($schema->anyOf)) { + $isValid = false; + $startErrors = $this->getErrors(); + $coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS); + + foreach ($schema->anyOf as $anyOf) { + $initErrors = $this->getErrors(); + try { + $anyOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value; + $this->checkUndefined($anyOfValue, $anyOf, $path, $i); + if ($isValid = (count($this->getErrors()) === count($initErrors))) { + $value = $anyOfValue; + break; + } + } catch (ValidationException $e) { + $isValid = false; + } + } + if (!$isValid) { + $this->addError(ConstraintError::ANY_OF(), $path); + } else { + $this->errors = $startErrors; + } + } + + if (isset($schema->oneOf)) { + $allErrors = []; + $matchedSchemas = []; + $startErrors = $this->getErrors(); + $coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS); + + foreach ($schema->oneOf as $oneOf) { + try { + $this->errors = []; + + $oneOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value; + $this->checkUndefined($oneOfValue, $oneOf, $path, $i); + if (count($this->getErrors()) === 0) { + $matchedSchemas[] = ['schema' => $oneOf, 'value' => $oneOfValue]; + } + $allErrors = array_merge($allErrors, array_values($this->getErrors())); + } catch (ValidationException $e) { + // deliberately do nothing here - validation failed, but we want to check + // other schema options in the OneOf field. + } + } + if (count($matchedSchemas) !== 1) { + $this->addErrors(array_merge($allErrors, $startErrors)); + $this->addError(ConstraintError::ONE_OF(), $path); + } else { + $this->errors = $startErrors; + $value = $matchedSchemas[0]['value']; + } + } + } + + /** + * Validate dependencies + * + * @param mixed $value + * @param mixed $dependencies + * @param JsonPointer $path + * @param string $i + */ + protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '') + { + foreach ($dependencies as $key => $dependency) { + if ($this->getTypeCheck()->propertyExists($value, $key)) { + if (is_string($dependency)) { + // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"} + if (!$this->getTypeCheck()->propertyExists($value, $dependency)) { + $this->addError(ConstraintError::DEPENDENCIES(), $path, [ + 'key' => $key, + 'dependency' => $dependency + ]); + } + } elseif (is_array($dependency)) { + // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]} + foreach ($dependency as $d) { + if (!$this->getTypeCheck()->propertyExists($value, $d)) { + $this->addError(ConstraintError::DEPENDENCIES(), $path, [ + 'key' => $key, + 'dependency' => $dependency + ]); + } + } + } elseif (is_object($dependency)) { + // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}} + $this->checkUndefined($value, $dependency, $path, $i); + } + } + } + } + + protected function validateUri($schema, $schemaUri = null) + { + $resolver = new UriResolver(); + $retriever = $this->factory->getUriRetriever(); + + $jsonSchema = null; + if ($resolver->isValid($schemaUri)) { + $schemaId = property_exists($schema, 'id') ? $schema->id : null; + $jsonSchema = $retriever->retrieve($schemaId, $schemaUri); + } + + return $jsonSchema; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php b/vendor/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php new file mode 100644 index 000000000..e062b6a8a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php @@ -0,0 +1,63 @@ + */ + private const MAPPING = [ + self::DRAFT_3 => 'draft03', + self::DRAFT_4 => 'draft04', + self::DRAFT_6 => 'draft06', + self::DRAFT_7 => 'draft07', + self::DRAFT_2019_09 => 'draft2019-09', + self::DRAFT_2020_12 => 'draft2020-12', + ]; + + private const FALLBACK_MAPPING = [ + 'draft3' => self::DRAFT_3, + 'draft4' => self::DRAFT_4, + 'draft6' => self::DRAFT_6, + 'draft7' => self::DRAFT_7, + ]; + + public function toConstraintName(): string + { + return self::MAPPING[$this->getValue()]; + } + + public static function fromConstraintName(string $name): DraftIdentifiers + { + $reverseMap = array_flip(self::MAPPING); + if (!array_key_exists($name, $reverseMap)) { + if (array_key_exists($name, self::FALLBACK_MAPPING)) { + return DraftIdentifiers::byValue(self::FALLBACK_MAPPING[$name]); + } + throw new \InvalidArgumentException("$name is not a valid constraint name."); + } + + return DraftIdentifiers::byValue($reverseMap[$name]); + } + + public function withoutFragment(): string + { + return rtrim($this->getValue(), '#'); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBag.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBag.php new file mode 100644 index 000000000..e68ece4d6 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBag.php @@ -0,0 +1,109 @@ +}, + * "context": int-mask-of + * } + * @phpstan-type ErrorList list + */ +class ErrorBag +{ + /** @var Factory */ + private $factory; + + /** @var ErrorList */ + private $errors = []; + + /** + * @var int-mask-of All error types that have occurred + */ + protected $errorMask = Validator::ERROR_NONE; + + public function __construct(Factory $factory) + { + $this->factory = $factory; + } + + public function reset(): void + { + $this->errors = []; + $this->errorMask = Validator::ERROR_NONE; + } + + /** @return ErrorList */ + public function getErrors(): array + { + return $this->errors; + } + + /** @param array $more */ + public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void + { + $message = $constraint->getMessage(); + $name = $constraint->getValue(); + /** @var Error $error */ + $error = [ + 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), + 'pointer' => ltrim((string) ($path ?: new JsonPointer('')), '#'), + 'message' => ucfirst(vsprintf($message, array_map(static function ($val) { + if (is_scalar($val)) { + return is_bool($val) ? var_export($val, true) : $val; + } + + return json_encode($val); + }, array_values($more)))), + 'constraint' => [ + 'name' => $name, + 'params' => $more + ], + 'context' => $this->factory->getErrorContext(), + ]; + + if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { + throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); + } + $this->errors[] = $error; + /* @see https://github.com/phpstan/phpstan/issues/9384 */ + $this->errorMask |= $error['context']; // @phpstan-ignore assign.propertyType + } + + /** @param ErrorList $errors */ + public function addErrors(array $errors): void + { + if (!$errors) { + return; + } + + $this->errors = array_merge($this->errors, $errors); + $errorMask = &$this->errorMask; + array_walk($errors, static function ($error) use (&$errorMask) { + $errorMask |= $error['context']; + }); + } + + private function convertJsonPointerIntoPropertyPath(JsonPointer $pointer): string + { + $result = array_map( + static function ($path) { + return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path); + }, + $pointer->getPropertyPaths() + ); + + return trim(implode('', $result), '.'); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBagProxy.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBagProxy.php new file mode 100644 index 000000000..e9d584123 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBagProxy.php @@ -0,0 +1,66 @@ +errorBag()->getErrors(); + } + + /** @param ErrorList $errors */ + public function addErrors(array $errors): void + { + $this->errorBag()->addErrors($errors); + } + + /** + * @param array $more more array elements to add to the error + */ + public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void + { + $this->errorBag()->addError($constraint, $path, $more); + } + + public function isValid(): bool + { + return $this->errorBag()->getErrors() === []; + } + + protected function initialiseErrorBag(Factory $factory): ErrorBag + { + if (is_null($this->errorBag)) { + $this->errorBag = new ErrorBag($factory); + } + + return $this->errorBag; + } + + protected function errorBag(): ErrorBag + { + if (is_null($this->errorBag)) { + throw new \RuntimeException('ErrorBag not initialized'); + } + + return $this->errorBag; + } + + public function __clone() + { + $this->errorBag()->reset(); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php new file mode 100644 index 000000000..e674fa6f8 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php @@ -0,0 +1,163 @@ + + */ +class JsonPointer +{ + /** @var string */ + private $filename; + + /** @var string[] */ + private $propertyPaths = []; + + /** + * @var bool Whether the value at this path was set from a schema default + */ + private $fromDefault = false; + + /** + * @param string $value + * + * @throws InvalidArgumentException when $value is not a string + */ + public function __construct($value) + { + if (!is_string($value)) { + throw new InvalidArgumentException('Ref value must be a string'); + } + + $splitRef = explode('#', $value, 2); + $this->filename = $splitRef[0]; + if (array_key_exists(1, $splitRef)) { + $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]); + } + } + + /** + * @param string $propertyPathString + * + * @return string[] + */ + private function decodePropertyPaths($propertyPathString) + { + $paths = []; + foreach (explode('/', trim($propertyPathString, '/')) as $path) { + $path = $this->decodePath($path); + if (is_string($path) && '' !== $path) { + $paths[] = $path; + } + } + + return $paths; + } + + /** + * @return array + */ + private function encodePropertyPaths() + { + return array_map( + [$this, 'encodePath'], + $this->getPropertyPaths() + ); + } + + /** + * @param string $path + * + * @return string + */ + private function decodePath($path) + { + return strtr($path, ['~1' => '/', '~0' => '~', '%25' => '%']); + } + + /** + * @param string $path + * + * @return string + */ + private function encodePath($path) + { + return strtr($path, ['/' => '~1', '~' => '~0', '%' => '%25']); + } + + /** + * @return string + */ + public function getFilename() + { + return $this->filename; + } + + /** + * @return string[] + */ + public function getPropertyPaths() + { + return $this->propertyPaths; + } + + /** + * @param array $propertyPaths + * + * @return JsonPointer + */ + public function withPropertyPaths(array $propertyPaths) + { + $new = clone $this; + $new->propertyPaths = array_map(function ($p): string { return (string) $p; }, $propertyPaths); + + return $new; + } + + /** + * @return string + */ + public function getPropertyPathAsString() + { + return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/'); + } + + /** + * @return string + */ + public function __toString() + { + return $this->getFilename() . $this->getPropertyPathAsString(); + } + + /** + * Mark the value at this path as being set from a schema default + */ + public function setFromDefault(): void + { + $this->fromDefault = true; + } + + /** + * Check whether the value at this path was set from a schema default + * + * @return bool + */ + public function fromDefault() + { + return $this->fromDefault; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Enum.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Enum.php new file mode 100644 index 000000000..ef8cb2856 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Enum.php @@ -0,0 +1,9 @@ + + */ +class UnresolvableJsonPointerException extends InvalidArgumentException +{ +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php new file mode 100644 index 000000000..9170a3fcc --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php @@ -0,0 +1,19 @@ + + */ +class ObjectIterator implements \Iterator, \Countable +{ + /** @var object */ + private $object; + + /** @var int */ + private $position = 0; + + /** @var array */ + private $data = []; + + /** @var bool */ + private $initialized = false; + + /** + * @param object $object + */ + public function __construct($object) + { + $this->object = $object; + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function current() + { + $this->initialize(); + + return $this->data[$this->position]; + } + + /** + * {@inheritdoc} + */ + public function next(): void + { + $this->initialize(); + $this->position++; + } + + /** + * {@inheritdoc} + */ + public function key(): int + { + $this->initialize(); + + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function valid(): bool + { + $this->initialize(); + + return isset($this->data[$this->position]); + } + + /** + * {@inheritdoc} + */ + public function rewind(): void + { + $this->initialize(); + $this->position = 0; + } + + /** + * {@inheritdoc} + */ + public function count(): int + { + $this->initialize(); + + return count($this->data); + } + + /** + * Initializer + */ + private function initialize() + { + if (!$this->initialized) { + $this->data = $this->buildDataFromObject($this->object); + $this->initialized = true; + } + } + + /** + * @param object $object + * + * @return array + */ + private function buildDataFromObject($object) + { + $result = []; + + $stack = new \SplStack(); + $stack->push($object); + + while (!$stack->isEmpty()) { + $current = $stack->pop(); + if (is_object($current)) { + array_push($result, $current); + } + + foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) { + if (is_object($propertyValue) || is_array($propertyValue)) { + $stack->push($propertyValue); + } + } + } + + return $result; + } + + /** + * @param object|array $item + * + * @return array + */ + private function getDataFromItem($item) + { + if (!is_object($item) && !is_array($item)) { + return []; + } + + return is_object($item) ? get_object_vars($item) : $item; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php new file mode 100644 index 000000000..2a07060df --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php @@ -0,0 +1,60 @@ +setTimezone(new \DateTimeZone('+00:00')); + $oneSecond = new \DateInterval('PT1S'); + + // handle leap seconds + if ($matches[4] === '60' && $utcDateTime->sub($oneSecond)->format('H:i:s') === '23:59:59') { + $dateTime = $dateTime->sub($oneSecond); + $matches[1] = str_replace(':60', ':59', $matches[1]); + } + + // Ensure we still have the same year, month, day, hour, minutes and seconds to ensure no rollover took place. + if ($dateTime->format($inputHasTSeparator ? 'Y-m-d\TH:i:s' : 'Y-m-d H:i:s') !== $matches[1]) { + return null; + } + + $mutable = \DateTime::createFromFormat('U.u', $dateTime->format('U.u')); + if ($mutable === false) { + throw new \RuntimeException('Unable to create DateTime from DateTimeImmutable'); + } + + $mutable->setTimezone($dateTime->getTimezone()); + + return $mutable; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php b/vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php new file mode 100644 index 000000000..2c5832bf9 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php @@ -0,0 +1,245 @@ +uriRetriever = $uriRetriever ?: new UriRetriever(); + $this->uriResolver = $uriResolver ?: new UriResolver(); + } + + /** + * @return UriRetrieverInterface + */ + public function getUriRetriever() + { + return $this->uriRetriever; + } + + /** + * @return UriResolverInterface + */ + public function getUriResolver() + { + return $this->uriResolver; + } + + /** + * {@inheritdoc} + */ + public function addSchema(string $id, $schema = null): void + { + if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) { + // if the schema was user-provided to Validator and is still null, then assume this is + // what the user intended, as there's no way for us to retrieve anything else. User-supplied + // schemas do not have an associated URI when passed via Validator::validate(). + $schema = $this->uriRetriever->retrieve($id); + } + + // cast array schemas to object + if (is_array($schema)) { + $schema = BaseConstraint::arrayToObjectRecursive($schema); + } + + // workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format) + // see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367 + if (is_object($schema) && property_exists($schema, 'id')) { + if ($schema->id === DraftIdentifiers::DRAFT_4) { + $schema->properties->id->format = 'uri-reference'; + } elseif ($schema->id === DraftIdentifiers::DRAFT_3) { + $schema->properties->id->format = 'uri-reference'; + $schema->properties->{'$ref'}->format = 'uri-reference'; + } + } + + $this->scanForSubschemas($schema, $id); + + // resolve references + $this->expandRefs($schema, $id); + + $this->schemas[$id] = $schema; + } + + /** + * Recursively resolve all references against the provided base + * + * @param mixed $schema + * @param list $propertyStack + */ + private function expandRefs(&$schema, ?string $parentId = null, array $propertyStack = []): void + { + if (!is_object($schema)) { + if (is_array($schema)) { + foreach ($schema as &$member) { + $this->expandRefs($member, $parentId); + } + } + + return; + } + + if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) { + $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $parentId)); + $schema->{'$ref'} = (string) $refPointer; + } + + $parentProperty = array_slice($propertyStack, -1)[0] ?? ''; + foreach ($schema as $propertyName => &$member) { + if ($parentProperty !== 'properties' && in_array($propertyName, ['enum', 'const'])) { + // Enum and const don't allow $ref as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/445 + continue; + } + + $schemaId = $this->findSchemaIdInObject($schema); + $childId = $parentId; + if (is_string($schemaId) && $childId !== $schemaId) { + $childId = $this->uriResolver->resolve($schemaId, $childId); + } + + $clonedPropertyStack = $propertyStack; + $clonedPropertyStack[] = $propertyName; + $this->expandRefs($member, $childId, $clonedPropertyStack); + } + } + + /** + * {@inheritdoc} + */ + public function getSchema(string $id) + { + if (!array_key_exists($id, $this->schemas)) { + $this->addSchema($id); + } + + return $this->schemas[$id]; + } + + /** + * {@inheritdoc} + */ + public function resolveRef(string $ref, $resolveStack = []) + { + $jsonPointer = new JsonPointer($ref); + + // resolve filename for pointer + $fileName = $jsonPointer->getFilename(); + if (!strlen($fileName)) { + throw new UnresolvableJsonPointerException(sprintf( + "Could not resolve fragment '%s': no file is defined", + $jsonPointer->getPropertyPathAsString() + )); + } + + // get & process the schema + $refSchema = $this->getSchema($fileName); + foreach ($jsonPointer->getPropertyPaths() as $path) { + $path = urldecode($path); + if (is_object($refSchema) && property_exists($refSchema, $path)) { + $refSchema = $this->resolveRefSchema($refSchema->{$path}, $resolveStack); + } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) { + $refSchema = $this->resolveRefSchema($refSchema[$path], $resolveStack); + } else { + throw new UnresolvableJsonPointerException(sprintf( + 'File: %s is found, but could not resolve fragment: %s', + $jsonPointer->getFilename(), + $jsonPointer->getPropertyPathAsString() + )); + } + } + + return $refSchema; + } + + /** + * {@inheritdoc} + */ + public function resolveRefSchema($refSchema, $resolveStack = []) + { + if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) { + if (in_array($refSchema, $resolveStack, true)) { + throw new UnresolvableJsonPointerException(sprintf( + 'Dereferencing a pointer to %s results in an infinite loop', + $refSchema->{'$ref'} + )); + } + $resolveStack[] = $refSchema; + + return $this->resolveRef($refSchema->{'$ref'}, $resolveStack); + } + + if (is_object($refSchema) && array_keys(get_object_vars($refSchema)) === ['']) { + $refSchema = get_object_vars($refSchema)['']; + } + + return $refSchema; + } + + /** + * @param mixed $schema + */ + private function scanForSubschemas($schema, string $parentId): void + { + if (!$schema instanceof \stdClass && !is_array($schema)) { + return; + } + + foreach ($schema as $propertyName => $potentialSubSchema) { + if (!is_object($potentialSubSchema)) { + if (is_array($potentialSubSchema)) { + foreach ($potentialSubSchema as $potentialSubSchemaItem) { + $this->scanForSubschemas($potentialSubSchemaItem, $parentId); + } + } + continue; + } + + $potentialSubSchemaId = $this->findSchemaIdInObject($potentialSubSchema); + if (is_string($potentialSubSchemaId) && property_exists($potentialSubSchema, 'type')) { + // Enum and const don't allow id as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/471 + if (in_array($propertyName, ['enum', 'const'])) { + continue; + } + + // $id in unknow keywords is not valid + if (in_array($propertyName, [])) { + continue; + } + + // Found sub schema + $this->addSchema($this->uriResolver->resolve($potentialSubSchemaId, $parentId), $potentialSubSchema); + } + + $this->scanForSubschemas($potentialSubSchema, $parentId); + } + } + + private function findSchemaIdInObject(object $schema): ?string + { + if (property_exists($schema, 'id') && is_string($schema->id)) { + return $schema->id; + } + if (property_exists($schema, '$id') && is_string($schema->{'$id'})) { + return $schema->{'$id'}; + } + + return null; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php b/vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php new file mode 100644 index 000000000..e0aaf2377 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php @@ -0,0 +1,38 @@ + $left + * @param array $right + */ + private static function isArrayEqual(array $left, array $right): bool + { + if (count($left) !== count($right)) { + return false; + } + foreach ($left as $key => $value) { + if (!array_key_exists($key, $right)) { + return false; + } + + if (!self::isEqual($value, $right[$key])) { + return false; + } + } + + return true; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.php new file mode 100644 index 000000000..8b09156fc --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.php @@ -0,0 +1,38 @@ + 65535)) { + return false; + } + + // Validate the path (reject illegal characters: < > { } | \ ^ `) + if (!empty($matches[6]) && preg_match('/[<>{}|\\\^`]/', $matches[6])) { + return false; + } + + return true; + } + + // If not hierarchical, check non-hierarchical URIs + if (preg_match($nonHierarchicalPattern, $uri, $matches) === 1) { + $scheme = strtolower($matches[1]); // Extract the scheme + + // Special case: `mailto:` must contain a **valid email address** + if ($scheme === 'mailto') { + return preg_match($emailPattern, $matches[2]) === 1; + } + + if ($scheme === 'news') { + return preg_match($newsGroupPattern, $matches[2]) === 1; + } + + if ($scheme === 'tel') { + return preg_match($telPattern, $matches[2]) === 1; + } + + return true; // Valid non-hierarchical URI + } + + return false; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php new file mode 100644 index 000000000..f4ae718aa --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php @@ -0,0 +1,37 @@ + + */ +abstract class AbstractRetriever implements UriRetrieverInterface +{ + /** + * Media content type + * + * @var string + */ + protected $contentType; + + /** + * {@inheritdoc} + * + * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType() + */ + public function getContentType() + { + return $this->contentType; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php new file mode 100644 index 000000000..311f1d34b --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php @@ -0,0 +1,87 @@ + + */ +class Curl extends AbstractRetriever +{ + protected $messageBody; + + public function __construct() + { + if (!function_exists('curl_init')) { + // Cannot test this, because curl_init is present on all test platforms plus mock + throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore + } + } + + /** + * {@inheritdoc} + * + * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() + */ + public function retrieve($uri) + { + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $uri); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: ' . Validator::SCHEMA_MEDIA_TYPE]); + + $response = curl_exec($ch); + if (false === $response) { + throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found'); + } + + $this->fetchMessageBody($response); + $this->fetchContentType($response); + + if (PHP_VERSION_ID < 80000) { + curl_close($ch); + } + + return $this->messageBody; + } + + /** + * @param string $response cURL HTTP response + */ + private function fetchMessageBody($response) + { + preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match); + $this->messageBody = $match[1]; + } + + /** + * @param string $response cURL HTTP response + * + * @return bool Whether the Content-Type header was found or not + */ + protected function fetchContentType($response) + { + if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) { + $this->contentType = trim($match[1]); + + return true; + } + + return false; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php new file mode 100644 index 000000000..f60566930 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php @@ -0,0 +1,106 @@ + + */ +class FileGetContents extends AbstractRetriever +{ + protected $messageBody; + + /** + * {@inheritdoc} + * + * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() + */ + public function retrieve($uri) + { + if (function_exists('http_clear_last_response_headers')) { + http_clear_last_response_headers(); + } + + $errorMessage = null; + set_error_handler(function ($errno, $errstr) use (&$errorMessage) { + $errorMessage = $errstr; + }); + $response = file_get_contents($uri); + restore_error_handler(); + + if ($errorMessage) { + throw new ResourceNotFoundException($errorMessage); + } + + if (false === $response) { + throw new ResourceNotFoundException('JSON schema not found at ' . $uri); + } + + if ($response == '' + && substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/' + ) { + throw new ResourceNotFoundException('JSON schema not found at ' . $uri); + } + + $this->messageBody = $response; + + if (function_exists('http_get_last_response_headers')) { + // Use http_get_last_response_headers() for compatibility with PHP 8.5+ + // where $http_response_header is deprecated. + $httpResponseHeaders = http_get_last_response_headers(); + } else { + /** @phpstan-ignore nullCoalesce.variable ($http_response_header can non-existing when no http request was done) */ + $httpResponseHeaders = $http_response_header ?? []; + } + + if (!empty($httpResponseHeaders)) { + $this->fetchContentType($httpResponseHeaders); + } else { + $this->contentType = null; + } + + return $this->messageBody; + } + + /** + * @param array $headers HTTP Response Headers + * + * @return bool Whether the Content-Type header was found or not + */ + private function fetchContentType(array $headers): bool + { + foreach (array_reverse($headers) as $header) { + if ($this->contentType = self::getContentTypeMatchInHeader($header)) { + return true; + } + } + + return false; + } + + /** + * @param string $header + * + * @return string|null + */ + protected static function getContentTypeMatchInHeader($header) + { + if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) { + return trim($match[1]); + } + + return null; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php new file mode 100644 index 000000000..116930022 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php @@ -0,0 +1,58 @@ + '{ ... }', + * 'http://acme.com/schemas/address#' => '{ ... }', + * )) + * + * $schema = $retriever->retrieve('http://acme.com/schemas/person#'); + */ +class PredefinedArray extends AbstractRetriever +{ + /** + * Contains schemas as URI => JSON + * + * @var array + */ + private $schemas; + + /** + * Constructor + * + * @param array $schemas + * @param string $contentType + */ + public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE) + { + $this->schemas = $schemas; + $this->contentType = $contentType; + } + + /** + * {@inheritdoc} + * + * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() + */ + public function retrieve($uri) + { + if (!array_key_exists($uri, $this->schemas)) { + throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf( + 'The JSON schema "%s" was not found.', + $uri + )); + } + + return $this->schemas[$uri]; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php new file mode 100644 index 000000000..99a2d9e7f --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php @@ -0,0 +1,38 @@ + + */ +interface UriRetrieverInterface +{ + /** + * Retrieve a schema from the specified URI + * + * @param string $uri URI that resolves to a JSON schema + * + * @throws \JsonSchema\Exception\ResourceNotFoundException + * + * @return mixed string|null + */ + public function retrieve($uri); + + /** + * Get media content type + * + * @return string + */ + public function getContentType(); +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php new file mode 100644 index 000000000..00a47e812 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php @@ -0,0 +1,189 @@ + + */ +class UriResolver implements UriResolverInterface +{ + /** + * Parses a URI into five main components + * + * @param string $uri + * + * @return array + */ + public function parse($uri) + { + preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', (string) $uri, $match); + + $components = []; + if (5 < count($match)) { + $components = [ + 'scheme' => $match[2], + 'authority' => $match[4], + 'path' => $match[5] + ]; + } + if (7 < count($match)) { + $components['query'] = $match[7]; + } + if (9 < count($match)) { + $components['fragment'] = $match[9]; + } + + return $components; + } + + /** + * Builds a URI based on n array with the main components + * + * @param array $components + * + * @return string + */ + public function generate(array $components) + { + $uri = $components['scheme'] . '://' + . $components['authority'] + . $components['path']; + + if (array_key_exists('query', $components) && strlen($components['query'])) { + $uri .= '?' . $components['query']; + } + if (array_key_exists('fragment', $components)) { + $uri .= '#' . $components['fragment']; + } + + return $uri; + } + + /** + * {@inheritdoc} + */ + public function resolve($uri, $baseUri = null) + { + // treat non-uri base as local file path + if ( + !is_null($baseUri) && + !filter_var($baseUri, \FILTER_VALIDATE_URL) && + !preg_match('|^[^/]+://|u', $baseUri) + ) { + if (is_file($baseUri)) { + $baseUri = 'file://' . realpath($baseUri); + } elseif (is_dir($baseUri)) { + $baseUri = 'file://' . realpath($baseUri) . '/'; + } else { + $baseUri = 'file://' . getcwd() . '/' . $baseUri; + } + } + + if ($uri == '') { + return $baseUri; + } + + $components = $this->parse($uri); + $path = $components['path']; + + if (!empty($components['scheme'])) { + return $uri; + } + $baseComponents = $this->parse($baseUri); + $basePath = $baseComponents['path']; + + $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath); + if (isset($components['fragment'])) { + $baseComponents['fragment'] = $components['fragment']; + } + + return $this->generate($baseComponents); + } + + /** + * Tries to glue a relative path onto an absolute one + * + * @param string $relativePath + * @param string $basePath + * + * @throws UriResolverException + * + * @return string Merged path + */ + public static function combineRelativePathWithBasePath($relativePath, $basePath) + { + $relativePath = self::normalizePath($relativePath); + if (!$relativePath) { + return $basePath; + } + if ($relativePath[0] === '/') { + return $relativePath; + } + if (!$basePath) { + throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); + } + + $dirname = $basePath[strlen($basePath) - 1] === '/' ? $basePath : dirname($basePath); + $combined = rtrim($dirname, '/') . '/' . ltrim($relativePath, '/'); + $combinedSegments = explode('/', $combined); + $collapsedSegments = []; + while ($combinedSegments) { + $segment = array_shift($combinedSegments); + if ($segment === '..') { + if (count($collapsedSegments) <= 1) { + // Do not remove the top level (domain) + // This is not ideal - the domain should not be part of the path here. parse() and generate() + // should handle the "domain" separately, like the schema. + // Then the if-condition here would be `if (!$collapsedSegments) {`. + throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); + } + array_pop($collapsedSegments); + } else { + $collapsedSegments[] = $segment; + } + } + + return implode('/', $collapsedSegments); + } + + /** + * Normalizes a URI path component by removing dot-slash and double slashes + * + * @param string $path + * + * @return string + */ + private static function normalizePath($path) + { + $path = preg_replace('|((?parse($uri); + + return !empty($components); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php new file mode 100644 index 000000000..4d52937af --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php @@ -0,0 +1,351 @@ + + */ +class UriRetriever implements BaseUriRetrieverInterface +{ + /** + * @var array Map of URL translations + */ + protected $translationMap = [ + // use local copies of the spec schemas + '|^https?://json-schema.org/draft-(0[3467])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json' + ]; + + /** + * @var array A list of endpoints for media type check exclusion + */ + protected $allowedInvalidContentTypeEndpoints = [ + 'http://json-schema.org/', + 'https://json-schema.org/' + ]; + + /** + * @var null|UriRetrieverInterface + */ + protected $uriRetriever = null; + + /** + * @var array|object[] + * + * @see loadSchema + */ + private $schemaCache = []; + + /** + * Adds an endpoint to the media type validation exclusion list + * + * @param string $endpoint + */ + public function addInvalidContentTypeEndpoint($endpoint) + { + $this->allowedInvalidContentTypeEndpoints[] = $endpoint; + } + + /** + * Guarantee the correct media type was encountered + * + * @param UriRetrieverInterface $uriRetriever + * @param string $uri + * + * @return bool|void + */ + public function confirmMediaType($uriRetriever, $uri) + { + $contentType = $uriRetriever->getContentType(); + + if (is_null($contentType)) { + // Well, we didn't get an invalid one + return; + } + + if (in_array($contentType, [Validator::SCHEMA_MEDIA_TYPE, 'application/json'])) { + return; + } + + foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) { + if (!\is_null($uri) && strpos($uri, $endpoint) === 0) { + return true; + } + } + + throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected, but %s given', Validator::SCHEMA_MEDIA_TYPE, $contentType)); + } + + /** + * Get a URI Retriever + * + * If none is specified, sets a default FileGetContents retriever and + * returns that object. + * + * @return UriRetrieverInterface + */ + public function getUriRetriever() + { + if (is_null($this->uriRetriever)) { + $this->setUriRetriever(new FileGetContents()); + } + + return $this->uriRetriever; + } + + /** + * Resolve a schema based on pointer + * + * URIs can have a fragment at the end in the format of + * #/path/to/object and we are to look up the 'path' property of + * the first object then the 'to' and 'object' properties. + * + * @param object $jsonSchema JSON Schema contents + * @param string $uri JSON Schema URI + * + * @throws ResourceNotFoundException + * + * @return object JSON Schema after walking down the fragment pieces + */ + public function resolvePointer($jsonSchema, $uri) + { + $resolver = new UriResolver(); + $parsed = $resolver->parse($uri); + if (empty($parsed['fragment'])) { + return $jsonSchema; + } + + $path = explode('/', $parsed['fragment']); + while ($path) { + $pathElement = array_shift($path); + if (!empty($pathElement)) { + $pathElement = str_replace('~1', '/', $pathElement); + $pathElement = str_replace('~0', '~', $pathElement); + if (!empty($jsonSchema->$pathElement)) { + $jsonSchema = $jsonSchema->$pathElement; + } else { + throw new ResourceNotFoundException( + 'Fragment "' . $parsed['fragment'] . '" not found' + . ' in ' . $uri + ); + } + + if (!is_object($jsonSchema)) { + throw new ResourceNotFoundException( + 'Fragment part "' . $pathElement . '" is no object ' + . ' in ' . $uri + ); + } + } + } + + return $jsonSchema; + } + + /** + * {@inheritdoc} + */ + public function retrieve($uri, $baseUri = null, $translate = true) + { + $resolver = new UriResolver(); + $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); + + //fetch URL without #fragment + $arParts = $resolver->parse($resolvedUri); + if (isset($arParts['fragment'])) { + unset($arParts['fragment']); + $fetchUri = $resolver->generate($arParts); + } + + // apply URI translations + if ($translate) { + $fetchUri = $this->translate($fetchUri); + } + + $jsonSchema = $this->loadSchema($fetchUri); + + // Use the JSON pointer if specified + $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri); + + if ($jsonSchema instanceof \stdClass) { + $jsonSchema->id = $resolvedUri; + } + + return $jsonSchema; + } + + /** + * Fetch a schema from the given URI, json-decode it and return it. + * Caches schema objects. + * + * @param string $fetchUri Absolute URI + * + * @return object JSON schema object + */ + protected function loadSchema($fetchUri) + { + if (isset($this->schemaCache[$fetchUri])) { + return $this->schemaCache[$fetchUri]; + } + + $uriRetriever = $this->getUriRetriever(); + $contents = $this->uriRetriever->retrieve($fetchUri); + $this->confirmMediaType($uriRetriever, $fetchUri); + $jsonSchema = json_decode($contents); + + if (JSON_ERROR_NONE < $error = json_last_error()) { + throw new JsonDecodingException($error); + } + + $this->schemaCache[$fetchUri] = $jsonSchema; + + return $jsonSchema; + } + + /** + * Set the URI Retriever + * + * @param UriRetrieverInterface $uriRetriever + * + * @return $this for chaining + */ + public function setUriRetriever(UriRetrieverInterface $uriRetriever) + { + $this->uriRetriever = $uriRetriever; + + return $this; + } + + /** + * Parses a URI into five main components + * + * @param string $uri + * + * @return array + */ + public function parse($uri) + { + preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); + + $components = []; + if (5 < count($match)) { + $components = [ + 'scheme' => $match[2], + 'authority' => $match[4], + 'path' => $match[5] + ]; + } + + if (7 < count($match)) { + $components['query'] = $match[7]; + } + + if (9 < count($match)) { + $components['fragment'] = $match[9]; + } + + return $components; + } + + /** + * Builds a URI based on n array with the main components + * + * @param array $components + * + * @return string + */ + public function generate(array $components) + { + $uri = $components['scheme'] . '://' + . $components['authority'] + . $components['path']; + + if (array_key_exists('query', $components)) { + $uri .= $components['query']; + } + + if (array_key_exists('fragment', $components)) { + $uri .= $components['fragment']; + } + + return $uri; + } + + /** + * Resolves a URI + * + * @param string $uri Absolute or relative + * @param string $baseUri Optional base URI + * + * @return string + */ + public function resolve($uri, $baseUri = null) + { + $components = $this->parse($uri); + $path = $components['path']; + + if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) { + return $uri; + } + + $baseComponents = $this->parse($baseUri); + $basePath = $baseComponents['path']; + + $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath); + + return $this->generate($baseComponents); + } + + /** + * @param string $uri + * + * @return bool + */ + public function isValid($uri) + { + $components = $this->parse($uri); + + return !empty($components); + } + + /** + * Set a URL translation rule + */ + public function setTranslation($from, $to) + { + $this->translationMap[$from] = $to; + } + + /** + * Apply URI translation rules + */ + public function translate($uri) + { + foreach ($this->translationMap as $from => $to) { + $uri = preg_replace($from, $to, $uri); + } + + // translate references to local files within the json-schema package + $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri); + + return $uri; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php b/vendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php new file mode 100644 index 000000000..e80e2be73 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php @@ -0,0 +1,28 @@ + + * @author Bruno Prieto Reis + * + * @see README.md + */ +class Validator extends BaseConstraint +{ + public const SCHEMA_MEDIA_TYPE = 'application/schema+json'; + + public const ERROR_NONE = 0; + public const ERROR_ALL = -1; + public const ERROR_DOCUMENT_VALIDATION = 1; + public const ERROR_SCHEMA_VALIDATION = 2; + + /** + * Validates the given data against the schema and returns an object containing the results + * Both the php object and the schema are supposed to be a result of a json_decode call. + * The validation works as defined by the schema proposal in http://json-schema.org. + * + * Note that the first argument is passed by reference, so you must pass in a variable. + * + * @param mixed $value + * @param mixed $schema + * + * @phpstan-param int-mask-of $checkMode + * @phpstan-return int-mask-of + */ + public function validate(&$value, $schema = null, ?int $checkMode = null): int + { + // reset errors prior to validation + $this->reset(); + + // set checkMode + $initialCheckMode = $this->factory->getConfig(); + if ($checkMode !== null) { + $this->factory->setConfig($checkMode); + } + + // add provided schema to SchemaStorage with internal URI to allow internal $ref resolution + $schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI; + if (LooseTypeCheck::propertyExists($schema, 'id')) { + $schemaURI = LooseTypeCheck::propertyGet($schema, 'id'); + } + if (LooseTypeCheck::propertyExists($schema, '$id')) { + $schemaURI = LooseTypeCheck::propertyGet($schema, '$id'); + } + $this->factory->getSchemaStorage()->addSchema($schemaURI, $schema); + + $validator = $this->factory->createInstanceFor('schema'); + $schema = $this->factory->getSchemaStorage()->getSchema($schemaURI); + + // Boolean schema requires no further validation + if (is_bool($schema)) { + if ($schema === false) { + $this->addError(ConstraintError::FALSE()); + } + + return $this->getErrorMask(); + } + + if ($this->factory->getConfig(Constraint::CHECK_MODE_STRICT)) { + $dialect = $this->factory->getDefaultDialect(); + if (property_exists($schema, '$schema')) { + $dialect = $schema->{'$schema'}; + } + + $validator = $this->factory->createInstanceFor( + DraftIdentifiers::byValue($dialect)->toConstraintName() + ); + } + + $validator->check($value, $schema); + + $this->factory->setConfig($initialCheckMode); + + $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR)); + + return $validator->getErrorMask(); + } + + /** + * Alias to validate(), to maintain backwards-compatibility with the previous API + * + * @deprecated since 6.0.0, use Validator::validate() instead, to be removed in 7.0 + * + * @param mixed $value + * @param mixed $schema + * + * @phpstan-return int-mask-of + */ + public function check($value, $schema): int + { + return $this->validate($value, $schema); + } + + /** + * Alias to validate(), to maintain backwards-compatibility with the previous API + * + * @deprecated since 6.0.0, use Validator::validate() instead, to be removed in 7.0 + * + * @param mixed $value + * @param mixed $schema + * + * @phpstan-return int-mask-of + */ + public function coerce(&$value, $schema): int + { + return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES); + } +} diff --git a/vendor/league/flysystem-local/FallbackMimeTypeDetector.php b/vendor/league/flysystem-local/FallbackMimeTypeDetector.php new file mode 100644 index 000000000..382ecefb2 --- /dev/null +++ b/vendor/league/flysystem-local/FallbackMimeTypeDetector.php @@ -0,0 +1,52 @@ +detector->detectMimeType($path, $contents); + } + + public function detectMimeTypeFromBuffer(string $contents): ?string + { + return $this->detector->detectMimeTypeFromBuffer($contents); + } + + public function detectMimeTypeFromPath(string $path): ?string + { + return $this->detector->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromFile(string $path): ?string + { + $mimeType = $this->detector->detectMimeTypeFromFile($path); + + if ($mimeType !== null && ! in_array($mimeType, $this->inconclusiveMimetypes)) { + return $mimeType; + } + + return $this->detector->detectMimeTypeFromPath($path) ?? ($this->useInconclusiveMimeTypeFallback ? $mimeType : null); + } +} diff --git a/vendor/league/flysystem-local/LICENSE b/vendor/league/flysystem-local/LICENSE new file mode 100644 index 000000000..98e021e7b --- /dev/null +++ b/vendor/league/flysystem-local/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2026 Frank de Jonge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/flysystem-local/LocalFilesystemAdapter.php b/vendor/league/flysystem-local/LocalFilesystemAdapter.php new file mode 100644 index 000000000..36700dd2a --- /dev/null +++ b/vendor/league/flysystem-local/LocalFilesystemAdapter.php @@ -0,0 +1,487 @@ +prefixer = new PathPrefixer($location, DIRECTORY_SEPARATOR); + $visibility ??= new PortableVisibilityConverter(); + $this->visibility = $visibility; + $this->rootLocation = $location; + $this->mimeTypeDetector = $mimeTypeDetector ?? new FallbackMimeTypeDetector( + detector: new FinfoMimeTypeDetector(), + useInconclusiveMimeTypeFallback: $useInconclusiveMimeTypeFallback, + ); + + if ( ! $lazyRootCreation) { + $this->ensureRootDirectoryExists(); + } + } + + private function ensureRootDirectoryExists(): void + { + if ($this->rootLocationIsSetup) { + return; + } + + $this->ensureDirectoryExists($this->rootLocation, $this->visibility->defaultForDirectories()); + $this->rootLocationIsSetup = true; + } + + public function write(string $path, string $contents, Config $config): void + { + $this->writeToFile($path, $contents, $config); + } + + public function writeStream(string $path, $contents, Config $config): void + { + $this->writeToFile($path, $contents, $config); + } + + /** + * @param resource|string $contents + */ + private function writeToFile(string $path, $contents, Config $config): void + { + $prefixedLocation = $this->prefixer->prefixPath($path); + $this->ensureRootDirectoryExists(); + $this->ensureDirectoryExists( + dirname($prefixedLocation), + $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY)) + ); + error_clear_last(); + + if (@file_put_contents($prefixedLocation, $contents, $this->writeFlags) === false) { + throw UnableToWriteFile::atLocation($path, error_get_last()['message'] ?? ''); + } + + if ($visibility = $config->get(Config::OPTION_VISIBILITY)) { + $this->setVisibility($path, (string) $visibility); + } + } + + public function delete(string $path): void + { + $location = $this->prefixer->prefixPath($path); + + if ( ! file_exists($location)) { + return; + } + + error_clear_last(); + + if ( ! @unlink($location)) { + throw UnableToDeleteFile::atLocation($location, error_get_last()['message'] ?? ''); + } + } + + public function deleteDirectory(string $prefix): void + { + $location = $this->prefixer->prefixPath($prefix); + + if ( ! is_dir($location)) { + return; + } + + $contents = $this->listDirectoryRecursively($location, RecursiveIteratorIterator::CHILD_FIRST); + + /** @var SplFileInfo $file */ + foreach ($contents as $file) { + if ( ! $this->deleteFileInfoObject($file)) { + throw UnableToDeleteDirectory::atLocation($prefix, "Unable to delete file at " . $file->getPathname()); + } + } + + unset($contents); + + if ( ! @rmdir($location)) { + throw UnableToDeleteDirectory::atLocation($prefix, error_get_last()['message'] ?? ''); + } + } + + private function listDirectoryRecursively( + string $path, + int $mode = RecursiveIteratorIterator::SELF_FIRST + ): Generator { + if ( ! is_dir($path)) { + return; + } + + yield from new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), + $mode + ); + } + + protected function deleteFileInfoObject(SplFileInfo $file): bool + { + switch ($file->getType()) { + case 'dir': + return @rmdir((string) $file->getRealPath()); + case 'link': + return @unlink((string) $file->getPathname()); + default: + return @unlink((string) $file->getRealPath()); + } + } + + public function listContents(string $path, bool $deep): iterable + { + $location = $this->prefixer->prefixPath($path); + + if ( ! is_dir($location)) { + return; + } + + /** @var SplFileInfo[] $iterator */ + $iterator = $deep ? $this->listDirectoryRecursively($location) : $this->listDirectory($location); + + foreach ($iterator as $fileInfo) { + $pathName = $fileInfo->getPathname(); + + try { + if ($fileInfo->isLink()) { + if ($this->linkHandling & self::SKIP_LINKS) { + continue; + } + throw SymbolicLinkEncountered::atLocation($pathName); + } + + $path = $this->prefixer->stripPrefix($pathName); + $lastModified = $fileInfo->getMTime(); + $isDirectory = $fileInfo->isDir(); + $permissions = octdec(substr(sprintf('%o', $fileInfo->getPerms()), -4)); + $visibility = $isDirectory ? $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions); + + yield $isDirectory ? new DirectoryAttributes(str_replace('\\', '/', $path), $visibility, $lastModified) : new FileAttributes( + str_replace('\\', '/', $path), + $fileInfo->getSize(), + $visibility, + $lastModified + ); + } catch (Throwable $exception) { + if (file_exists($pathName)) { + throw $exception; + } + } + } + } + + public function move(string $source, string $destination, Config $config): void + { + $sourcePath = $this->prefixer->prefixPath($source); + $destinationPath = $this->prefixer->prefixPath($destination); + + $this->ensureRootDirectoryExists(); + $this->ensureDirectoryExists( + dirname($destinationPath), + $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY)) + ); + + error_clear_last(); + if ( ! @rename($sourcePath, $destinationPath)) { + throw UnableToMoveFile::because(error_get_last()['message'] ?? 'unknown reason', $source, $destination); + } + + if ($visibility = $config->get(Config::OPTION_VISIBILITY)) { + $this->setVisibility($destination, (string) $visibility); + } + } + + public function copy(string $source, string $destination, Config $config): void + { + $sourcePath = $this->prefixer->prefixPath($source); + $destinationPath = $this->prefixer->prefixPath($destination); + $this->ensureRootDirectoryExists(); + $this->ensureDirectoryExists( + dirname($destinationPath), + $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY)) + ); + + error_clear_last(); + if ($sourcePath !== $destinationPath && ! @copy($sourcePath, $destinationPath)) { + throw UnableToCopyFile::because(error_get_last()['message'] ?? 'unknown', $source, $destination); + } + + $visibility = $config->get( + Config::OPTION_VISIBILITY, + $config->get(Config::OPTION_RETAIN_VISIBILITY, true) + ? $this->visibility($source)->visibility() + : null, + ); + + if ($visibility) { + $this->setVisibility($destination, (string) $visibility); + } + } + + public function read(string $path): string + { + $location = $this->prefixer->prefixPath($path); + error_clear_last(); + $contents = @file_get_contents($location); + + if ($contents === false) { + throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? ''); + } + + return $contents; + } + + public function readStream(string $path) + { + $location = $this->prefixer->prefixPath($path); + error_clear_last(); + $contents = @fopen($location, 'rb'); + + if ($contents === false) { + throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? ''); + } + + return $contents; + } + + protected function ensureDirectoryExists(string $dirname, int $visibility): void + { + if (is_dir($dirname)) { + return; + } + + error_clear_last(); + + if ( ! @mkdir($dirname, $visibility, true)) { + $mkdirError = error_get_last(); + } + + clearstatcache(true, $dirname); + + if ( ! is_dir($dirname)) { + $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : ''; + + throw UnableToCreateDirectory::atLocation($dirname, $errorMessage); + } + } + + public function fileExists(string $location): bool + { + $location = $this->prefixer->prefixPath($location); + clearstatcache(); + return is_file($location); + } + + public function directoryExists(string $location): bool + { + $location = $this->prefixer->prefixPath($location); + clearstatcache(); + return is_dir($location); + } + + public function createDirectory(string $path, Config $config): void + { + $this->ensureRootDirectoryExists(); + $location = $this->prefixer->prefixPath($path); + $visibility = $config->get(Config::OPTION_VISIBILITY, $config->get(Config::OPTION_DIRECTORY_VISIBILITY)); + $permissions = $this->resolveDirectoryVisibility($visibility); + + if (is_dir($location)) { + $this->setPermissions($location, $permissions); + + return; + } + + error_clear_last(); + + if ( ! @mkdir($location, $permissions, true)) { + throw UnableToCreateDirectory::atLocation($path, error_get_last()['message'] ?? ''); + } + } + + public function setVisibility(string $path, string $visibility): void + { + $path = $this->prefixer->prefixPath($path); + $visibility = is_dir($path) ? $this->visibility->forDirectory($visibility) : $this->visibility->forFile( + $visibility + ); + + $this->setPermissions($path, $visibility); + } + + public function visibility(string $path): FileAttributes + { + $location = $this->prefixer->prefixPath($path); + clearstatcache(false, $location); + error_clear_last(); + $fileperms = @fileperms($location); + + if ($fileperms === false) { + throw UnableToRetrieveMetadata::visibility($path, error_get_last()['message'] ?? ''); + } + + $permissions = $fileperms & 0777; + $visibility = $this->visibility->inverseForFile($permissions); + + return new FileAttributes($path, null, $visibility); + } + + private function resolveDirectoryVisibility(?string $visibility): int + { + return $visibility === null ? $this->visibility->defaultForDirectories() : $this->visibility->forDirectory( + $visibility + ); + } + + public function mimeType(string $path): FileAttributes + { + $location = $this->prefixer->prefixPath($path); + error_clear_last(); + + if ( ! is_file($location)) { + throw UnableToRetrieveMetadata::mimeType($location, 'No such file exists.'); + } + + $mimeType = $this->mimeTypeDetector->detectMimeTypeFromFile($location); + + if ($mimeType === null) { + throw UnableToRetrieveMetadata::mimeType($path, error_get_last()['message'] ?? ''); + } + + return new FileAttributes($path, null, null, null, $mimeType); + } + + public function lastModified(string $path): FileAttributes + { + $location = $this->prefixer->prefixPath($path); + clearstatcache(); + error_clear_last(); + $lastModified = @filemtime($location); + + if ($lastModified === false) { + throw UnableToRetrieveMetadata::lastModified($path, error_get_last()['message'] ?? ''); + } + + return new FileAttributes($path, null, null, $lastModified); + } + + public function fileSize(string $path): FileAttributes + { + $location = $this->prefixer->prefixPath($path); + clearstatcache(); + error_clear_last(); + + if (is_file($location) && ($fileSize = @filesize($location)) !== false) { + return new FileAttributes($path, $fileSize); + } + + throw UnableToRetrieveMetadata::fileSize($path, error_get_last()['message'] ?? ''); + } + + public function checksum(string $path, Config $config): string + { + $algo = $config->get('checksum_algo', 'md5'); + $location = $this->prefixer->prefixPath($path); + error_clear_last(); + $checksum = @hash_file($algo, $location); + + if ($checksum === false) { + throw new UnableToProvideChecksum(error_get_last()['message'] ?? '', $path); + } + + return $checksum; + } + + private function listDirectory(string $location): Generator + { + $iterator = new DirectoryIterator($location); + + foreach ($iterator as $item) { + if ($item->isDot()) { + continue; + } + + yield $item; + } + } + + private function setPermissions(string $location, int $visibility): void + { + error_clear_last(); + if ( ! @chmod($location, $visibility)) { + $extraMessage = error_get_last()['message'] ?? ''; + throw UnableToSetVisibility::atLocation($this->prefixer->stripPrefix($location), $extraMessage); + } + } +} diff --git a/vendor/league/flysystem-local/composer.json b/vendor/league/flysystem-local/composer.json new file mode 100644 index 000000000..cd9ef2540 --- /dev/null +++ b/vendor/league/flysystem-local/composer.json @@ -0,0 +1,25 @@ +{ + "name": "league/flysystem-local", + "description": "Local filesystem adapter for Flysystem.", + "keywords": ["flysystem", "filesystem", "local", "file", "files"], + "type": "library", + "prefer-stable": true, + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "require": { + "php": "^8.0.2", + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0" + }, + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ] +} diff --git a/vendor/league/flysystem/INFO.md b/vendor/league/flysystem/INFO.md new file mode 100644 index 000000000..70c07b412 --- /dev/null +++ b/vendor/league/flysystem/INFO.md @@ -0,0 +1,2 @@ +View the docs at: https://flysystem.thephpleague.com/docs/ +Changelog at: https://github.com/thephpleague/flysystem/blob/3.x/CHANGELOG.md diff --git a/vendor/league/flysystem/LICENSE b/vendor/league/flysystem/LICENSE new file mode 100644 index 000000000..98e021e7b --- /dev/null +++ b/vendor/league/flysystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2026 Frank de Jonge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/flysystem/composer.json b/vendor/league/flysystem/composer.json new file mode 100644 index 000000000..700b0ffdf --- /dev/null +++ b/vendor/league/flysystem/composer.json @@ -0,0 +1,72 @@ +{ + "name": "league/flysystem", + "description": "File storage abstraction for PHP", + "keywords": [ + "filesystem", "filesystems", "files", "storage", "aws", + "s3", "ftp", "sftp", "webdav", "file", "cloud" + ], + "scripts": { + "phpstan": "vendor/bin/phpstan analyse -l 6 src" + }, + "type": "library", + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "require": { + "php": "^8.0.2", + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0" + }, + "require-dev": { + "ext-zip": "*", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "microsoft/azure-storage-blob": "^1.1", + "phpunit/phpunit": "^9.5.11|^10.0", + "phpstan/phpstan": "^1.10", + "phpseclib/phpseclib": "^3.0.36", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "mongodb/mongodb": "^1.2|^2", + "sabre/dav": "^4.6.0", + "guzzlehttp/psr7": "^2.6" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "symfony/http-client": "<5.2", + "guzzlehttp/ringphp": "<1.1.1", + "guzzlehttp/guzzle": "<7.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "phpseclib/phpseclib": "3.0.15" + }, + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "repositories": [ + { + "type": "package", + "package": { + "name": "league/flysystem-local", + "version": "3.0.0", + "dist": { + "type": "path", + "url": "src/Local" + } + } + } + ] +} diff --git a/vendor/league/flysystem/readme.md b/vendor/league/flysystem/readme.md new file mode 100644 index 000000000..b28d243bb --- /dev/null +++ b/vendor/league/flysystem/readme.md @@ -0,0 +1,61 @@ +# League\Flysystem + +[![Author](https://img.shields.io/badge/author-@frankdejonge-blue.svg)](https://twitter.com/frankdejonge) +[![Source Code](https://img.shields.io/badge/source-thephpleague/flysystem-blue.svg)](https://github.com/thephpleague/flysystem) +[![Latest Version](https://img.shields.io/github/tag/thephpleague/flysystem.svg)](https://github.com/thephpleague/flysystem/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/thephpleague/flysystem/blob/master/LICENSE) +[![Quality Assurance](https://github.com/thephpleague/flysystem/workflows/Quality%20Assurance/badge.svg?branch=2.x)](https://github.com/thephpleague/flysystem/actions?query=workflow%3A%22Quality+Assurance%22) +[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem.svg)](https://packagist.org/packages/league/flysystem) +![php 7.2+](https://img.shields.io/badge/php-min%208.0.2-red.svg) + +## About Flysystem + +Flysystem is a file storage library for PHP. It provides one interface to +interact with many types of filesystems. When you use Flysystem, you're +not only protected from vendor lock-in, you'll also have a consistent experience +for which ever storage is right for you. + +## Getting Started + +* **[New in V3](https://flysystem.thephpleague.com/docs/what-is-new/)**: What is new in Flysystem V2/V3? +* **[Architecture](https://flysystem.thephpleague.com/docs/architecture/)**: Flysystem's internal architecture +* **[Flysystem API](https://flysystem.thephpleague.com/docs/usage/filesystem-api/)**: How to interact with your Flysystem instance +* **[Upgrade from 1x](https://flysystem.thephpleague.com/docs/upgrade-from-1.x/)**: How to upgrade from 1.x/2.x + +### Officially supported adapters + +* **[Local](https://flysystem.thephpleague.com/docs/adapter/local/)** +* **[FTP](https://flysystem.thephpleague.com/docs/adapter/ftp/)** +* **[SFTP](https://flysystem.thephpleague.com/docs/adapter/sftp-v3/)** +* **[Memory](https://flysystem.thephpleague.com/docs/adapter/in-memory/)** +* **[AWS S3](https://flysystem.thephpleague.com/docs/adapter/aws-s3-v3/)** +* **[AsyncAws S3](https://flysystem.thephpleague.com/docs/adapter/async-aws-s3/)** +* **[Google Cloud Storage](https://flysystem.thephpleague.com/docs/adapter/google-cloud-storage/)** +* **[MongoDB GridFS](https://flysystem.thephpleague.com/docs/adapter/gridfs/)** +* **[WebDAV](https://flysystem.thephpleague.com/docs/adapter/webdav/)** +* **[ZipArchive](https://flysystem.thephpleague.com/docs/adapter/zip-archive/)** + +### Third party Adapters + +* **[Azure Blob Storage](https://github.com/Azure-OSS/azure-storage-php-adapter-flysystem)** +* **[Gitlab](https://github.com/RoyVoetman/flysystem-gitlab-storage)** +* **[Google Drive (using regular paths)](https://github.com/masbug/flysystem-google-drive-ext)** +* **[bunny.net / BunnyCDN](https://github.com/PlatformCommunity/flysystem-bunnycdn/tree/v3)** +* **[Sharepoint 365 / One Drive (Using MS Graph)](https://github.com/shitware-ltd/flysystem-msgraph)** +* **[OneDrive](https://github.com/doerffler/flysystem-onedrive)** +* **[Dropbox](https://github.com/spatie/flysystem-dropbox)** +* **[ReplicateAdapter](https://github.com/ajgarlag/flysystem-replicate)** +* **[Uploadcare](https://github.com/vormkracht10/flysystem-uploadcare)** +* **[Useful adapters (FallbackAdapter, LogAdapter, ReadWriteAdapter, RetryAdapter)](https://github.com/ElGigi/FlysystemUsefulAdapters)** +* **[Metadata Cache](https://github.com/jgivoni/flysystem-cache-adapter)** +* **[Migration adapter (lazy)](https://github.com/antonsacred/flysystem-lazy-migration-adapter)** + +You can always [create an adapter](https://flysystem.thephpleague.com/docs/advanced/creating-an-adapter/) yourself. + +## Security + +If you discover any security related issues, please email info@frankdejonge.nl instead of using the issue tracker. + +## Enjoy + +Oh, and if you've come down this far, you might as well follow me on [twitter](https://twitter.com/frankdejonge). diff --git a/vendor/league/flysystem/src/CalculateChecksumFromStream.php b/vendor/league/flysystem/src/CalculateChecksumFromStream.php new file mode 100644 index 000000000..61b1fa11b --- /dev/null +++ b/vendor/league/flysystem/src/CalculateChecksumFromStream.php @@ -0,0 +1,30 @@ +readStream($path); + $algo = (string) $config->get('checksum_algo', 'md5'); + $context = hash_init($algo); + hash_update_stream($context, $stream); + + return hash_final($context); + } catch (FilesystemException $exception) { + throw new UnableToProvideChecksum($exception->getMessage(), $path, $exception); + } + } + + /** + * @return resource + */ + abstract public function readStream(string $path); +} diff --git a/vendor/league/flysystem/src/ChecksumAlgoIsNotSupported.php b/vendor/league/flysystem/src/ChecksumAlgoIsNotSupported.php new file mode 100644 index 000000000..5d33c0cfe --- /dev/null +++ b/vendor/league/flysystem/src/ChecksumAlgoIsNotSupported.php @@ -0,0 +1,11 @@ +options[$property] ?? $default; + } + + public function extend(array $options): Config + { + return new Config(array_merge($this->options, $options)); + } + + public function withDefaults(array $defaults): Config + { + return new Config($this->options + $defaults); + } + + public function toArray(): array + { + return $this->options; + } + + public function withSetting(string $property, mixed $setting): Config + { + return $this->extend([$property => $setting]); + } + + public function withoutSettings(string ...$settings): Config + { + return new Config(array_diff_key($this->options, array_flip($settings))); + } +} diff --git a/vendor/league/flysystem/src/CorruptedPathDetected.php b/vendor/league/flysystem/src/CorruptedPathDetected.php new file mode 100644 index 000000000..70631ccc7 --- /dev/null +++ b/vendor/league/flysystem/src/CorruptedPathDetected.php @@ -0,0 +1,13 @@ +adapter->fileExists($path); + } + + public function directoryExists(string $path): bool + { + return $this->adapter->directoryExists($path); + } + + public function write(string $path, string $contents, Config $config): void + { + $this->adapter->write($path, $contents, $config); + } + + public function writeStream(string $path, $contents, Config $config): void + { + $this->adapter->writeStream($path, $contents, $config); + } + + public function read(string $path): string + { + return $this->adapter->read($path); + } + + public function readStream(string $path) + { + return $this->adapter->readStream($path); + } + + public function delete(string $path): void + { + $this->adapter->delete($path); + } + + public function deleteDirectory(string $path): void + { + $this->adapter->deleteDirectory($path); + } + + public function createDirectory(string $path, Config $config): void + { + $this->adapter->createDirectory($path, $config); + } + + public function setVisibility(string $path, string $visibility): void + { + $this->adapter->setVisibility($path, $visibility); + } + + public function visibility(string $path): FileAttributes + { + return $this->adapter->visibility($path); + } + + public function mimeType(string $path): FileAttributes + { + return $this->adapter->mimeType($path); + } + + public function lastModified(string $path): FileAttributes + { + return $this->adapter->lastModified($path); + } + + public function fileSize(string $path): FileAttributes + { + return $this->adapter->fileSize($path); + } + + public function listContents(string $path, bool $deep): iterable + { + return $this->adapter->listContents($path, $deep); + } + + public function move(string $source, string $destination, Config $config): void + { + $this->adapter->move($source, $destination, $config); + } + + public function copy(string $source, string $destination, Config $config): void + { + $this->adapter->copy($source, $destination, $config); + } +} diff --git a/vendor/league/flysystem/src/DirectoryAttributes.php b/vendor/league/flysystem/src/DirectoryAttributes.php new file mode 100644 index 000000000..3fe44c1b4 --- /dev/null +++ b/vendor/league/flysystem/src/DirectoryAttributes.php @@ -0,0 +1,87 @@ +path = trim($this->path, '/'); + } + + public function path(): string + { + return $this->path; + } + + public function type(): string + { + return $this->type; + } + + public function visibility(): ?string + { + return $this->visibility; + } + + public function lastModified(): ?int + { + return $this->lastModified; + } + + public function extraMetadata(): array + { + return $this->extraMetadata; + } + + public function isFile(): bool + { + return false; + } + + public function isDir(): bool + { + return true; + } + + public function withPath(string $path): self + { + $clone = clone $this; + $clone->path = $path; + + return $clone; + } + + public static function fromArray(array $attributes): self + { + return new DirectoryAttributes( + $attributes[StorageAttributes::ATTRIBUTE_PATH], + $attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null, + $attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null, + $attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? [] + ); + } + + /** + * @inheritDoc + */ + public function jsonSerialize(): array + { + return [ + StorageAttributes::ATTRIBUTE_TYPE => $this->type, + StorageAttributes::ATTRIBUTE_PATH => $this->path, + StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility, + StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified, + StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata, + ]; + } +} diff --git a/vendor/league/flysystem/src/DirectoryListing.php b/vendor/league/flysystem/src/DirectoryListing.php new file mode 100644 index 000000000..7b1a1f589 --- /dev/null +++ b/vendor/league/flysystem/src/DirectoryListing.php @@ -0,0 +1,93 @@ + $listing + */ + public function __construct(private iterable $listing) + { + } + + /** + * @param callable(T): bool $filter + * + * @return DirectoryListing + */ + public function filter(callable $filter): DirectoryListing + { + $generator = (static function (iterable $listing) use ($filter): Generator { + foreach ($listing as $item) { + if ($filter($item)) { + yield $item; + } + } + })($this->listing); + + return new DirectoryListing($generator); + } + + /** + * @template R + * + * @param callable(T): R $mapper + * + * @return DirectoryListing + */ + public function map(callable $mapper): DirectoryListing + { + $generator = (static function (iterable $listing) use ($mapper): Generator { + foreach ($listing as $item) { + yield $mapper($item); + } + })($this->listing); + + return new DirectoryListing($generator); + } + + /** + * @return DirectoryListing + */ + public function sortByPath(): DirectoryListing + { + $listing = $this->toArray(); + + usort($listing, function (StorageAttributes $a, StorageAttributes $b) { + return $a->path() <=> $b->path(); + }); + + return new DirectoryListing($listing); + } + + /** + * @return Traversable + */ + public function getIterator(): Traversable + { + return $this->listing instanceof Traversable + ? $this->listing + : new ArrayIterator($this->listing); + } + + /** + * @return T[] + */ + public function toArray(): array + { + return $this->listing instanceof Traversable + ? iterator_to_array($this->listing, false) + : (array) $this->listing; + } +} diff --git a/vendor/league/flysystem/src/FileAttributes.php b/vendor/league/flysystem/src/FileAttributes.php new file mode 100644 index 000000000..42172f8c4 --- /dev/null +++ b/vendor/league/flysystem/src/FileAttributes.php @@ -0,0 +1,100 @@ +path = ltrim($this->path, '/'); + } + + public function type(): string + { + return $this->type; + } + + public function path(): string + { + return $this->path; + } + + public function fileSize(): ?int + { + return $this->fileSize; + } + + public function visibility(): ?string + { + return $this->visibility; + } + + public function lastModified(): ?int + { + return $this->lastModified; + } + + public function mimeType(): ?string + { + return $this->mimeType; + } + + public function extraMetadata(): array + { + return $this->extraMetadata; + } + + public function isFile(): bool + { + return true; + } + + public function isDir(): bool + { + return false; + } + + public function withPath(string $path): self + { + $clone = clone $this; + $clone->path = $path; + + return $clone; + } + + public static function fromArray(array $attributes): self + { + return new FileAttributes( + $attributes[StorageAttributes::ATTRIBUTE_PATH], + $attributes[StorageAttributes::ATTRIBUTE_FILE_SIZE] ?? null, + $attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null, + $attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null, + $attributes[StorageAttributes::ATTRIBUTE_MIME_TYPE] ?? null, + $attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? [] + ); + } + + public function jsonSerialize(): array + { + return [ + StorageAttributes::ATTRIBUTE_TYPE => self::TYPE_FILE, + StorageAttributes::ATTRIBUTE_PATH => $this->path, + StorageAttributes::ATTRIBUTE_FILE_SIZE => $this->fileSize, + StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility, + StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified, + StorageAttributes::ATTRIBUTE_MIME_TYPE => $this->mimeType, + StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata, + ]; + } +} diff --git a/vendor/league/flysystem/src/Filesystem.php b/vendor/league/flysystem/src/Filesystem.php new file mode 100644 index 000000000..4fb30cc10 --- /dev/null +++ b/vendor/league/flysystem/src/Filesystem.php @@ -0,0 +1,290 @@ +config = new Config($config); + $this->pathNormalizer = $pathNormalizer ?? new WhitespacePathNormalizer(); + } + + public function fileExists(string $location): bool + { + return $this->adapter->fileExists($this->pathNormalizer->normalizePath($location)); + } + + public function directoryExists(string $location): bool + { + return $this->adapter->directoryExists($this->pathNormalizer->normalizePath($location)); + } + + public function has(string $location): bool + { + $path = $this->pathNormalizer->normalizePath($location); + + return $this->adapter->fileExists($path) || $this->adapter->directoryExists($path); + } + + public function write(string $location, string $contents, array $config = []): void + { + $this->adapter->write( + $this->pathNormalizer->normalizePath($location), + $contents, + $this->config->extend($config) + ); + } + + public function writeStream(string $location, $contents, array $config = []): void + { + /* @var resource $contents */ + $this->assertIsResource($contents); + $this->rewindStream($contents); + $this->adapter->writeStream( + $this->pathNormalizer->normalizePath($location), + $contents, + $this->config->extend($config) + ); + } + + public function read(string $location): string + { + return $this->adapter->read($this->pathNormalizer->normalizePath($location)); + } + + public function readStream(string $location) + { + return $this->adapter->readStream($this->pathNormalizer->normalizePath($location)); + } + + public function delete(string $location): void + { + $this->adapter->delete($this->pathNormalizer->normalizePath($location)); + } + + public function deleteDirectory(string $location): void + { + $this->adapter->deleteDirectory($this->pathNormalizer->normalizePath($location)); + } + + public function createDirectory(string $location, array $config = []): void + { + $this->adapter->createDirectory( + $this->pathNormalizer->normalizePath($location), + $this->config->extend($config) + ); + } + + public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing + { + $path = $this->pathNormalizer->normalizePath($location); + $listing = $this->adapter->listContents($path, $deep); + + return new DirectoryListing($this->pipeListing($location, $deep, $listing)); + } + + private function pipeListing(string $location, bool $deep, iterable $listing): Generator + { + try { + foreach ($listing as $item) { + yield $item; + } + } catch (Throwable $exception) { + throw UnableToListContents::atLocation($location, $deep, $exception); + } + } + + public function move(string $source, string $destination, array $config = []): void + { + $config = $this->resolveConfigForMoveAndCopy($config); + $from = $this->pathNormalizer->normalizePath($source); + $to = $this->pathNormalizer->normalizePath($destination); + + if ($from === $to) { + $resolutionStrategy = $config->get(Config::OPTION_MOVE_IDENTICAL_PATH, ResolveIdenticalPathConflict::TRY); + + if ($resolutionStrategy === ResolveIdenticalPathConflict::FAIL) { + throw UnableToMoveFile::sourceAndDestinationAreTheSame($source, $destination); + } elseif ($resolutionStrategy === ResolveIdenticalPathConflict::IGNORE) { + return; + } + } + + $this->adapter->move($from, $to, $config); + } + + public function copy(string $source, string $destination, array $config = []): void + { + $config = $this->resolveConfigForMoveAndCopy($config); + $from = $this->pathNormalizer->normalizePath($source); + $to = $this->pathNormalizer->normalizePath($destination); + + if ($from === $to) { + $resolutionStrategy = $config->get(Config::OPTION_COPY_IDENTICAL_PATH, ResolveIdenticalPathConflict::TRY); + + if ($resolutionStrategy === ResolveIdenticalPathConflict::FAIL) { + throw UnableToCopyFile::sourceAndDestinationAreTheSame($source, $destination); + } elseif ($resolutionStrategy === ResolveIdenticalPathConflict::IGNORE) { + return; + } + } + + $this->adapter->copy($from, $to, $config); + } + + public function lastModified(string $path): int + { + return $this->adapter->lastModified($this->pathNormalizer->normalizePath($path))->lastModified(); + } + + public function fileSize(string $path): int + { + return $this->adapter->fileSize($this->pathNormalizer->normalizePath($path))->fileSize(); + } + + public function mimeType(string $path): string + { + return $this->adapter->mimeType($this->pathNormalizer->normalizePath($path))->mimeType(); + } + + public function setVisibility(string $path, string $visibility): void + { + $this->adapter->setVisibility($this->pathNormalizer->normalizePath($path), $visibility); + } + + public function visibility(string $path): string + { + return $this->adapter->visibility($this->pathNormalizer->normalizePath($path))->visibility(); + } + + public function publicUrl(string $path, array $config = []): string + { + $this->publicUrlGenerator ??= $this->resolvePublicUrlGenerator() + ?? throw UnableToGeneratePublicUrl::noGeneratorConfigured($path); + $config = $this->config->extend($config); + + return $this->publicUrlGenerator->publicUrl( + $this->pathNormalizer->normalizePath($path), + $config, + ); + } + + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []): string + { + $generator = $this->temporaryUrlGenerator ?? $this->adapter; + + if ($generator instanceof TemporaryUrlGenerator) { + return $generator->temporaryUrl( + $this->pathNormalizer->normalizePath($path), + $expiresAt, + $this->config->extend($config) + ); + } + + throw UnableToGenerateTemporaryUrl::noGeneratorConfigured($path); + } + + public function checksum(string $path, array $config = []): string + { + $config = $this->config->extend($config); + + if ( ! $this->adapter instanceof ChecksumProvider) { + return $this->calculateChecksumFromStream($path, $config); + } + + try { + return $this->adapter->checksum( + $this->pathNormalizer->normalizePath($path), + $config, + ); + } catch (ChecksumAlgoIsNotSupported) { + return $this->calculateChecksumFromStream( + $this->pathNormalizer->normalizePath($path), + $config, + ); + } + } + + private function resolvePublicUrlGenerator(): ?PublicUrlGenerator + { + if ($publicUrl = $this->config->get('public_url')) { + return match (true) { + is_array($publicUrl) => new ShardedPrefixPublicUrlGenerator($publicUrl), + default => new PrefixPublicUrlGenerator($publicUrl), + }; + } + + if ($this->adapter instanceof PublicUrlGenerator) { + return $this->adapter; + } + + return null; + } + + /** + * @param mixed $contents + */ + private function assertIsResource($contents): void + { + if (is_resource($contents) === false) { + throw new InvalidStreamProvided( + "Invalid stream provided, expected stream resource, received " . gettype($contents) + ); + } elseif ($type = get_resource_type($contents) !== 'stream') { + throw new InvalidStreamProvided( + "Invalid stream provided, expected stream resource, received resource of type " . $type + ); + } + } + + /** + * @param resource $resource + */ + private function rewindStream($resource): void + { + if (ftell($resource) !== 0 && stream_get_meta_data($resource)['seekable']) { + rewind($resource); + } + } + + private function resolveConfigForMoveAndCopy(array $config): Config + { + $retainVisibility = $this->config->get(Config::OPTION_RETAIN_VISIBILITY, $config[Config::OPTION_RETAIN_VISIBILITY] ?? true); + $fullConfig = $this->config->extend($config); + + /* + * By default, we retain visibility. When we do not retain visibility, the visibility setting + * from the default configuration is ignored. Only when it is set explicitly, we propagate the + * setting. + */ + if ($retainVisibility && ! array_key_exists(Config::OPTION_VISIBILITY, $config)) { + $fullConfig = $fullConfig->withoutSettings(Config::OPTION_VISIBILITY)->extend($config); + } + + return $fullConfig; + } +} diff --git a/vendor/league/flysystem/src/FilesystemAdapter.php b/vendor/league/flysystem/src/FilesystemAdapter.php new file mode 100644 index 000000000..714309f31 --- /dev/null +++ b/vendor/league/flysystem/src/FilesystemAdapter.php @@ -0,0 +1,115 @@ + + * + * @throws FilesystemException + */ + public function listContents(string $path, bool $deep): iterable; + + /** + * @throws UnableToMoveFile + * @throws FilesystemException + */ + public function move(string $source, string $destination, Config $config): void; + + /** + * @throws UnableToCopyFile + * @throws FilesystemException + */ + public function copy(string $source, string $destination, Config $config): void; +} diff --git a/vendor/league/flysystem/src/FilesystemException.php b/vendor/league/flysystem/src/FilesystemException.php new file mode 100644 index 000000000..f9d60185f --- /dev/null +++ b/vendor/league/flysystem/src/FilesystemException.php @@ -0,0 +1,11 @@ + + * + * @throws FilesystemException + * @throws UnableToListContents + */ + public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing; + + /** + * @throws UnableToRetrieveMetadata + * @throws FilesystemException + */ + public function lastModified(string $path): int; + + /** + * @throws UnableToRetrieveMetadata + * @throws FilesystemException + */ + public function fileSize(string $path): int; + + /** + * @throws UnableToRetrieveMetadata + * @throws FilesystemException + */ + public function mimeType(string $path): string; + + /** + * @throws UnableToRetrieveMetadata + * @throws FilesystemException + */ + public function visibility(string $path): string; +} diff --git a/vendor/league/flysystem/src/FilesystemWriter.php b/vendor/league/flysystem/src/FilesystemWriter.php new file mode 100644 index 000000000..a24bb0fcf --- /dev/null +++ b/vendor/league/flysystem/src/FilesystemWriter.php @@ -0,0 +1,58 @@ + + */ + private $filesystems = []; + + /** + * @var Config + */ + private $config; + + /** + * MountManager constructor. + * + * @param array $filesystems + */ + public function __construct(array $filesystems = [], array $config = []) + { + $this->mountFilesystems($filesystems); + $this->config = new Config($config); + } + + /** + * It is not recommended to mount filesystems after creation because interacting + * with the Mount Manager becomes unpredictable. Use this as an escape hatch. + */ + public function dangerouslyMountFilesystems(string $key, FilesystemOperator $filesystem): void + { + $this->mountFilesystem($key, $filesystem); + } + + /** + * @param array $filesystems + */ + public function extend(array $filesystems, array $config = []): MountManager + { + $clone = clone $this; + $clone->config = $this->config->extend($config); + $clone->mountFilesystems($filesystems); + + return $clone; + } + + public function fileExists(string $location): bool + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->fileExists($path); + } catch (Throwable $exception) { + throw UnableToCheckFileExistence::forLocation($location, $exception); + } + } + + public function has(string $location): bool + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->fileExists($path) || $filesystem->directoryExists($path); + } catch (Throwable $exception) { + throw UnableToCheckExistence::forLocation($location, $exception); + } + } + + public function directoryExists(string $location): bool + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->directoryExists($path); + } catch (Throwable $exception) { + throw UnableToCheckDirectoryExistence::forLocation($location, $exception); + } + } + + public function read(string $location): string + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->read($path); + } catch (UnableToReadFile $exception) { + throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception); + } + } + + public function readStream(string $location) + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->readStream($path); + } catch (UnableToReadFile $exception) { + throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception); + } + } + + public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path, $mountIdentifier] = $this->determineFilesystemAndPath($location); + + return + $filesystem + ->listContents($path, $deep) + ->map( + function (StorageAttributes $attributes) use ($mountIdentifier) { + return $attributes->withPath(sprintf('%s://%s', $mountIdentifier, $attributes->path())); + } + ); + } + + public function lastModified(string $location): int + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->lastModified($path); + } catch (UnableToRetrieveMetadata $exception) { + throw UnableToRetrieveMetadata::lastModified($location, $exception->reason(), $exception); + } + } + + public function fileSize(string $location): int + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->fileSize($path); + } catch (UnableToRetrieveMetadata $exception) { + throw UnableToRetrieveMetadata::fileSize($location, $exception->reason(), $exception); + } + } + + public function mimeType(string $location): string + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->mimeType($path); + } catch (UnableToRetrieveMetadata $exception) { + throw UnableToRetrieveMetadata::mimeType($location, $exception->reason(), $exception); + } + } + + public function visibility(string $path): string + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $location] = $this->determineFilesystemAndPath($path); + + try { + return $filesystem->visibility($location); + } catch (UnableToRetrieveMetadata $exception) { + throw UnableToRetrieveMetadata::visibility($path, $exception->reason(), $exception); + } + } + + public function write(string $location, string $contents, array $config = []): void + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + $filesystem->write($path, $contents, $this->config->extend($config)->toArray()); + } catch (UnableToWriteFile $exception) { + throw UnableToWriteFile::atLocation($location, $exception->reason(), $exception); + } + } + + public function writeStream(string $location, $contents, array $config = []): void + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + $filesystem->writeStream($path, $contents, $this->config->extend($config)->toArray()); + } + + public function setVisibility(string $path, string $visibility): void + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($path); + $filesystem->setVisibility($path, $visibility); + } + + public function delete(string $location): void + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + $filesystem->delete($path); + } catch (UnableToDeleteFile $exception) { + throw UnableToDeleteFile::atLocation($location, $exception->reason(), $exception); + } + } + + public function deleteDirectory(string $location): void + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + $filesystem->deleteDirectory($path); + } catch (UnableToDeleteDirectory $exception) { + throw UnableToDeleteDirectory::atLocation($location, $exception->reason(), $exception); + } + } + + public function createDirectory(string $location, array $config = []): void + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + $filesystem->createDirectory($path, $this->config->extend($config)->toArray()); + } catch (UnableToCreateDirectory $exception) { + throw UnableToCreateDirectory::dueToFailure($location, $exception); + } + } + + public function move(string $source, string $destination, array $config = []): void + { + /** @var FilesystemOperator $sourceFilesystem */ + /* @var FilesystemOperator $destinationFilesystem */ + [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source); + [$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination); + + $sourceFilesystem === $destinationFilesystem ? $this->moveInTheSameFilesystem( + $sourceFilesystem, + $sourcePath, + $destinationPath, + $source, + $destination, + $config, + ) : $this->moveAcrossFilesystems($source, $destination, $config); + } + + public function copy(string $source, string $destination, array $config = []): void + { + /** @var FilesystemOperator $sourceFilesystem */ + /* @var FilesystemOperator $destinationFilesystem */ + [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source); + [$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination); + + $sourceFilesystem === $destinationFilesystem ? $this->copyInSameFilesystem( + $sourceFilesystem, + $sourcePath, + $destinationPath, + $source, + $destination, + $config, + ) : $this->copyAcrossFilesystem( + $sourceFilesystem, + $sourcePath, + $destinationFilesystem, + $destinationPath, + $source, + $destination, + $config, + ); + } + + public function publicUrl(string $path, array $config = []): string + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($path); + + if ( ! method_exists($filesystem, 'publicUrl')) { + throw new UnableToGeneratePublicUrl(sprintf('%s does not support generating public urls.', $filesystem::class), $path); + } + + return $filesystem->publicUrl($path, $config); + } + + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []): string + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($path); + + if ( ! method_exists($filesystem, 'temporaryUrl')) { + throw new UnableToGenerateTemporaryUrl(sprintf('%s does not support generating public urls.', $filesystem::class), $path); + } + + return $filesystem->temporaryUrl($path, $expiresAt, $this->config->extend($config)->toArray()); + } + + public function checksum(string $path, array $config = []): string + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($path); + + if ( ! method_exists($filesystem, 'checksum')) { + throw new UnableToProvideChecksum(sprintf('%s does not support providing checksums.', $filesystem::class), $path); + } + + return $filesystem->checksum($path, $this->config->extend($config)->toArray()); + } + + private function mountFilesystems(array $filesystems): void + { + foreach ($filesystems as $key => $filesystem) { + $this->guardAgainstInvalidMount($key, $filesystem); + /* @var string $key */ + /* @var FilesystemOperator $filesystem */ + $this->mountFilesystem($key, $filesystem); + } + } + + private function guardAgainstInvalidMount(mixed $key, mixed $filesystem): void + { + if ( ! is_string($key)) { + throw UnableToMountFilesystem::becauseTheKeyIsNotValid($key); + } + + if ( ! $filesystem instanceof FilesystemOperator) { + throw UnableToMountFilesystem::becauseTheFilesystemWasNotValid($filesystem); + } + } + + private function mountFilesystem(string $key, FilesystemOperator $filesystem): void + { + $this->filesystems[$key] = $filesystem; + } + + /** + * @param string $path + * + * @return array{0:FilesystemOperator, 1:string, 2:string} + */ + private function determineFilesystemAndPath(string $path): array + { + if (strpos($path, '://') < 1) { + throw UnableToResolveFilesystemMount::becauseTheSeparatorIsMissing($path); + } + + /** @var string $mountIdentifier */ + /** @var string $mountPath */ + [$mountIdentifier, $mountPath] = explode('://', $path, 2); + + if ( ! array_key_exists($mountIdentifier, $this->filesystems)) { + throw UnableToResolveFilesystemMount::becauseTheMountWasNotRegistered($mountIdentifier); + } + + return [$this->filesystems[$mountIdentifier], $mountPath, $mountIdentifier]; + } + + private function copyInSameFilesystem( + FilesystemOperator $sourceFilesystem, + string $sourcePath, + string $destinationPath, + string $source, + string $destination, + array $config, + ): void { + try { + $sourceFilesystem->copy($sourcePath, $destinationPath, $this->config->extend($config)->toArray()); + } catch (UnableToCopyFile $exception) { + throw UnableToCopyFile::fromLocationTo($source, $destination, $exception); + } + } + + private function copyAcrossFilesystem( + FilesystemOperator $sourceFilesystem, + string $sourcePath, + FilesystemOperator $destinationFilesystem, + string $destinationPath, + string $source, + string $destination, + array $config, + ): void { + $config = $this->config->extend($config); + $retainVisibility = (bool) $config->get(Config::OPTION_RETAIN_VISIBILITY, true); + $visibility = $config->get(Config::OPTION_VISIBILITY); + + try { + if ($visibility == null && $retainVisibility) { + $visibility = $sourceFilesystem->visibility($sourcePath); + $config = $config->extend(compact('visibility')); + } + + $stream = $sourceFilesystem->readStream($sourcePath); + $destinationFilesystem->writeStream($destinationPath, $stream, $config->toArray()); + } catch (UnableToRetrieveMetadata | UnableToReadFile | UnableToWriteFile $exception) { + throw UnableToCopyFile::fromLocationTo($source, $destination, $exception); + } + } + + private function moveInTheSameFilesystem( + FilesystemOperator $sourceFilesystem, + string $sourcePath, + string $destinationPath, + string $source, + string $destination, + array $config, + ): void { + try { + $sourceFilesystem->move($sourcePath, $destinationPath, $this->config->extend($config)->toArray()); + } catch (UnableToMoveFile $exception) { + throw UnableToMoveFile::fromLocationTo($source, $destination, $exception); + } + } + + private function moveAcrossFilesystems(string $source, string $destination, array $config = []): void + { + try { + $this->copy($source, $destination, $config); + $this->delete($source); + } catch (UnableToCopyFile | UnableToDeleteFile $exception) { + throw UnableToMoveFile::fromLocationTo($source, $destination, $exception); + } + } +} diff --git a/vendor/league/flysystem/src/PathNormalizer.php b/vendor/league/flysystem/src/PathNormalizer.php new file mode 100644 index 000000000..54da201ae --- /dev/null +++ b/vendor/league/flysystem/src/PathNormalizer.php @@ -0,0 +1,10 @@ +prefix = rtrim($prefix, '\\/'); + + if ($this->prefix !== '' || $prefix === $separator) { + $this->prefix .= $separator; + } + } + + public function prefixPath(string $path): string + { + return $this->prefix . ltrim($path, '\\/'); + } + + public function stripPrefix(string $path): string + { + /* @var string */ + return substr($path, strlen($this->prefix)); + } + + public function stripDirectoryPrefix(string $path): string + { + return rtrim($this->stripPrefix($path), '\\/'); + } + + public function prefixDirectoryPath(string $path): string + { + $prefixedPath = $this->prefixPath(rtrim($path, '\\/')); + + if ($prefixedPath === '' || substr($prefixedPath, -1) === $this->separator) { + return $prefixedPath; + } + + return $prefixedPath . $this->separator; + } +} diff --git a/vendor/league/flysystem/src/PathTraversalDetected.php b/vendor/league/flysystem/src/PathTraversalDetected.php new file mode 100644 index 000000000..fef3edf18 --- /dev/null +++ b/vendor/league/flysystem/src/PathTraversalDetected.php @@ -0,0 +1,25 @@ +path; + } + + public static function forPath(string $path): PathTraversalDetected + { + $e = new PathTraversalDetected("Path traversal detected: {$path}"); + $e->path = $path; + + return $e; + } +} diff --git a/vendor/league/flysystem/src/PortableVisibilityGuard.php b/vendor/league/flysystem/src/PortableVisibilityGuard.php new file mode 100644 index 000000000..6e2498b42 --- /dev/null +++ b/vendor/league/flysystem/src/PortableVisibilityGuard.php @@ -0,0 +1,19 @@ +formatPropertyName((string) $offset); + + return isset($this->{$property}); + } + + /** + * @param mixed $offset + * + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + $property = $this->formatPropertyName((string) $offset); + + return $this->{$property}; + } + + /** + * @param mixed $offset + * @param mixed $value + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value): void + { + throw new RuntimeException('Properties can not be manipulated'); + } + + /** + * @param mixed $offset + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset): void + { + throw new RuntimeException('Properties can not be manipulated'); + } +} diff --git a/vendor/league/flysystem/src/ResolveIdenticalPathConflict.php b/vendor/league/flysystem/src/ResolveIdenticalPathConflict.php new file mode 100644 index 000000000..849659b06 --- /dev/null +++ b/vendor/league/flysystem/src/ResolveIdenticalPathConflict.php @@ -0,0 +1,11 @@ +location; + } + + public static function atLocation(string $pathName): SymbolicLinkEncountered + { + $e = new static("Unsupported symbolic link encountered at location $pathName"); + $e->location = $pathName; + + return $e; + } +} diff --git a/vendor/league/flysystem/src/UnableToCheckDirectoryExistence.php b/vendor/league/flysystem/src/UnableToCheckDirectoryExistence.php new file mode 100644 index 000000000..73ce858b9 --- /dev/null +++ b/vendor/league/flysystem/src/UnableToCheckDirectoryExistence.php @@ -0,0 +1,13 @@ +source; + } + + public function destination(): string + { + return $this->destination; + } + + public static function fromLocationTo( + string $sourcePath, + string $destinationPath, + ?Throwable $previous = null + ): UnableToCopyFile { + $e = new static("Unable to copy file from $sourcePath to $destinationPath", 0 , $previous); + $e->source = $sourcePath; + $e->destination = $destinationPath; + + return $e; + } + + public static function sourceAndDestinationAreTheSame(string $source, string $destination): UnableToCopyFile + { + return UnableToCopyFile::because('Source and destination are the same', $source, $destination); + } + + public static function because(string $reason, string $sourcePath, string $destinationPath): UnableToCopyFile + { + $e = new static("Unable to copy file from $sourcePath to $destinationPath, because $reason"); + $e->source = $sourcePath; + $e->destination = $destinationPath; + + return $e; + } + + public function operation(): string + { + return FilesystemOperationFailed::OPERATION_COPY; + } +} diff --git a/vendor/league/flysystem/src/UnableToCreateDirectory.php b/vendor/league/flysystem/src/UnableToCreateDirectory.php new file mode 100644 index 000000000..f4eead0c7 --- /dev/null +++ b/vendor/league/flysystem/src/UnableToCreateDirectory.php @@ -0,0 +1,50 @@ +location = $dirname; + $e->reason = $errorMessage; + + return $e; + } + + public static function dueToFailure(string $dirname, Throwable $previous): UnableToCreateDirectory + { + $reason = $previous instanceof UnableToCreateDirectory ? $previous->reason() : ''; + $message = "Unable to create a directory at $dirname. $reason"; + $e = new static(rtrim($message), 0, $previous); + $e->location = $dirname; + $e->reason = $reason ?: $message; + + return $e; + } + + public function operation(): string + { + return FilesystemOperationFailed::OPERATION_CREATE_DIRECTORY; + } + + public function reason(): string + { + return $this->reason; + } + + public function location(): string + { + return $this->location; + } +} diff --git a/vendor/league/flysystem/src/UnableToDeleteDirectory.php b/vendor/league/flysystem/src/UnableToDeleteDirectory.php new file mode 100644 index 000000000..bf6cf3b98 --- /dev/null +++ b/vendor/league/flysystem/src/UnableToDeleteDirectory.php @@ -0,0 +1,48 @@ +location = $location; + $e->reason = $reason; + + return $e; + } + + public function operation(): string + { + return FilesystemOperationFailed::OPERATION_DELETE_DIRECTORY; + } + + public function reason(): string + { + return $this->reason; + } + + public function location(): string + { + return $this->location; + } +} diff --git a/vendor/league/flysystem/src/UnableToDeleteFile.php b/vendor/league/flysystem/src/UnableToDeleteFile.php new file mode 100644 index 000000000..e388f3320 --- /dev/null +++ b/vendor/league/flysystem/src/UnableToDeleteFile.php @@ -0,0 +1,45 @@ +location = $location; + $e->reason = $reason; + + return $e; + } + + public function operation(): string + { + return FilesystemOperationFailed::OPERATION_DELETE; + } + + public function reason(): string + { + return $this->reason; + } + + public function location(): string + { + return $this->location; + } +} diff --git a/vendor/league/flysystem/src/UnableToGeneratePublicUrl.php b/vendor/league/flysystem/src/UnableToGeneratePublicUrl.php new file mode 100644 index 000000000..e3648c75f --- /dev/null +++ b/vendor/league/flysystem/src/UnableToGeneratePublicUrl.php @@ -0,0 +1,26 @@ +getMessage(), $path, $exception); + } + + public static function noGeneratorConfigured(string $path, string $extraReason = ''): static + { + return new static('No generator was configured ' . $extraReason, $path); + } +} diff --git a/vendor/league/flysystem/src/UnableToGenerateTemporaryUrl.php b/vendor/league/flysystem/src/UnableToGenerateTemporaryUrl.php new file mode 100644 index 000000000..338601c73 --- /dev/null +++ b/vendor/league/flysystem/src/UnableToGenerateTemporaryUrl.php @@ -0,0 +1,26 @@ +getMessage(), $path, $exception); + } + + public static function noGeneratorConfigured(string $path, string $extraReason = ''): static + { + return new static('No generator was configured ' . $extraReason, $path); + } +} diff --git a/vendor/league/flysystem/src/UnableToListContents.php b/vendor/league/flysystem/src/UnableToListContents.php new file mode 100644 index 000000000..4f7961dd1 --- /dev/null +++ b/vendor/league/flysystem/src/UnableToListContents.php @@ -0,0 +1,24 @@ +getMessage(); + + return new UnableToListContents($message, 0, $previous); + } + + public function operation(): string + { + return self::OPERATION_LIST_CONTENTS; + } +} diff --git a/vendor/league/flysystem/src/UnableToMountFilesystem.php b/vendor/league/flysystem/src/UnableToMountFilesystem.php new file mode 100644 index 000000000..bd41bc98f --- /dev/null +++ b/vendor/league/flysystem/src/UnableToMountFilesystem.php @@ -0,0 +1,32 @@ +source; + } + + public function destination(): string + { + return $this->destination; + } + + public static function fromLocationTo( + string $sourcePath, + string $destinationPath, + ?Throwable $previous = null + ): UnableToMoveFile { + $message = $previous?->getMessage() ?? "Unable to move file from $sourcePath to $destinationPath"; + $e = new static($message, 0, $previous); + $e->source = $sourcePath; + $e->destination = $destinationPath; + + return $e; + } + + public static function because( + string $reason, + string $sourcePath, + string $destinationPath, + ): UnableToMoveFile { + $message = "Unable to move file from $sourcePath to $destinationPath, because $reason"; + $e = new static($message); + $e->source = $sourcePath; + $e->destination = $destinationPath; + + return $e; + } + + public function operation(): string + { + return FilesystemOperationFailed::OPERATION_MOVE; + } +} diff --git a/vendor/league/flysystem/src/UnableToProvideChecksum.php b/vendor/league/flysystem/src/UnableToProvideChecksum.php new file mode 100644 index 000000000..35e050237 --- /dev/null +++ b/vendor/league/flysystem/src/UnableToProvideChecksum.php @@ -0,0 +1,16 @@ +location = $location; + $e->reason = $reason; + + return $e; + } + + public function operation(): string + { + return FilesystemOperationFailed::OPERATION_READ; + } + + public function reason(): string + { + return $this->reason; + } + + public function location(): string + { + return $this->location; + } +} diff --git a/vendor/league/flysystem/src/UnableToResolveFilesystemMount.php b/vendor/league/flysystem/src/UnableToResolveFilesystemMount.php new file mode 100644 index 000000000..91a9ee3e3 --- /dev/null +++ b/vendor/league/flysystem/src/UnableToResolveFilesystemMount.php @@ -0,0 +1,20 @@ +reason = $reason; + $e->location = $location; + $e->metadataType = $type; + + return $e; + } + + public function reason(): string + { + return $this->reason; + } + + public function location(): string + { + return $this->location; + } + + public function metadataType(): string + { + return $this->metadataType; + } + + public function operation(): string + { + return FilesystemOperationFailed::OPERATION_RETRIEVE_METADATA; + } +} diff --git a/vendor/league/flysystem/src/UnableToSetVisibility.php b/vendor/league/flysystem/src/UnableToSetVisibility.php new file mode 100644 index 000000000..d0126098d --- /dev/null +++ b/vendor/league/flysystem/src/UnableToSetVisibility.php @@ -0,0 +1,49 @@ +reason; + } + + public static function atLocation(string $filename, string $extraMessage = '', ?Throwable $previous = null): self + { + $message = "Unable to set visibility for file {$filename}. $extraMessage"; + $e = new static(rtrim($message), 0, $previous); + $e->reason = $extraMessage; + $e->location = $filename; + + return $e; + } + + public function operation(): string + { + return FilesystemOperationFailed::OPERATION_SET_VISIBILITY; + } + + public function location(): string + { + return $this->location; + } +} diff --git a/vendor/league/flysystem/src/UnableToWriteFile.php b/vendor/league/flysystem/src/UnableToWriteFile.php new file mode 100644 index 000000000..fb9a1006f --- /dev/null +++ b/vendor/league/flysystem/src/UnableToWriteFile.php @@ -0,0 +1,45 @@ +location = $location; + $e->reason = $reason; + + return $e; + } + + public function operation(): string + { + return FilesystemOperationFailed::OPERATION_WRITE; + } + + public function reason(): string + { + return $this->reason; + } + + public function location(): string + { + return $this->location; + } +} diff --git a/vendor/league/flysystem/src/UnixVisibility/PortableVisibilityConverter.php b/vendor/league/flysystem/src/UnixVisibility/PortableVisibilityConverter.php new file mode 100644 index 000000000..117b4d966 --- /dev/null +++ b/vendor/league/flysystem/src/UnixVisibility/PortableVisibilityConverter.php @@ -0,0 +1,79 @@ +filePublic + : $this->filePrivate; + } + + public function forDirectory(string $visibility): int + { + PortableVisibilityGuard::guardAgainstInvalidInput($visibility); + + return $visibility === Visibility::PUBLIC + ? $this->directoryPublic + : $this->directoryPrivate; + } + + public function inverseForFile(int $visibility): string + { + if ($visibility === $this->filePublic) { + return Visibility::PUBLIC; + } elseif ($visibility === $this->filePrivate) { + return Visibility::PRIVATE; + } + + return Visibility::PUBLIC; // default + } + + public function inverseForDirectory(int $visibility): string + { + if ($visibility === $this->directoryPublic) { + return Visibility::PUBLIC; + } elseif ($visibility === $this->directoryPrivate) { + return Visibility::PRIVATE; + } + + return Visibility::PUBLIC; // default + } + + public function defaultForDirectories(): int + { + return $this->defaultForDirectories === Visibility::PUBLIC ? $this->directoryPublic : $this->directoryPrivate; + } + + /** + * @param array $permissionMap + */ + public static function fromArray(array $permissionMap, string $defaultForDirectories = Visibility::PRIVATE): PortableVisibilityConverter + { + return new PortableVisibilityConverter( + $permissionMap['file']['public'] ?? 0644, + $permissionMap['file']['private'] ?? 0600, + $permissionMap['dir']['public'] ?? 0755, + $permissionMap['dir']['private'] ?? 0700, + $defaultForDirectories + ); + } +} diff --git a/vendor/league/flysystem/src/UnixVisibility/VisibilityConverter.php b/vendor/league/flysystem/src/UnixVisibility/VisibilityConverter.php new file mode 100644 index 000000000..64af86a07 --- /dev/null +++ b/vendor/league/flysystem/src/UnixVisibility/VisibilityConverter.php @@ -0,0 +1,14 @@ +location; + } + + public static function atLocation(string $location): UnreadableFileEncountered + { + $e = new static("Unreadable file encountered at location {$location}."); + $e->location = $location; + + return $e; + } +} diff --git a/vendor/league/flysystem/src/UrlGeneration/ChainedPublicUrlGenerator.php b/vendor/league/flysystem/src/UrlGeneration/ChainedPublicUrlGenerator.php new file mode 100644 index 000000000..079375a16 --- /dev/null +++ b/vendor/league/flysystem/src/UrlGeneration/ChainedPublicUrlGenerator.php @@ -0,0 +1,30 @@ +generators as $generator) { + try { + return $generator->publicUrl($path, $config); + } catch (UnableToGeneratePublicUrl) { + } + } + + throw new UnableToGeneratePublicUrl('No supported public url generator found.', $path); + } +} diff --git a/vendor/league/flysystem/src/UrlGeneration/PrefixPublicUrlGenerator.php b/vendor/league/flysystem/src/UrlGeneration/PrefixPublicUrlGenerator.php new file mode 100644 index 000000000..792171551 --- /dev/null +++ b/vendor/league/flysystem/src/UrlGeneration/PrefixPublicUrlGenerator.php @@ -0,0 +1,23 @@ +prefixer = new PathPrefixer($urlPrefix, '/'); + } + + public function publicUrl(string $path, Config $config): string + { + return $this->prefixer->prefixPath($path); + } +} diff --git a/vendor/league/flysystem/src/UrlGeneration/PublicUrlGenerator.php b/vendor/league/flysystem/src/UrlGeneration/PublicUrlGenerator.php new file mode 100644 index 000000000..08c987fd2 --- /dev/null +++ b/vendor/league/flysystem/src/UrlGeneration/PublicUrlGenerator.php @@ -0,0 +1,16 @@ +count = count($prefixes); + + if ($this->count === 0) { + throw new InvalidArgumentException('At least one prefix is required.'); + } + + $this->prefixes = array_map(static fn (string $prefix) => new PathPrefixer($prefix, '/'), $prefixes); + } + + public function publicUrl(string $path, Config $config): string + { + $index = abs(crc32($path)) % $this->count; + + return $this->prefixes[$index]->prefixPath($path); + } +} diff --git a/vendor/league/flysystem/src/UrlGeneration/TemporaryUrlGenerator.php b/vendor/league/flysystem/src/UrlGeneration/TemporaryUrlGenerator.php new file mode 100644 index 000000000..87074f753 --- /dev/null +++ b/vendor/league/flysystem/src/UrlGeneration/TemporaryUrlGenerator.php @@ -0,0 +1,16 @@ +rejectFunkyWhiteSpace($path); + + return $this->normalizeRelativePath($path); + } + + private function rejectFunkyWhiteSpace(string $path): void + { + if (preg_match('#\p{C}+#u', $path)) { + throw CorruptedPathDetected::forPath($path); + } + } + + private function normalizeRelativePath(string $path): string + { + $parts = []; + + foreach (explode('/', $path) as $part) { + switch ($part) { + case '': + case '.': + break; + + case '..': + if (empty($parts)) { + throw PathTraversalDetected::forPath($path); + } + array_pop($parts); + break; + + default: + $parts[] = $part; + break; + } + } + + return implode('/', $parts); + } +} diff --git a/vendor/league/mime-type-detection/CHANGELOG.md b/vendor/league/mime-type-detection/CHANGELOG.md new file mode 100644 index 000000000..eb138635a --- /dev/null +++ b/vendor/league/mime-type-detection/CHANGELOG.md @@ -0,0 +1,64 @@ +# Changelog + +## 1.16.0 - 2025-09-21 + +- Updated lookup +- Prepped for 8.4 implicit nullable deprecation + +## 1.15.0 - 2024-01-28 + +- Updated lookup + +## 1.14.0 - 2022-10-17 + +### Updated + +- Updated lookup + +## 1.13.0 - 2023-08-05 + +### Added + +- A reverse lookup mechanism to fetch one or all extensions for a given mimetype + +## 1.12.0 - 2023-08-03 + +### Updated + +- Updated lookup + +## 1.11.0 - 2023-04-17 + +### Updated + +- Updated lookup + +## 1.10.0 - 2022-04-11 + +### Fixed + +- Added Flysystem v1 inconclusive mime-types and made it configurable as a constructor parameter. + +## 1.9.0 - 2021-11-21 + +### Updated + +- Updated lookup + +## 1.8.0 - 2021-09-25 + +### Added + +- Added the decorator `OverridingExtensionToMimeTypeMap` which allows you to override values. + +## 1.7.0 - 2021-01-18 + +### Added + +- Added a `bufferSampleSize` parameter to the `FinfoMimeTypeDetector` class that allows you to send a reduced content sample which costs less memory. + +## 1.6.0 - 2021-01-18 + +### Changes + +- Updated generated mime-type map diff --git a/vendor/league/mime-type-detection/LICENSE b/vendor/league/mime-type-detection/LICENSE new file mode 100644 index 000000000..39d50b5e7 --- /dev/null +++ b/vendor/league/mime-type-detection/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2023 Frank de Jonge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/mime-type-detection/composer.json b/vendor/league/mime-type-detection/composer.json new file mode 100644 index 000000000..cd75beea7 --- /dev/null +++ b/vendor/league/mime-type-detection/composer.json @@ -0,0 +1,34 @@ +{ + "name": "league/mime-type-detection", + "description": "Mime-type detection for Flysystem", + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "scripts": { + "test": "vendor/bin/phpunit", + "phpstan": "vendor/bin/phpstan analyse -l 6 src" + }, + "require": { + "php": "^7.4 || ^8.0", + "ext-fileinfo": "*" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0", + "phpstan/phpstan": "^0.12.68", + "friendsofphp/php-cs-fixer": "^3.2" + }, + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "config": { + "platform": { + "php": "7.4.0" + } + } +} diff --git a/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php new file mode 100644 index 000000000..fc0424161 --- /dev/null +++ b/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php @@ -0,0 +1,13 @@ +extensions = $extensions ?: new GeneratedExtensionToMimeTypeMap(); + } + + public function detectMimeType(string $path, $contents): ?string + { + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromPath(string $path): ?string + { + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + return $this->extensions->lookupMimeType($extension); + } + + public function detectMimeTypeFromFile(string $path): ?string + { + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromBuffer(string $contents): ?string + { + return null; + } + + public function lookupExtension(string $mimetype): ?string + { + return $this->extensions instanceof ExtensionLookup + ? $this->extensions->lookupExtension($mimetype) + : null; + } + + public function lookupAllExtensions(string $mimetype): array + { + return $this->extensions instanceof ExtensionLookup + ? $this->extensions->lookupAllExtensions($mimetype) + : []; + } +} diff --git a/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php new file mode 100644 index 000000000..1dad7bc1a --- /dev/null +++ b/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php @@ -0,0 +1,10 @@ + + */ + private $inconclusiveMimetypes; + + public function __construct( + string $magicFile = '', + ?ExtensionToMimeTypeMap $extensionMap = null, + ?int $bufferSampleSize = null, + array $inconclusiveMimetypes = self::INCONCLUSIVE_MIME_TYPES + ) { + $this->finfo = new finfo(FILEINFO_MIME_TYPE, $magicFile); + $this->extensionMap = $extensionMap ?: new GeneratedExtensionToMimeTypeMap(); + $this->bufferSampleSize = $bufferSampleSize; + $this->inconclusiveMimetypes = $inconclusiveMimetypes; + } + + public function detectMimeType(string $path, $contents): ?string + { + $mimeType = is_string($contents) + ? (@$this->finfo->buffer($this->takeSample($contents)) ?: null) + : null; + + if ($mimeType !== null && ! in_array($mimeType, $this->inconclusiveMimetypes)) { + return $mimeType; + } + + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromPath(string $path): ?string + { + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + return $this->extensionMap->lookupMimeType($extension); + } + + public function detectMimeTypeFromFile(string $path): ?string + { + return @$this->finfo->file($path) ?: null; + } + + public function detectMimeTypeFromBuffer(string $contents): ?string + { + return @$this->finfo->buffer($this->takeSample($contents)) ?: null; + } + + private function takeSample(string $contents): string + { + if ($this->bufferSampleSize === null) { + return $contents; + } + + return (string) substr($contents, 0, $this->bufferSampleSize); + } + + public function lookupExtension(string $mimetype): ?string + { + return $this->extensionMap instanceof ExtensionLookup + ? $this->extensionMap->lookupExtension($mimetype) + : null; + } + + public function lookupAllExtensions(string $mimetype): array + { + return $this->extensionMap instanceof ExtensionLookup + ? $this->extensionMap->lookupAllExtensions($mimetype) + : []; + } +} diff --git a/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php new file mode 100644 index 000000000..65e986aec --- /dev/null +++ b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php @@ -0,0 +1,2310 @@ + + * + * @internal + */ + public const MIME_TYPES_FOR_EXTENSIONS = [ + '1km' => 'application/vnd.1000minds.decision-model+xml', + '3dml' => 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + '3gpp' => 'video/3gpp', + '3mf' => 'model/3mf', + '7z' => 'application/x-7z-compressed', + '7zip' => 'application/x-7z-compressed', + '123' => 'application/vnd.lotus-1-2-3', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/acc', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac3' => 'audio/ac3', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'adts' => 'audio/aac', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'age' => 'application/vnd.age', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/pdf', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'aml' => 'application/automationml-aml+xml', + 'amlx' => 'application/automationml-amlx+zip', + 'amr' => 'audio/amr', + 'apk' => 'application/vnd.android.package-archive', + 'apng' => 'image/apng', + 'appcache' => 'text/cache-manifest', + 'appinstaller' => 'application/appinstaller', + 'application' => 'application/x-ms-application', + 'appx' => 'application/appx', + 'appxbundle' => 'application/appxbundle', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'arj' => 'application/x-arj', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomdeleted' => 'application/atomdeleted+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/x-au', + 'avci' => 'image/avci', + 'avcs' => 'image/avcs', + 'avi' => 'video/x-msvideo', + 'avif' => 'image/avif', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azv' => 'image/vnd.airzip.accelerator.azv', + 'azw' => 'application/vnd.amazon.ebook', + 'b16' => 'image/vnd.pco.b16', + 'bary' => 'model/vnd.bary', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bdo' => 'application/vnd.nato.bindingdataobject+xml', + 'bdoc' => 'application/x-bdoc', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmml' => 'application/vnd.balsamiq.bmml+xml', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'brf' => 'application/braille', + 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btf' => 'image/prs.btif', + 'btif' => 'image/prs.btif', + 'buffer' => 'application/octet-stream', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cco' => 'application/x-cocoa', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdfx' => 'application/cdfx+xml', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdr' => 'application/cdr', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cjs' => 'application/node', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/octet-stream', + 'cld' => 'model/vnd.cld', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'coffee' => 'text/coffeescript', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpl' => 'application/cpl+xml', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'crx' => 'application/x-chrome-extension', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csl' => 'application/vnd.citationstyles.style+xml', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'csr' => 'application/octet-stream', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cwl' => 'application/cwl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbf' => 'application/vnd.dbf', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'ddf' => 'application/vnd.syncml.dmddf+xml', + 'dds' => 'image/vnd.ms-dds', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dib' => 'image/bmp', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'disposition-notification' => 'message/disposition-notification', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/x-apple-diskimage', + 'dmn' => 'application/octet-stream', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dpx' => 'image/dpx', + 'dra' => 'audio/vnd.dra', + 'drle' => 'image/dicom-rle', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dst' => 'application/octet-stream', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwd' => 'application/atsc-dwd+xml', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ear' => 'application/java-archive', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'image/emf', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emotionml' => 'application/emotionml+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'exi' => 'application/exi', + 'exp' => 'application/express', + 'exr' => 'image/aces', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/mp4', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fdt' => 'application/fdt+xml', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fits' => 'image/fits', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'fo' => 'application/vnd.software602.filler.form+xml', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'gdoc' => 'application/vnd.google-apps.document', + 'ged' => 'text/vnd.familysearch.gedcom', + 'geo' => 'application/vnd.dynageo', + 'geojson' => 'application/geo+json', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggs' => 'application/vnd.geogebra.slides', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'glb' => 'model/gltf-binary', + 'gltf' => 'model/gltf+json', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gpg' => 'application/gpg-keys', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gslides' => 'application/vnd.google-apps.presentation', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/gzip', + 'gzip' => 'application/gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hbs' => 'text/x-handlebars-template', + 'hdd' => 'application/x-virtualbox-hdd', + 'hdf' => 'application/x-hdf', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'hej2' => 'image/hej2k', + 'held' => 'application/atsc-held+xml', + 'hh' => 'text/x-c', + 'hjson' => 'application/hjson', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hsj2' => 'image/hsj2', + 'htc' => 'text/x-component', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'img' => 'application/octet-stream', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'its' => 'application/its+xml', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jade' => 'text/jade', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'jardiff' => 'application/x-java-archive-diff', + 'java' => 'text/x-java-source', + 'jhc' => 'image/jphc', + 'jisp' => 'application/vnd.jisp', + 'jls' => 'image/jls', + 'jlt' => 'application/vnd.hp-jlyt', + 'jng' => 'image/x-jng', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpf' => 'image/jpx', + 'jpg' => 'image/jpeg', + 'jpg2' => 'image/jp2', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jph' => 'image/jph', + 'jpm' => 'video/jpm', + 'jpx' => 'image/jpx', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'json5' => 'application/json5', + 'jsonld' => 'application/ld+json', + 'jsonml' => 'application/jsonml+json', + 'jsx' => 'text/jsx', + 'jt' => 'model/jt', + 'jxl' => 'image/jxl', + 'jxr' => 'image/jxr', + 'jxra' => 'image/jxra', + 'jxrs' => 'image/jxrs', + 'jxs' => 'image/jxs', + 'jxsc' => 'image/jxsc', + 'jxsi' => 'image/jxsi', + 'jxss' => 'image/jxss', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kdb' => 'application/octet-stream', + 'kdbx' => 'application/x-keepass2', + 'key' => 'application/x-iwork-keynote-sffkey', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktx2' => 'image/ktx2', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'less' => 'text/less', + 'lgr' => 'application/lgr+xml', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'litcoffee' => 'text/coffeescript', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lua' => 'text/x-lua', + 'luac' => 'application/x-lua-bytecode', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2t' => 'video/mp2t', + 'm2ts' => 'video/mp2t', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'text/plain', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/x-m4a', + 'm4p' => 'application/mp4', + 'm4s' => 'video/iso.segment', + 'm4u' => 'application/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm21' => 'application/mp21', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'maei' => 'application/mmt-aei+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'manifest' => 'text/cache-manifest', + 'map' => 'application/json', + 'mar' => 'application/octet-stream', + 'markdown' => 'text/markdown', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'md' => 'text/markdown', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'mdx' => 'text/mdx', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mjs' => 'text/javascript', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mkd' => 'text/x-markdown', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mml' => 'text/mathml', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mp21' => 'application/mp21', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpd' => 'application/dash+xml', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpf' => 'application/media-policy-dataset+xml', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msg' => 'application/vnd.ms-outlook', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msix' => 'application/msix', + 'msixbundle' => 'application/msixbundle', + 'msl' => 'application/vnd.mobius.msl', + 'msm' => 'application/octet-stream', + 'msp' => 'application/octet-stream', + 'msty' => 'application/vnd.muvee.style', + 'mtl' => 'model/mtl', + 'mts' => 'video/mp2t', + 'mus' => 'application/vnd.musician', + 'musd' => 'application/mmt-usd+xml', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mvt' => 'application/vnd.mapbox-vector-tile', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxmf' => 'audio/mobile-xmf', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'ndjson' => 'application/x-ndjson', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nq' => 'application/n-quads', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'nt' => 'application/n-triples', + 'ntf' => 'application/vnd.nitf', + 'numbers' => 'application/x-iwork-numbers-sffnumbers', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obgx' => 'application/vnd.openblox.game+xml', + 'obj' => 'model/obj', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogex' => 'model/vnd.opengex', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'opus' => 'audio/ogg', + 'org' => 'text/x-org', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'osm' => 'application/vnd.openstreetmap.data+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'font/otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ova' => 'application/x-virtualbox-ova', + 'ovf' => 'application/x-virtualbox-ovf', + 'owl' => 'application/rdf+xml', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p7a' => 'application/x-pkcs7-signature', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'pages' => 'application/x-iwork-pages-sffpages', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/x-pilot', + 'pde' => 'text/x-processing', + 'pdf' => 'application/pdf', + 'pem' => 'application/x-x509-user-cert', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp', + 'phar' => 'application/octet-stream', + 'php' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'phtml' => 'application/x-httpd-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'pkpass' => 'application/vnd.apple.pkpass', + 'pl' => 'application/x-perl', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pm' => 'application/x-perl', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'model/prc', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'provx' => 'application/provenance+xml', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'application/x-photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'pti' => 'image/prs.pti', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pv' => 'application/octet-stream', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pxf' => 'application/octet-stream', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyo' => 'model/vnd.pytha.pyox', + 'pyox' => 'model/vnd.pytha.pyox', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'raml' => 'application/raml+yaml', + 'rapd' => 'application/route-apd+xml', + 'rar' => 'application/x-rar', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'relo' => 'application/p2p-overlay+xml', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rng' => 'application/xml', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsa' => 'application/x-pkcs7', + 'rsat' => 'application/atsc-rsat+xml', + 'rsd' => 'application/rsd+xml', + 'rsheet' => 'application/urc-ressheet+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'run' => 'application/x-makeself', + 'rusd' => 'application/route-usd+xml', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sass' => 'text/x-sass', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scss' => 'text/x-scss', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'sea' => 'application/octet-stream', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'senmlx' => 'application/senml+xml', + 'sensmlx' => 'application/sensml+xml', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shex' => 'text/shex', + 'shf' => 'application/shf+xml', + 'shtml' => 'text/html', + 'sid' => 'image/x-mrsid-image', + 'sieve' => 'application/sieve', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'siv' => 'application/sieve', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slim' => 'text/slim', + 'slm' => 'text/slim', + 'sls' => 'application/route-s-tsid+xml', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spdx' => 'text/spdx', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'sst' => 'application/octet-stream', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'step' => 'application/STEP', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'model/stl', + 'stp' => 'application/STEP', + 'stpx' => 'model/step+xml', + 'stpxz' => 'model/step-xml+zip', + 'stpz' => 'model/step+zip', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'styl' => 'text/stylus', + 'stylus' => 'text/stylus', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'swidtag' => 'application/swid+xml', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 't38' => 'image/t38', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tap' => 'image/vnd.tencent.tap', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'td' => 'application/urc-targetdesc+xml', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tfx' => 'image/tiff-fx', + 'tga' => 'image/x-tga', + 'tgz' => 'application/x-tar', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tk' => 'application/x-tcl', + 'tmo' => 'application/vnd.tmobile-livetv', + 'toml' => 'application/toml', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trig' => 'application/trig', + 'trm' => 'application/x-msterminal', + 'ts' => 'video/mp2t', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'font/collection', + 'ttf' => 'font/ttf', + 'ttl' => 'text/turtle', + 'ttml' => 'application/ttml+xml', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u3d' => 'model/u3d', + 'u8dsn' => 'message/global-delivery-status', + 'u8hdr' => 'message/global-headers', + 'u8mdn' => 'message/global-disposition-notification', + 'u8msg' => 'message/global', + 'u32' => 'application/x-authorware-bin', + 'ubj' => 'application/ubjson', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uo' => 'application/vnd.uoml+xml', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'usda' => 'model/vnd.usda', + 'usdz' => 'model/vnd.usdz+zip', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vbox' => 'application/x-virtualbox-vbox', + 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vdi' => 'application/x-virtualbox-vdi', + 'vds' => 'model/vnd.sap.vds', + 'vhd' => 'application/x-virtualbox-vhd', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vlc' => 'application/videolan', + 'vmdk' => 'application/x-virtualbox-vmdk', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtf' => 'image/vnd.valve.source.texture', + 'vtt' => 'text/vtt', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wadl' => 'application/vnd.sun.wadl+xml', + 'war' => 'application/java-archive', + 'wasm' => 'application/wasm', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webapp' => 'application/x-web-app-manifest+json', + 'webm' => 'video/webm', + 'webmanifest' => 'application/manifest+json', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgsl' => 'text/wgsl', + 'wgt' => 'application/widget', + 'wif' => 'application/watcherinfo+xml', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'image/wmf', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + 'word' => 'application/msword', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsc' => 'message/vnd.wfa.wsc', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+fastinfoset', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d-vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'x32' => 'application/x-authorware-bin', + 'x_b' => 'model/vnd.parasolid.transmit.binary', + 'x_t' => 'model/vnd.parasolid.transmit.text', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xav' => 'application/xcap-att+xml', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xca' => 'application/xcap-caps+xml', + 'xcs' => 'application/calendar+xml', + 'xdcf' => 'application/vnd.gov.sk.xmldatacontainer+xml', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xel' => 'application/xcap-el+xml', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtm' => 'application/vnd.pwg-xhtml-print+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xns' => 'application/xcap-ns+xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsd' => 'application/xml', + 'xsf' => 'application/prs.xsf+xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'ymp' => 'text/x-suse-ymp', + 'z' => 'application/x-compress', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'zsh' => 'text/x-scriptzsh', + ]; + + /** + * @var array + * + * @internal + */ + public const EXTENSIONS_FOR_MIME_TIMES = [ + 'application/andrew-inset' => ['ez'], + 'application/appinstaller' => ['appinstaller'], + 'application/applixware' => ['aw'], + 'application/appx' => ['appx'], + 'application/appxbundle' => ['appxbundle'], + 'application/atom+xml' => ['atom'], + 'application/atomcat+xml' => ['atomcat'], + 'application/atomdeleted+xml' => ['atomdeleted'], + 'application/atomsvc+xml' => ['atomsvc'], + 'application/atsc-dwd+xml' => ['dwd'], + 'application/atsc-held+xml' => ['held'], + 'application/atsc-rsat+xml' => ['rsat'], + 'application/automationml-aml+xml' => ['aml'], + 'application/automationml-amlx+zip' => ['amlx'], + 'application/bdoc' => ['bdoc'], + 'application/calendar+xml' => ['xcs'], + 'application/ccxml+xml' => ['ccxml'], + 'application/cdfx+xml' => ['cdfx'], + 'application/cdmi-capability' => ['cdmia'], + 'application/cdmi-container' => ['cdmic'], + 'application/cdmi-domain' => ['cdmid'], + 'application/cdmi-object' => ['cdmio'], + 'application/cdmi-queue' => ['cdmiq'], + 'application/cpl+xml' => ['cpl'], + 'application/cu-seeme' => ['cu'], + 'application/cwl' => ['cwl'], + 'application/dash+xml' => ['mpd'], + 'application/dash-patch+xml' => ['mpp'], + 'application/davmount+xml' => ['davmount'], + 'application/docbook+xml' => ['dbk'], + 'application/dssc+der' => ['dssc'], + 'application/dssc+xml' => ['xdssc'], + 'application/ecmascript' => ['ecma'], + 'application/emma+xml' => ['emma'], + 'application/emotionml+xml' => ['emotionml'], + 'application/epub+zip' => ['epub'], + 'application/exi' => ['exi'], + 'application/express' => ['exp'], + 'application/fdf' => ['fdf'], + 'application/fdt+xml' => ['fdt'], + 'application/font-tdpfr' => ['pfr'], + 'application/geo+json' => ['geojson'], + 'application/gml+xml' => ['gml'], + 'application/gpx+xml' => ['gpx'], + 'application/gxf' => ['gxf'], + 'application/gzip' => ['gz', 'gzip'], + 'application/hjson' => ['hjson'], + 'application/hyperstudio' => ['stk'], + 'application/inkml+xml' => ['ink', 'inkml'], + 'application/ipfix' => ['ipfix'], + 'application/its+xml' => ['its'], + 'application/java-archive' => ['jar', 'war', 'ear'], + 'application/java-serialized-object' => ['ser'], + 'application/java-vm' => ['class'], + 'application/javascript' => ['js'], + 'application/json' => ['json', 'map'], + 'application/json5' => ['json5'], + 'application/jsonml+json' => ['jsonml'], + 'application/ld+json' => ['jsonld'], + 'application/lgr+xml' => ['lgr'], + 'application/lost+xml' => ['lostxml'], + 'application/mac-binhex40' => ['hqx'], + 'application/mac-compactpro' => ['cpt'], + 'application/mads+xml' => ['mads'], + 'application/manifest+json' => ['webmanifest'], + 'application/marc' => ['mrc'], + 'application/marcxml+xml' => ['mrcx'], + 'application/mathematica' => ['ma', 'nb', 'mb'], + 'application/mathml+xml' => ['mathml'], + 'application/mbox' => ['mbox'], + 'application/media-policy-dataset+xml' => ['mpf'], + 'application/mediaservercontrol+xml' => ['mscml'], + 'application/metalink+xml' => ['metalink'], + 'application/metalink4+xml' => ['meta4'], + 'application/mets+xml' => ['mets'], + 'application/mmt-aei+xml' => ['maei'], + 'application/mmt-usd+xml' => ['musd'], + 'application/mods+xml' => ['mods'], + 'application/mp21' => ['m21', 'mp21'], + 'application/mp4' => ['mp4', 'mpg4', 'mp4s', 'm4p'], + 'application/msix' => ['msix'], + 'application/msixbundle' => ['msixbundle'], + 'application/msword' => ['doc', 'dot', 'word'], + 'application/mxf' => ['mxf'], + 'application/n-quads' => ['nq'], + 'application/n-triples' => ['nt'], + 'application/node' => ['cjs'], + 'application/octet-stream' => ['bin', 'dms', 'lrf', 'mar', 'so', 'dist', 'distz', 'pkg', 'bpk', 'dump', 'elc', 'deploy', 'exe', 'dll', 'deb', 'dmg', 'iso', 'img', 'msi', 'msp', 'msm', 'buffer', 'phar', 'lha', 'lzh', 'class', 'sea', 'dmn', 'bpmn', 'kdb', 'sst', 'csr', 'dst', 'pv', 'pxf'], + 'application/oda' => ['oda'], + 'application/oebps-package+xml' => ['opf'], + 'application/ogg' => ['ogx'], + 'application/omdoc+xml' => ['omdoc'], + 'application/onenote' => ['onetoc', 'onetoc2', 'onetmp', 'onepkg'], + 'application/oxps' => ['oxps'], + 'application/p2p-overlay+xml' => ['relo'], + 'application/patch-ops-error+xml' => ['xer'], + 'application/pdf' => ['pdf', 'ai'], + 'application/pgp-encrypted' => ['pgp'], + 'application/pgp-keys' => ['asc'], + 'application/pgp-signature' => ['sig', 'asc'], + 'application/pics-rules' => ['prf'], + 'application/pkcs10' => ['p10'], + 'application/pkcs7-mime' => ['p7m', 'p7c'], + 'application/pkcs7-signature' => ['p7s'], + 'application/pkcs8' => ['p8'], + 'application/pkix-attr-cert' => ['ac'], + 'application/pkix-cert' => ['cer'], + 'application/pkix-crl' => ['crl'], + 'application/pkix-pkipath' => ['pkipath'], + 'application/pkixcmp' => ['pki'], + 'application/pls+xml' => ['pls'], + 'application/postscript' => ['ai', 'eps', 'ps'], + 'application/provenance+xml' => ['provx'], + 'application/prs.cww' => ['cww'], + 'application/prs.xsf+xml' => ['xsf'], + 'application/pskc+xml' => ['pskcxml'], + 'application/raml+yaml' => ['raml'], + 'application/rdf+xml' => ['rdf', 'owl'], + 'application/reginfo+xml' => ['rif'], + 'application/relax-ng-compact-syntax' => ['rnc'], + 'application/resource-lists+xml' => ['rl'], + 'application/resource-lists-diff+xml' => ['rld'], + 'application/rls-services+xml' => ['rs'], + 'application/route-apd+xml' => ['rapd'], + 'application/route-s-tsid+xml' => ['sls'], + 'application/route-usd+xml' => ['rusd'], + 'application/rpki-ghostbusters' => ['gbr'], + 'application/rpki-manifest' => ['mft'], + 'application/rpki-roa' => ['roa'], + 'application/rsd+xml' => ['rsd'], + 'application/rss+xml' => ['rss'], + 'application/rtf' => ['rtf'], + 'application/sbml+xml' => ['sbml'], + 'application/scvp-cv-request' => ['scq'], + 'application/scvp-cv-response' => ['scs'], + 'application/scvp-vp-request' => ['spq'], + 'application/scvp-vp-response' => ['spp'], + 'application/sdp' => ['sdp'], + 'application/senml+xml' => ['senmlx'], + 'application/sensml+xml' => ['sensmlx'], + 'application/set-payment-initiation' => ['setpay'], + 'application/set-registration-initiation' => ['setreg'], + 'application/shf+xml' => ['shf'], + 'application/sieve' => ['siv', 'sieve'], + 'application/smil+xml' => ['smi', 'smil'], + 'application/sparql-query' => ['rq'], + 'application/sparql-results+xml' => ['srx'], + 'application/sql' => ['sql'], + 'application/srgs' => ['gram'], + 'application/srgs+xml' => ['grxml'], + 'application/sru+xml' => ['sru'], + 'application/ssdl+xml' => ['ssdl'], + 'application/ssml+xml' => ['ssml'], + 'application/swid+xml' => ['swidtag'], + 'application/tei+xml' => ['tei', 'teicorpus'], + 'application/thraud+xml' => ['tfi'], + 'application/timestamped-data' => ['tsd'], + 'application/toml' => ['toml'], + 'application/trig' => ['trig'], + 'application/ttml+xml' => ['ttml'], + 'application/ubjson' => ['ubj'], + 'application/urc-ressheet+xml' => ['rsheet'], + 'application/urc-targetdesc+xml' => ['td'], + 'application/vnd.1000minds.decision-model+xml' => ['1km'], + 'application/vnd.3gpp.pic-bw-large' => ['plb'], + 'application/vnd.3gpp.pic-bw-small' => ['psb'], + 'application/vnd.3gpp.pic-bw-var' => ['pvb'], + 'application/vnd.3gpp2.tcap' => ['tcap'], + 'application/vnd.3m.post-it-notes' => ['pwn'], + 'application/vnd.accpac.simply.aso' => ['aso'], + 'application/vnd.accpac.simply.imp' => ['imp'], + 'application/vnd.acucobol' => ['acu'], + 'application/vnd.acucorp' => ['atc', 'acutc'], + 'application/vnd.adobe.air-application-installer-package+zip' => ['air'], + 'application/vnd.adobe.formscentral.fcdt' => ['fcdt'], + 'application/vnd.adobe.fxp' => ['fxp', 'fxpl'], + 'application/vnd.adobe.xdp+xml' => ['xdp'], + 'application/vnd.adobe.xfdf' => ['xfdf'], + 'application/vnd.age' => ['age'], + 'application/vnd.ahead.space' => ['ahead'], + 'application/vnd.airzip.filesecure.azf' => ['azf'], + 'application/vnd.airzip.filesecure.azs' => ['azs'], + 'application/vnd.amazon.ebook' => ['azw'], + 'application/vnd.americandynamics.acc' => ['acc'], + 'application/vnd.amiga.ami' => ['ami'], + 'application/vnd.android.package-archive' => ['apk'], + 'application/vnd.anser-web-certificate-issue-initiation' => ['cii'], + 'application/vnd.anser-web-funds-transfer-initiation' => ['fti'], + 'application/vnd.antix.game-component' => ['atx'], + 'application/vnd.apple.installer+xml' => ['mpkg'], + 'application/vnd.apple.keynote' => ['key'], + 'application/vnd.apple.mpegurl' => ['m3u8'], + 'application/vnd.apple.numbers' => ['numbers'], + 'application/vnd.apple.pages' => ['pages'], + 'application/vnd.apple.pkpass' => ['pkpass'], + 'application/vnd.aristanetworks.swi' => ['swi'], + 'application/vnd.astraea-software.iota' => ['iota'], + 'application/vnd.audiograph' => ['aep'], + 'application/vnd.balsamiq.bmml+xml' => ['bmml'], + 'application/vnd.blueice.multipass' => ['mpm'], + 'application/vnd.bmi' => ['bmi'], + 'application/vnd.businessobjects' => ['rep'], + 'application/vnd.chemdraw+xml' => ['cdxml'], + 'application/vnd.chipnuts.karaoke-mmd' => ['mmd'], + 'application/vnd.cinderella' => ['cdy'], + 'application/vnd.citationstyles.style+xml' => ['csl'], + 'application/vnd.claymore' => ['cla'], + 'application/vnd.cloanto.rp9' => ['rp9'], + 'application/vnd.clonk.c4group' => ['c4g', 'c4d', 'c4f', 'c4p', 'c4u'], + 'application/vnd.cluetrust.cartomobile-config' => ['c11amc'], + 'application/vnd.cluetrust.cartomobile-config-pkg' => ['c11amz'], + 'application/vnd.commonspace' => ['csp'], + 'application/vnd.contact.cmsg' => ['cdbcmsg'], + 'application/vnd.cosmocaller' => ['cmc'], + 'application/vnd.crick.clicker' => ['clkx'], + 'application/vnd.crick.clicker.keyboard' => ['clkk'], + 'application/vnd.crick.clicker.palette' => ['clkp'], + 'application/vnd.crick.clicker.template' => ['clkt'], + 'application/vnd.crick.clicker.wordbank' => ['clkw'], + 'application/vnd.criticaltools.wbs+xml' => ['wbs'], + 'application/vnd.ctc-posml' => ['pml'], + 'application/vnd.cups-ppd' => ['ppd'], + 'application/vnd.curl.car' => ['car'], + 'application/vnd.curl.pcurl' => ['pcurl'], + 'application/vnd.dart' => ['dart'], + 'application/vnd.data-vision.rdz' => ['rdz'], + 'application/vnd.dbf' => ['dbf'], + 'application/vnd.dece.data' => ['uvf', 'uvvf', 'uvd', 'uvvd'], + 'application/vnd.dece.ttml+xml' => ['uvt', 'uvvt'], + 'application/vnd.dece.unspecified' => ['uvx', 'uvvx'], + 'application/vnd.dece.zip' => ['uvz', 'uvvz'], + 'application/vnd.denovo.fcselayout-link' => ['fe_launch'], + 'application/vnd.dna' => ['dna'], + 'application/vnd.dolby.mlp' => ['mlp'], + 'application/vnd.dpgraph' => ['dpg'], + 'application/vnd.dreamfactory' => ['dfac'], + 'application/vnd.ds-keypoint' => ['kpxx'], + 'application/vnd.dvb.ait' => ['ait'], + 'application/vnd.dvb.service' => ['svc'], + 'application/vnd.dynageo' => ['geo'], + 'application/vnd.ecowin.chart' => ['mag'], + 'application/vnd.enliven' => ['nml'], + 'application/vnd.epson.esf' => ['esf'], + 'application/vnd.epson.msf' => ['msf'], + 'application/vnd.epson.quickanime' => ['qam'], + 'application/vnd.epson.salt' => ['slt'], + 'application/vnd.epson.ssf' => ['ssf'], + 'application/vnd.eszigno3+xml' => ['es3', 'et3'], + 'application/vnd.ezpix-album' => ['ez2'], + 'application/vnd.ezpix-package' => ['ez3'], + 'application/vnd.fdf' => ['fdf'], + 'application/vnd.fdsn.mseed' => ['mseed'], + 'application/vnd.fdsn.seed' => ['seed', 'dataless'], + 'application/vnd.flographit' => ['gph'], + 'application/vnd.fluxtime.clip' => ['ftc'], + 'application/vnd.framemaker' => ['fm', 'frame', 'maker', 'book'], + 'application/vnd.frogans.fnc' => ['fnc'], + 'application/vnd.frogans.ltf' => ['ltf'], + 'application/vnd.fsc.weblaunch' => ['fsc'], + 'application/vnd.fujitsu.oasys' => ['oas'], + 'application/vnd.fujitsu.oasys2' => ['oa2'], + 'application/vnd.fujitsu.oasys3' => ['oa3'], + 'application/vnd.fujitsu.oasysgp' => ['fg5'], + 'application/vnd.fujitsu.oasysprs' => ['bh2'], + 'application/vnd.fujixerox.ddd' => ['ddd'], + 'application/vnd.fujixerox.docuworks' => ['xdw'], + 'application/vnd.fujixerox.docuworks.binder' => ['xbd'], + 'application/vnd.fuzzysheet' => ['fzs'], + 'application/vnd.genomatix.tuxedo' => ['txd'], + 'application/vnd.geogebra.file' => ['ggb'], + 'application/vnd.geogebra.slides' => ['ggs'], + 'application/vnd.geogebra.tool' => ['ggt'], + 'application/vnd.geometry-explorer' => ['gex', 'gre'], + 'application/vnd.geonext' => ['gxt'], + 'application/vnd.geoplan' => ['g2w'], + 'application/vnd.geospace' => ['g3w'], + 'application/vnd.gmx' => ['gmx'], + 'application/vnd.google-apps.document' => ['gdoc'], + 'application/vnd.google-apps.presentation' => ['gslides'], + 'application/vnd.google-apps.spreadsheet' => ['gsheet'], + 'application/vnd.google-earth.kml+xml' => ['kml'], + 'application/vnd.google-earth.kmz' => ['kmz'], + 'application/vnd.gov.sk.xmldatacontainer+xml' => ['xdcf'], + 'application/vnd.grafeq' => ['gqf', 'gqs'], + 'application/vnd.groove-account' => ['gac'], + 'application/vnd.groove-help' => ['ghf'], + 'application/vnd.groove-identity-message' => ['gim'], + 'application/vnd.groove-injector' => ['grv'], + 'application/vnd.groove-tool-message' => ['gtm'], + 'application/vnd.groove-tool-template' => ['tpl'], + 'application/vnd.groove-vcard' => ['vcg'], + 'application/vnd.hal+xml' => ['hal'], + 'application/vnd.handheld-entertainment+xml' => ['zmm'], + 'application/vnd.hbci' => ['hbci'], + 'application/vnd.hhe.lesson-player' => ['les'], + 'application/vnd.hp-hpgl' => ['hpgl'], + 'application/vnd.hp-hpid' => ['hpid'], + 'application/vnd.hp-hps' => ['hps'], + 'application/vnd.hp-jlyt' => ['jlt'], + 'application/vnd.hp-pcl' => ['pcl'], + 'application/vnd.hp-pclxl' => ['pclxl'], + 'application/vnd.hydrostatix.sof-data' => ['sfd-hdstx'], + 'application/vnd.ibm.minipay' => ['mpy'], + 'application/vnd.ibm.modcap' => ['afp', 'listafp', 'list3820'], + 'application/vnd.ibm.rights-management' => ['irm'], + 'application/vnd.ibm.secure-container' => ['sc'], + 'application/vnd.iccprofile' => ['icc', 'icm'], + 'application/vnd.igloader' => ['igl'], + 'application/vnd.immervision-ivp' => ['ivp'], + 'application/vnd.immervision-ivu' => ['ivu'], + 'application/vnd.insors.igm' => ['igm'], + 'application/vnd.intercon.formnet' => ['xpw', 'xpx'], + 'application/vnd.intergeo' => ['i2g'], + 'application/vnd.intu.qbo' => ['qbo'], + 'application/vnd.intu.qfx' => ['qfx'], + 'application/vnd.ipunplugged.rcprofile' => ['rcprofile'], + 'application/vnd.irepository.package+xml' => ['irp'], + 'application/vnd.is-xpr' => ['xpr'], + 'application/vnd.isac.fcs' => ['fcs'], + 'application/vnd.jam' => ['jam'], + 'application/vnd.jcp.javame.midlet-rms' => ['rms'], + 'application/vnd.jisp' => ['jisp'], + 'application/vnd.joost.joda-archive' => ['joda'], + 'application/vnd.kahootz' => ['ktz', 'ktr'], + 'application/vnd.kde.karbon' => ['karbon'], + 'application/vnd.kde.kchart' => ['chrt'], + 'application/vnd.kde.kformula' => ['kfo'], + 'application/vnd.kde.kivio' => ['flw'], + 'application/vnd.kde.kontour' => ['kon'], + 'application/vnd.kde.kpresenter' => ['kpr', 'kpt'], + 'application/vnd.kde.kspread' => ['ksp'], + 'application/vnd.kde.kword' => ['kwd', 'kwt'], + 'application/vnd.kenameaapp' => ['htke'], + 'application/vnd.kidspiration' => ['kia'], + 'application/vnd.kinar' => ['kne', 'knp'], + 'application/vnd.koan' => ['skp', 'skd', 'skt', 'skm'], + 'application/vnd.kodak-descriptor' => ['sse'], + 'application/vnd.las.las+xml' => ['lasxml'], + 'application/vnd.llamagraphics.life-balance.desktop' => ['lbd'], + 'application/vnd.llamagraphics.life-balance.exchange+xml' => ['lbe'], + 'application/vnd.lotus-1-2-3' => ['123'], + 'application/vnd.lotus-approach' => ['apr'], + 'application/vnd.lotus-freelance' => ['pre'], + 'application/vnd.lotus-notes' => ['nsf'], + 'application/vnd.lotus-organizer' => ['org'], + 'application/vnd.lotus-screencam' => ['scm'], + 'application/vnd.lotus-wordpro' => ['lwp'], + 'application/vnd.macports.portpkg' => ['portpkg'], + 'application/vnd.mapbox-vector-tile' => ['mvt'], + 'application/vnd.mcd' => ['mcd'], + 'application/vnd.medcalcdata' => ['mc1'], + 'application/vnd.mediastation.cdkey' => ['cdkey'], + 'application/vnd.mfer' => ['mwf'], + 'application/vnd.mfmp' => ['mfm'], + 'application/vnd.micrografx.flo' => ['flo'], + 'application/vnd.micrografx.igx' => ['igx'], + 'application/vnd.mif' => ['mif'], + 'application/vnd.mobius.daf' => ['daf'], + 'application/vnd.mobius.dis' => ['dis'], + 'application/vnd.mobius.mbk' => ['mbk'], + 'application/vnd.mobius.mqy' => ['mqy'], + 'application/vnd.mobius.msl' => ['msl'], + 'application/vnd.mobius.plc' => ['plc'], + 'application/vnd.mobius.txf' => ['txf'], + 'application/vnd.mophun.application' => ['mpn'], + 'application/vnd.mophun.certificate' => ['mpc'], + 'application/vnd.mozilla.xul+xml' => ['xul'], + 'application/vnd.ms-artgalry' => ['cil'], + 'application/vnd.ms-cab-compressed' => ['cab'], + 'application/vnd.ms-excel' => ['xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw'], + 'application/vnd.ms-excel.addin.macroenabled.12' => ['xlam'], + 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => ['xlsb'], + 'application/vnd.ms-excel.sheet.macroenabled.12' => ['xlsm'], + 'application/vnd.ms-excel.template.macroenabled.12' => ['xltm'], + 'application/vnd.ms-fontobject' => ['eot'], + 'application/vnd.ms-htmlhelp' => ['chm'], + 'application/vnd.ms-ims' => ['ims'], + 'application/vnd.ms-lrm' => ['lrm'], + 'application/vnd.ms-officetheme' => ['thmx'], + 'application/vnd.ms-outlook' => ['msg'], + 'application/vnd.ms-pki.seccat' => ['cat'], + 'application/vnd.ms-pki.stl' => ['stl'], + 'application/vnd.ms-powerpoint' => ['ppt', 'pps', 'pot', 'ppa'], + 'application/vnd.ms-powerpoint.addin.macroenabled.12' => ['ppam'], + 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => ['pptm'], + 'application/vnd.ms-powerpoint.slide.macroenabled.12' => ['sldm'], + 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => ['ppsm'], + 'application/vnd.ms-powerpoint.template.macroenabled.12' => ['potm'], + 'application/vnd.ms-project' => ['mpp', 'mpt'], + 'application/vnd.ms-word.document.macroenabled.12' => ['docm'], + 'application/vnd.ms-word.template.macroenabled.12' => ['dotm'], + 'application/vnd.ms-works' => ['wps', 'wks', 'wcm', 'wdb'], + 'application/vnd.ms-wpl' => ['wpl'], + 'application/vnd.ms-xpsdocument' => ['xps'], + 'application/vnd.mseq' => ['mseq'], + 'application/vnd.musician' => ['mus'], + 'application/vnd.muvee.style' => ['msty'], + 'application/vnd.mynfc' => ['taglet'], + 'application/vnd.nato.bindingdataobject+xml' => ['bdo'], + 'application/vnd.neurolanguage.nlu' => ['nlu'], + 'application/vnd.nitf' => ['ntf', 'nitf'], + 'application/vnd.noblenet-directory' => ['nnd'], + 'application/vnd.noblenet-sealer' => ['nns'], + 'application/vnd.noblenet-web' => ['nnw'], + 'application/vnd.nokia.n-gage.ac+xml' => ['ac'], + 'application/vnd.nokia.n-gage.data' => ['ngdat'], + 'application/vnd.nokia.n-gage.symbian.install' => ['n-gage'], + 'application/vnd.nokia.radio-preset' => ['rpst'], + 'application/vnd.nokia.radio-presets' => ['rpss'], + 'application/vnd.novadigm.edm' => ['edm'], + 'application/vnd.novadigm.edx' => ['edx'], + 'application/vnd.novadigm.ext' => ['ext'], + 'application/vnd.oasis.opendocument.chart' => ['odc'], + 'application/vnd.oasis.opendocument.chart-template' => ['otc'], + 'application/vnd.oasis.opendocument.database' => ['odb'], + 'application/vnd.oasis.opendocument.formula' => ['odf'], + 'application/vnd.oasis.opendocument.formula-template' => ['odft'], + 'application/vnd.oasis.opendocument.graphics' => ['odg'], + 'application/vnd.oasis.opendocument.graphics-template' => ['otg'], + 'application/vnd.oasis.opendocument.image' => ['odi'], + 'application/vnd.oasis.opendocument.image-template' => ['oti'], + 'application/vnd.oasis.opendocument.presentation' => ['odp'], + 'application/vnd.oasis.opendocument.presentation-template' => ['otp'], + 'application/vnd.oasis.opendocument.spreadsheet' => ['ods'], + 'application/vnd.oasis.opendocument.spreadsheet-template' => ['ots'], + 'application/vnd.oasis.opendocument.text' => ['odt'], + 'application/vnd.oasis.opendocument.text-master' => ['odm'], + 'application/vnd.oasis.opendocument.text-template' => ['ott'], + 'application/vnd.oasis.opendocument.text-web' => ['oth'], + 'application/vnd.olpc-sugar' => ['xo'], + 'application/vnd.oma.dd2+xml' => ['dd2'], + 'application/vnd.openblox.game+xml' => ['obgx'], + 'application/vnd.openofficeorg.extension' => ['oxt'], + 'application/vnd.openstreetmap.data+xml' => ['osm'], + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => ['pptx'], + 'application/vnd.openxmlformats-officedocument.presentationml.slide' => ['sldx'], + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => ['ppsx'], + 'application/vnd.openxmlformats-officedocument.presentationml.template' => ['potx'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => ['xltx'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => ['dotx'], + 'application/vnd.osgeo.mapguide.package' => ['mgp'], + 'application/vnd.osgi.dp' => ['dp'], + 'application/vnd.osgi.subsystem' => ['esa'], + 'application/vnd.palm' => ['pdb', 'pqa', 'oprc'], + 'application/vnd.pawaafile' => ['paw'], + 'application/vnd.pg.format' => ['str'], + 'application/vnd.pg.osasli' => ['ei6'], + 'application/vnd.picsel' => ['efif'], + 'application/vnd.pmi.widget' => ['wg'], + 'application/vnd.pocketlearn' => ['plf'], + 'application/vnd.powerbuilder6' => ['pbd'], + 'application/vnd.previewsystems.box' => ['box'], + 'application/vnd.proteus.magazine' => ['mgz'], + 'application/vnd.publishare-delta-tree' => ['qps'], + 'application/vnd.pvi.ptid1' => ['ptid'], + 'application/vnd.pwg-xhtml-print+xml' => ['xhtm'], + 'application/vnd.quark.quarkxpress' => ['qxd', 'qxt', 'qwd', 'qwt', 'qxl', 'qxb'], + 'application/vnd.rar' => ['rar'], + 'application/vnd.realvnc.bed' => ['bed'], + 'application/vnd.recordare.musicxml' => ['mxl'], + 'application/vnd.recordare.musicxml+xml' => ['musicxml'], + 'application/vnd.rig.cryptonote' => ['cryptonote'], + 'application/vnd.rim.cod' => ['cod'], + 'application/vnd.rn-realmedia' => ['rm'], + 'application/vnd.rn-realmedia-vbr' => ['rmvb'], + 'application/vnd.route66.link66+xml' => ['link66'], + 'application/vnd.sailingtracker.track' => ['st'], + 'application/vnd.seemail' => ['see'], + 'application/vnd.sema' => ['sema'], + 'application/vnd.semd' => ['semd'], + 'application/vnd.semf' => ['semf'], + 'application/vnd.shana.informed.formdata' => ['ifm'], + 'application/vnd.shana.informed.formtemplate' => ['itp'], + 'application/vnd.shana.informed.interchange' => ['iif'], + 'application/vnd.shana.informed.package' => ['ipk'], + 'application/vnd.simtech-mindmapper' => ['twd', 'twds'], + 'application/vnd.smaf' => ['mmf'], + 'application/vnd.smart.teacher' => ['teacher'], + 'application/vnd.software602.filler.form+xml' => ['fo'], + 'application/vnd.solent.sdkm+xml' => ['sdkm', 'sdkd'], + 'application/vnd.spotfire.dxp' => ['dxp'], + 'application/vnd.spotfire.sfs' => ['sfs'], + 'application/vnd.stardivision.calc' => ['sdc'], + 'application/vnd.stardivision.draw' => ['sda'], + 'application/vnd.stardivision.impress' => ['sdd'], + 'application/vnd.stardivision.math' => ['smf'], + 'application/vnd.stardivision.writer' => ['sdw', 'vor'], + 'application/vnd.stardivision.writer-global' => ['sgl'], + 'application/vnd.stepmania.package' => ['smzip'], + 'application/vnd.stepmania.stepchart' => ['sm'], + 'application/vnd.sun.wadl+xml' => ['wadl'], + 'application/vnd.sun.xml.calc' => ['sxc'], + 'application/vnd.sun.xml.calc.template' => ['stc'], + 'application/vnd.sun.xml.draw' => ['sxd'], + 'application/vnd.sun.xml.draw.template' => ['std'], + 'application/vnd.sun.xml.impress' => ['sxi'], + 'application/vnd.sun.xml.impress.template' => ['sti'], + 'application/vnd.sun.xml.math' => ['sxm'], + 'application/vnd.sun.xml.writer' => ['sxw'], + 'application/vnd.sun.xml.writer.global' => ['sxg'], + 'application/vnd.sun.xml.writer.template' => ['stw'], + 'application/vnd.sus-calendar' => ['sus', 'susp'], + 'application/vnd.svd' => ['svd'], + 'application/vnd.symbian.install' => ['sis', 'sisx'], + 'application/vnd.syncml+xml' => ['xsm'], + 'application/vnd.syncml.dm+wbxml' => ['bdm'], + 'application/vnd.syncml.dm+xml' => ['xdm'], + 'application/vnd.syncml.dmddf+xml' => ['ddf'], + 'application/vnd.tao.intent-module-archive' => ['tao'], + 'application/vnd.tcpdump.pcap' => ['pcap', 'cap', 'dmp'], + 'application/vnd.tmobile-livetv' => ['tmo'], + 'application/vnd.trid.tpt' => ['tpt'], + 'application/vnd.triscape.mxs' => ['mxs'], + 'application/vnd.trueapp' => ['tra'], + 'application/vnd.ufdl' => ['ufd', 'ufdl'], + 'application/vnd.uiq.theme' => ['utz'], + 'application/vnd.umajin' => ['umj'], + 'application/vnd.unity' => ['unityweb'], + 'application/vnd.uoml+xml' => ['uoml', 'uo'], + 'application/vnd.vcx' => ['vcx'], + 'application/vnd.visio' => ['vsd', 'vst', 'vss', 'vsw'], + 'application/vnd.visionary' => ['vis'], + 'application/vnd.vsf' => ['vsf'], + 'application/vnd.wap.wbxml' => ['wbxml'], + 'application/vnd.wap.wmlc' => ['wmlc'], + 'application/vnd.wap.wmlscriptc' => ['wmlsc'], + 'application/vnd.webturbo' => ['wtb'], + 'application/vnd.wolfram.player' => ['nbp'], + 'application/vnd.wordperfect' => ['wpd'], + 'application/vnd.wqd' => ['wqd'], + 'application/vnd.wt.stf' => ['stf'], + 'application/vnd.xara' => ['xar'], + 'application/vnd.xfdl' => ['xfdl'], + 'application/vnd.yamaha.hv-dic' => ['hvd'], + 'application/vnd.yamaha.hv-script' => ['hvs'], + 'application/vnd.yamaha.hv-voice' => ['hvp'], + 'application/vnd.yamaha.openscoreformat' => ['osf'], + 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => ['osfpvg'], + 'application/vnd.yamaha.smaf-audio' => ['saf'], + 'application/vnd.yamaha.smaf-phrase' => ['spf'], + 'application/vnd.yellowriver-custom-menu' => ['cmp'], + 'application/vnd.zul' => ['zir', 'zirz'], + 'application/vnd.zzazz.deck+xml' => ['zaz'], + 'application/voicexml+xml' => ['vxml'], + 'application/wasm' => ['wasm'], + 'application/watcherinfo+xml' => ['wif'], + 'application/widget' => ['wgt'], + 'application/winhlp' => ['hlp'], + 'application/wsdl+xml' => ['wsdl'], + 'application/wspolicy+xml' => ['wspolicy'], + 'application/x-7z-compressed' => ['7z', '7zip'], + 'application/x-abiword' => ['abw'], + 'application/x-ace-compressed' => ['ace'], + 'application/x-apple-diskimage' => ['dmg'], + 'application/x-arj' => ['arj'], + 'application/x-authorware-bin' => ['aab', 'x32', 'u32', 'vox'], + 'application/x-authorware-map' => ['aam'], + 'application/x-authorware-seg' => ['aas'], + 'application/x-bcpio' => ['bcpio'], + 'application/x-bdoc' => ['bdoc'], + 'application/x-bittorrent' => ['torrent'], + 'application/x-blorb' => ['blb', 'blorb'], + 'application/x-bzip' => ['bz'], + 'application/x-bzip2' => ['bz2', 'boz'], + 'application/x-cbr' => ['cbr', 'cba', 'cbt', 'cbz', 'cb7'], + 'application/x-cdlink' => ['vcd'], + 'application/x-cfs-compressed' => ['cfs'], + 'application/x-chat' => ['chat'], + 'application/x-chess-pgn' => ['pgn'], + 'application/x-chrome-extension' => ['crx'], + 'application/x-cocoa' => ['cco'], + 'application/x-conference' => ['nsc'], + 'application/x-cpio' => ['cpio'], + 'application/x-csh' => ['csh'], + 'application/x-debian-package' => ['deb', 'udeb'], + 'application/x-dgc-compressed' => ['dgc'], + 'application/x-director' => ['dir', 'dcr', 'dxr', 'cst', 'cct', 'cxt', 'w3d', 'fgd', 'swa'], + 'application/x-doom' => ['wad'], + 'application/x-dtbncx+xml' => ['ncx'], + 'application/x-dtbook+xml' => ['dtb'], + 'application/x-dtbresource+xml' => ['res'], + 'application/x-dvi' => ['dvi'], + 'application/x-envoy' => ['evy'], + 'application/x-eva' => ['eva'], + 'application/x-font-bdf' => ['bdf'], + 'application/x-font-ghostscript' => ['gsf'], + 'application/x-font-linux-psf' => ['psf'], + 'application/x-font-pcf' => ['pcf'], + 'application/x-font-snf' => ['snf'], + 'application/x-font-type1' => ['pfa', 'pfb', 'pfm', 'afm'], + 'application/x-freearc' => ['arc'], + 'application/x-futuresplash' => ['spl'], + 'application/x-gca-compressed' => ['gca'], + 'application/x-glulx' => ['ulx'], + 'application/x-gnumeric' => ['gnumeric'], + 'application/x-gramps-xml' => ['gramps'], + 'application/x-gtar' => ['gtar'], + 'application/x-hdf' => ['hdf'], + 'application/x-httpd-php' => ['php', 'php4', 'php3', 'phtml'], + 'application/x-install-instructions' => ['install'], + 'application/x-iso9660-image' => ['iso'], + 'application/x-iwork-keynote-sffkey' => ['key'], + 'application/x-iwork-numbers-sffnumbers' => ['numbers'], + 'application/x-iwork-pages-sffpages' => ['pages'], + 'application/x-java-archive-diff' => ['jardiff'], + 'application/x-java-jnlp-file' => ['jnlp'], + 'application/x-keepass2' => ['kdbx'], + 'application/x-latex' => ['latex'], + 'application/x-lua-bytecode' => ['luac'], + 'application/x-lzh-compressed' => ['lzh', 'lha'], + 'application/x-makeself' => ['run'], + 'application/x-mie' => ['mie'], + 'application/x-mobipocket-ebook' => ['prc', 'mobi'], + 'application/x-ms-application' => ['application'], + 'application/x-ms-shortcut' => ['lnk'], + 'application/x-ms-wmd' => ['wmd'], + 'application/x-ms-wmz' => ['wmz'], + 'application/x-ms-xbap' => ['xbap'], + 'application/x-msaccess' => ['mdb'], + 'application/x-msbinder' => ['obd'], + 'application/x-mscardfile' => ['crd'], + 'application/x-msclip' => ['clp'], + 'application/x-msdos-program' => ['exe'], + 'application/x-msdownload' => ['exe', 'dll', 'com', 'bat', 'msi'], + 'application/x-msmediaview' => ['mvb', 'm13', 'm14'], + 'application/x-msmetafile' => ['wmf', 'wmz', 'emf', 'emz'], + 'application/x-msmoney' => ['mny'], + 'application/x-mspublisher' => ['pub'], + 'application/x-msschedule' => ['scd'], + 'application/x-msterminal' => ['trm'], + 'application/x-mswrite' => ['wri'], + 'application/x-netcdf' => ['nc', 'cdf'], + 'application/x-ns-proxy-autoconfig' => ['pac'], + 'application/x-nzb' => ['nzb'], + 'application/x-perl' => ['pl', 'pm'], + 'application/x-pilot' => ['prc', 'pdb'], + 'application/x-pkcs12' => ['p12', 'pfx'], + 'application/x-pkcs7-certificates' => ['p7b', 'spc'], + 'application/x-pkcs7-certreqresp' => ['p7r'], + 'application/x-rar-compressed' => ['rar'], + 'application/x-redhat-package-manager' => ['rpm'], + 'application/x-research-info-systems' => ['ris'], + 'application/x-sea' => ['sea'], + 'application/x-sh' => ['sh'], + 'application/x-shar' => ['shar'], + 'application/x-shockwave-flash' => ['swf'], + 'application/x-silverlight-app' => ['xap'], + 'application/x-sql' => ['sql'], + 'application/x-stuffit' => ['sit'], + 'application/x-stuffitx' => ['sitx'], + 'application/x-subrip' => ['srt'], + 'application/x-sv4cpio' => ['sv4cpio'], + 'application/x-sv4crc' => ['sv4crc'], + 'application/x-t3vm-image' => ['t3'], + 'application/x-tads' => ['gam'], + 'application/x-tar' => ['tar', 'tgz'], + 'application/x-tcl' => ['tcl', 'tk'], + 'application/x-tex' => ['tex'], + 'application/x-tex-tfm' => ['tfm'], + 'application/x-texinfo' => ['texinfo', 'texi'], + 'application/x-tgif' => ['obj'], + 'application/x-ustar' => ['ustar'], + 'application/x-virtualbox-hdd' => ['hdd'], + 'application/x-virtualbox-ova' => ['ova'], + 'application/x-virtualbox-ovf' => ['ovf'], + 'application/x-virtualbox-vbox' => ['vbox'], + 'application/x-virtualbox-vbox-extpack' => ['vbox-extpack'], + 'application/x-virtualbox-vdi' => ['vdi'], + 'application/x-virtualbox-vhd' => ['vhd'], + 'application/x-virtualbox-vmdk' => ['vmdk'], + 'application/x-wais-source' => ['src'], + 'application/x-web-app-manifest+json' => ['webapp'], + 'application/x-x509-ca-cert' => ['der', 'crt', 'pem'], + 'application/x-xfig' => ['fig'], + 'application/x-xliff+xml' => ['xlf'], + 'application/x-xpinstall' => ['xpi'], + 'application/x-xz' => ['xz'], + 'application/x-zmachine' => ['z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7', 'z8'], + 'application/xaml+xml' => ['xaml'], + 'application/xcap-att+xml' => ['xav'], + 'application/xcap-caps+xml' => ['xca'], + 'application/xcap-diff+xml' => ['xdf'], + 'application/xcap-el+xml' => ['xel'], + 'application/xcap-ns+xml' => ['xns'], + 'application/xenc+xml' => ['xenc'], + 'application/xfdf' => ['xfdf'], + 'application/xhtml+xml' => ['xhtml', 'xht'], + 'application/xliff+xml' => ['xlf'], + 'application/xml' => ['xml', 'xsl', 'xsd', 'rng'], + 'application/xml-dtd' => ['dtd'], + 'application/xop+xml' => ['xop'], + 'application/xproc+xml' => ['xpl'], + 'application/xslt+xml' => ['xsl', 'xslt'], + 'application/xspf+xml' => ['xspf'], + 'application/xv+xml' => ['mxml', 'xhvml', 'xvml', 'xvm'], + 'application/yang' => ['yang'], + 'application/yin+xml' => ['yin'], + 'application/zip' => ['zip'], + 'audio/3gpp' => ['3gpp'], + 'audio/aac' => ['adts', 'aac'], + 'audio/adpcm' => ['adp'], + 'audio/amr' => ['amr'], + 'audio/basic' => ['au', 'snd'], + 'audio/midi' => ['mid', 'midi', 'kar', 'rmi'], + 'audio/mobile-xmf' => ['mxmf'], + 'audio/mp3' => ['mp3'], + 'audio/mp4' => ['m4a', 'mp4a'], + 'audio/mpeg' => ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'], + 'audio/ogg' => ['oga', 'ogg', 'spx', 'opus'], + 'audio/s3m' => ['s3m'], + 'audio/silk' => ['sil'], + 'audio/vnd.dece.audio' => ['uva', 'uvva'], + 'audio/vnd.digital-winds' => ['eol'], + 'audio/vnd.dra' => ['dra'], + 'audio/vnd.dts' => ['dts'], + 'audio/vnd.dts.hd' => ['dtshd'], + 'audio/vnd.lucent.voice' => ['lvp'], + 'audio/vnd.ms-playready.media.pya' => ['pya'], + 'audio/vnd.nuera.ecelp4800' => ['ecelp4800'], + 'audio/vnd.nuera.ecelp7470' => ['ecelp7470'], + 'audio/vnd.nuera.ecelp9600' => ['ecelp9600'], + 'audio/vnd.rip' => ['rip'], + 'audio/wav' => ['wav'], + 'audio/wave' => ['wav'], + 'audio/webm' => ['weba'], + 'audio/x-aac' => ['aac'], + 'audio/x-aiff' => ['aif', 'aiff', 'aifc'], + 'audio/x-caf' => ['caf'], + 'audio/x-flac' => ['flac'], + 'audio/x-m4a' => ['m4a'], + 'audio/x-matroska' => ['mka'], + 'audio/x-mpegurl' => ['m3u'], + 'audio/x-ms-wax' => ['wax'], + 'audio/x-ms-wma' => ['wma'], + 'audio/x-pn-realaudio' => ['ram', 'ra', 'rm'], + 'audio/x-pn-realaudio-plugin' => ['rmp', 'rpm'], + 'audio/x-realaudio' => ['ra'], + 'audio/x-wav' => ['wav'], + 'audio/xm' => ['xm'], + 'chemical/x-cdx' => ['cdx'], + 'chemical/x-cif' => ['cif'], + 'chemical/x-cmdf' => ['cmdf'], + 'chemical/x-cml' => ['cml'], + 'chemical/x-csml' => ['csml'], + 'chemical/x-xyz' => ['xyz'], + 'font/collection' => ['ttc'], + 'font/otf' => ['otf'], + 'font/ttf' => ['ttf'], + 'font/woff' => ['woff'], + 'font/woff2' => ['woff2'], + 'image/aces' => ['exr'], + 'image/apng' => ['apng'], + 'image/avci' => ['avci'], + 'image/avcs' => ['avcs'], + 'image/avif' => ['avif'], + 'image/bmp' => ['bmp', 'dib'], + 'image/cgm' => ['cgm'], + 'image/dicom-rle' => ['drle'], + 'image/dpx' => ['dpx'], + 'image/emf' => ['emf'], + 'image/fits' => ['fits'], + 'image/g3fax' => ['g3'], + 'image/gif' => ['gif'], + 'image/heic' => ['heic'], + 'image/heic-sequence' => ['heics'], + 'image/heif' => ['heif'], + 'image/heif-sequence' => ['heifs'], + 'image/hej2k' => ['hej2'], + 'image/hsj2' => ['hsj2'], + 'image/ief' => ['ief'], + 'image/jls' => ['jls'], + 'image/jp2' => ['jp2', 'jpg2'], + 'image/jpeg' => ['jpeg', 'jpg', 'jpe'], + 'image/jph' => ['jph'], + 'image/jphc' => ['jhc'], + 'image/jpm' => ['jpm', 'jpgm'], + 'image/jpx' => ['jpx', 'jpf'], + 'image/jxl' => ['jxl'], + 'image/jxr' => ['jxr'], + 'image/jxra' => ['jxra'], + 'image/jxrs' => ['jxrs'], + 'image/jxs' => ['jxs'], + 'image/jxsc' => ['jxsc'], + 'image/jxsi' => ['jxsi'], + 'image/jxss' => ['jxss'], + 'image/ktx' => ['ktx'], + 'image/ktx2' => ['ktx2'], + 'image/png' => ['png'], + 'image/prs.btif' => ['btif', 'btf'], + 'image/prs.pti' => ['pti'], + 'image/sgi' => ['sgi'], + 'image/svg+xml' => ['svg', 'svgz'], + 'image/t38' => ['t38'], + 'image/tiff' => ['tif', 'tiff'], + 'image/tiff-fx' => ['tfx'], + 'image/vnd.adobe.photoshop' => ['psd'], + 'image/vnd.airzip.accelerator.azv' => ['azv'], + 'image/vnd.dece.graphic' => ['uvi', 'uvvi', 'uvg', 'uvvg'], + 'image/vnd.djvu' => ['djvu', 'djv'], + 'image/vnd.dvb.subtitle' => ['sub'], + 'image/vnd.dwg' => ['dwg'], + 'image/vnd.dxf' => ['dxf'], + 'image/vnd.fastbidsheet' => ['fbs'], + 'image/vnd.fpx' => ['fpx'], + 'image/vnd.fst' => ['fst'], + 'image/vnd.fujixerox.edmics-mmr' => ['mmr'], + 'image/vnd.fujixerox.edmics-rlc' => ['rlc'], + 'image/vnd.microsoft.icon' => ['ico'], + 'image/vnd.ms-dds' => ['dds'], + 'image/vnd.ms-modi' => ['mdi'], + 'image/vnd.ms-photo' => ['wdp'], + 'image/vnd.net-fpx' => ['npx'], + 'image/vnd.pco.b16' => ['b16'], + 'image/vnd.tencent.tap' => ['tap'], + 'image/vnd.valve.source.texture' => ['vtf'], + 'image/vnd.wap.wbmp' => ['wbmp'], + 'image/vnd.xiff' => ['xif'], + 'image/vnd.zbrush.pcx' => ['pcx'], + 'image/webp' => ['webp'], + 'image/wmf' => ['wmf'], + 'image/x-3ds' => ['3ds'], + 'image/x-cmu-raster' => ['ras'], + 'image/x-cmx' => ['cmx'], + 'image/x-freehand' => ['fh', 'fhc', 'fh4', 'fh5', 'fh7'], + 'image/x-icon' => ['ico'], + 'image/x-jng' => ['jng'], + 'image/x-mrsid-image' => ['sid'], + 'image/x-ms-bmp' => ['bmp'], + 'image/x-pcx' => ['pcx'], + 'image/x-pict' => ['pic', 'pct'], + 'image/x-portable-anymap' => ['pnm'], + 'image/x-portable-bitmap' => ['pbm'], + 'image/x-portable-graymap' => ['pgm'], + 'image/x-portable-pixmap' => ['ppm'], + 'image/x-rgb' => ['rgb'], + 'image/x-tga' => ['tga'], + 'image/x-xbitmap' => ['xbm'], + 'image/x-xpixmap' => ['xpm'], + 'image/x-xwindowdump' => ['xwd'], + 'message/disposition-notification' => ['disposition-notification'], + 'message/global' => ['u8msg'], + 'message/global-delivery-status' => ['u8dsn'], + 'message/global-disposition-notification' => ['u8mdn'], + 'message/global-headers' => ['u8hdr'], + 'message/rfc822' => ['eml', 'mime'], + 'message/vnd.wfa.wsc' => ['wsc'], + 'model/3mf' => ['3mf'], + 'model/gltf+json' => ['gltf'], + 'model/gltf-binary' => ['glb'], + 'model/iges' => ['igs', 'iges'], + 'model/jt' => ['jt'], + 'model/mesh' => ['msh', 'mesh', 'silo'], + 'model/mtl' => ['mtl'], + 'model/obj' => ['obj'], + 'model/prc' => ['prc'], + 'model/step+xml' => ['stpx'], + 'model/step+zip' => ['stpz'], + 'model/step-xml+zip' => ['stpxz'], + 'model/stl' => ['stl'], + 'model/u3d' => ['u3d'], + 'model/vnd.bary' => ['bary'], + 'model/vnd.cld' => ['cld'], + 'model/vnd.collada+xml' => ['dae'], + 'model/vnd.dwf' => ['dwf'], + 'model/vnd.gdl' => ['gdl'], + 'model/vnd.gtw' => ['gtw'], + 'model/vnd.mts' => ['mts'], + 'model/vnd.opengex' => ['ogex'], + 'model/vnd.parasolid.transmit.binary' => ['x_b'], + 'model/vnd.parasolid.transmit.text' => ['x_t'], + 'model/vnd.pytha.pyox' => ['pyo', 'pyox'], + 'model/vnd.sap.vds' => ['vds'], + 'model/vnd.usda' => ['usda'], + 'model/vnd.usdz+zip' => ['usdz'], + 'model/vnd.valve.source.compiled-map' => ['bsp'], + 'model/vnd.vtu' => ['vtu'], + 'model/vrml' => ['wrl', 'vrml'], + 'model/x3d+binary' => ['x3db', 'x3dbz'], + 'model/x3d+fastinfoset' => ['x3db'], + 'model/x3d+vrml' => ['x3dv', 'x3dvz'], + 'model/x3d+xml' => ['x3d', 'x3dz'], + 'model/x3d-vrml' => ['x3dv'], + 'text/cache-manifest' => ['appcache', 'manifest'], + 'text/calendar' => ['ics', 'ifb'], + 'text/coffeescript' => ['coffee', 'litcoffee'], + 'text/css' => ['css'], + 'text/csv' => ['csv'], + 'text/html' => ['html', 'htm', 'shtml'], + 'text/jade' => ['jade'], + 'text/javascript' => ['js', 'mjs'], + 'text/jsx' => ['jsx'], + 'text/less' => ['less'], + 'text/markdown' => ['md', 'markdown'], + 'text/mathml' => ['mml'], + 'text/mdx' => ['mdx'], + 'text/n3' => ['n3'], + 'text/plain' => ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini', 'm3u'], + 'text/prs.lines.tag' => ['dsc'], + 'text/richtext' => ['rtx'], + 'text/rtf' => ['rtf'], + 'text/sgml' => ['sgml', 'sgm'], + 'text/shex' => ['shex'], + 'text/slim' => ['slim', 'slm'], + 'text/spdx' => ['spdx'], + 'text/stylus' => ['stylus', 'styl'], + 'text/tab-separated-values' => ['tsv'], + 'text/troff' => ['t', 'tr', 'roff', 'man', 'me', 'ms'], + 'text/turtle' => ['ttl'], + 'text/uri-list' => ['uri', 'uris', 'urls'], + 'text/vcard' => ['vcard'], + 'text/vnd.curl' => ['curl'], + 'text/vnd.curl.dcurl' => ['dcurl'], + 'text/vnd.curl.mcurl' => ['mcurl'], + 'text/vnd.curl.scurl' => ['scurl'], + 'text/vnd.dvb.subtitle' => ['sub'], + 'text/vnd.familysearch.gedcom' => ['ged'], + 'text/vnd.fly' => ['fly'], + 'text/vnd.fmi.flexstor' => ['flx'], + 'text/vnd.graphviz' => ['gv'], + 'text/vnd.in3d.3dml' => ['3dml'], + 'text/vnd.in3d.spot' => ['spot'], + 'text/vnd.sun.j2me.app-descriptor' => ['jad'], + 'text/vnd.wap.wml' => ['wml'], + 'text/vnd.wap.wmlscript' => ['wmls'], + 'text/vtt' => ['vtt'], + 'text/wgsl' => ['wgsl'], + 'text/x-asm' => ['s', 'asm'], + 'text/x-c' => ['c', 'cc', 'cxx', 'cpp', 'h', 'hh', 'dic'], + 'text/x-component' => ['htc'], + 'text/x-fortran' => ['f', 'for', 'f77', 'f90'], + 'text/x-handlebars-template' => ['hbs'], + 'text/x-java-source' => ['java'], + 'text/x-lua' => ['lua'], + 'text/x-markdown' => ['mkd'], + 'text/x-nfo' => ['nfo'], + 'text/x-opml' => ['opml'], + 'text/x-org' => ['org'], + 'text/x-pascal' => ['p', 'pas'], + 'text/x-processing' => ['pde'], + 'text/x-sass' => ['sass'], + 'text/x-scss' => ['scss'], + 'text/x-setext' => ['etx'], + 'text/x-sfv' => ['sfv'], + 'text/x-suse-ymp' => ['ymp'], + 'text/x-uuencode' => ['uu'], + 'text/x-vcalendar' => ['vcs'], + 'text/x-vcard' => ['vcf'], + 'text/xml' => ['xml'], + 'text/yaml' => ['yaml', 'yml'], + 'video/3gpp' => ['3gp', '3gpp'], + 'video/3gpp2' => ['3g2'], + 'video/h261' => ['h261'], + 'video/h263' => ['h263'], + 'video/h264' => ['h264'], + 'video/iso.segment' => ['m4s'], + 'video/jpeg' => ['jpgv'], + 'video/jpm' => ['jpm', 'jpgm'], + 'video/mj2' => ['mj2', 'mjp2'], + 'video/mp2t' => ['ts', 'm2t', 'm2ts', 'mts'], + 'video/mp4' => ['mp4', 'mp4v', 'mpg4', 'f4v'], + 'video/mpeg' => ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'], + 'video/ogg' => ['ogv'], + 'video/quicktime' => ['qt', 'mov'], + 'video/vnd.dece.hd' => ['uvh', 'uvvh'], + 'video/vnd.dece.mobile' => ['uvm', 'uvvm'], + 'video/vnd.dece.pd' => ['uvp', 'uvvp'], + 'video/vnd.dece.sd' => ['uvs', 'uvvs'], + 'video/vnd.dece.video' => ['uvv', 'uvvv'], + 'video/vnd.dvb.file' => ['dvb'], + 'video/vnd.fvt' => ['fvt'], + 'video/vnd.mpegurl' => ['mxu', 'm4u'], + 'video/vnd.ms-playready.media.pyv' => ['pyv'], + 'video/vnd.uvvu.mp4' => ['uvu', 'uvvu'], + 'video/vnd.vivo' => ['viv'], + 'video/webm' => ['webm'], + 'video/x-f4v' => ['f4v'], + 'video/x-fli' => ['fli'], + 'video/x-flv' => ['flv'], + 'video/x-m4v' => ['m4v'], + 'video/x-matroska' => ['mkv', 'mk3d', 'mks'], + 'video/x-mng' => ['mng'], + 'video/x-ms-asf' => ['asf', 'asx'], + 'video/x-ms-vob' => ['vob'], + 'video/x-ms-wm' => ['wm'], + 'video/x-ms-wmv' => ['wmv'], + 'video/x-ms-wmx' => ['wmx'], + 'video/x-ms-wvx' => ['wvx'], + 'video/x-msvideo' => ['avi'], + 'video/x-sgi-movie' => ['movie'], + 'video/x-smv' => ['smv'], + 'x-conference/x-cooltalk' => ['ice'], + 'application/x-photoshop' => ['psd'], + 'application/smil' => ['smi', 'smil'], + 'application/powerpoint' => ['ppt'], + 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => ['ppam'], + 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => ['pptm', 'potm'], + 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => ['ppsm'], + 'application/wbxml' => ['wbxml'], + 'application/wmlc' => ['wmlc'], + 'application/x-httpd-php-source' => ['phps'], + 'application/x-compress' => ['z'], + 'application/x-rar' => ['rar'], + 'video/vnd.rn-realvideo' => ['rv'], + 'application/vnd.ms-word.template.macroEnabled.12' => ['docm', 'dotm'], + 'application/vnd.ms-excel.sheet.macroEnabled.12' => ['xlsm'], + 'application/vnd.ms-excel.template.macroEnabled.12' => ['xltm'], + 'application/vnd.ms-excel.addin.macroEnabled.12' => ['xlam'], + 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => ['xlsb'], + 'application/excel' => ['xl'], + 'application/x-x509-user-cert' => ['pem'], + 'application/x-pkcs10' => ['p10'], + 'application/x-pkcs7-signature' => ['p7a'], + 'application/pgp' => ['pgp'], + 'application/gpg-keys' => ['gpg'], + 'application/x-pkcs7' => ['rsa'], + 'video/3gp' => ['3gp'], + 'audio/acc' => ['aac'], + 'application/vnd.mpegurl' => ['m4u'], + 'application/videolan' => ['vlc'], + 'audio/x-au' => ['au'], + 'audio/ac3' => ['ac3'], + 'text/x-scriptzsh' => ['zsh'], + 'application/cdr' => ['cdr'], + 'application/STEP' => ['step', 'stp'], + 'application/x-ndjson' => ['ndjson'], + 'application/braille' => ['brf'], + ]; + + public function lookupMimeType(string $extension): ?string + { + return self::MIME_TYPES_FOR_EXTENSIONS[$extension] ?? null; + } + + public function lookupExtension(string $mimetype): ?string + { + return self::EXTENSIONS_FOR_MIME_TIMES[$mimetype][0] ?? null; + } + + /** + * @return string[] + */ + public function lookupAllExtensions(string $mimetype): array + { + return self::EXTENSIONS_FOR_MIME_TIMES[$mimetype] ?? []; + } +} diff --git a/vendor/league/mime-type-detection/src/MimeTypeDetector.php b/vendor/league/mime-type-detection/src/MimeTypeDetector.php new file mode 100644 index 000000000..5d799d2a8 --- /dev/null +++ b/vendor/league/mime-type-detection/src/MimeTypeDetector.php @@ -0,0 +1,19 @@ + $overrides + */ + public function __construct(ExtensionToMimeTypeMap $innerMap, array $overrides) + { + $this->innerMap = $innerMap; + $this->overrides = $overrides; + } + + public function lookupMimeType(string $extension): ?string + { + return $this->overrides[$extension] ?? $this->innerMap->lookupMimeType($extension); + } +} diff --git a/vendor/marc-mabe/php-enum/LICENSE.txt b/vendor/marc-mabe/php-enum/LICENSE.txt new file mode 100644 index 000000000..02bcecec7 --- /dev/null +++ b/vendor/marc-mabe/php-enum/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2020, Marc Bennewitz +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 organisation 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 HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/marc-mabe/php-enum/README.md b/vendor/marc-mabe/php-enum/README.md new file mode 100644 index 000000000..37ef675ef --- /dev/null +++ b/vendor/marc-mabe/php-enum/README.md @@ -0,0 +1,479 @@ +# php-enum +[![Build Status](https://github.com/marc-mabe/php-enum/workflows/Test/badge.svg?branch=master)](https://github.com/marc-mabe/php-enum/actions?query=workflow%3ATest%20branch%3Amaster) +[![Code Coverage](https://codecov.io/github/marc-mabe/php-enum/coverage.svg?branch=master)](https://codecov.io/gh/marc-mabe/php-enum/branch/master/) +[![License](https://poser.pugx.org/marc-mabe/php-enum/license)](https://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt) +[![Latest Stable](https://poser.pugx.org/marc-mabe/php-enum/v/stable.png)](https://packagist.org/packages/marc-mabe/php-enum) +[![Total Downloads](https://poser.pugx.org/marc-mabe/php-enum/downloads.png)](https://packagist.org/packages/marc-mabe/php-enum) +[![Monthly Downloads](https://poser.pugx.org/marc-mabe/php-enum/d/monthly)](https://packagist.org/packages/marc-mabe/php-enum) +[![Dependents](https://poser.pugx.org/marc-mabe/php-enum/dependents)](https://packagist.org/packages/marc-mabe/php-enum/dependents?order_by=downloads) + +This is a native PHP implementation to add enumeration support to PHP. +It's an abstract class that needs to be extended to use it. + + +# What is an Enumeration? + +[Wikipedia](http://wikipedia.org/wiki/Enumerated_type) +> In computer programming, an enumerated type (also called enumeration or enum) +> is a data type consisting of a set of named values called elements, members +> or enumerators of the type. The enumerator names are usually identifiers that +> behave as constants in the language. A variable that has been declared as +> having an enumerated type can be assigned any of the enumerators as a value. +> In other words, an enumerated type has values that are different from each +> other, and that can be compared and assigned, but which do not have any +> particular concrete representation in the computer's memory; compilers and +> interpreters can represent them arbitrarily. + + +# Usage + +## Basics + +```php +use MabeEnum\Enum; + +// define an own enumeration class +class UserStatus extends Enum +{ + const INACTIVE = 'i'; + const ACTIVE = 'a'; + const DELETED = 'd'; + + // all scalar data types and arrays are supported as enumerator values + const NIL = null; + const BOOLEAN = true; + const INT = 1234; + const STR = 'string'; + const FLOAT = 0.123; + const ARR = ['this', 'is', ['an', 'array']]; + + // Enumerators will be generated from public constants only + public const PUBLIC_CONST = 'public constant'; // this will be an enumerator + protected const PROTECTED_CONST = 'protected constant'; // this will NOT be an enumerator + private const PRIVATE_CONST = 'private constant'; // this will NOT be an enumerator + + // works since PHP-7.0 - see https://wiki.php.net/rfc/context_sensitive_lexer + const TRUE = 'true'; + const FALSE = 'false'; + const NULL = 'null'; + const PUBLIC = 'public'; + const PRIVATE = 'private'; + const PROTECTED = 'protected'; + const FUNCTION = 'function'; + const TRAIT = 'trait'; + const INTERFACE = 'interface'; + + // Doesn't work - see https://wiki.php.net/rfc/class_name_scalars + // const CLASS = 'class'; +} + +// ways to instantiate an enumerator +$status = UserStatus::get(UserStatus::ACTIVE); // by value or instance +$status = UserStatus::ACTIVE(); // by name as callable +$status = UserStatus::byValue('a'); // by value +$status = UserStatus::byName('ACTIVE'); // by name +$status = UserStatus::byOrdinal(1); // by ordinal number + +// basic methods of an instantiated enumerator +$status->getValue(); // returns the selected constant value +$status->getName(); // returns the selected constant name +$status->getOrdinal(); // returns the ordinal number of the selected constant + +// basic methods to list defined enumerators +UserStatus::getEnumerators(); // returns a list of enumerator instances +UserStatus::getValues(); // returns a list of enumerator values +UserStatus::getNames(); // returns a list of enumerator names +UserStatus::getOrdinals(); // returns a list of ordinal numbers +UserStatus::getConstants(); // returns an associative array of enumerator names to enumerator values + +// same enumerators (of the same enumeration class) holds the same instance +UserStatus::get(UserStatus::ACTIVE) === UserStatus::ACTIVE() +UserStatus::get(UserStatus::DELETED) != UserStatus::INACTIVE() + +// simplified way to compare two enumerators +$status = UserStatus::ACTIVE(); +$status->is(UserStatus::ACTIVE); // true +$status->is(UserStatus::ACTIVE()); // true +$status->is(UserStatus::DELETED); // false +$status->is(UserStatus::DELETED()); // false +``` + +## Type-Hint + +```php +use MabeEnum\Enum; + +class User +{ + protected $status; + + public function setStatus(UserStatus $status) + { + $this->status = $status; + } + + public function getStatus() + { + if (!$this->status) { + // initialize default + $this->status = UserStatus::INACTIVE(); + } + return $this->status; + } +} +``` + +### Type-Hint issue + +Because in normal OOP the above example allows `UserStatus` and types inherited from it. + +Please think about the following example: + +```php +class ExtendedUserStatus extends UserStatus +{ + const EXTENDED = 'extended'; +} + +$user = new User(); +$user->setStatus(ExtendedUserStatus::EXTENDED()); +``` + +Now the setter receives a status it doesn't know about but allows it. + +#### Solution 1: Finalize the enumeration + +```php +final class UserStatus extends Enum +{ + // ... +} + +class User +{ + protected $status; + + public function setStatus(UserStatus $status) + { + $this->status = $status; + } +} +```` + +* Nice and obvious solution + +* Resulting behaviour matches native enumeration implementation of most other languages (like Java) + +But as this library emulates enumerations it has a few downsides: + +* Enumerator values can not be used directly + * `$user->setStatus(UserStatus::ACTIVE)` fails + * `$user->setStatus(UserStatus::ACTIVE())` works + +* Does not help if the enumeration was defined in an external library + + +#### Solution 2: Using `Enum::get()` + +```php +class User +{ + public function setStatus($status) + { + $this->status = UserStatus::get($status); + } +} +``` + +* Makes sure the resulting enumerator exactly matches an enumeration. (Inherited enumerators are not allowed). + +* Allows enumerator values directly + * `$user->setStatus(UserStatus::ACTIVE)` works + * `$user->setStatus(UserStatus::ACTIVE())` works + +* Also works for enumerations defined in external libraries + +But of course this solution has downsides, too: + +* Looses declarative type-hint + +* A bit slower + + +## EnumSet + +An `EnumSet` is a specialized Set implementation for use with enumeration types. +All of the enumerators in an `EnumSet` must come from a single enumeration type that is specified, when the set is +created. + +Enum sets are represented internally as bit vectors. The bit vector is either an integer type or a binary string type +depending on how many enumerators are defined in the enumeration type. This representation is extremely compact and +efficient. Bulk operations will run very quickly. Enumerators of an `EnumSet` are unique and ordered based on its +ordinal number by design. + +It implements `IteratorAggregate` and `Countable` to be directly iterable with `foreach` and countable with `count()`. + +The `EnumSet` has a mutable and an immutable interface. +Mutable methods start with `set`, `add` or `remove` while immutable methods start with `with`. + +```php +use MabeEnum\EnumSet; + +// create a new EnumSet and initialize with the given enumerators +$enumSet = new EnumSet('UserStatus', [UserStatus::ACTIVE()]); + +// modify an EnumSet (mutable interface) + +// add enumerators (by value or by instance) +$enumSet->addIterable([UserStatus::INACTIVE, UserStatus::DELETED()]); +// or +$enumSet->add(UserStatus::INACTIVE); +$enumSet->add(UserStatus::DELETED()); + +// remove enumerators (by value or by instance) +$enumSet->removeIterable([UserStatus::INACTIVE, UserStatus::DELETED()]); +// or +$enumSet->remove(UserStatus::INACTIVE); +$enumSet->remove(UserStatus::DELETED()); + + +// The immutable interface will create a new EnumSet for each modification + +// add enumerators (by value or by instance) +$enumSet = $enumSet->withIterable([UserStatus::INACTIVE, UserStatus::DELETED()]); +// or +$enumSet = $enumSet->with(UserStatus::INACTIVE); +$enumSet = $enumSet->with(UserStatus::DELETED()); + +// remove enumerators (by value or by instance) +$enumSet->withoutIterable([UserStatus::INACTIVE, UserStatus::DELETED()]); +// or +$enumSet = $enumSet->without(UserStatus::INACTIVE); +$enumSet = $enumSet->without(UserStatus::DELETED()); + + +// Test if an enumerator exists (by value or by instance) +$enumSet->has(UserStatus::INACTIVE); // bool + + +// count the number of enumerators +$enumSet->count(); +count($enumSet); + +// test for elements +$enumSet->isEmpty(); + +// convert to array +$enumSet->getValues(); // List of enumerator values +$enumSet->getEnumerators(); // List of enumerator instances +$enumSet->getNames(); // List of enumerator names +$enumSet->getOrdinals(); // List of ordinal numbers + + +// iterating over the set +foreach ($enumSet as $ordinal => $enum) { + gettype($ordinal); // int (the ordinal number of the enumerator) + get_class($enum); // UserStatus (enumerator object) +} + + +// compare two EnumSets +$enumSet->isEqual($other); // Check if the EnumSet is the same as other +$enumSet->isSubset($other); // Check if the EnumSet is a subset of other +$enumSet->isSuperset($other); // Check if the EnumSet is a superset of other + + +// union, intersect, difference and symmetric difference + +// ... the mutable interface will modify the set +$enumSet->setUnion($other); // Enumerators from both this and other (this | other) +$enumSet->setIntersect($other); // Enumerators common to both this and other (this & other) +$enumSet->setDiff($other); // Enumerators in this but not in other (this - other) +$enumSet->setSymDiff($other); // Enumerators in either this and other but not in both (this ^ other) + +// ... the immutable interface will produce a new set +$enumSet = $enumSet->withUnion($other); // Enumerators from both this and other (this | other) +$enumSet = $enumSet->withIntersect($other); // Enumerators common to both this and other (this & other) +$enumSet = $enumSet->withDiff($other); // Enumerators in this but not in other (this - other) +$enumSet = $enumSet->withSymDiff($other); // Enumerators in either this and other but not in both (this ^ other) +``` + + +## EnumMap + +An `EnumMap` maps enumerators of the same type to data assigned to. + +It implements `ArrayAccess`, `Countable` and `IteratorAggregate` +so elements can be accessed, iterated and counted like a normal array +using `$enumMap[$key]`, `foreach` and `count()`. + +```php +use MabeEnum\EnumMap; + +// create a new EnumMap +$enumMap = new EnumMap('UserStatus'); + + +// read and write key-value-pairs like an array +$enumMap[UserStatus::INACTIVE] = 'inaktiv'; +$enumMap[UserStatus::ACTIVE] = 'aktiv'; +$enumMap[UserStatus::DELETED] = 'gelöscht'; +$enumMap[UserStatus::INACTIVE]; // 'inaktiv'; +$enumMap[UserStatus::ACTIVE]; // 'aktiv'; +$enumMap[UserStatus::DELETED]; // 'gelöscht'; + +isset($enumMap[UserStatus::DELETED]); // true +unset($enumMap[UserStatus::DELETED]); +isset($enumMap[UserStatus::DELETED]); // false + +// ... no matter if you use enumerator values or enumerator objects +$enumMap[UserStatus::INACTIVE()] = 'inaktiv'; +$enumMap[UserStatus::ACTIVE()] = 'aktiv'; +$enumMap[UserStatus::DELETED()] = 'gelöscht'; +$enumMap[UserStatus::INACTIVE()]; // 'inaktiv'; +$enumMap[UserStatus::ACTIVE()]; // 'aktiv'; +$enumMap[UserStatus::DELETED()]; // 'gelöscht'; + +isset($enumMap[UserStatus::DELETED()]); // true +unset($enumMap[UserStatus::DELETED()]); +isset($enumMap[UserStatus::DELETED()]); // false + + +// count number of attached elements +$enumMap->count(); +count($enumMap); + +// test for elements +$enumMap->isEmpty(); + +// support for null aware exists check +$enumMap[UserStatus::NULL] = null; +isset($enumMap[UserStatus::NULL]); // false +$enumMap->has(UserStatus::NULL); // true + + +// iterating over the map +foreach ($enumMap as $enum => $value) { + get_class($enum); // UserStatus (enumerator object) + gettype($value); // mixed (the value the enumerators maps to) +} + +// get a list of keys (= a list of enumerator objects) +$enumMap->getKeys(); + +// get a list of values (= a list of values the enumerator maps to) +$enumMap->getValues(); +``` + + +## Serializing + +Because this enumeration implementation is based on a singleton pattern and in PHP +it's currently impossible to unserialize a singleton without creating a new instance +this feature isn't supported without any additional work. + +As of it's an often requested feature there is a trait that can be added to your +enumeration definition. The trait adds serialization functionallity and injects +the unserialized enumeration instance in case it's the first one. +This reduces singleton behavior breakage but still it beaks if it's not the first +instance and you could result in two different instance of the same enumeration. + +**Use it with caution!** + +PS: `EnumSet` and `EnumMap` are serializable by default as long as you don't set other non-serializable values. + + +### Example of using EnumSerializableTrait + +```php +use MabeEnum\Enum; +use MabeEnum\EnumSerializableTrait; +use Serializable; + +class CardinalDirection extends Enum implements Serializable +{ + use EnumSerializableTrait; + + const NORTH = 'n'; + const EAST = 'e'; + const WEST = 'w'; + const SOUTH = 's'; +} + +$north1 = CardinalDirection::NORTH(); +$north2 = unserialize(serialize($north1)); + +var_dump($north1 === $north2); // returns FALSE as described above +var_dump($north1->is($north2)); // returns TRUE - this way the two instances are treated equal +var_dump($north2->is($north1)); // returns TRUE - equality works in both directions +``` + + +# Generics and Static Code Analyzer + +With version 4.3 we have added support for generics and added better type support. + +* `EnumSet` +* `EnumMap` + +Generic types will be detected by [PHPStan](https://phpstan.org/) and [Psalm](https://psalm.dev/). + +Additionally, we have developed an [extension for PHPStan](https://github.com/marc-mabe/php-enum-phpstan/) +to make enumerator accessor methods known. + + +# Why not `SplEnum` + +* `SplEnum` is not built-in into PHP and requires pecl extension installed. +* Instances of the same value of an `SplEnum` are not the same instance. +* No support for `EnumMap` or `EnumSet`. + + +# Changelog + +Changes are documented in the [release page](https://github.com/marc-mabe/php-enum/releases). + + +# Install + +## Composer + +Add `marc-mabe/php-enum` to the project's composer.json dependencies and run +`php composer.phar install` + +## GIT + +`git clone git://github.com/marc-mabe/php-enum.git` + +## ZIP / TAR + +Download the last version from [Github](https://github.com/marc-mabe/php-enum/tags) +and extract it. + + +# Versioning and Releases + +This project follows [SemVer](https://semver.org/) specification. + +There are **no** [LTS](https://en.wikipedia.org/wiki/Long-term_support) releases +and we don't have (fixed) time based release windows. +Instead releases happen as necessary. + +We do support at least all maintained PHP versions. + +Bug fixes will be backported to the latest maintained minor release. + +Critical bug fixes and security relates fixes can also be backported to older releases. + +| Release | Status | PHP-Version | +|---------|-------------|-----------------| +| 1.x | EOL | \>=5.3 | +| 2.x | EOL | \>=5.3 & HHVM<4 | +| 3.x | EOL | \>=5.6 & HHVM<4 | +| 4.x | active | \>=7.1 | + + +# New BSD License + +The files in this archive are released under the New BSD License. +You can find a copy of this license in LICENSE.txt file. diff --git a/vendor/marc-mabe/php-enum/composer.json b/vendor/marc-mabe/php-enum/composer.json new file mode 100644 index 000000000..b6dfe92d3 --- /dev/null +++ b/vendor/marc-mabe/php-enum/composer.json @@ -0,0 +1,50 @@ +{ + "name": "marc-mabe/php-enum", + "description": "Simple and fast implementation of enumerations with native PHP", + "type": "library", + "keywords": [ + "enum", "enumeration", "enumerator", + "enumset", "enum-set", "set", + "enummap", "enum-map", "map", + "type", "typehint", "type-hint" + ], + "homepage": "https://github.com/marc-mabe/php-enum", + "authors": [{ + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + }], + "license": "BSD-3-Clause", + "require": { + "php": "^7.1 | ^8.0", + "ext-reflection": "*" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "autoload-dev": { + "psr-4": { + "MabeEnumTest\\": "tests/MabeEnumTest/", + "MabeEnumStaticAnalysis\\": "tests/MabeEnumStaticAnalysis/", + "MabeEnumBench\\": "bench/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "4.7-dev", + "dev-3.x": "3.2-dev" + } + } +} diff --git a/vendor/marc-mabe/php-enum/src/Enum.php b/vendor/marc-mabe/php-enum/src/Enum.php new file mode 100644 index 000000000..e9ef6ff8e --- /dev/null +++ b/vendor/marc-mabe/php-enum/src/Enum.php @@ -0,0 +1,504 @@ + + */ + private $value; + + /** + * The ordinal number of the enumerator + * + * @var null|int + */ + private $ordinal; + + /** + * A map of enumerator names and values by enumeration class + * + * @var array, array>> + */ + private static $constants = []; + + /** + * A List of available enumerator names by enumeration class + * + * @var array, string[]> + */ + private static $names = []; + + /** + * A map of enumerator names and instances by enumeration class + * + * @var array, array> + */ + private static $instances = []; + + /** + * Constructor + * + * @param null|bool|int|float|string|array $value The value of the enumerator + * @param int|null $ordinal The ordinal number of the enumerator + */ + final private function __construct($value, $ordinal = null) + { + $this->value = $value; + $this->ordinal = $ordinal; + } + + /** + * Get the name of the enumerator + * + * @return string + * @see getName() + */ + public function __toString(): string + { + return $this->getName(); + } + + /** + * @throws LogicException Enums are not cloneable + * because instances are implemented as singletons + */ + final public function __clone() + { + throw new LogicException('Enums are not cloneable'); + } + + /** + * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation + * + * @psalm-return never-return + */ + final public function __sleep() + { + throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation'); + } + + /** + * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation + * + * @return array + */ + public function __serialize(): array + { + throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation'); + } + + /** + * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation + * + * @psalm-return never-return + */ + final public function __wakeup() + { + throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation'); + } + + /** + * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation + * + * @param array $data + */ + public function __unserialize(array $data): void + { + throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation'); + } + + /** + * Get the value of the enumerator + * + * @return null|bool|int|float|string|array + */ + final public function getValue() + { + return $this->value; + } + + /** + * Get the name of the enumerator + * + * @return string + * + * @phpstan-return string + * @psalm-return non-empty-string + */ + final public function getName() + { + return self::$names[static::class][$this->ordinal ?? $this->getOrdinal()]; + } + + /** + * Get the ordinal number of the enumerator + * + * @return int + */ + final public function getOrdinal() + { + if ($this->ordinal === null) { + $ordinal = 0; + $value = $this->value; + $constants = self::$constants[static::class] ?? static::getConstants(); + foreach ($constants as $constValue) { + if ($value === $constValue) { + break; + } + ++$ordinal; + } + + $this->ordinal = $ordinal; + } + + return $this->ordinal; + } + + /** + * Compare this enumerator against another and check if it's the same. + * + * @param static|null|bool|int|float|string|array $enumerator An enumerator object or value + * @return bool + */ + final public function is($enumerator) + { + return $this === $enumerator || $this->value === $enumerator + + // The following additional conditions are required only because of the issue of serializable singletons + || ($enumerator instanceof static + && \get_class($enumerator) === static::class + && $enumerator->value === $this->value + ); + } + + /** + * Get an enumerator instance of the given enumerator value or instance + * + * @param static|null|bool|int|float|string|array $enumerator An enumerator object or value + * @return static + * @throws InvalidArgumentException On an unknown or invalid value + * @throws LogicException On ambiguous constant values + * + * @psalm-pure + */ + final public static function get($enumerator) + { + if ($enumerator instanceof static) { + if (\get_class($enumerator) !== static::class) { + throw new InvalidArgumentException(sprintf( + 'Invalid value of type %s for enumeration %s', + \get_class($enumerator), + static::class + )); + } + + return $enumerator; + } + + return static::byValue($enumerator); + } + + /** + * Get an enumerator instance by the given value + * + * @param null|bool|int|float|string|array $value Enumerator value + * @return static + * @throws InvalidArgumentException On an unknown or invalid value + * @throws LogicException On ambiguous constant values + * + * @psalm-pure + */ + final public static function byValue($value) + { + /** @var mixed $value */ + + $constants = self::$constants[static::class] ?? static::getConstants(); + + $name = \array_search($value, $constants, true); + if ($name === false) { + throw new InvalidArgumentException(sprintf( + 'Unknown value %s for enumeration %s', + \is_scalar($value) + ? \var_export($value, true) + : 'of type ' . (\is_object($value) ? \get_class($value) : \gettype($value)), + static::class + )); + } + + /** @var static $instance */ + $instance = self::$instances[static::class][$name] + ?? self::$instances[static::class][$name] = new static($constants[$name]); + + return $instance; + } + + /** + * Get an enumerator instance by the given name + * + * @param string $name The name of the enumerator + * @return static + * @throws InvalidArgumentException On an invalid or unknown name + * @throws LogicException On ambiguous values + * + * @psalm-pure + */ + final public static function byName(string $name) + { + if (isset(self::$instances[static::class][$name])) { + /** @var static $instance */ + $instance = self::$instances[static::class][$name]; + return $instance; + } + + $const = static::class . "::{$name}"; + if (!\defined($const)) { + throw new InvalidArgumentException("{$const} not defined"); + } + + assert( + self::noAmbiguousValues(static::getConstants()), + 'Ambiguous enumerator values detected for ' . static::class + ); + + /** @var array|bool|float|int|string|null $value */ + $value = \constant($const); + return self::$instances[static::class][$name] = new static($value); + } + + /** + * Get an enumeration instance by the given ordinal number + * + * @param int $ordinal The ordinal number of the enumerator + * @return static + * @throws InvalidArgumentException On an invalid ordinal number + * @throws LogicException On ambiguous values + * + * @psalm-pure + */ + final public static function byOrdinal(int $ordinal) + { + $constants = self::$constants[static::class] ?? static::getConstants(); + + if (!isset(self::$names[static::class][$ordinal])) { + throw new InvalidArgumentException(\sprintf( + 'Invalid ordinal number %s, must between 0 and %s', + $ordinal, + \count(self::$names[static::class]) - 1 + )); + } + + $name = self::$names[static::class][$ordinal]; + + /** @var static $instance */ + $instance = self::$instances[static::class][$name] + ?? self::$instances[static::class][$name] = new static($constants[$name], $ordinal); + + return $instance; + } + + /** + * Get a list of enumerator instances ordered by ordinal number + * + * @return static[] + * + * @phpstan-return array + * @psalm-return list + * @psalm-pure + */ + final public static function getEnumerators() + { + if (!isset(self::$names[static::class])) { + static::getConstants(); + } + + /** @var callable $byNameFn */ + $byNameFn = [static::class, 'byName']; + return \array_map($byNameFn, self::$names[static::class]); + } + + /** + * Get a list of enumerator values ordered by ordinal number + * + * @return (null|bool|int|float|string|array)[] + * + * @phpstan-return array> + * @psalm-return list + * @psalm-pure + */ + final public static function getValues() + { + return \array_values(self::$constants[static::class] ?? static::getConstants()); + } + + /** + * Get a list of enumerator names ordered by ordinal number + * + * @return string[] + * + * @phpstan-return array + * @psalm-return list + * @psalm-pure + */ + final public static function getNames() + { + if (!isset(self::$names[static::class])) { + static::getConstants(); + } + return self::$names[static::class]; + } + + /** + * Get a list of enumerator ordinal numbers + * + * @return int[] + * + * @phpstan-return array + * @psalm-return list + * @psalm-pure + */ + final public static function getOrdinals() + { + $count = \count(self::$constants[static::class] ?? static::getConstants()); + return $count ? \range(0, $count - 1) : []; + } + + /** + * Get all available constants of the called class + * + * @return (null|bool|int|float|string|array)[] + * @throws LogicException On ambiguous constant values + * + * @phpstan-return array> + * @psalm-return array + * @psalm-pure + */ + final public static function getConstants() + { + if (isset(self::$constants[static::class])) { + return self::$constants[static::class]; + } + + $reflection = new ReflectionClass(static::class); + $constants = []; + + do { + $scopeConstants = []; + // Enumerators must be defined as public class constants + foreach ($reflection->getReflectionConstants() as $reflConstant) { + if ($reflConstant->isPublic()) { + $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue(); + } + } + + $constants = $scopeConstants + $constants; + } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__); + + /** @var array> $constants */ + + assert( + self::noAmbiguousValues($constants), + 'Ambiguous enumerator values detected for ' . static::class + ); + + self::$names[static::class] = \array_keys($constants); + return self::$constants[static::class] = $constants; + } + + /** + * Test that the given constants does not contain ambiguous values + * @param array> $constants + * @return bool + */ + private static function noAmbiguousValues($constants) + { + foreach ($constants as $value) { + $names = \array_keys($constants, $value, true); + if (\count($names) > 1) { + return false; + } + } + + return true; + } + + /** + * Test if the given enumerator is part of this enumeration + * + * @param static|null|bool|int|float|string|array $enumerator + * @return bool + * + * @psalm-pure + */ + final public static function has($enumerator) + { + if ($enumerator instanceof static) { + return \get_class($enumerator) === static::class; + } + + return static::hasValue($enumerator); + } + + /** + * Test if the given enumerator value is part of this enumeration + * + * @param null|bool|int|float|string|array $value + * @return bool + * + * @psalm-pure + */ + final public static function hasValue($value) + { + return \in_array($value, self::$constants[static::class] ?? static::getConstants(), true); + } + + /** + * Test if the given enumerator name is part of this enumeration + * + * @param string $name + * @return bool + * + * @psalm-pure + */ + final public static function hasName(string $name) + { + return \defined("static::{$name}"); + } + + /** + * Get an enumerator instance by the given name. + * + * This will be called automatically on calling a method + * with the same name of a defined enumerator. + * + * @param string $method The name of the enumerator (called as method) + * @param array $args There should be no arguments + * @return static + * @throws InvalidArgumentException On an invalid or unknown name + * @throws LogicException On ambiguous constant values + * + * @psalm-pure + */ + final public static function __callStatic(string $method, array $args) + { + return static::byName($method); + } +} diff --git a/vendor/marc-mabe/php-enum/src/EnumMap.php b/vendor/marc-mabe/php-enum/src/EnumMap.php new file mode 100644 index 000000000..eae61bac8 --- /dev/null +++ b/vendor/marc-mabe/php-enum/src/EnumMap.php @@ -0,0 +1,393 @@ +). + * + * @template T of Enum + * @implements ArrayAccess + * @implements IteratorAggregate + * + * @copyright 2020, Marc Bennewitz + * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License + * @link http://github.com/marc-mabe/php-enum for the canonical source repository + */ +class EnumMap implements ArrayAccess, Countable, IteratorAggregate +{ + /** + * The classname of the enumeration type + * @var class-string + */ + private $enumeration; + + /** + * Internal map of ordinal number and data value + * @var array + */ + private $map = []; + + /** + * Constructor + * @param class-string $enumeration The classname of the enumeration type + * @param null|iterable, mixed> $map Initialize map + * @throws InvalidArgumentException + */ + public function __construct(string $enumeration, ?iterable $map = null) + { + if (!\is_subclass_of($enumeration, Enum::class)) { + throw new InvalidArgumentException(\sprintf( + '%s can handle subclasses of %s only', + __CLASS__, + Enum::class + )); + } + $this->enumeration = $enumeration; + + if ($map) { + $this->addIterable($map); + } + } + + /** + * Add virtual private property "__pairs" with a list of key-value-pairs + * to the result of var_dump. + * + * This helps debugging as internally the map is using the ordinal number. + * + * @return array + */ + public function __debugInfo(): array { + $dbg = (array)$this; + $dbg["\0" . self::class . "\0__pairs"] = array_map(function ($k, $v) { + return [$k, $v]; + }, $this->getKeys(), $this->getValues()); + return $dbg; + } + + /* write access (mutable) */ + + /** + * Adds the given enumerator (object or value) mapping to the specified data value. + * @param T|null|bool|int|float|string|array $enumerator + * @param mixed $value + * @throws InvalidArgumentException On an invalid given enumerator + * @see offsetSet() + */ + public function add($enumerator, $value): void + { + $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); + $this->map[$ord] = $value; + } + + /** + * Adds the given iterable, mapping enumerators (objects or values) to data values. + * @param iterable, mixed> $map + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function addIterable(iterable $map): void + { + $innerMap = $this->map; + foreach ($map as $enumerator => $value) { + $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); + $innerMap[$ord] = $value; + } + $this->map = $innerMap; + } + + /** + * Removes the given enumerator (object or value) mapping. + * @param T|null|bool|int|float|string|array $enumerator + * @throws InvalidArgumentException On an invalid given enumerator + * @see offsetUnset() + */ + public function remove($enumerator): void + { + $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); + unset($this->map[$ord]); + } + + /** + * Removes the given iterable enumerator (object or value) mappings. + * @param iterable> $enumerators + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function removeIterable(iterable $enumerators): void + { + $map = $this->map; + foreach ($enumerators as $enumerator) { + $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); + unset($map[$ord]); + } + + $this->map = $map; + } + + /* write access (immutable) */ + + /** + * Creates a new map with the given enumerator (object or value) mapping to the specified data value added. + * @param T|null|bool|int|float|string|array $enumerator + * @param mixed $value + * @return static + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function with($enumerator, $value): self + { + $clone = clone $this; + $clone->add($enumerator, $value); + return $clone; + } + + /** + * Creates a new map with the given iterable mapping enumerators (objects or values) to data values added. + * @param iterable, mixed> $map + * @return static + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function withIterable(iterable $map): self + { + $clone = clone $this; + $clone->addIterable($map); + return $clone; + } + + /** + * Create a new map with the given enumerator mapping removed. + * @param T|null|bool|int|float|string|array $enumerator + * @return static + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function without($enumerator): self + { + $clone = clone $this; + $clone->remove($enumerator); + return $clone; + } + + /** + * Creates a new map with the given iterable enumerator (object or value) mappings removed. + * @param iterable> $enumerators + * @return static + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function withoutIterable(iterable $enumerators): self + { + $clone = clone $this; + $clone->removeIterable($enumerators); + return $clone; + } + + /* read access */ + + /** + * Get the classname of the enumeration type. + * @return class-string + */ + public function getEnumeration(): string + { + return $this->enumeration; + } + + /** + * Get the mapped data value of the given enumerator (object or value). + * @param T|null|bool|int|float|string|array $enumerator + * @return mixed + * @throws InvalidArgumentException On an invalid given enumerator + * @throws UnexpectedValueException If the given enumerator does not exist in this map + * @see offsetGet() + */ + public function get($enumerator) + { + $enumerator = ($this->enumeration)::get($enumerator); + $ord = $enumerator->getOrdinal(); + if (!\array_key_exists($ord, $this->map)) { + throw new UnexpectedValueException(sprintf( + 'Enumerator %s could not be found', + \var_export($enumerator->getValue(), true) + )); + } + + return $this->map[$ord]; + } + + /** + * Get a list of enumerator keys. + * @return T[] + * + * @phpstan-return array + * @psalm-return list + */ + public function getKeys(): array + { + /** @var callable $byOrdinalFn */ + $byOrdinalFn = [$this->enumeration, 'byOrdinal']; + + return \array_map($byOrdinalFn, \array_keys($this->map)); + } + + /** + * Get a list of mapped data values. + * @return mixed[] + * + * @phpstan-return array + * @psalm-return list + */ + public function getValues(): array + { + return \array_values($this->map); + } + + /** + * Search for the given data value. + * @param mixed $value + * @param bool $strict Use strict type comparison + * @return T|null The enumerator object of the first matching data value or NULL + */ + public function search($value, bool $strict = false) + { + /** @var false|int $ord */ + $ord = \array_search($value, $this->map, $strict); + if ($ord !== false) { + return ($this->enumeration)::byOrdinal($ord); + } + + return null; + } + + /** + * Test if the given enumerator key (object or value) exists. + * @param T|null|bool|int|float|string|array $enumerator + * @return bool + * @see offsetExists() + */ + public function has($enumerator): bool + { + try { + $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); + return \array_key_exists($ord, $this->map); + } catch (InvalidArgumentException $e) { + // An invalid enumerator can't be contained in this map + return false; + } + } + + /** + * Test if the given enumerator key (object or value) exists. + * @param T|null|bool|int|float|string|array $enumerator + * @return bool + * @see offsetExists() + * @see has() + * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x + */ + public function contains($enumerator): bool + { + return $this->has($enumerator); + } + + /* ArrayAccess */ + + /** + * Test if the given enumerator key (object or value) exists and is not NULL + * @param T|null|bool|int|float|string|array $enumerator + * @return bool + * @see contains() + */ + public function offsetExists($enumerator): bool + { + try { + return isset($this->map[($this->enumeration)::get($enumerator)->getOrdinal()]); + } catch (InvalidArgumentException $e) { + // An invalid enumerator can't be an offset of this map + return false; + } + } + + /** + * Get the mapped data value of the given enumerator (object or value). + * @param T|null|bool|int|float|string|array $enumerator + * @return mixed The mapped date value of the given enumerator or NULL + * @throws InvalidArgumentException On an invalid given enumerator + * @see get() + */ + #[\ReturnTypeWillChange] + public function offsetGet($enumerator) + { + try { + return $this->get($enumerator); + } catch (UnexpectedValueException $e) { + return null; + } + } + + /** + * Adds the given enumerator (object or value) mapping to the specified data value. + * @param T|null|bool|int|float|string|array $enumerator + * @param mixed $value + * @return void + * @throws InvalidArgumentException On an invalid given enumerator + * @see add() + */ + public function offsetSet($enumerator, $value = null): void + { + $this->add($enumerator, $value); + } + + /** + * Removes the given enumerator (object or value) mapping. + * @param T|null|bool|int|float|string|array $enumerator + * @return void + * @throws InvalidArgumentException On an invalid given enumerator + * @see remove() + */ + public function offsetUnset($enumerator): void + { + $this->remove($enumerator); + } + + /* IteratorAggregate */ + + /** + * Get a new Iterator. + * + * @return Iterator Iterator + */ + public function getIterator(): Iterator + { + $map = $this->map; + foreach ($map as $ordinal => $value) { + yield ($this->enumeration)::byOrdinal($ordinal) => $value; + } + } + + /* Countable */ + + /** + * Count the number of elements + * + * @return int + */ + public function count(): int + { + return \count($this->map); + } + + /** + * Tests if the map is empty + * + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->map); + } +} diff --git a/vendor/marc-mabe/php-enum/src/EnumSerializableTrait.php b/vendor/marc-mabe/php-enum/src/EnumSerializableTrait.php new file mode 100644 index 000000000..1f54b9d4a --- /dev/null +++ b/vendor/marc-mabe/php-enum/src/EnumSerializableTrait.php @@ -0,0 +1,110 @@ + + */ + abstract public function getValue(); + + /** + * Returns an array of data to be serialized. + * This magic method will be called by serialize() in PHP >= 7.4 + * + * @return array> + */ + public function __serialize(): array + { + return ['value' => $this->getValue()]; + } + + /** + * Receives an array of data to be unserialized on a new instance without constructor. + * This magic method will be called in PHP >= 7.4 is the data where serialized with PHP >= 7.4. + * + * @throws RuntimeException On missing, unknown or invalid value + * @throws LogicException On calling this method on an already initialized enumerator + * + * @param array $data + * @return void + */ + public function __unserialize(array $data): void + { + if (!\array_key_exists('value', $data)) { + throw new RuntimeException('Missing array key "value"'); + } + + /** @var mixed $value */ + $value = $data['value']; + $constants = self::getConstants(); + $name = \array_search($value, $constants, true); + if ($name === false) { + $message = \is_scalar($value) + ? 'Unknown value ' . \var_export($value, true) + : 'Invalid value of type ' . (\is_object($value) ? \get_class($value) : \gettype($value)); + throw new RuntimeException($message); + } + + $class = static::class; + $enumerator = $this; + $closure = function () use ($class, $name, $value, $enumerator) { + if ($value !== null && $this->value !== null) { + throw new LogicException('Do not call this directly - please use unserialize($enum) instead'); + } + + $this->value = $value; + + if (!isset(self::$instances[$class][$name])) { + self::$instances[$class][$name] = $enumerator; + } + }; + $closure->bindTo($this, Enum::class)(); + } + + /** + * Serialize the value of the enumeration + * This will be called automatically on `serialize()` if the enumeration implements the `Serializable` interface + * + * @return string + * @deprecated Since PHP 7.4 + */ + public function serialize(): string + { + return \serialize($this->getValue()); + } + + /** + * Unserializes a given serialized value and push it into the current instance + * This will be called automatically on `unserialize()` if the enumeration implements the `Serializable` interface + * @param string $serialized + * @return void + * @throws RuntimeException On an unknown or invalid value + * @throws LogicException On calling this method on an already initialized enumerator + * @deprecated Since PHP 7.4 + */ + public function unserialize($serialized): void + { + $this->__unserialize(['value' => \unserialize($serialized)]); + } +} diff --git a/vendor/marc-mabe/php-enum/src/EnumSet.php b/vendor/marc-mabe/php-enum/src/EnumSet.php new file mode 100644 index 000000000..42e34b398 --- /dev/null +++ b/vendor/marc-mabe/php-enum/src/EnumSet.php @@ -0,0 +1,1193 @@ +) + * based on an integer or binary bitset depending of given enumeration size. + * + * @template T of Enum + * @implements IteratorAggregate + * + * @copyright 2020, Marc Bennewitz + * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License + * @link http://github.com/marc-mabe/php-enum for the canonical source repository + */ +class EnumSet implements IteratorAggregate, Countable +{ + /** + * The classname of the Enumeration + * @var class-string + */ + private $enumeration; + + /** + * Number of enumerators defined in the enumeration + * @var int + */ + private $enumerationCount; + + /** + * Integer or binary (little endian) bitset + * @var int|string + */ + private $bitset = 0; + + /** + * Integer or binary (little endian) empty bitset + * + * @var int|string + */ + private $emptyBitset = 0; + + /**#@+ + * Defines private method names to be called depended of how the bitset type was set too. + * ... Integer or binary bitset. + * ... *Int or *Bin method + * + * @var string + */ + /** @var string */ + private $fnDoGetIterator = 'doGetIteratorInt'; + + /** @var string */ + private $fnDoCount = 'doCountInt'; + + /** @var string */ + private $fnDoGetOrdinals = 'doGetOrdinalsInt'; + + /** @var string */ + private $fnDoGetBit = 'doGetBitInt'; + + /** @var string */ + private $fnDoSetBit = 'doSetBitInt'; + + /** @var string */ + private $fnDoUnsetBit = 'doUnsetBitInt'; + + /** @var string */ + private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt'; + + /** @var string */ + private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt'; + /**#@-*/ + + /** + * Constructor + * + * @param class-string $enumeration The classname of the enumeration + * @param iterable>|null $enumerators iterable list of enumerators initializing the set + * @throws InvalidArgumentException + */ + public function __construct(string $enumeration, ?iterable $enumerators = null) + { + if (!\is_subclass_of($enumeration, Enum::class)) { + throw new InvalidArgumentException(\sprintf( + '%s can handle subclasses of %s only', + __METHOD__, + Enum::class + )); + } + + $this->enumeration = $enumeration; + $this->enumerationCount = \count($enumeration::getConstants()); + + // By default the bitset is initialized as integer bitset + // in case the enumeration has more enumerators then integer bits + // we will switch this into a binary bitset + if ($this->enumerationCount > \PHP_INT_SIZE * 8) { + // init binary bitset with zeros + $this->bitset = $this->emptyBitset = \str_repeat("\0", (int)\ceil($this->enumerationCount / 8)); + + // switch internal binary bitset functions + $this->fnDoGetIterator = 'doGetIteratorBin'; + $this->fnDoCount = 'doCountBin'; + $this->fnDoGetOrdinals = 'doGetOrdinalsBin'; + $this->fnDoGetBit = 'doGetBitBin'; + $this->fnDoSetBit = 'doSetBitBin'; + $this->fnDoUnsetBit = 'doUnsetBitBin'; + $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin'; + $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin'; + } + + if ($enumerators !== null) { + foreach ($enumerators as $enumerator) { + $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal()); + } + } + } + + /** + * Add virtual private property "__enumerators" with a list of enumerator values set + * to the result of var_dump. + * + * This helps debugging as internally the enumerators of this EnumSet gets stored + * as either integer or binary bit-array. + * + * @return array + */ + public function __debugInfo() { + $dbg = (array)$this; + $dbg["\0" . self::class . "\0__enumerators"] = $this->getValues(); + return $dbg; + } + + /** + * Get the classname of the enumeration + * @return class-string + */ + public function getEnumeration(): string + { + return $this->enumeration; + } + + /* write access (mutable) */ + + /** + * Adds an enumerator object or value + * @param T|null|bool|int|float|string|array $enumerator Enumerator object or value + * @return void + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function add($enumerator): void + { + $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); + } + + /** + * Adds all enumerator objects or values of the given iterable + * @param iterable> $enumerators Iterable list of enumerator objects or values + * @return void + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function addIterable(iterable $enumerators): void + { + $bitset = $this->bitset; + + try { + foreach ($enumerators as $enumerator) { + $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); + } + } catch (\Throwable $e) { + // reset all changes until error happened + $this->bitset = $bitset; + throw $e; + } + } + + /** + * Removes the given enumerator object or value + * @param T|null|bool|int|float|string|array $enumerator Enumerator object or value + * @return void + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function remove($enumerator): void + { + $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); + } + + /** + * Adds an enumerator object or value + * @param T|null|bool|int|float|string|array $enumerator Enumerator object or value + * @return void + * @throws InvalidArgumentException On an invalid given enumerator + * @see add() + * @see with() + * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x + */ + public function attach($enumerator): void + { + $this->add($enumerator); + } + + /** + * Removes the given enumerator object or value + * @param T|null|bool|int|float|string|array $enumerator Enumerator object or value + * @return void + * @throws InvalidArgumentException On an invalid given enumerator + * @see remove() + * @see without() + * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x + */ + public function detach($enumerator): void + { + $this->remove($enumerator); + } + + /** + * Removes all enumerator objects or values of the given iterable + * @param iterable> $enumerators Iterable list of enumerator objects or values + * @return void + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function removeIterable(iterable $enumerators): void + { + $bitset = $this->bitset; + + try { + foreach ($enumerators as $enumerator) { + $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); + } + } catch (\Throwable $e) { + // reset all changes until error happened + $this->bitset = $bitset; + throw $e; + } + } + + /** + * Modify this set from both this and other (this | other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the union + * @return void + * @throws InvalidArgumentException If $other doesn't match the enumeration + */ + public function setUnion(EnumSet $other): void + { + if ($this->enumeration !== $other->enumeration) { + throw new InvalidArgumentException(\sprintf( + 'Other should be of the same enumeration as this %s', + $this->enumeration + )); + } + + $this->bitset = $this->bitset | $other->bitset; + } + + /** + * Modify this set with enumerators common to both this and other (this & other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the intersect + * @return void + * @throws InvalidArgumentException If $other doesn't match the enumeration + */ + public function setIntersect(EnumSet $other): void + { + if ($this->enumeration !== $other->enumeration) { + throw new InvalidArgumentException(\sprintf( + 'Other should be of the same enumeration as this %s', + $this->enumeration + )); + } + + $this->bitset = $this->bitset & $other->bitset; + } + + /** + * Modify this set with enumerators in this but not in other (this - other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the diff + * @return void + * @throws InvalidArgumentException If $other doesn't match the enumeration + */ + public function setDiff(EnumSet $other): void + { + if ($this->enumeration !== $other->enumeration) { + throw new InvalidArgumentException(\sprintf( + 'Other should be of the same enumeration as this %s', + $this->enumeration + )); + } + + $this->bitset = $this->bitset & ~$other->bitset; + } + + /** + * Modify this set with enumerators in either this and other but not in both (this ^ other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the symmetric difference + * @return void + * @throws InvalidArgumentException If $other doesn't match the enumeration + */ + public function setSymDiff(EnumSet $other): void + { + if ($this->enumeration !== $other->enumeration) { + throw new InvalidArgumentException(\sprintf( + 'Other should be of the same enumeration as this %s', + $this->enumeration + )); + } + + $this->bitset = $this->bitset ^ $other->bitset; + } + + /** + * Set the given binary bitset in little-endian order + * + * @param string $bitset + * @return void + * @throws InvalidArgumentException On out-of-range bits given as input bitset + * @uses doSetBinaryBitsetLeBin() + * @uses doSetBinaryBitsetLeInt() + */ + public function setBinaryBitsetLe(string $bitset): void + { + $this->{$this->fnDoSetBinaryBitsetLe}($bitset); + } + + /** + * Set binary bitset in little-endian order + * + * @param string $bitset + * @return void + * @throws InvalidArgumentException On out-of-range bits given as input bitset + * @see setBinaryBitsetLeBin() + * @see doSetBinaryBitsetLeInt() + */ + private function doSetBinaryBitsetLeBin($bitset): void + { + /** @var string $thisBitset */ + $thisBitset = $this->bitset; + + $size = \strlen($thisBitset); + $sizeIn = \strlen($bitset); + + if ($sizeIn < $size) { + // add "\0" if the given bitset is not long enough + $bitset .= \str_repeat("\0", $size - $sizeIn); + } elseif ($sizeIn > $size) { + if (\ltrim(\substr($bitset, $size), "\0") !== '') { + throw new InvalidArgumentException('out-of-range bits detected'); + } + $bitset = \substr($bitset, 0, $size); + } + + // truncate out-of-range bits of last byte + $lastByteMaxOrd = $this->enumerationCount % 8; + if ($lastByteMaxOrd !== 0) { + $lastByte = $bitset[-1]; + $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte; + if ($lastByte !== $lastByteExpected) { + throw new InvalidArgumentException('out-of-range bits detected'); + } + + $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected; + } + + $this->bitset = $bitset; + } + + /** + * Set binary bitset in little-endian order + * + * @param string $bitset + * @return void + * @throws InvalidArgumentException On out-of-range bits given as input bitset + * @see setBinaryBitsetLeBin() + * @see doSetBinaryBitsetLeBin() + */ + private function doSetBinaryBitsetLeInt($bitset): void + { + $len = \strlen($bitset); + $int = 0; + for ($i = 0; $i < $len; ++$i) { + $ord = \ord($bitset[$i]); + + if ($ord && $i > \PHP_INT_SIZE - 1) { + throw new InvalidArgumentException('out-of-range bits detected'); + } + + $int |= $ord << (8 * $i); + } + + if ($int & (~0 << $this->enumerationCount)) { + throw new InvalidArgumentException('out-of-range bits detected'); + } + + $this->bitset = $int; + } + + /** + * Set the given binary bitset in big-endian order + * + * @param string $bitset + * @return void + * @throws InvalidArgumentException On out-of-range bits given as input bitset + */ + public function setBinaryBitsetBe(string $bitset): void + { + $this->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset)); + } + + /** + * Set a bit at the given ordinal number + * + * @param int $ordinal Ordinal number of bit to set + * @param bool $bit The bit to set + * @return void + * @throws InvalidArgumentException If the given ordinal number is out-of-range + * @uses doSetBitBin() + * @uses doSetBitInt() + * @uses doUnsetBitBin() + * @uses doUnsetBitInt() + */ + public function setBit(int $ordinal, bool $bit): void + { + if ($ordinal < 0 || $ordinal > $this->enumerationCount) { + throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}"); + } + + if ($bit) { + $this->{$this->fnDoSetBit}($ordinal); + } else { + $this->{$this->fnDoUnsetBit}($ordinal); + } + } + + /** + * Set a bit at the given ordinal number. + * + * This is the binary bitset implementation. + * + * @param int $ordinal Ordinal number of bit to set + * @return void + * @see setBit() + * @see doSetBitInt() + */ + private function doSetBitBin($ordinal): void + { + /** @var string $thisBitset */ + $thisBitset = $this->bitset; + + $byte = (int) ($ordinal / 8); + $thisBitset[$byte] = $thisBitset[$byte] | \chr(1 << ($ordinal % 8)); + + $this->bitset = $thisBitset; + } + + /** + * Set a bit at the given ordinal number. + * + * This is the binary bitset implementation. + * + * @param int $ordinal Ordinal number of bit to set + * @return void + * @see setBit() + * @see doSetBitBin() + */ + private function doSetBitInt($ordinal): void + { + /** @var int $thisBitset */ + $thisBitset = $this->bitset; + + $this->bitset = $thisBitset | (1 << $ordinal); + } + + /** + * Unset a bit at the given ordinal number. + * + * This is the binary bitset implementation. + * + * @param int $ordinal Ordinal number of bit to unset + * @return void + * @see setBit() + * @see doUnsetBitInt() + */ + private function doUnsetBitBin($ordinal): void + { + /** @var string $thisBitset */ + $thisBitset = $this->bitset; + + $byte = (int) ($ordinal / 8); + $thisBitset[$byte] = $thisBitset[$byte] & \chr(~(1 << ($ordinal % 8))); + + $this->bitset = $thisBitset; + } + + /** + * Unset a bit at the given ordinal number. + * + * This is the integer bitset implementation. + * + * @param int $ordinal Ordinal number of bit to unset + * @return void + * @see setBit() + * @see doUnsetBitBin() + */ + private function doUnsetBitInt($ordinal): void + { + /** @var int $thisBitset */ + $thisBitset = $this->bitset; + + $this->bitset = $thisBitset & ~(1 << $ordinal); + } + + /* write access (immutable) */ + + /** + * Creates a new set with the given enumerator object or value added + * @param T|null|bool|int|float|string|array $enumerator Enumerator object or value + * @return static + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function with($enumerator): self + { + $clone = clone $this; + $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); + return $clone; + } + + /** + * Creates a new set with the given enumeration objects or values added + * @param iterable> $enumerators Iterable list of enumerator objects or values + * @return static + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function withIterable(iterable $enumerators): self + { + $clone = clone $this; + foreach ($enumerators as $enumerator) { + $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); + } + return $clone; + } + + /** + * Create a new set with the given enumerator object or value removed + * @param T|null|bool|int|float|string|array $enumerator Enumerator object or value + * @return static + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function without($enumerator): self + { + $clone = clone $this; + $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); + return $clone; + } + + /** + * Creates a new set with the given enumeration objects or values removed + * @param iterable> $enumerators Iterable list of enumerator objects or values + * @return static + * @throws InvalidArgumentException On an invalid given enumerator + */ + public function withoutIterable(iterable $enumerators): self + { + $clone = clone $this; + foreach ($enumerators as $enumerator) { + $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); + } + return $clone; + } + + /** + * Create a new set with enumerators from both this and other (this | other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the union + * @return static + * @throws InvalidArgumentException If $other doesn't match the enumeration + */ + public function withUnion(EnumSet $other): self + { + $clone = clone $this; + $clone->setUnion($other); + return $clone; + } + + /** + * Create a new set with enumerators from both this and other (this | other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the union + * @return static + * @throws InvalidArgumentException If $other doesn't match the enumeration + * @see withUnion() + * @see setUnion() + * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x + */ + public function union(EnumSet $other): self + { + return $this->withUnion($other); + } + + /** + * Create a new set with enumerators common to both this and other (this & other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the intersect + * @return static + * @throws InvalidArgumentException If $other doesn't match the enumeration + */ + public function withIntersect(EnumSet $other): self + { + $clone = clone $this; + $clone->setIntersect($other); + return $clone; + } + + /** + * Create a new set with enumerators common to both this and other (this & other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the intersect + * @return static + * @throws InvalidArgumentException If $other doesn't match the enumeration + * @see withIntersect() + * @see setIntersect() + * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x + */ + public function intersect(EnumSet $other): self + { + return $this->withIntersect($other); + } + + /** + * Create a new set with enumerators in this but not in other (this - other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the diff + * @return static + * @throws InvalidArgumentException If $other doesn't match the enumeration + */ + public function withDiff(EnumSet $other): self + { + $clone = clone $this; + $clone->setDiff($other); + return $clone; + } + + /** + * Create a new set with enumerators in this but not in other (this - other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the diff + * @return static + * @throws InvalidArgumentException If $other doesn't match the enumeration + * @see withDiff() + * @see setDiff() + * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x + */ + public function diff(EnumSet $other): self + { + return $this->withDiff($other); + } + + /** + * Create a new set with enumerators in either this and other but not in both (this ^ other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the symmetric difference + * @return static + * @throws InvalidArgumentException If $other doesn't match the enumeration + */ + public function withSymDiff(EnumSet $other): self + { + $clone = clone $this; + $clone->setSymDiff($other); + return $clone; + } + + /** + * Create a new set with enumerators in either this and other but not in both (this ^ other) + * + * @param EnumSet $other EnumSet of the same enumeration to produce the symmetric difference + * @return static + * @throws InvalidArgumentException If $other doesn't match the enumeration + * @see withSymDiff() + * @see setSymDiff() + * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x + */ + public function symDiff(EnumSet $other): self + { + return $this->withSymDiff($other); + } + + /** + * Create a new set with the given binary bitset in little-endian order + * + * @param string $bitset + * @return static + * @throws InvalidArgumentException On out-of-range bits given as input bitset + * @uses doSetBinaryBitsetLeBin() + * @uses doSetBinaryBitsetLeInt() + */ + public function withBinaryBitsetLe(string $bitset): self + { + $clone = clone $this; + $clone->{$this->fnDoSetBinaryBitsetLe}($bitset); + return $clone; + } + + /** + * Create a new set with the given binary bitset in big-endian order + * + * @param string $bitset + * @return static + * @throws InvalidArgumentException On out-of-range bits given as input bitset + */ + public function withBinaryBitsetBe(string $bitset): self + { + $clone = $this; + $clone->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset)); + return $clone; + } + + /** + * Create a new set with the bit at the given ordinal number set + * + * @param int $ordinal Ordinal number of bit to set + * @param bool $bit The bit to set + * @return static + * @throws InvalidArgumentException If the given ordinal number is out-of-range + * @uses doSetBitBin() + * @uses doSetBitInt() + * @uses doUnsetBitBin() + * @uses doUnsetBitInt() + */ + public function withBit(int $ordinal, bool $bit): self + { + $clone = clone $this; + $clone->setBit($ordinal, $bit); + return $clone; + } + + /* read access */ + + /** + * Test if the given enumerator exists + * @param T|null|bool|int|float|string|array $enumerator + * @return bool + */ + public function has($enumerator): bool + { + return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); + } + + /** + * Test if the given enumerator exists + * @param T|null|bool|int|float|string|array $enumerator + * @return bool + * @see has() + * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x + */ + public function contains($enumerator): bool + { + return $this->has($enumerator); + } + + /* IteratorAggregate */ + + /** + * Get a new iterator + * @return Iterator + * @uses doGetIteratorInt() + * @uses doGetIteratorBin() + */ + public function getIterator(): Iterator + { + return $this->{$this->fnDoGetIterator}(); + } + + /** + * Get a new Iterator. + * + * This is the binary bitset implementation. + * + * @return Iterator + * @see getIterator() + * @see goGetIteratorInt() + */ + private function doGetIteratorBin() + { + /** @var string $bitset */ + $bitset = $this->bitset; + $byteLen = \strlen($bitset); + for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) { + if ($bitset[$bytePos] === "\0") { + // fast skip null byte + continue; + } + + $ord = \ord($bitset[$bytePos]); + for ($bitPos = 0; $bitPos < 8; ++$bitPos) { + if ($ord & (1 << $bitPos)) { + $ordinal = $bytePos * 8 + $bitPos; + yield $ordinal => ($this->enumeration)::byOrdinal($ordinal); + } + } + } + } + + /** + * Get a new Iterator. + * + * This is the integer bitset implementation. + * + * @return Iterator + * @see getIterator() + * @see doGetIteratorBin() + */ + private function doGetIteratorInt() + { + /** @var int $bitset */ + $bitset = $this->bitset; + $count = $this->enumerationCount; + for ($ordinal = 0; $ordinal < $count; ++$ordinal) { + if ($bitset & (1 << $ordinal)) { + yield $ordinal => ($this->enumeration)::byOrdinal($ordinal); + } + } + } + + /* Countable */ + + /** + * Count the number of elements + * + * @return int + * @uses doCountBin() + * @uses doCountInt() + */ + public function count(): int + { + return $this->{$this->fnDoCount}(); + } + + /** + * Count the number of elements. + * + * This is the binary bitset implementation. + * + * @return int + * @see count() + * @see doCountInt() + */ + private function doCountBin() + { + /** @var string $bitset */ + $bitset = $this->bitset; + $count = 0; + $byteLen = \strlen($bitset); + for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) { + if ($bitset[$bytePos] === "\0") { + // fast skip null byte + continue; + } + + $ord = \ord($bitset[$bytePos]); + if ($ord & 0b00000001) ++$count; + if ($ord & 0b00000010) ++$count; + if ($ord & 0b00000100) ++$count; + if ($ord & 0b00001000) ++$count; + if ($ord & 0b00010000) ++$count; + if ($ord & 0b00100000) ++$count; + if ($ord & 0b01000000) ++$count; + if ($ord & 0b10000000) ++$count; + } + return $count; + } + + /** + * Count the number of elements. + * + * This is the integer bitset implementation. + * + * @return int + * @see count() + * @see doCountBin() + */ + private function doCountInt() + { + /** @var int $bitset */ + $bitset = $this->bitset; + $count = 0; + + // PHP does not support right shift unsigned + if ($bitset < 0) { + $count = 1; + $bitset = $bitset & \PHP_INT_MAX; + } + + // iterate byte by byte and count set bits + $phpIntBitSize = \PHP_INT_SIZE * 8; + for ($bitPos = 0; $bitPos < $phpIntBitSize; $bitPos += 8) { + $bitChk = 0xff << $bitPos; + $byte = $bitset & $bitChk; + if ($byte) { + $byte = $byte >> $bitPos; + if ($byte & 0b00000001) ++$count; + if ($byte & 0b00000010) ++$count; + if ($byte & 0b00000100) ++$count; + if ($byte & 0b00001000) ++$count; + if ($byte & 0b00010000) ++$count; + if ($byte & 0b00100000) ++$count; + if ($byte & 0b01000000) ++$count; + if ($byte & 0b10000000) ++$count; + } + + if ($bitset <= $bitChk) { + break; + } + } + + return $count; + } + + /** + * Check if this EnumSet is the same as other + * @param EnumSet $other + * @return bool + */ + public function isEqual(EnumSet $other): bool + { + return $this->enumeration === $other->enumeration + && $this->bitset === $other->bitset; + } + + /** + * Check if this EnumSet is a subset of other + * @param EnumSet $other + * @return bool + */ + public function isSubset(EnumSet $other): bool + { + return $this->enumeration === $other->enumeration + && ($this->bitset & $other->bitset) === $this->bitset; + } + + /** + * Check if this EnumSet is a superset of other + * @param EnumSet $other + * @return bool + */ + public function isSuperset(EnumSet $other): bool + { + return $this->enumeration === $other->enumeration + && ($this->bitset | $other->bitset) === $this->bitset; + } + + /** + * Tests if the set is empty + * + * @return bool + */ + public function isEmpty(): bool + { + return $this->bitset === $this->emptyBitset; + } + + /** + * Get ordinal numbers of the defined enumerators as array + * @return array + * @uses doGetOrdinalsBin() + * @uses doGetOrdinalsInt() + */ + public function getOrdinals(): array + { + return $this->{$this->fnDoGetOrdinals}(); + } + + /** + * Get ordinal numbers of the defined enumerators as array. + * + * This is the binary bitset implementation. + * + * @return array + * @see getOrdinals() + * @see goGetOrdinalsInt() + */ + private function doGetOrdinalsBin() + { + /** @var string $bitset */ + $bitset = $this->bitset; + $ordinals = []; + $byteLen = \strlen($bitset); + for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) { + if ($bitset[$bytePos] === "\0") { + // fast skip null byte + continue; + } + + $ord = \ord($bitset[$bytePos]); + for ($bitPos = 0; $bitPos < 8; ++$bitPos) { + if ($ord & (1 << $bitPos)) { + $ordinals[] = $bytePos * 8 + $bitPos; + } + } + } + return $ordinals; + } + + /** + * Get ordinal numbers of the defined enumerators as array. + * + * This is the integer bitset implementation. + * + * @return array + * @see getOrdinals() + * @see doGetOrdinalsBin() + */ + private function doGetOrdinalsInt() + { + /** @var int $bitset */ + $bitset = $this->bitset; + $ordinals = []; + $count = $this->enumerationCount; + for ($ordinal = 0; $ordinal < $count; ++$ordinal) { + if ($bitset & (1 << $ordinal)) { + $ordinals[] = $ordinal; + } + } + return $ordinals; + } + + /** + * Get values of the defined enumerators as array + * @return (null|bool|int|float|string|array)[] + * + * @phpstan-return array> + * @psalm-return list + */ + public function getValues(): array + { + $enumeration = $this->enumeration; + $values = []; + foreach ($this->getOrdinals() as $ord) { + $values[] = $enumeration::byOrdinal($ord)->getValue(); + } + return $values; + } + + /** + * Get names of the defined enumerators as array + * @return string[] + * + * @phpstan-return array + * @psalm-return list + */ + public function getNames(): array + { + $enumeration = $this->enumeration; + $names = []; + foreach ($this->getOrdinals() as $ord) { + $names[] = $enumeration::byOrdinal($ord)->getName(); + } + return $names; + } + + /** + * Get the defined enumerators as array + * @return Enum[] + * + * @phpstan-return array + * @psalm-return list + */ + public function getEnumerators(): array + { + $enumeration = $this->enumeration; + $enumerators = []; + foreach ($this->getOrdinals() as $ord) { + $enumerators[] = $enumeration::byOrdinal($ord); + } + return $enumerators; + } + + /** + * Get binary bitset in little-endian order + * + * @return string + * @uses doGetBinaryBitsetLeBin() + * @uses doGetBinaryBitsetLeInt() + */ + public function getBinaryBitsetLe(): string + { + return $this->{$this->fnDoGetBinaryBitsetLe}(); + } + + /** + * Get binary bitset in little-endian order. + * + * This is the binary bitset implementation. + * + * @return string + * @see getBinaryBitsetLe() + * @see doGetBinaryBitsetLeInt() + */ + private function doGetBinaryBitsetLeBin() + { + /** @var string $bitset */ + $bitset = $this->bitset; + + return $bitset; + } + + /** + * Get binary bitset in little-endian order. + * + * This is the integer bitset implementation. + * + * @return string + * @see getBinaryBitsetLe() + * @see doGetBinaryBitsetLeBin() + */ + private function doGetBinaryBitsetLeInt() + { + $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset); + return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8)); + } + + /** + * Get binary bitset in big-endian order + * + * @return string + */ + public function getBinaryBitsetBe(): string + { + return \strrev($this->getBinaryBitsetLe()); + } + + /** + * Get a bit at the given ordinal number + * + * @param int $ordinal Ordinal number of bit to get + * @return bool + * @throws InvalidArgumentException If the given ordinal number is out-of-range + * @uses doGetBitBin() + * @uses doGetBitInt() + */ + public function getBit(int $ordinal): bool + { + if ($ordinal < 0 || $ordinal > $this->enumerationCount) { + throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}"); + } + + return $this->{$this->fnDoGetBit}($ordinal); + } + + /** + * Get a bit at the given ordinal number. + * + * This is the binary bitset implementation. + * + * @param int $ordinal Ordinal number of bit to get + * @return bool + * @see getBit() + * @see doGetBitInt() + */ + private function doGetBitBin($ordinal) + { + /** @var string $bitset */ + $bitset = $this->bitset; + + return (\ord($bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0; + } + + /** + * Get a bit at the given ordinal number. + * + * This is the integer bitset implementation. + * + * @param int $ordinal Ordinal number of bit to get + * @return bool + * @see getBit() + * @see doGetBitBin() + */ + private function doGetBitInt($ordinal) + { + /** @var int $bitset */ + $bitset = $this->bitset; + + return (bool)($bitset & (1 << $ordinal)); + } +} diff --git a/vendor/marc-mabe/php-enum/stubs/Stringable.php b/vendor/marc-mabe/php-enum/stubs/Stringable.php new file mode 100644 index 000000000..77e037cb5 --- /dev/null +++ b/vendor/marc-mabe/php-enum/stubs/Stringable.php @@ -0,0 +1,11 @@ +setBasePath(...)` (and `JsonFormatter` by extension) that allows removing the project's path from the stack trace output (47e301d3e) + * Fixed JsonFormatter handling of incomplete classes (#1834) + * Fixed private error handlers causing problems with custom StreamHandler implementations (#1866) + +### 3.6.0 (2024-04-12) + + * Added `LineFormatter->setBasePath(...)` that allows removing the project's path from the stack trace output (#1873) + * Added `$includeExtra` option in `PsrHandler` to also use extra data to replace placeholder values in the message (#1852) + * Added ability to customize what is a duplicated message by extending the `DeduplicationHandler` (#1879) + * Added handling for using `GelfMessageFormatter` together with the `AmqpHandler` (#1869) + * Added ability to extend `GoogleCloudLoggingFormatter` (#1859) + * Fixed `__toString` failures in context data crashing the normalization process (#1868) + * Fixed PHP 8.4 deprecation warnings (#1874) + +### 3.5.0 (2023-10-27) + + * Added ability to indent stack traces in LineFormatter via e.g. `indentStacktraces(' ')` (#1835) + * Added ability to configure a max level name length in LineFormatter via e.g. `setMaxLevelNameLength(3)` (#1850) + * Added support for indexed arrays (i.e. `[]` and not `{}` arrays once json serialized) containing inline linebreaks in LineFormatter (#1818) + * Added `WithMonologChannel` attribute for integrators to use to configure autowiring (#1847) + * Fixed log record `extra` data leaking between handlers that have handler-specific processors set (#1819) + * Fixed LogglyHandler issue with record level filtering (#1841) + * Fixed display_errors parsing in ErrorHandler which did not support string values (#1804) + * Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815) + * Fixed normalization error when normalizing incomplete classes (#1833) + +### 3.4.0 (2023-06-21) + + * Added `LoadAverageProcessor` to track one of the 1, 5 or 15min load averages (#1803) + * Added support for priority to the `AsMonologProcessor` attribute (#1797) + * Added `TelegramBotHandler` `topic`/`message_thread_id` support (#1802) + * Fixed `FingersCrossedHandler` passthruLevel checking (#1801) + * Fixed support of yearly and monthly rotation log file to rotate only once a month/year (#1805) + * Fixed `TestHandler` method docs (#1794) + * Fixed handling of falsey `display_errors` string values (#1804) + +### 3.3.1 (2023-02-06) + + * Fixed Logger not being serializable anymore (#1792) + +### 3.3.0 (2023-02-06) + + * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748) + * Added `ClosureContextProcessor` to allow delaying the creation of context data by setting a Closure in context which is called when the log record is used (#1745) + * Added an ElasticsearchHandler option to set the `op_type` to `create` instead of the default `index` (#1766) + * Added support for enum context values in PsrLogMessageProcessor (#1773) + * Added graylog2/gelf-php 2.x support (#1747) + * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739) + * Fixed GitProcessor not filtering correctly based on Level (#1749) + * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791) + * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758) + * Fixed infinite loop detection within Fibers (#1753) + * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781) + +### 3.2.0 (2022-07-24) + + * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734) + * Marked `Logger` `@final` as it should not be extended, prefer composition or talk to us if you are missing something + * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723) + * Added `SyslogFormatter` to output syslog-like files which can be consumed by tools like [lnav](https://lnav.org/) (#1689) + * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733) + * Added `GoogleCloudLoggingFormatter` (#1719) + * Added support for Predis 2.x (#1732) + * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724) + * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727) + * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720) + * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726) + * Fixed PHP 8.2 deprecation warnings (#1722) + * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678) + +### 3.1.0 (2022-06-09) + + * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682) + * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681) + * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670) + * Fixed interop issue by removing the need for a return type in ProcessorInterface (#1680) + * Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677) + * Fixed RotatingFileHandler issue when the date format contained slashes (#1671) + +### 3.0.0 (2022-05-10) + +Changes from RC1 + +- The `Monolog\LevelName` enum does not exist anymore, use `Monolog\Level->getName()` instead. + +### 3.0.0-RC1 (2022-05-08) + +This is mostly a cleanup release offering stronger type guarantees for integrators with the +array->object/enum changes, but there is no big new feature for end users. + +See [UPGRADE notes](UPGRADE.md#300) for details on all breaking changes especially if you are extending/implementing Monolog classes/interfaces. + +Noteworthy BC Breaks: + +- The minimum supported PHP version is now `8.1.0`. +- Log records have been converted from an array to a [`Monolog\LogRecord` object](src/Monolog/LogRecord.php) + with public (and mostly readonly) properties. e.g. instead of doing + `$record['context']` use `$record->context`. + In formatters or handlers if you rather need an array to work with you can use `$record->toArray()` + to get back a Monolog 1/2 style record array. This will contain the enum values instead of enum cases + in the `level` and `level_name` keys to be more backwards compatible and use simpler data types. +- `FormatterInterface`, `HandlerInterface`, `ProcessorInterface`, etc. changed to contain `LogRecord $record` + instead of `array $record` parameter types. If you want to support multiple Monolog versions this should + be possible by type-hinting nothing, or `array|LogRecord` if you support PHP 8.0+. You can then code + against the $record using Monolog 2 style as LogRecord implements ArrayAccess for BC. + The interfaces do not require a `LogRecord` return type even where it would be applicable, but if you only + support Monolog 3 in integration code I would recommend you use `LogRecord` return types wherever fitting + to ensure forward compatibility as it may be added in Monolog 4. +- Log levels are now enums [`Monolog\Level`](src/Monolog/Level.php) and [`Monolog\LevelName`](src/Monolog/LevelName.php) +- Removed deprecated SwiftMailerHandler, migrate to SymfonyMailerHandler instead. +- `ResettableInterface::reset()` now requires a void return type. +- All properties have had types added, which may require you to do so as well if you extended + a Monolog class and declared the same property. + +New deprecations: + +- `Logger::DEBUG`, `Logger::ERROR`, etc. are now deprecated in favor of the `Monolog\Level` enum. + e.g. instead of `Logger::WARNING` use `Level::Warning` if you need to pass the enum case + to Monolog or one of its handlers, or `Level::Warning->value` if you need the integer + value equal to what `Logger::WARNING` was giving you. +- `Logger::getLevelName()` is now deprecated. + +### 2.10.0 (2024-11-12) + + * Added `$fileOpenMode` to `StreamHandler` to define a custom fopen mode to open the log file (#1913) + * Fixed `StreamHandler` handling of write failures so that it now closes/reopens the stream and retries the write once before failing (#1882) + * Fixed `StreamHandler` error handler causing issues if a stream handler triggers an error (#1866) + * Fixed `JsonFormatter` handling of incomplete classes (#1834) + * Fixed `RotatingFileHandler` bug where rotation could sometimes not happen correctly (#1905) + +### 2.9.3 (2024-04-12) + + * Fixed PHP 8.4 deprecation warnings (#1874) + +### 2.9.2 (2023-10-27) + + * Fixed display_errors parsing in ErrorHandler which did not support string values (#1804) + * Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815) + * Fixed normalization error when normalizing incomplete classes (#1833) + +### 2.9.1 (2023-02-06) + + * Fixed Logger not being serializable anymore (#1792) + +### 2.9.0 (2023-02-05) + + * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748) + * Added support for enum context values in PsrLogMessageProcessor (#1773) + * Added graylog2/gelf-php 2.x support (#1747) + * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739) + * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791) + * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758) + * Fixed infinite loop detection within Fibers (#1753) + * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781) + +### 2.8.0 (2022-07-24) + + * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734) + * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723) + * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733) + * Added `GoogleCloudLoggingFormatter` (#1719) + * Added support for Predis 2.x (#1732) + * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724) + * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727) + * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720) + * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726) + * Fixed PHP 8.2 deprecation warnings (#1722) + * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678) + +### 2.7.0 (2022-06-09) + + * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682) + * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681) + * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670) + * Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677) + * Fixed RotatingFileHandler issue when the date format contained slashes (#1671) + +### 2.6.0 (2022-05-10) + + * Deprecated `SwiftMailerHandler`, use `SymfonyMailerHandler` instead + * Added `SymfonyMailerHandler` (#1663) + * Added ElasticSearch 8.x support to the ElasticsearchHandler (#1662) + * Added a way to filter/modify stack traces in LineFormatter (#1665) + * Fixed UdpSocket not being able to reopen/reconnect after close() + * Fixed infinite loops if a Handler is triggering logging while handling log records + +### 2.5.0 (2022-04-08) + + * Added `callType` to IntrospectionProcessor (#1612) + * Fixed AsMonologProcessor syntax to be compatible with PHP 7.2 (#1651) + +### 2.4.0 (2022-03-14) + + * Added [`Monolog\LogRecord`](src/Monolog/LogRecord.php) interface that can be used to type-hint records like `array|\Monolog\LogRecord $record` to be forward compatible with the upcoming Monolog 3 changes + * Added `includeStacktraces` constructor params to LineFormatter & JsonFormatter (#1603) + * Added `persistent`, `timeout`, `writingTimeout`, `connectionTimeout`, `chunkSize` constructor params to SocketHandler and derivatives (#1600) + * Added `AsMonologProcessor` PHP attribute which can help autowiring / autoconfiguration of processors if frameworks / integrations decide to make use of it. This is useless when used purely with Monolog (#1637) + * Added support for keeping native BSON types as is in MongoDBFormatter (#1620) + * Added support for a `user_agent` key in WebProcessor, disabled by default but you can use it by configuring the $extraFields you want (#1613) + * Added support for username/userIcon in SlackWebhookHandler (#1617) + * Added extension points to BrowserConsoleHandler (#1593) + * Added record message/context/extra info to exceptions thrown when a StreamHandler cannot open its stream to avoid completely losing the data logged (#1630) + * Fixed error handler signature to accept a null $context which happens with internal PHP errors (#1614) + * Fixed a few setter methods not returning `self` (#1609) + * Fixed handling of records going over the max Telegram message length (#1616) + +### 2.3.5 (2021-10-01) + + * Fixed regression in StreamHandler since 2.3.3 on systems with the memory_limit set to >=20GB (#1592) + +### 2.3.4 (2021-09-15) + + * Fixed support for psr/log 3.x (#1589) + +### 2.3.3 (2021-09-14) + + * Fixed memory usage when using StreamHandler and calling stream_get_contents on the resource you passed to it (#1578, #1577) + * Fixed support for psr/log 2.x (#1587) + * Fixed some type annotations + +### 2.3.2 (2021-07-23) + + * Fixed compatibility with PHP 7.2 - 7.4 when experiencing PCRE errors (#1568) + +### 2.3.1 (2021-07-14) + + * Fixed Utils::getClass handling of anonymous classes not being fully compatible with PHP 8 (#1563) + * Fixed some `@inheritDoc` annotations having the wrong case + +### 2.3.0 (2021-07-05) + + * Added a ton of PHPStan type annotations as well as type aliases on Monolog\Logger for Record, Level and LevelName that you can import (#1557) + * Added ability to customize date format when using JsonFormatter (#1561) + * Fixed FilterHandler not calling reset on its internal handler when reset() is called on it (#1531) + * Fixed SyslogUdpHandler not setting the timezone correctly on DateTimeImmutable instances (#1540) + * Fixed StreamHandler thread safety - chunk size set to 2GB now to avoid interlacing when doing concurrent writes (#1553) + +### 2.2.0 (2020-12-14) + + * Added JSON_PARTIAL_OUTPUT_ON_ERROR to default json encoding flags, to avoid dropping entire context data or even records due to an invalid subset of it somewhere + * Added setDateFormat to NormalizerFormatter (and Line/Json formatters by extension) to allow changing this after object creation + * Added RedisPubSubHandler to log records to a Redis channel using PUBLISH + * Added support for Elastica 7, and deprecated the $type argument of ElasticaFormatter which is not in use anymore as of Elastica 7 + * Added support for millisecond write timeouts in SocketHandler, you can now pass floats to setWritingTimeout, e.g. 0.2 is 200ms + * Added support for unix sockets in SyslogUdpHandler (set $port to 0 to make the $host a unix socket) + * Added handleBatch support for TelegramBotHandler + * Added RFC5424e extended date format including milliseconds to SyslogUdpHandler + * Added support for configuring handlers with numeric level values in strings (coming from e.g. env vars) + * Fixed Wildfire/FirePHP/ChromePHP handling of unicode characters + * Fixed PHP 8 issues in SyslogUdpHandler + * Fixed internal type error when mbstring is missing + +### 2.1.1 (2020-07-23) + + * Fixed removing of json encoding options + * Fixed type hint of $level not accepting strings in SendGridHandler and OverflowHandler + * Fixed SwiftMailerHandler not accepting email templates with an empty subject + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 2.1.0 (2020-05-22) + + * Added `JSON_INVALID_UTF8_SUBSTITUTE` to default json flags, so that invalid UTF8 characters now get converted to [�](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) instead of being converted from ISO-8859-15 to UTF8 as it was before, which was hardly a comprehensive solution + * Added `$ignoreEmptyContextAndExtra` option to JsonFormatter to skip empty context/extra entirely from the output + * Added `$parseMode`, `$disableWebPagePreview` and `$disableNotification` options to TelegramBotHandler + * Added tentative support for PHP 8 + * NormalizerFormatter::addJsonEncodeOption and removeJsonEncodeOption are now public to allow modifying default json flags + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 2.0.2 (2019-12-20) + + * Fixed ElasticsearchHandler swallowing exceptions details when failing to index log records + * Fixed normalization of SoapFault objects containing non-strings as "detail" in LineFormatter + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 2.0.1 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler, OverflowHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + * Fixed type error in BrowserConsoleHandler when the context array of log records was not associative. + +### 2.0.0 (2019-08-30) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: Logger methods log/debug/info/notice/warning/error/critical/alert/emergency now have explicit void return types + * Added FallbackGroupHandler which works like the WhatFailureGroupHandler but stops dispatching log records as soon as one handler accepted it + * Fixed support for UTF-8 when cutting strings to avoid cutting a multibyte-character in half + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed date timezone handling in SyslogUdpHandler + +### 2.0.0-beta2 (2019-07-06) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.2 is now the minimum required PHP version. + * BC Break: Removed SlackbotHandler, RavenHandler and HipChatHandler, see [UPGRADE.md](UPGRADE.md) for details + * Added OverflowHandler which will only flush log records to its nested handler when reaching a certain amount of logs (i.e. only pass through when things go really bad) + * Added TelegramBotHandler to log records to a [Telegram](https://core.telegram.org/bots/api) bot account + * Added support for JsonSerializable when normalizing exceptions + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Added SoapFault details to formatted exceptions + * Fixed DeduplicationHandler silently failing to start when file could not be opened + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed GelfFormatter losing some data when one attachment was too long + * Fixed issue in SignalHandler restarting syscalls functionality + * Improved performance of LogglyHandler when sending multiple logs in a single request + +### 2.0.0-beta1 (2018-12-08) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.1 is now the minimum required PHP version. + * BC Break: Quite a few interface changes, only relevant if you implemented your own handlers/processors/formatters + * BC Break: Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn` + * BC Break: The record timezone is now set per Logger instance and not statically anymore + * BC Break: There is no more default handler configured on empty Logger instances + * BC Break: ElasticSearchHandler renamed to ElasticaHandler + * BC Break: Various handler-specific breaks, see [UPGRADE.md](UPGRADE.md) for details + * Added scalar type hints and return hints in all the places it was possible. Switched strict_types on for more reliability. + * Added DateTimeImmutable support, all record datetime are now immutable, and will toString/json serialize with the correct date format, including microseconds (unless disabled) + * Added timezone and microseconds to the default date format + * Added SendGridHandler to use the SendGrid API to send emails + * Added LogmaticHandler to use the Logmatic.io API to store log records + * Added SqsHandler to send log records to an AWS SQS queue + * Added ElasticsearchHandler to send records via the official ES library. Elastica users should now use ElasticaHandler instead of ElasticSearchHandler + * Added NoopHandler which is similar to the NullHandle but does not prevent the bubbling of log records to handlers further down the configuration, useful for temporarily disabling a handler in configuration files + * Added ProcessHandler to write log output to the STDIN of a given process + * Added HostnameProcessor that adds the machine's hostname to log records + * Added a `$dateFormat` option to the PsrLogMessageProcessor which lets you format DateTime instances nicely + * Added support for the PHP 7.x `mongodb` extension in the MongoDBHandler + * Fixed many minor issues in various handlers, and probably added a few regressions too + +### 1.26.1 (2021-05-28) + + * Fixed PHP 8.1 deprecation warning + +### 1.26.0 (2020-12-14) + + * Added $dateFormat and $removeUsedContextFields arguments to PsrLogMessageProcessor (backport from 2.x) + +### 1.25.5 (2020-07-23) + + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 1.25.4 (2020-05-22) + + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 1.25.3 (2019-12-20) + + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 1.25.2 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + +### 1.25.1 (2019-09-06) + + * Fixed forward-compatible interfaces to be compatible with Monolog 1.x too. + +### 1.25.0 (2019-09-06) + + * Deprecated SlackbotHandler, use SlackWebhookHandler or SlackHandler instead + * Deprecated RavenHandler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead + * Deprecated HipChatHandler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead + * Added forward-compatible interfaces and traits FormattableHandlerInterface, FormattableHandlerTrait, ProcessableHandlerInterface, ProcessableHandlerTrait. If you use modern PHP and want to make code compatible with Monolog 1 and 2 this can help. You will have to require at least Monolog 1.25 though. + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed issue in SignalHandler restarting syscalls functionality + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed ZendMonitorHandler to work with the latest Zend Server versions + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + +### 1.24.0 (2018-11-05) + + * BC Notice: If you are extending any of the Monolog's Formatters' `normalize` method, make sure you add the new `$depth = 0` argument to your function signature to avoid strict PHP warnings. + * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors + * Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers) + * Added a way to log signals being received using Monolog\SignalHandler + * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler + * Added InsightOpsHandler to migrate users of the LogEntriesHandler + * Added protection to NormalizerFormatter against circular and very deep structures, it now stops normalizing at a depth of 9 + * Added capture of stack traces to ErrorHandler when logging PHP errors + * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts + * Added forwarding of context info to FluentdFormatter + * Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example + * Added ability to extend/override BrowserConsoleHandler + * Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility + * Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility + * Dropped official support for HHVM in test builds + * Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain + * Fixed naming of fields in Slack handler, all field names are now capitalized in all cases + * Fixed HipChatHandler bug where slack dropped messages randomly + * Fixed normalization of objects in Slack handlers + * Fixed support for PHP7's Throwable in NewRelicHandler + * Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory + * Fixed table row styling issues in HtmlFormatter + * Fixed RavenHandler dropping the message when logging exception + * Fixed WhatFailureGroupHandler skipping processors when using handleBatch + and implement it where possible + * Fixed display of anonymous class names + +### 1.23.0 (2017-06-19) + + * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument + * Fixed GelfHandler truncation to be per field and not per message + * Fixed compatibility issue with PHP <5.3.6 + * Fixed support for headless Chrome in ChromePHPHandler + * Fixed support for latest Aws SDK in DynamoDbHandler + * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler + +### 1.22.1 (2017-03-13) + + * Fixed lots of minor issues in the new Slack integrations + * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces + +### 1.22.0 (2016-11-26) + + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily + * Added MercurialProcessor to add mercurial revision and branch names to log records + * Added support for AWS SDK v3 in DynamoDbHandler + * Fixed fatal errors occurring when normalizing generators that have been fully consumed + * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) + * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore + * Fixed SyslogUdpHandler to avoid sending empty frames + * Fixed a few PHP 7.0 and 7.1 compatibility issues + +### 1.21.0 (2016-07-29) + + * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues + * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order + * Added ability to format the main line of text the SlackHandler sends by explicitly setting a formatter on the handler + * Added information about SoapFault instances in NormalizerFormatter + * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level + +### 1.20.0 (2016-07-02) + + * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy + * Added StreamHandler::getUrl to retrieve the stream's URL + * Added ability to override addRow/addTitle in HtmlFormatter + * Added the $context to context information when the ErrorHandler handles a regular php error + * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d + * Fixed WhatFailureGroupHandler to work with PHP7 throwables + * Fixed a few minor bugs + +### 1.19.0 (2016-04-12) + + * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed + * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors + * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler + * Fixed HipChatHandler handling of long messages + +### 1.18.2 (2016-04-02) + + * Fixed ElasticaFormatter to use more precise dates + * Fixed GelfMessageFormatter sending too long messages + +### 1.18.1 (2016-03-13) + + * Fixed SlackHandler bug where slack dropped messages randomly + * Fixed RedisHandler issue when using with the PHPRedis extension + * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension + * Fixed BrowserConsoleHandler regression + +### 1.18.0 (2016-03-01) + + * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond + * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames + * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name + * Added FluentdFormatter for the Fluentd unix socket protocol + * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed + * Added support for replacing context sub-keys using `%context.*%` in LineFormatter + * Added support for `payload` context value in RollbarHandler + * Added setRelease to RavenHandler to describe the application version, sent with every log + * Added support for `fingerprint` context value in RavenHandler + * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed + * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` + * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places + +### 1.17.2 (2015-10-14) + + * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers + * Fixed SlackHandler handling to use slack functionalities better + * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id + * Fixed 5.3 compatibility regression + +### 1.17.1 (2015-08-31) + + * Fixed RollbarHandler triggering PHP notices + +### 1.17.0 (2015-08-30) + + * Added support for `checksum` and `release` context/extra values in RavenHandler + * Added better support for exceptions in RollbarHandler + * Added UidProcessor::getUid + * Added support for showing the resource type in NormalizedFormatter + * Fixed IntrospectionProcessor triggering PHP notices + +### 1.16.0 (2015-08-09) + + * Added IFTTTHandler to notify ifttt.com triggers + * Added Logger::setHandlers() to allow setting/replacing all handlers + * Added $capSize in RedisHandler to cap the log size + * Fixed StreamHandler creation of directory to only trigger when the first log write happens + * Fixed bug in the handling of curl failures + * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler + * Fixed missing fatal errors records with handlers that need to be closed to flush log records + * Fixed TagProcessor::addTags support for associative arrays + +### 1.15.0 (2015-07-12) + + * Added addTags and setTags methods to change a TagProcessor + * Added automatic creation of directories if they are missing for a StreamHandler to open a log file + * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure + * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used + * Fixed HTML/JS escaping in BrowserConsoleHandler + * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) + +### 1.14.0 (2015-06-19) + + * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library + * Added support for objects implementing __toString in the NormalizerFormatter + * Added support for HipChat's v2 API in HipChatHandler + * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app + * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) + * Fixed curl errors being silently suppressed + +### 1.13.1 (2015-03-09) + + * Fixed regression in HipChat requiring a new token to be created + +### 1.13.0 (2015-03-05) + + * Added Registry::hasLogger to check for the presence of a logger instance + * Added context.user support to RavenHandler + * Added HipChat API v2 support in the HipChatHandler + * Added NativeMailerHandler::addParameter to pass params to the mail() process + * Added context data to SlackHandler when $includeContextAndExtra is true + * Added ability to customize the Swift_Message per-email in SwiftMailerHandler + * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided + * Fixed serialization of INF and NaN values in Normalizer and LineFormatter + +### 1.12.0 (2014-12-29) + + * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. + * Added PsrHandler to forward records to another PSR-3 logger + * Added SamplingHandler to wrap around a handler and include only every Nth record + * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) + * Added exception codes in the output of most formatters + * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) + * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data + * Added $host to HipChatHandler for users of private instances + * Added $transactionName to NewRelicHandler and support for a transaction_name context value + * Fixed MandrillHandler to avoid outputting API call responses + * Fixed some non-standard behaviors in SyslogUdpHandler + +### 1.11.0 (2014-09-30) + + * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names + * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails + * Added MandrillHandler to send emails via the Mandrillapp.com API + * Added SlackHandler to log records to a Slack.com account + * Added FleepHookHandler to log records to a Fleep.io account + * Added LogglyHandler::addTag to allow adding tags to an existing handler + * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end + * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing + * Added support for PhpAmqpLib in the AmqpHandler + * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs + * Added support for adding extra fields from $_SERVER in the WebProcessor + * Fixed support for non-string values in PrsLogMessageProcessor + * Fixed SwiftMailer messages being sent with the wrong date in long running scripts + * Fixed minor PHP 5.6 compatibility issues + * Fixed BufferHandler::close being called twice + +### 1.10.0 (2014-06-04) + + * Added Logger::getHandlers() and Logger::getProcessors() methods + * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached + * Added support for extra data in NewRelicHandler + * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines + +### 1.9.1 (2014-04-24) + + * Fixed regression in RotatingFileHandler file permissions + * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records + * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative + +### 1.9.0 (2014-04-20) + + * Added LogEntriesHandler to send logs to a LogEntries account + * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler + * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes + * Added support for table formatting in FirePHPHandler via the table context key + * Added a TagProcessor to add tags to records, and support for tags in RavenHandler + * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files + * Added sound support to the PushoverHandler + * Fixed multi-threading support in StreamHandler + * Fixed empty headers issue when ChromePHPHandler received no records + * Fixed default format of the ErrorLogHandler + +### 1.8.0 (2014-03-23) + + * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them + * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output + * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler + * Added FlowdockHandler to send logs to a Flowdock account + * Added RollbarHandler to send logs to a Rollbar account + * Added HtmlFormatter to send prettier log emails with colors for each log level + * Added GitProcessor to add the current branch/commit to extra record data + * Added a Monolog\Registry class to allow easier global access to pre-configured loggers + * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement + * Added support for HHVM + * Added support for Loggly batch uploads + * Added support for tweaking the content type and encoding in NativeMailerHandler + * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor + * Fixed batch request support in GelfHandler + +### 1.7.0 (2013-11-14) + + * Added ElasticSearchHandler to send logs to an Elastic Search server + * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB + * Added SyslogUdpHandler to send logs to a remote syslogd server + * Added LogglyHandler to send logs to a Loggly account + * Added $level to IntrospectionProcessor so it only adds backtraces when needed + * Added $version to LogstashFormatter to allow using the new v1 Logstash format + * Added $appName to NewRelicHandler + * Added configuration of Pushover notification retries/expiry + * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default + * Added chainability to most setters for all handlers + * Fixed RavenHandler batch processing so it takes the message from the record with highest priority + * Fixed HipChatHandler batch processing so it sends all messages at once + * Fixed issues with eAccelerator + * Fixed and improved many small things + +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE new file mode 100644 index 000000000..aa2a0426c --- /dev/null +++ b/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2020 Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/monolog/monolog/README.md b/vendor/monolog/monolog/README.md new file mode 100644 index 000000000..0564f96f0 --- /dev/null +++ b/vendor/monolog/monolog/README.md @@ -0,0 +1,136 @@ +

Monolog

+ +# Monolog - Logging for PHP [![Continuous Integration](https://github.com/Seldaek/monolog/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/Seldaek/monolog/actions) + +[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) + +>**Note** This is the **documentation for Monolog 3.x**, if you are using older releases +>see the documentation for [Monolog 2.x](https://github.com/Seldaek/monolog/blob/2.x/README.md) or [Monolog 1.x](https://github.com/Seldaek/monolog/blob/1.x/README.md) + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. +As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. +Internally Monolog still uses its own level scheme since it predates PSR-3. + + + +## Installation + +Install the latest version with + +```bash +composer require monolog/monolog +``` + +## Basic Usage + +```php +pushHandler(new StreamHandler('path/to/your.log', Level::Warning)); + +// add records to the log +$log->warning('Foo'); +$log->error('Bar'); +``` + +## Documentation + +- [Usage Instructions](doc/01-usage.md) +- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) +- [Utility Classes](doc/03-utilities.md) +- [Extending Monolog](doc/04-extending.md) +- [Log Record Structure](doc/message-structure.md) + +## Support Monolog Financially + +Get supported Monolog and help fund the project with the [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-monolog-monolog?utm_source=packagist-monolog-monolog&utm_medium=referral&utm_campaign=enterprise) or via [GitHub sponsorship](https://github.com/sponsors/Seldaek). + +Tidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +## About + +### Requirements + +- Monolog `^3.0` works with PHP 8.1 or above. +- Monolog `^2.5` works with PHP 7.2 or above. +- Monolog `^1.25` works with PHP 5.3 up to 8.1, but is not very maintained anymore and will not receive PHP support fixes anymore. + +### Support + +Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 or 3 where possible to benefit from all the latest features and fixes. + +### Submitting bugs and feature requests + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +### Framework Integrations + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony](http://symfony.com) comes out of the box with Monolog. +- [Laravel](http://laravel.com/) comes out of the box with Monolog. +- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. +- [PPI](https://github.com/ppi/framework) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. +- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. +- [Nette Framework](http://nette.org/en/) is usable with Monolog via the [contributte/monolog](https://github.com/contributte/monolog) or [orisai/nette-monolog](https://github.com/orisai/nette-monolog) extensions. +- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. +- [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog. +- [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog. +- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins. +- [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog. +- [SilverStripe 4](https://www.silverstripe.org/) comes out of the box with Monolog. +- [Drupal](https://www.drupal.org/) is usable with Monolog via the [monolog](https://www.drupal.org/project/monolog) module. +- [Aimeos ecommerce framework](https://aimeos.org/) is usable with Monolog via the [ai-monolog](https://github.com/aimeos/ai-monolog) extension. +- [Magento](https://magento.com/) comes out of the box with Monolog. +- [Spiral Framework](https://spiral.dev) comes out of the box with Monolog bridge. +- [WebFramework](https://web-framework.com/) comes out of the box with Monolog. + +### Author + +Jordi Boggiano - -
+See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) who participated in this project. + +### License + +Monolog is licensed under the MIT License - see the [LICENSE](LICENSE) file for details + +### Acknowledgements + +This library is heavily inspired by Python's [Logbook](https://logbook.readthedocs.io/en/stable/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json new file mode 100644 index 000000000..b0fb2d7a5 --- /dev/null +++ b/vendor/monolog/monolog/composer.json @@ -0,0 +1,82 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "https://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "require-dev": { + "ext-json": "*", + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-openssl": "Required to send log messages using SSL" + }, + "autoload": { + "psr-4": {"Monolog\\": "src/Monolog"} + }, + "autoload-dev": { + "psr-4": {"Monolog\\": "tests/Monolog"} + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "scripts": { + "test": "@php vendor/bin/phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" + }, + "config": { + "lock": false, + "sort-packages": true, + "platform-check": false, + "allow-plugins": { + "php-http/discovery": false + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php b/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php new file mode 100644 index 000000000..c519e0537 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Attribute; + +/** + * A reusable attribute to help configure a class or a method as a processor. + * + * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer. + * + * Using it with the Monolog library only has no effect at all: processors should still be turned into a callable if + * needed and manually pushed to the loggers and to the processable handlers. + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class AsMonologProcessor +{ + /** + * @param string|null $channel The logging channel the processor should be pushed to. + * @param string|null $handler The handler the processor should be pushed to. + * @param string|null $method The method that processes the records (if the attribute is used at the class level). + * @param int|null $priority The priority of the processor so the order can be determined. + */ + public function __construct( + public readonly ?string $channel = null, + public readonly ?string $handler = null, + public readonly ?string $method = null, + public readonly ?int $priority = null + ) { + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Attribute/WithMonologChannel.php b/vendor/monolog/monolog/src/Monolog/Attribute/WithMonologChannel.php new file mode 100644 index 000000000..862e05b33 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Attribute/WithMonologChannel.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Attribute; + +/** + * A reusable attribute to help configure a class as expecting a given logger channel. + * + * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer. + * + * Using it with the Monolog library only has no effect at all: wiring the logger instance into + * other classes is not managed by Monolog. + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +final class WithMonologChannel +{ + public function __construct( + public readonly string $channel + ) { + } +} diff --git a/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php new file mode 100644 index 000000000..3cb7086d7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class_alias(JsonSerializableDateTimeImmutable::class, 'Monolog\DateTimeImmutable'); + +// @phpstan-ignore-next-line +if (false) { + /** + * @deprecated Use \Monolog\JsonSerializableDateTimeImmutable instead. + */ + class DateTimeImmutable extends JsonSerializableDateTimeImmutable + { + } +} diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 000000000..805f2dfd9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Closure; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private Closure|null $previousExceptionHandler = null; + + /** @var array an array of class name to LogLevel::* constant mapping */ + private array $uncaughtExceptionLevelMap = []; + + /** @var Closure|true|null */ + private Closure|bool|null $previousErrorHandler = null; + + /** @var array an array of E_* constant to LogLevel::* constant mapping */ + private array $errorLevelMap = []; + + private bool $handleOnlyReportedErrors = true; + + private bool $hasFatalErrorHandler = false; + + private string $fatalLevel = LogLevel::ALERT; + + private string|null $reservedMemory = null; + + /** @var ?array{type: int, message: string, file: string, line: int, trace: mixed} */ + private array|null $lastFatalData = null; + + private const FATAL_ERRORS = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR]; + + public function __construct( + private LoggerInterface $logger + ) { + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling + * @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling + * @return static + */ + public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self + { + /** @phpstan-ignore-next-line */ + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevelMap !== false) { + $handler->registerExceptionHandler($exceptionLevelMap); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + /** + * @param array $levelMap an array of class name to LogLevel::* constant mapping + * @return $this + */ + public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = true): self + { + $prev = set_exception_handler(function (\Throwable $e): void { + $this->handleException($e); + }); + $this->uncaughtExceptionLevelMap = $levelMap; + foreach ($this->defaultExceptionLevelMap() as $class => $level) { + if (!isset($this->uncaughtExceptionLevelMap[$class])) { + $this->uncaughtExceptionLevelMap[$class] = $level; + } + } + if ($callPrevious && null !== $prev) { + $this->previousExceptionHandler = $prev(...); + } + + return $this; + } + + /** + * @param array $levelMap an array of E_* constant to LogLevel::* constant mapping + * @return $this + */ + public function registerErrorHandler(array $levelMap = [], bool $callPrevious = true, int $errorTypes = -1, bool $handleOnlyReportedErrors = true): self + { + $prev = set_error_handler($this->handleError(...), $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev !== null ? $prev(...) : true; + } else { + $this->previousErrorHandler = null; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + + return $this; + } + + /** + * @param LogLevel::*|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT + * @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done + * @return $this + */ + public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self + { + register_shutdown_function($this->handleFatalError(...)); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = null === $level ? LogLevel::ALERT : $level; + $this->hasFatalErrorHandler = true; + + return $this; + } + + /** + * @return array + */ + protected function defaultExceptionLevelMap(): array + { + return [ + 'ParseError' => LogLevel::CRITICAL, + 'Throwable' => LogLevel::ERROR, + ]; + } + + /** + * @return array + */ + protected function defaultErrorLevelMap(): array + { + return [ + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + 2048 => LogLevel::NOTICE, // E_STRICT + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ]; + } + + private function handleException(\Throwable $e): never + { + $level = LogLevel::ERROR; + foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) { + if ($e instanceof $class) { + $level = $candidate; + break; + } + } + + $this->logger->log( + $level, + sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), + ['exception' => $e] + ); + + if (null !== $this->previousExceptionHandler) { + ($this->previousExceptionHandler)($e); + } + + if (!headers_sent() && \in_array(strtolower((string) \ini_get('display_errors')), ['0', '', 'false', 'off', 'none', 'no'], true)) { + http_response_code(500); + } + + exit(255); + } + + private function handleError(int $code, string $message, string $file = '', int $line = 0): bool + { + if ($this->handleOnlyReportedErrors && 0 === (error_reporting() & $code)) { + return false; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !\in_array($code, self::FATAL_ERRORS, true)) { + $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]); + } else { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + array_shift($trace); // Exclude handleError from trace + $this->lastFatalData = ['type' => $code, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace]; + } + + if ($this->previousErrorHandler === true) { + return false; + } + if ($this->previousErrorHandler instanceof Closure) { + return (bool) ($this->previousErrorHandler)($code, $message, $file, $line); + } + + return true; + } + + /** + * @private + */ + public function handleFatalError(): void + { + $this->reservedMemory = ''; + + if (\is_array($this->lastFatalData)) { + $lastError = $this->lastFatalData; + } else { + $lastError = error_get_last(); + } + if (\is_array($lastError) && \in_array($lastError['type'], self::FATAL_ERRORS, true)) { + $trace = $lastError['trace'] ?? null; + $this->logger->log( + $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $trace] + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + $handler->close(); + } + } + } + } + + private static function codeToString(int $code): string + { + return match ($code) { + E_ERROR => 'E_ERROR', + E_WARNING => 'E_WARNING', + E_PARSE => 'E_PARSE', + E_NOTICE => 'E_NOTICE', + E_CORE_ERROR => 'E_CORE_ERROR', + E_CORE_WARNING => 'E_CORE_WARNING', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_USER_WARNING => 'E_USER_WARNING', + E_USER_NOTICE => 'E_USER_NOTICE', + 2048 => 'E_STRICT', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + E_DEPRECATED => 'E_DEPRECATED', + E_USER_DEPRECATED => 'E_USER_DEPRECATED', + default => 'Unknown PHP error', + }; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 000000000..beb5106af --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + * + * @return 'log'|'info'|'warn'|'error' + */ + private function toWildfireLevel(Level $level): string + { + return match ($level) { + Level::Debug => 'log', + Level::Info => 'info', + Level::Notice => 'info', + Level::Warning => 'warn', + Level::Error => 'error', + Level::Critical => 'error', + Level::Alert => 'error', + Level::Emergency => 'error', + }; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record->extra['file'], $record->extra['line'])) { + $backtrace = $record->extra['file'].' : '.$record->extra['line']; + unset($record->extra['file'], $record->extra['line']); + } + + $message = ['message' => $record->message]; + if (\count($record->context) > 0) { + $message['context'] = $record->context; + } + if (\count($record->extra) > 0) { + $message['extra'] = $record->extra; + } + if (\count($message) === 1) { + $message = reset($message); + } + + return [ + $record->channel, + $message, + $backtrace, + $this->toWildfireLevel($record->level), + ]; + } + + /** + * @inheritDoc + */ + public function formatBatch(array $records) + { + $formatted = []; + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php new file mode 100644 index 000000000..a0fa4a9e1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Elastica\Document; +use Monolog\LogRecord; + +/** + * Format a log message into an Elastica Document + * + * @author Jelle Vink + */ +class ElasticaFormatter extends NormalizerFormatter +{ + /** + * @var string Elastic search index name + */ + protected string $index; + + /** + * @var string|null Elastic search document type + */ + protected string|null $type; + + /** + * @param string $index Elastic Search index name + * @param ?string $type Elastic Search document type, deprecated as of Elastica 7 + */ + public function __construct(string $index, ?string $type) + { + // elasticsearch requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->index = $index; + $this->type = $type; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + public function getIndex(): string + { + return $this->index; + } + + /** + * @deprecated since Elastica 7 type has no effect + */ + public function getType(): string + { + /** @phpstan-ignore-next-line */ + return $this->type; + } + + /** + * Convert a log message into an Elastica Document + * + * @param mixed[] $record + */ + protected function getDocument(array $record): Document + { + $document = new Document(); + $document->setData($record); + $document->setIndex($this->index); + + return $document; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php new file mode 100644 index 000000000..6326cf5d7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use DateTimeInterface; +use Monolog\LogRecord; + +/** + * Format a log message into an Elasticsearch record + * + * @author Avtandil Kikabidze + */ +class ElasticsearchFormatter extends NormalizerFormatter +{ + /** + * @var string Elasticsearch index name + */ + protected string $index; + + /** + * @var string Elasticsearch record type + */ + protected string $type; + + /** + * @param string $index Elasticsearch index name + * @param string $type Elasticsearch record type + */ + public function __construct(string $index, string $type) + { + // Elasticsearch requires an ISO 8601 format date with optional millisecond precision. + parent::__construct(DateTimeInterface::ATOM); + + $this->index = $index; + $this->type = $type; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + */ + public function getIndex(): string + { + return $this->index; + } + + /** + * Getter type + */ + public function getType(): string + { + return $this->type; + } + + /** + * Convert a log message into an Elasticsearch record + * + * @param mixed[] $record Log message + * @return mixed[] + */ + protected function getDocument(array $record): array + { + $record['_index'] = $this->index; + $record['_type'] = $this->type; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php new file mode 100644 index 000000000..cc805c892 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * formats the record to be used in the FlowdockHandler + * + * @author Dominik Liebler + * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 + */ +class FlowdockFormatter implements FormatterInterface +{ + private string $source; + + private string $sourceEmail; + + public function __construct(string $source, string $sourceEmail) + { + $this->source = $source; + $this->sourceEmail = $sourceEmail; + } + + /** + * @inheritDoc + * + * @return mixed[] + */ + public function format(LogRecord $record): array + { + $tags = [ + '#logs', + '#' . $record->level->toPsrLogLevel(), + '#' . $record->channel, + ]; + + foreach ($record->extra as $value) { + $tags[] = '#' . $value; + } + + $subject = sprintf( + 'in %s: %s - %s', + $this->source, + $record->level->getName(), + $this->getShortMessage($record->message) + ); + + return [ + 'source' => $this->source, + 'from_address' => $this->sourceEmail, + 'subject' => $subject, + 'content' => $record->message, + 'tags' => $tags, + 'project' => $this->source, + ]; + } + + /** + * @inheritDoc + * + * @return mixed[][] + */ + public function formatBatch(array $records): array + { + $formatted = []; + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + public function getShortMessage(string $message): string + { + static $hasMbString; + + if (null === $hasMbString) { + $hasMbString = \function_exists('mb_strlen'); + } + + $maxLength = 45; + + if ($hasMbString) { + if (mb_strlen($message, 'UTF-8') > $maxLength) { + $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; + } + } else { + if (\strlen($message) > $maxLength) { + $message = substr($message, 0, $maxLength - 4) . ' ...'; + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php new file mode 100644 index 000000000..5f7811735 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Class FluentdFormatter + * + * Serializes a log message to Fluentd unix socket protocol + * + * Fluentd config: + * + * + * type unix + * path /var/run/td-agent/td-agent.sock + * + * + * Monolog setup: + * + * $logger = new Monolog\Logger('fluent.tag'); + * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); + * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); + * $logger->pushHandler($fluentHandler); + * + * @author Andrius Putna + */ +class FluentdFormatter implements FormatterInterface +{ + /** + * @var bool $levelTag should message level be a part of the fluentd tag + */ + protected bool $levelTag = false; + + public function __construct(bool $levelTag = false) + { + $this->levelTag = $levelTag; + } + + public function isUsingLevelsInTag(): bool + { + return $this->levelTag; + } + + public function format(LogRecord $record): string + { + $tag = $record->channel; + if ($this->levelTag) { + $tag .= '.' . $record->level->toPsrLogLevel(); + } + + $message = [ + 'message' => $record->message, + 'context' => $record->context, + 'extra' => $record->extra, + ]; + + if (!$this->levelTag) { + $message['level'] = $record->level->value; + $message['level_name'] = $record->level->getName(); + } + + return Utils::jsonEncode([$tag, $record->datetime->getTimestamp(), $message]); + } + + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 000000000..3413a4b05 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param LogRecord $record A record to format + * @return mixed The formatted record + */ + public function format(LogRecord $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 000000000..bf8ed2411 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Gelf\Message; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Serializes a log message to GELF + * @see http://docs.graylog.org/en/latest/pages/gelf.html + * + * @author Matt Lehner + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + protected const DEFAULT_MAX_LENGTH = 32766; + + /** + * @var string the name of the system for the Gelf log message + */ + protected string $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected string $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected string $contextPrefix; + + /** + * @var int max length per field + */ + protected int $maxLength; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private function getGraylog2Priority(Level $level): int + { + return match ($level) { + Level::Debug => 7, + Level::Info => 6, + Level::Notice => 5, + Level::Warning => 4, + Level::Error => 3, + Level::Critical => 2, + Level::Alert => 1, + Level::Emergency => 0, + }; + } + + /** + * @throws \RuntimeException + */ + public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null) + { + if (!class_exists(Message::class)) { + throw new \RuntimeException('Composer package graylog2/gelf-php is required to use Monolog\'s GelfMessageFormatter'); + } + + parent::__construct('U.u'); + + $this->systemName = (null === $systemName || $systemName === '') ? (string) gethostname() : $systemName; + + $this->extraPrefix = null === $extraPrefix ? '' : $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->maxLength = null === $maxLength ? self::DEFAULT_MAX_LENGTH : $maxLength; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): Message + { + $context = $extra = []; + if (isset($record->context)) { + /** @var array|bool|float|int|string|null> $context */ + $context = parent::normalize($record->context); + } + if (isset($record->extra)) { + /** @var array|bool|float|int|string|null> $extra */ + $extra = parent::normalize($record->extra); + } + + $message = new Message(); + $message + ->setTimestamp($record->datetime) + ->setShortMessage($record->message) + ->setHost($this->systemName) + ->setLevel($this->getGraylog2Priority($record->level)); + + // message length + system name length + 200 for padding / metadata + $len = 200 + \strlen($record->message) + \strlen($this->systemName); + + if ($len > $this->maxLength) { + $message->setShortMessage(Utils::substr($record->message, 0, $this->maxLength)); + } + + if (isset($record->channel)) { + $message->setAdditional('facility', $record->channel); + } + + foreach ($extra as $key => $val) { + $key = (string) preg_replace('#[^\w.-]#', '-', (string) $key); + $val = \is_bool($val) ? ($val ? 1 : 0) : $val; + $val = \is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = \strlen($this->extraPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->extraPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); + + continue; + } + $message->setAdditional($this->extraPrefix . $key, $val); + } + + foreach ($context as $key => $val) { + $key = (string) preg_replace('#[^\w.-]#', '-', (string) $key); + $val = \is_bool($val) ? ($val ? 1 : 0) : $val; + $val = \is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = \strlen($this->contextPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); + + continue; + } + $message->setAdditional($this->contextPrefix . $key, $val); + } + + if (!$message->hasAdditional('file') && isset($context['exception']['file'])) { + if (1 === preg_match("/^(.+):([0-9]+)$/", $context['exception']['file'], $matches)) { + $message->setAdditional('file', $matches[1]); + $message->setAdditional('line', $matches[2]); + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php new file mode 100644 index 000000000..c97b9122c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use DateTimeInterface; +use Monolog\LogRecord; + +/** + * Encodes message information into JSON in a format compatible with Cloud logging. + * + * @see https://cloud.google.com/logging/docs/structured-logging + * @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + * + * @author Luís Cobucci + */ +class GoogleCloudLoggingFormatter extends JsonFormatter +{ + protected function normalizeRecord(LogRecord $record): array + { + $normalized = parent::normalizeRecord($record); + + // Re-key level for GCP logging + $normalized['severity'] = $normalized['level_name']; + $normalized['time'] = $record->datetime->format(DateTimeInterface::RFC3339_EXTENDED); + + // Remove keys that are not used by GCP + unset($normalized['level'], $normalized['level_name'], $normalized['datetime']); + + return $normalized; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 000000000..09cbea9b6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to html color priorities. + */ + protected function getLevelColor(Level $level): string + { + return match ($level) { + Level::Debug => '#CCCCCC', + Level::Info => '#28A745', + Level::Notice => '#17A2B8', + Level::Warning => '#FFC107', + Level::Error => '#FD7E14', + Level::Critical => '#DC3545', + Level::Alert => '#821722', + Level::Emergency => '#000000', + }; + } + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + */ + protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
'; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + */ + protected function addTitle(string $title, Level $level): string + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

'.$title.'

'; + } + + /** + * Formats a log record. + * + * @return string The formatted record + */ + public function format(LogRecord $record): string + { + $output = $this->addTitle($record->level->getName(), $record->level); + $output .= ''; + + $output .= $this->addRow('Message', $record->message); + $output .= $this->addRow('Time', $this->formatDate($record->datetime)); + $output .= $this->addRow('Channel', $record->channel); + if (\count($record->context) > 0) { + $embeddedTable = '
'; + foreach ($record->context as $key => $value) { + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if (\count($record->extra) > 0) { + $embeddedTable = ''; + foreach ($record->extra as $key => $value) { + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @return string The formatted set of records + */ + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + /** + * @param mixed $data + */ + protected function convertToString($data): string + { + if (null === $data || \is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + + return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 000000000..fa11d0ef0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Stringable; +use Throwable; +use Monolog\LogRecord; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter extends NormalizerFormatter +{ + public const BATCH_MODE_JSON = 1; + public const BATCH_MODE_NEWLINES = 2; + + /** @var self::BATCH_MODE_* */ + protected int $batchMode; + + protected bool $appendNewline; + + protected bool $ignoreEmptyContextAndExtra; + + protected bool $includeStacktraces = false; + + /** + * @param self::BATCH_MODE_* $batchMode + */ + public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false) + { + $this->batchMode = $batchMode; + $this->appendNewline = $appendNewline; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + $this->includeStacktraces = $includeStacktraces; + + parent::__construct(); + } + + /** + * The batch mode option configures the formatting style for + * multiple records. By default, multiple records will be + * formatted as a JSON-encoded array. However, for + * compatibility with some API endpoints, alternative styles + * are available. + */ + public function getBatchMode(): int + { + return $this->batchMode; + } + + /** + * True if newlines are appended to every formatted record + */ + public function isAppendingNewlines(): bool + { + return $this->appendNewline; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): string + { + $normalized = $this->normalizeRecord($record); + + return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : ''); + } + + /** + * @inheritDoc + */ + public function formatBatch(array $records): string + { + return match ($this->batchMode) { + static::BATCH_MODE_NEWLINES => $this->formatBatchNewlines($records), + default => $this->formatBatchJson($records), + }; + } + + /** + * @return $this + */ + public function includeStacktraces(bool $include = true): self + { + $this->includeStacktraces = $include; + + return $this; + } + + /** + * @return array|bool|float|int|\stdClass|string|null> + */ + protected function normalizeRecord(LogRecord $record): array + { + $normalized = parent::normalizeRecord($record); + + if (isset($normalized['context']) && $normalized['context'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['context']); + } else { + $normalized['context'] = new \stdClass; + } + } + if (isset($normalized['extra']) && $normalized['extra'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['extra']); + } else { + $normalized['extra'] = new \stdClass; + } + } + + return $normalized; + } + + /** + * Return a JSON-encoded array of records. + * + * @phpstan-param LogRecord[] $records + */ + protected function formatBatchJson(array $records): string + { + $formatted = array_map(fn (LogRecord $record) => $this->normalizeRecord($record), $records); + + return $this->toJson($formatted, true); + } + + /** + * Use new lines to separate records instead of a + * JSON-encoded array. + * + * @phpstan-param LogRecord[] $records + */ + protected function formatBatchNewlines(array $records): string + { + $oldNewline = $this->appendNewline; + $this->appendNewline = false; + $formatted = array_map(fn (LogRecord $record) => $this->format($record), $records); + $this->appendNewline = $oldNewline; + + return implode("\n", $formatted); + } + + /** + * Normalizes given $data. + * + * @return null|scalar|array|object + */ + protected function normalize(mixed $data, int $depth = 0): mixed + { + if ($depth > $this->maxNormalizeDepth) { + return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization'; + } + + if (\is_array($data)) { + $normalized = []; + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.\count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth + 1); + } + + return $normalized; + } + + if (\is_object($data)) { + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + // if the object has specific json serializability we want to make sure we skip the __toString treatment below + if ($data instanceof \JsonSerializable) { + return $data; + } + + if ($data instanceof Stringable) { + try { + return $data->__toString(); + } catch (Throwable) { + return $data::class; + } + } + + if (\get_class($data) === '__PHP_Incomplete_Class') { + return new \ArrayObject($data); + } + + return $data; + } + + if (\is_resource($data)) { + return parent::normalize($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * @return array>> + */ + protected function normalizeException(Throwable $e, int $depth = 0): array + { + $data = parent::normalizeException($e, $depth); + if (!$this->includeStacktraces) { + unset($data['trace']); + } + + return $data; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 000000000..d44e7aef0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,317 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Closure; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected string $format; + protected bool $allowInlineLineBreaks; + protected bool $ignoreEmptyContextAndExtra; + protected bool $includeStacktraces; + protected ?int $maxLevelNameLength = null; + protected string $indentStacktraces = ''; + protected Closure|null $stacktracesParser = null; + protected string $basePath = ''; + + /** + * @param string|null $format The format of the message + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + */ + public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false) + { + $this->format = $format === null ? static::SIMPLE_FORMAT : $format; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + $this->includeStacktraces($includeStacktraces); + parent::__construct($dateFormat); + } + + /** + * Setting a base path will hide the base path from exception and stack trace file names to shorten them + * @return $this + */ + public function setBasePath(string $path = ''): self + { + if ($path !== '') { + $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + } + + $this->basePath = $path; + + return $this; + } + + /** + * @return $this + */ + public function includeStacktraces(bool $include = true, ?Closure $parser = null): self + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + $this->stacktracesParser = $parser; + } + + return $this; + } + + /** + * Indent stack traces to separate them a bit from the main log record messages + * + * @param string $indent The string used to indent, for example " " + * @return $this + */ + public function indentStacktraces(string $indent): self + { + $this->indentStacktraces = $indent; + + return $this; + } + + /** + * @return $this + */ + public function allowInlineLineBreaks(bool $allow = true): self + { + $this->allowInlineLineBreaks = $allow; + + return $this; + } + + /** + * @return $this + */ + public function ignoreEmptyContextAndExtra(bool $ignore = true): self + { + $this->ignoreEmptyContextAndExtra = $ignore; + + return $this; + } + + /** + * Allows cutting the level name to get fixed-length levels like INF for INFO, ERR for ERROR if you set this to 3 for example + * + * @param int|null $maxLevelNameLength Maximum characters for the level name. Set null for infinite length (default) + * @return $this + */ + public function setMaxLevelNameLength(?int $maxLevelNameLength = null): self + { + $this->maxLevelNameLength = $maxLevelNameLength; + + return $this; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): string + { + $vars = parent::format($record); + + if ($this->maxLevelNameLength !== null) { + $vars['level_name'] = substr($vars['level_name'], 0, $this->maxLevelNameLength); + } + + $output = $this->format; + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (\count($vars['context']) === 0) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (\count($vars['extra']) === 0) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + if (null === $output) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . preg_last_error_msg()); + } + } + + return $output; + } + + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + /** + * @param mixed $value + */ + public function stringify($value): string + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException(\Throwable $e, int $depth = 0): string + { + $str = $this->formatException($e); + + $previous = $e->getPrevious(); + while ($previous instanceof \Throwable) { + $depth++; + if ($depth > $this->maxNormalizeDepth) { + $str .= "\n[previous exception] Over " . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; + break; + } + $str .= "\n[previous exception] " . $this->formatException($previous); + $previous = $previous->getPrevious(); + } + + return $str; + } + + /** + * @param mixed $data + */ + protected function convertToString($data): string + { + if (null === $data || \is_bool($data)) { + return var_export($data, true); + } + + if (\is_scalar($data)) { + return (string) $data; + } + + return $this->toJson($data, true); + } + + protected function replaceNewlines(string $str): string + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{') || 0 === strpos($str, '[')) { + $str = preg_replace('/(?getCode(); + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $str .= ' faultcode: ' . $e->faultcode; + } + + if (isset($e->faultactor)) { + $str .= ' faultactor: ' . $e->faultactor; + } + + if (isset($e->detail)) { + if (\is_string($e->detail)) { + $str .= ' detail: ' . $e->detail; + } elseif (\is_object($e->detail) || \is_array($e->detail)) { + $str .= ' detail: ' . $this->toJson($e->detail, true); + } + } + } + + $file = $e->getFile(); + if ($this->basePath !== '') { + $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file); + } + + $str .= '): ' . $e->getMessage() . ' at ' . strtr((string) $file, DIRECTORY_SEPARATOR, '/') . ':' . $e->getLine() . ')'; + + if ($this->includeStacktraces) { + $str .= $this->stacktracesParser($e); + } + + return $str; + } + + private function stacktracesParser(\Throwable $e): string + { + $trace = $e->getTraceAsString(); + + if ($this->basePath !== '') { + $trace = preg_replace('{^(#\d+ )' . preg_quote($this->basePath) . '}m', '$1', $trace) ?? $trace; + } + + if ($this->stacktracesParser !== null) { + $trace = $this->stacktracesParserCustom($trace); + } + + if ($this->indentStacktraces !== '') { + $trace = str_replace("\n", "\n{$this->indentStacktraces}", $trace); + } + + if (trim($trace) === '') { + return ''; + } + + return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}" . strtr($trace, DIRECTORY_SEPARATOR, '/') . "\n"; + } + + private function stacktracesParserCustom(string $trace): string + { + return implode("\n", array_filter(array_map($this->stacktracesParser, explode("\n", $trace)), fn ($line) => is_string($line) && trim($line) !== '')); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php new file mode 100644 index 000000000..5f0b6a453 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Encodes message information into JSON in a format compatible with Loggly. + * + * @author Adam Pancutt + */ +class LogglyFormatter extends JsonFormatter +{ + /** + * Overrides the default batch mode to new lines for compatibility with the + * Loggly bulk API. + */ + public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = false) + { + parent::__construct($batchMode, $appendNewline); + } + + /** + * Appends the 'timestamp' parameter for indexing by Loggly. + * + * @see https://www.loggly.com/docs/automated-parsing/#json + * @see \Monolog\Formatter\JsonFormatter::format() + */ + protected function normalizeRecord(LogRecord $record): array + { + $recordData = parent::normalizeRecord($record); + + $recordData["timestamp"] = $record->datetime->format("Y-m-d\TH:i:s.uO"); + unset($recordData["datetime"]); + + return $recordData; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php new file mode 100644 index 000000000..9e44c19f4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Encodes message information into JSON in a format compatible with Logmatic. + * + * @author Julien Breux + */ +class LogmaticFormatter extends JsonFormatter +{ + protected const MARKERS = ["sourcecode", "php"]; + + protected string $hostname = ''; + + protected string $appName = ''; + + /** + * @return $this + */ + public function setHostname(string $hostname): self + { + $this->hostname = $hostname; + + return $this; + } + + /** + * @return $this + */ + public function setAppName(string $appName): self + { + $this->appName = $appName; + + return $this; + } + + /** + * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic. + * + * @see http://doc.logmatic.io/docs/basics-to-send-data + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function normalizeRecord(LogRecord $record): array + { + $record = parent::normalizeRecord($record); + + if ($this->hostname !== '') { + $record["hostname"] = $this->hostname; + } + if ($this->appName !== '') { + $record["appname"] = $this->appName; + } + + $record["@marker"] = static::MARKERS; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 000000000..d0e8749e3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Serializes a log message to Logstash Event Format + * + * @see https://www.elastic.co/products/logstash + * @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected string $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected string $applicationName; + + /** + * @var string the key for 'extra' fields from the Monolog record + */ + protected string $extraKey; + + /** + * @var string the key for 'context' fields from the Monolog record + */ + protected string $contextKey; + + /** + * @param string $applicationName The application that sends the data, used as the "type" field of logstash + * @param string|null $systemName The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraKey The key for extra keys inside logstash "fields", defaults to extra + * @param string $contextKey The key for context keys inside logstash "fields", defaults to context + */ + public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context') + { + // logstash requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->systemName = $systemName === null ? (string) gethostname() : $systemName; + $this->applicationName = $applicationName; + $this->extraKey = $extraKey; + $this->contextKey = $contextKey; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): string + { + $recordData = parent::format($record); + + $message = [ + '@timestamp' => $recordData['datetime'], + '@version' => 1, + 'host' => $this->systemName, + ]; + if (isset($recordData['message'])) { + $message['message'] = $recordData['message']; + } + if (isset($recordData['channel'])) { + $message['type'] = $recordData['channel']; + $message['channel'] = $recordData['channel']; + } + if (isset($recordData['level_name'])) { + $message['level'] = $recordData['level_name']; + } + if (isset($recordData['level'])) { + $message['monolog_level'] = $recordData['level']; + } + if ('' !== $this->applicationName) { + $message['type'] = $this->applicationName; + } + if (\count($recordData['extra']) > 0) { + $message[$this->extraKey] = $recordData['extra']; + } + if (\count($recordData['context']) > 0) { + $message[$this->contextKey] = $recordData['context']; + } + + return $this->toJson($message) . "\n"; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php new file mode 100644 index 000000000..64ecef2a4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use MongoDB\BSON\Type; +use MongoDB\BSON\UTCDateTime; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Formats a record for use with the MongoDBHandler. + * + * @author Florian Plattner + */ +class MongoDBFormatter implements FormatterInterface +{ + private bool $exceptionTraceAsString; + private int $maxNestingLevel; + + /** + * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record->context is 2 + * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings + */ + public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true) + { + $this->maxNestingLevel = max($maxNestingLevel, 0); + $this->exceptionTraceAsString = $exceptionTraceAsString; + } + + /** + * @inheritDoc + * + * @return mixed[] + */ + public function format(LogRecord $record): array + { + /** @var mixed[] $res */ + $res = $this->formatArray($record->toArray()); + + return $res; + } + + /** + * @inheritDoc + * + * @return array + */ + public function formatBatch(array $records): array + { + $formatted = []; + foreach ($records as $key => $record) { + $formatted[$key] = $this->format($record); + } + + return $formatted; + } + + /** + * @param mixed[] $array + * @return mixed[]|string Array except when max nesting level is reached then a string "[...]" + */ + protected function formatArray(array $array, int $nestingLevel = 0) + { + if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) { + return '[...]'; + } + + foreach ($array as $name => $value) { + if ($value instanceof \DateTimeInterface) { + $array[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Throwable) { + $array[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (\is_array($value)) { + $array[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (\is_object($value) && !$value instanceof Type) { + $array[$name] = $this->formatObject($value, $nestingLevel + 1); + } + } + + return $array; + } + + /** + * @param mixed $value + * @return mixed[]|string + */ + protected function formatObject($value, int $nestingLevel) + { + $objectVars = get_object_vars($value); + $objectVars['class'] = Utils::getClass($value); + + return $this->formatArray($objectVars, $nestingLevel); + } + + /** + * @return mixed[]|string + */ + protected function formatException(\Throwable $exception, int $nestingLevel) + { + $formattedException = [ + 'class' => Utils::getClass($exception), + 'message' => $exception->getMessage(), + 'code' => (int) $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + ]; + + if ($this->exceptionTraceAsString === true) { + $formattedException['trace'] = $exception->getTraceAsString(); + } else { + $formattedException['trace'] = $exception->getTrace(); + } + + return $this->formatArray($formattedException, $nestingLevel); + } + + protected function formatDate(\DateTimeInterface $value, int $nestingLevel): UTCDateTime + { + return new UTCDateTime((int) floor(((float) $value->format('U.u')) * 1000)); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 000000000..60da29cf7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,353 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\JsonSerializableDateTimeImmutable; +use Monolog\Utils; +use Throwable; +use Monolog\LogRecord; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + public const SIMPLE_DATE = "Y-m-d\TH:i:sP"; + + protected string $dateFormat; + protected int $maxNormalizeDepth = 9; + protected int $maxNormalizeItemCount = 1000; + + private int $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS; + + protected string $basePath = ''; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record) + { + return $this->normalizeRecord($record); + } + + /** + * Normalize an arbitrary value to a scalar|array|null + * + * @return null|scalar|array + */ + public function normalizeValue(mixed $data): mixed + { + return $this->normalize($data); + } + + /** + * @inheritDoc + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + public function getDateFormat(): string + { + return $this->dateFormat; + } + + /** + * @return $this + */ + public function setDateFormat(string $dateFormat): self + { + $this->dateFormat = $dateFormat; + + return $this; + } + + /** + * The maximum number of normalization levels to go through + */ + public function getMaxNormalizeDepth(): int + { + return $this->maxNormalizeDepth; + } + + /** + * @return $this + */ + public function setMaxNormalizeDepth(int $maxNormalizeDepth): self + { + $this->maxNormalizeDepth = $maxNormalizeDepth; + + return $this; + } + + /** + * The maximum number of items to normalize per level + */ + public function getMaxNormalizeItemCount(): int + { + return $this->maxNormalizeItemCount; + } + + /** + * @return $this + */ + public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self + { + $this->maxNormalizeItemCount = $maxNormalizeItemCount; + + return $this; + } + + /** + * Enables `json_encode` pretty print. + * + * @return $this + */ + public function setJsonPrettyPrint(bool $enable): self + { + if ($enable) { + $this->jsonEncodeOptions |= JSON_PRETTY_PRINT; + } else { + $this->jsonEncodeOptions &= ~JSON_PRETTY_PRINT; + } + + return $this; + } + + /** + * Setting a base path will hide the base path from exception and stack trace file names to shorten them + * @return $this + */ + public function setBasePath(string $path = ''): self + { + if ($path !== '') { + $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + } + + $this->basePath = $path; + + return $this; + } + + /** + * Provided as extension point + * + * Because normalize is called with sub-values of context data etc, normalizeRecord can be + * extended when data needs to be appended on the record array but not to other normalized data. + * + * @return array + */ + protected function normalizeRecord(LogRecord $record): array + { + /** @var array $normalized */ + $normalized = $this->normalize($record->toArray()); + + return $normalized; + } + + /** + * @return null|scalar|array + */ + protected function normalize(mixed $data, int $depth = 0): mixed + { + if ($depth > $this->maxNormalizeDepth) { + return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; + } + + if (null === $data || \is_scalar($data)) { + if (\is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (\is_array($data)) { + $normalized = []; + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.\count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth + 1); + } + + return $normalized; + } + + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + if (\is_object($data)) { + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + if ($data instanceof \JsonSerializable) { + /** @var null|scalar|array $value */ + $value = $data->jsonSerialize(); + } elseif (\get_class($data) === '__PHP_Incomplete_Class') { + $accessor = new \ArrayObject($data); + $value = (string) $accessor['__PHP_Incomplete_Class_Name']; + } elseif (method_exists($data, '__toString')) { + try { + /** @var string $value */ + $value = $data->__toString(); + } catch (\Throwable) { + // if the toString method is failing, use the default behavior + /** @var null|scalar|array $value */ + $value = json_decode($this->toJson($data, true), true); + } + } else { + // the rest is normalized by json encoding and decoding it + /** @var null|scalar|array $value */ + $value = json_decode($this->toJson($data, true), true); + } + + return [Utils::getClass($data) => $value]; + } + + if (\is_resource($data)) { + return sprintf('[resource(%s)]', get_resource_type($data)); + } + + return '[unknown('.\gettype($data).')]'; + } + + /** + * @return array>> + */ + protected function normalizeException(Throwable $e, int $depth = 0) + { + if ($depth > $this->maxNormalizeDepth) { + return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization']; + } + + if ($e instanceof \JsonSerializable) { + return (array) $e->jsonSerialize(); + } + + $file = $e->getFile(); + if ($this->basePath !== '') { + $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file); + } + + $data = [ + 'class' => Utils::getClass($e), + 'message' => $e->getMessage(), + 'code' => (int) $e->getCode(), + 'file' => $file.':'.$e->getLine(), + ]; + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + if (\is_string($e->detail)) { + $data['detail'] = $e->detail; + } elseif (\is_object($e->detail) || \is_array($e->detail)) { + $data['detail'] = $this->toJson($e->detail, true); + } + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'], $frame['line'])) { + $file = $frame['file']; + if ($this->basePath !== '') { + $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file); + } + $data['trace'][] = $file.':'.$frame['line']; + } + } + + if (($previous = $e->getPrevious()) instanceof \Throwable) { + $data['previous'] = $this->normalizeException($previous, $depth + 1); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string if encoding fails and ignoreErrors is true 'null' is returned + */ + protected function toJson($data, bool $ignoreErrors = false): string + { + return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors); + } + + protected function formatDate(\DateTimeInterface $date): string + { + // in case the date format isn't custom then we defer to the custom JsonSerializableDateTimeImmutable + // formatting logic, which will pick the right format based on whether useMicroseconds is on + if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof JsonSerializableDateTimeImmutable) { + return (string) $date; + } + + return $date->format($this->dateFormat); + } + + /** + * @return $this + */ + public function addJsonEncodeOption(int $option): self + { + $this->jsonEncodeOptions |= $option; + + return $this; + } + + /** + * @return $this + */ + public function removeJsonEncodeOption(int $option): self + { + $this->jsonEncodeOptions &= ~$option; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 000000000..ec73a0ec4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Formats data into an associative array of scalar (+ null) values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter extends NormalizerFormatter +{ + /** + * @inheritDoc + * + * @phpstan-return array $record + */ + public function format(LogRecord $record): array + { + $result = []; + foreach ($record->toArray() as $key => $value) { + $result[$key] = $this->toScalar($value); + } + + return $result; + } + + protected function toScalar(mixed $value): string|int|float|bool|null + { + $normalized = $this->normalize($value); + + if (\is_array($normalized)) { + return $this->toJson($normalized, true); + } + + return $normalized; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php new file mode 100644 index 000000000..ccaddf77a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Serializes a log message according to RFC 5424 + * + * @author Dalibor Karlović + * @author Renat Gabdullin + */ +class SyslogFormatter extends LineFormatter +{ + private const SYSLOG_FACILITY_USER = 1; + private const FORMAT = "<%extra.priority%>1 %datetime% %extra.hostname% %extra.app-name% %extra.procid% %channel% %extra.structured-data% %level_name%: %message% %context% %extra%\n"; + private const NILVALUE = '-'; + + private string $hostname; + private int $procid; + + public function __construct(private string $applicationName = self::NILVALUE) + { + parent::__construct(self::FORMAT, 'Y-m-d\TH:i:s.uP', true, true); + $this->hostname = (string) gethostname(); + $this->procid = (int) getmypid(); + } + + public function format(LogRecord $record): string + { + $record->extra = $this->formatExtra($record); + + return parent::format($record); + } + + /** + * @return array + */ + private function formatExtra(LogRecord $record): array + { + $extra = $record->extra; + $extra['app-name'] = $this->applicationName; + $extra['hostname'] = $this->hostname; + $extra['procid'] = $this->procid; + $extra['priority'] = self::calculatePriority($record->level); + $extra['structured-data'] = self::NILVALUE; + + return $extra; + } + + private static function calculatePriority(Level $level): int + { + return (self::SYSLOG_FACILITY_USER * 8) + $level->toRFC5424Level(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 000000000..4acfb9291 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter extends NormalizerFormatter +{ + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + parent::__construct($dateFormat); + + // http headers do not like non-ISO-8559-1 characters + $this->removeJsonEncodeOption(JSON_UNESCAPED_UNICODE); + } + + /** + * Translates Monolog log levels to Wildfire levels. + * + * @return 'LOG'|'INFO'|'WARN'|'ERROR' + */ + private function toWildfireLevel(Level $level): string + { + return match ($level) { + Level::Debug => 'LOG', + Level::Info => 'INFO', + Level::Notice => 'INFO', + Level::Warning => 'WARN', + Level::Error => 'ERROR', + Level::Critical => 'ERROR', + Level::Alert => 'ERROR', + Level::Emergency => 'ERROR', + }; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): string + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record->extra['file'])) { + $file = $record->extra['file']; + unset($record->extra['file']); + } + if (isset($record->extra['line'])) { + $line = $record->extra['line']; + unset($record->extra['line']); + } + + $message = ['message' => $record->message]; + $handleError = false; + if (\count($record->context) > 0) { + $message['context'] = $this->normalize($record->context); + $handleError = true; + } + if (\count($record->extra) > 0) { + $message['extra'] = $this->normalize($record->extra); + $handleError = true; + } + if (\count($message) === 1) { + $message = reset($message); + } + + if (is_array($message) && isset($message['context']) && \is_array($message['context']) && isset($message['context']['table'])) { + $type = 'TABLE'; + $label = $record->channel .': '. $record->message; + $message = $message['context']['table']; + } else { + $type = $this->toWildfireLevel($record->level); + $label = $record->channel; + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson([ + [ + 'Type' => $type, + 'File' => $file, + 'Line' => $line, + 'Label' => $label, + ], + $message, + ], $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%d|%s|', + \strlen($json), + $json + ); + } + + /** + * @inheritDoc + * + * @phpstan-return never + */ + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + /** + * @inheritDoc + * + * @return null|scalar|array|object + */ + protected function normalize(mixed $data, int $depth = 0): mixed + { + if (\is_object($data) && !$data instanceof \DateTimeInterface) { + return $data; + } + + return parent::normalize($data, $depth); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 000000000..61d45d58b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Logger; +use Monolog\ResettableInterface; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Base Handler class providing basic level/bubble support + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler extends Handler implements ResettableInterface +{ + protected Level $level = Level::Debug; + protected bool $bubble = true; + + /** + * @param int|string|Level|LogLevel::* $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return $record->level->value >= $this->level->value; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param Level|LogLevel::* $level Level or level name + * @return $this + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function setLevel(int|string|Level $level): self + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + */ + public function getLevel(): Level + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param bool $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return $this + */ + public function setBubble(bool $bubble): self + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return bool true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble(): bool + { + return $this->bubble; + } + + /** + * @inheritDoc + */ + public function reset(): void + { + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 000000000..de13a76be --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\LogRecord; + +/** + * Base Handler class providing the Handler structure, including processors and formatters + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + use FormattableHandlerTrait; + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $record->formatted = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the (already formatted) record down to the log of the implementing handler + */ + abstract protected function write(LogRecord $record): void; + + public function reset(): void + { + parent::reset(); + + $this->resetProcessors(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php new file mode 100644 index 000000000..4a7031706 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Common syslog functionality + */ +abstract class AbstractSyslogHandler extends AbstractProcessingHandler +{ + protected int $facility; + + /** + * List of valid log facility names. + * @var array + */ + protected array $facilities = [ + 'auth' => \LOG_AUTH, + 'authpriv' => \LOG_AUTHPRIV, + 'cron' => \LOG_CRON, + 'daemon' => \LOG_DAEMON, + 'kern' => \LOG_KERN, + 'lpr' => \LOG_LPR, + 'mail' => \LOG_MAIL, + 'news' => \LOG_NEWS, + 'syslog' => \LOG_SYSLOG, + 'user' => \LOG_USER, + 'uucp' => \LOG_UUCP, + ]; + + /** + * Translates Monolog log levels to syslog log priorities. + */ + protected function toSyslogPriority(Level $level): int + { + return $level->toRFC5424Level(); + } + + /** + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + */ + public function __construct(string|int $facility = \LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + + if (!\defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = \LOG_LOCAL0; + $this->facilities['local1'] = \LOG_LOCAL1; + $this->facilities['local2'] = \LOG_LOCAL2; + $this->facilities['local3'] = \LOG_LOCAL3; + $this->facilities['local4'] = \LOG_LOCAL4; + $this->facilities['local5'] = \LOG_LOCAL5; + $this->facilities['local6'] = \LOG_LOCAL6; + $this->facilities['local7'] = \LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 + } + + // convert textual description of facility to syslog constant + if (\is_string($facility) && \array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!\in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->facility = $facility; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 000000000..119f33947 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\Message as GelfMessage; +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; +use AMQPExchange; +use Monolog\LogRecord; + +class AmqpHandler extends AbstractProcessingHandler +{ + protected AMQPExchange|AMQPChannel $exchange; + + /** @var array */ + private array $extraAttributes = []; + + protected string $exchangeName; + + /** + * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use + * @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only + */ + public function __construct(AMQPExchange|AMQPChannel $exchange, ?string $exchangeName = null, int|string|Level $level = Level::Debug, bool $bubble = true) + { + if ($exchange instanceof AMQPChannel) { + $this->exchangeName = (string) $exchangeName; + } elseif ($exchangeName !== null) { + @trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED); + } + $this->exchange = $exchange; + + parent::__construct($level, $bubble); + } + + /** + * @return array + */ + public function getExtraAttributes(): array + { + return $this->extraAttributes; + } + + /** + * Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension) + * + * @param array $extraAttributes One of content_type, content_encoding, + * message_id, user_id, app_id, delivery_mode, + * priority, timestamp, expiration, type + * or reply_to, headers. + * @return $this + */ + public function setExtraAttributes(array $extraAttributes): self + { + $this->extraAttributes = $extraAttributes; + + return $this; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $data = $record->formatted; + $routingKey = $this->getRoutingKey($record); + + if($data instanceof GelfMessage) { + $data = json_encode($data->toArray()); + } + + if ($this->exchange instanceof AMQPExchange) { + $attributes = [ + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ]; + if (\count($this->extraAttributes) > 0) { + $attributes = array_merge($attributes, $this->extraAttributes); + } + $this->exchange->publish( + $data, + $routingKey, + 0, + $attributes + ); + } else { + $this->exchange->basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $routingKey + ); + } + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + if($data instanceof GelfMessage) { + $data = json_encode($data->toArray()); + } + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + */ + protected function getRoutingKey(LogRecord $record): string + { + $routingKey = sprintf('%s.%s', $record->level->name, $record->channel); + + return strtolower($routingKey); + } + + private function createAmqpMessage(string $data): AMQPMessage + { + $attributes = [ + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ]; + if (\count($this->extraAttributes) > 0) { + $attributes = array_merge($attributes, $this->extraAttributes); + } + + return new AMQPMessage($data, $attributes); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php new file mode 100644 index 000000000..788d7d068 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php @@ -0,0 +1,300 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Utils; +use Monolog\LogRecord; +use Monolog\Level; + +use function headers_list; +use function stripos; + +/** + * Handler sending logs to browser's javascript console with no browser extension required + * + * @author Olivier Poitrey + */ +class BrowserConsoleHandler extends AbstractProcessingHandler +{ + protected static bool $initialized = false; + + /** @var LogRecord[] */ + protected static array $records = []; + + protected const FORMAT_HTML = 'html'; + protected const FORMAT_JS = 'js'; + protected const FORMAT_UNKNOWN = 'unknown'; + + /** + * @inheritDoc + * + * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. + * + * Example of formatted string: + * + * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + // Accumulate records + static::$records[] = $record; + + // Register shutdown handler if not already done + if (!static::$initialized) { + static::$initialized = true; + $this->registerShutdownFunction(); + } + } + + /** + * Convert records to javascript console commands and send it to the browser. + * This method is automatically called on PHP shutdown if output is HTML or Javascript. + */ + public static function send(): void + { + $format = static::getResponseFormat(); + if ($format === self::FORMAT_UNKNOWN) { + return; + } + + if (\count(static::$records) > 0) { + if ($format === self::FORMAT_HTML) { + static::writeOutput(''); + } else { // js format + static::writeOutput(self::generateScript()); + } + static::resetStatic(); + } + } + + public function close(): void + { + self::resetStatic(); + } + + public function reset(): void + { + parent::reset(); + + self::resetStatic(); + } + + /** + * Forget all logged records + */ + public static function resetStatic(): void + { + static::$records = []; + } + + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction(): void + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']); + } + } + + /** + * Wrapper for echo to allow overriding + */ + protected static function writeOutput(string $str): void + { + echo $str; + } + + /** + * Checks the format of the response + * + * If Content-Type is set to application/javascript or text/javascript -> js + * If Content-Type is set to text/html, or is unset -> html + * If Content-Type is anything else -> unknown + * + * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* + */ + protected static function getResponseFormat(): string + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + return static::getResponseFormatFromContentType($header); + } + } + + return self::FORMAT_HTML; + } + + /** + * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* + */ + protected static function getResponseFormatFromContentType(string $contentType): string + { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) { + return self::FORMAT_JS; + } + + if (stripos($contentType, 'text/html') !== false) { + return self::FORMAT_HTML; + } + + return self::FORMAT_UNKNOWN; + } + + private static function generateScript(): string + { + $script = []; + foreach (static::$records as $record) { + $context = self::dump('Context', $record->context); + $extra = self::dump('Extra', $record->extra); + + if (\count($context) === 0 && \count($extra) === 0) { + $script[] = self::call_array(self::getConsoleMethodForLevel($record->level), self::handleStyles($record->formatted)); + } else { + $script = array_merge( + $script, + [self::call_array('groupCollapsed', self::handleStyles($record->formatted))], + $context, + $extra, + [self::call('groupEnd')] + ); + } + } + + return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; + } + + private static function getConsoleMethodForLevel(Level $level): string + { + return match ($level) { + Level::Debug => 'debug', + Level::Info, Level::Notice => 'info', + Level::Warning => 'warn', + Level::Error, Level::Critical, Level::Alert, Level::Emergency => 'error', + }; + } + + /** + * @return string[] + */ + private static function handleStyles(string $formatted): array + { + $args = []; + $format = '%c' . $formatted; + preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + foreach (array_reverse($matches) as $match) { + $args[] = '"font-weight: normal"'; + $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0])); + + $pos = $match[0][1]; + $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + \strlen($match[0][0])); + } + + $args[] = self::quote('font-weight: normal'); + $args[] = self::quote($format); + + return array_reverse($args); + } + + private static function handleCustomStyles(string $style, string $string): string + { + static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; + static $labels = []; + + $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) { + if (trim($m[1]) === 'autolabel') { + // Format the string as a label with consistent auto assigned background color + if (!isset($labels[$string])) { + $labels[$string] = $colors[\count($labels) % \count($colors)]; + } + $color = $labels[$string]; + + return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; + } + + return $m[1]; + }, $style); + + if (null === $style) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . preg_last_error_msg()); + } + + return $style; + } + + /** + * @param mixed[] $dict + * @return mixed[] + */ + private static function dump(string $title, array $dict): array + { + $script = []; + $dict = array_filter($dict, fn ($value) => $value !== null); + if (\count($dict) === 0) { + return $script; + } + $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title)); + foreach ($dict as $key => $value) { + $value = json_encode($value); + if (false === $value) { + $value = self::quote(''); + } + $script[] = self::call('log', self::quote('%s: %o'), self::quote((string) $key), $value); + } + + return $script; + } + + private static function quote(string $arg): string + { + return '"' . addcslashes($arg, "\"\n\\") . '"'; + } + + /** + * @param mixed $args + */ + private static function call(...$args): string + { + $method = array_shift($args); + if (!\is_string($method)) { + throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true)); + } + + return self::call_array($method, $args); + } + + /** + * @param mixed[] $args + */ + private static function call_array(string $method, array $args): string + { + return 'c.' . $method . '(' . implode(', ', $args) . ');'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 000000000..c799b6b9c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + protected HandlerInterface $handler; + + protected int $bufferSize = 0; + + protected int $bufferLimit; + + protected bool $flushOnOverflow; + + /** @var LogRecord[] */ + protected array $buffer = []; + + protected bool $initialized = false; + + /** + * @param HandlerInterface $handler Handler. + * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, int $bufferLimit = 0, int|string|Level $level = Level::Debug, bool $bubble = true, bool $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if ($record->level->isLowerThan($this->level)) { + return false; + } + + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function([$this, 'close']); + $this->initialized = true; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush(): void + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->clear(); + } + + public function __destruct() + { + // suppress the parent behavior since we already have register_shutdown_function() + // to call close(), and the reference contained there will prevent this from being + // GC'd until the end of the request + } + + /** + * @inheritDoc + */ + public function close(): void + { + $this->flush(); + + $this->handler->close(); + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + */ + public function clear(): void + { + $this->bufferSize = 0; + $this->buffer = []; + } + + public function reset(): void + { + $this->flush(); + + parent::reset(); + + $this->resetProcessors(); + + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($this->handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($this->handler).' does not support formatters.'); + } + + public function setHandler(HandlerInterface $handler): void + { + $this->handler = $handler; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 000000000..6598e822c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; +use Monolog\JsonSerializableDateTimeImmutable; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * This also works out of the box with Firefox 43+ + * + * @author Christophe Coevoet + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + use WebRequestRecognizerTrait; + + /** + * Version of the extension + */ + protected const VERSION = '4.0'; + + /** + * Header name + */ + protected const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) + */ + protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + + protected static bool $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending + */ + protected static bool $overflowed = false; + + /** @var mixed[] */ + protected static array $json = [ + 'version' => self::VERSION, + 'columns' => ['label', 'log', 'backtrace', 'type'], + 'rows' => [], + ]; + + protected static bool $sendHeaders = true; + + public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if (!$this->isWebRequest()) { + return; + } + + $messages = []; + + foreach ($records as $record) { + if ($record->level < $this->level) { + continue; + } + + $message = $this->processRecord($record); + $messages[] = $message; + } + + if (\count($messages) > 0) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + */ + protected function write(LogRecord $record): void + { + if (!$this->isWebRequest()) { + return; + } + + self::$json['rows'][] = $record->formatted; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send(): void + { + if (self::$overflowed || !self::$sendHeaders) { + return; + } + + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; + } + + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); + if (\strlen($data) > 3 * 1024) { + self::$overflowed = true; + + $record = new LogRecord( + message: 'Incomplete logs, chrome header size limit reached', + level: Level::Warning, + channel: 'monolog', + datetime: new JsonSerializableDateTimeImmutable(true), + ); + self::$json['rows'][\count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); + } + + if (trim($data) !== '') { + $this->sendHeader(static::HEADER_NAME, $data); + } + } + + /** + * Send header string to the client + */ + protected function sendHeader(string $header, string $content): void + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + */ + protected function headersAccepted(): bool + { + if (!isset($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 000000000..8d9c10e76 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\JsonFormatter; +use Monolog\Level; +use Monolog\LogRecord; + +/** + * CouchDB handler + * + * @author Markus Bachmann + * @phpstan-type Options array{ + * host: string, + * port: int, + * dbname: string, + * username: string|null, + * password: string|null + * } + * @phpstan-type InputOptions array{ + * host?: string, + * port?: int, + * dbname?: string, + * username?: string|null, + * password?: string|null + * } + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + /** + * @var mixed[] + * @phpstan-var Options + */ + private array $options; + + /** + * @param mixed[] $options + * + * @phpstan-param InputOptions $options + */ + public function __construct(array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->options = array_merge([ + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ], $options); + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $basicAuth = null; + if (null !== $this->options['username'] && null !== $this->options['password']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'content' => $record->formatted, + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ], + ]); + + if (false === @file_get_contents($url, false, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 000000000..f9ebedece --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Logs to Cube. + * + * @link https://github.com/square/cube/wiki + * @author Wan Chen + * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4 + */ +class CubeHandler extends AbstractProcessingHandler +{ + private ?\Socket $udpConnection = null; + private ?\CurlHandle $httpConnection = null; + private string $scheme; + private string $host; + private int $port; + /** @var string[] */ + private array $acceptedSchemes = ['http', 'udp']; + + /** + * Create a Cube handler + * + * @throws \UnexpectedValueException when given url is not a valid url. + * A valid url must consist of three parts : protocol://host:port + * Only valid protocols used by Cube are http and udp + */ + public function __construct(string $url, int|string|Level $level = Level::Debug, bool $bubble = true) + { + $urlInfo = parse_url($url); + + if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!\in_array($urlInfo['scheme'], $this->acceptedSchemes, true)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfo['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes) + ); + } + + $this->scheme = $urlInfo['scheme']; + $this->host = $urlInfo['host']; + $this->port = $urlInfo['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when there is no socket extension + */ + protected function connectUdp(): void + { + if (!\extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (false === $udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + $this->udpConnection = $udpConnection; + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to an http server + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when no curl extension + */ + protected function connectHttp(): void + { + if (!\extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler'); + } + + $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + if (false === $httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + $this->httpConnection = $httpConnection; + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $date = $record->datetime; + + $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')]; + $context = $record->context; + + if (isset($context['type'])) { + $data['type'] = $context['type']; + unset($context['type']); + } else { + $data['type'] = $record->channel; + } + + $data['data'] = $context; + $data['data']['level'] = $record->level; + + if ($this->scheme === 'http') { + $this->writeHttp(Utils::jsonEncode($data)); + } else { + $this->writeUdp(Utils::jsonEncode($data)); + } + } + + private function writeUdp(string $data): void + { + if (null === $this->udpConnection) { + $this->connectUdp(); + } + + if (null === $this->udpConnection) { + throw new \LogicException('No UDP socket could be opened'); + } + + socket_send($this->udpConnection, $data, \strlen($data), 0); + } + + private function writeHttp(string $data): void + { + if (null === $this->httpConnection) { + $this->connectHttp(); + } + + if (null === $this->httpConnection) { + throw new \LogicException('No connection could be established'); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . \strlen('['.$data.']'), + ]); + + Curl\Util::execute($this->httpConnection, 5); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php new file mode 100644 index 000000000..2ef58877d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Curl; + +use CurlHandle; + +/** + * This class is marked as internal and it is not under the BC promise of the package. + * + * @internal + */ +final class Util +{ + /** @var array */ + private static array $retriableErrorCodes = [ + CURLE_COULDNT_RESOLVE_HOST, + CURLE_COULDNT_CONNECT, + CURLE_HTTP_NOT_FOUND, + CURLE_READ_ERROR, + CURLE_OPERATION_TIMEOUTED, + CURLE_HTTP_POST_ERROR, + CURLE_SSL_CONNECT_ERROR, + ]; + + /** + * Executes a CURL request with optional retries and exception on failure + * + * @param CurlHandle $ch curl handler + * @return bool|string @see curl_exec + */ + public static function execute(CurlHandle $ch, int $retries = 5): bool|string + { + while ($retries > 0) { + $retries--; + $curlResponse = curl_exec($ch); + if ($curlResponse === false) { + $curlErrno = curl_errno($ch); + + if (false === \in_array($curlErrno, self::$retriableErrorCodes, true) || $retries === 0) { + $curlError = curl_error($ch); + + throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError)); + } + continue; + } + + return $curlResponse; + } + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php new file mode 100644 index 000000000..2b1d0b867 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Simple handler wrapper that deduplicates log records across multiple requests + * + * It also includes the BufferHandler functionality and will buffer + * all messages until the end of the request or flush() is called. + * + * This works by storing all log records' messages above $deduplicationLevel + * to the file specified by $deduplicationStore. When further logs come in at the end of the + * request (or when flush() is called), all those above $deduplicationLevel are checked + * against the existing stored logs. If they match and the timestamps in the stored log is + * not older than $time seconds, the new log record is discarded. If no log record is new, the + * whole data set is discarded. + * + * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers + * that send messages to people, to avoid spamming with the same message over and over in case of + * a major component failure like a database server being down which makes all requests fail in the + * same way. + * + * @author Jordi Boggiano + */ +class DeduplicationHandler extends BufferHandler +{ + protected string $deduplicationStore; + + protected Level $deduplicationLevel; + + protected int $time; + protected bool $gc = false; + + /** + * @param HandlerInterface $handler Handler. + * @param string|null $deduplicationStore The file/path where the deduplication log should be kept + * @param int|string|Level|LogLevel::* $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $deduplicationLevel + */ + public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, int|string|Level $deduplicationLevel = Level::Error, int $time = 60, bool $bubble = true) + { + parent::__construct($handler, 0, Level::Debug, $bubble, false); + + $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; + $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); + $this->time = $time; + } + + public function flush(): void + { + if ($this->bufferSize === 0) { + return; + } + + $store = null; + + if (file_exists($this->deduplicationStore)) { + $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + } + + $passthru = null; + + foreach ($this->buffer as $record) { + if ($record->level->value >= $this->deduplicationLevel->value) { + $passthru = $passthru === true || !\is_array($store) || !$this->isDuplicate($store, $record); + if ($passthru) { + $line = $this->buildDeduplicationStoreEntry($record); + file_put_contents($this->deduplicationStore, $line . "\n", FILE_APPEND); + if (!\is_array($store)) { + $store = []; + } + $store[] = $line; + } + } + } + + // default of null is valid as well as if no record matches duplicationLevel we just pass through + if ($passthru === true || $passthru === null) { + $this->handler->handleBatch($this->buffer); + } + + $this->clear(); + + if ($this->gc) { + $this->collectLogs(); + } + } + + /** + * If there is a store entry older than e.g. a day, this method should set `$this->gc` to `true` to trigger garbage collection. + * @param string[] $store The deduplication store + */ + protected function isDuplicate(array $store, LogRecord $record): bool + { + $timestampValidity = $record->datetime->getTimestamp() - $this->time; + $expectedMessage = preg_replace('{[\r\n].*}', '', $record->message); + $yesterday = time() - 86400; + + for ($i = \count($store) - 1; $i >= 0; $i--) { + list($timestamp, $level, $message) = explode(':', $store[$i], 3); + + if ($level === $record->level->getName() && $message === $expectedMessage && $timestamp > $timestampValidity) { + return true; + } + + if ($timestamp < $yesterday) { + $this->gc = true; + } + } + + return false; + } + + /** + * @return string The given record serialized as a single line of text + */ + protected function buildDeduplicationStoreEntry(LogRecord $record): string + { + return $record->datetime->getTimestamp() . ':' . $record->level->getName() . ':' . preg_replace('{[\r\n].*}', '', $record->message); + } + + private function collectLogs(): void + { + if (!file_exists($this->deduplicationStore)) { + return; + } + + $handle = fopen($this->deduplicationStore, 'rw+'); + + if (false === $handle) { + throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); + } + + if (false === flock($handle, LOCK_EX)) { + return; + } + $validLogs = []; + + $timestampValidity = time() - $this->time; + + while (!feof($handle)) { + $log = fgets($handle); + if (\is_string($log) && '' !== $log && substr($log, 0, 10) >= $timestampValidity) { + $validLogs[] = $log; + } + } + + ftruncate($handle, 0); + rewind($handle); + foreach ($validLogs as $log) { + fwrite($handle, $log); + } + + flock($handle, LOCK_UN); + fclose($handle); + + $this->gc = false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 000000000..eab9f1089 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; +use Doctrine\CouchDB\CouchDBClient; +use Monolog\LogRecord; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + private CouchDBClient $client; + + public function __construct(CouchDBClient $client, int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->client->postDocument($record->formatted); + } + + protected function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php new file mode 100644 index 000000000..f1c5a9590 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sdk; +use Aws\DynamoDb\DynamoDbClient; +use Monolog\Formatter\FormatterInterface; +use Aws\DynamoDb\Marshaler; +use Monolog\Formatter\ScalarFormatter; +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) + * + * @link https://github.com/aws/aws-sdk-php/ + * @author Andrew Lawson + */ +class DynamoDbHandler extends AbstractProcessingHandler +{ + public const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; + + protected DynamoDbClient $client; + + protected string $table; + + protected Marshaler $marshaler; + + public function __construct(DynamoDbClient $client, string $table, int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->marshaler = new Marshaler; + + $this->client = $client; + $this->table = $table; + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $filtered = $this->filterEmptyFields($record->formatted); + $formatted = $this->marshaler->marshalItem($filtered); + + $this->client->putItem([ + 'TableName' => $this->table, + 'Item' => $formatted, + ]); + } + + /** + * @param mixed[] $record + * @return mixed[] + */ + protected function filterEmptyFields(array $record): array + { + return array_filter($record, function ($value) { + return [] !== $value; + }); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ScalarFormatter(self::DATE_FORMAT); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php new file mode 100644 index 000000000..4a184b393 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastic\Transport\Exception\TransportException; +use Elastica\Document; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Level; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; +use Monolog\LogRecord; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7 + * ); + * $handler = new ElasticaHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + * @phpstan-type Options array{ + * index: string, + * type: string, + * ignore_error: bool + * } + * @phpstan-type InputOptions array{ + * index?: string, + * type?: string, + * ignore_error?: bool + * } + */ +class ElasticaHandler extends AbstractProcessingHandler +{ + protected Client $client; + + /** + * @var mixed[] Handler config options + * @phpstan-var Options + */ + protected array $options; + + /** + * @param Client $client Elastica Client object + * @param mixed[] $options Handler configuration + * + * @phpstan-param InputOptions $options + */ + public function __construct(Client $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ], + $options + ); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->bulkSend([$record->formatted]); + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + + throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter'); + } + + /** + * @return mixed[] + * + * @phpstan-return Options + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param Document[] $documents + * + * @throws \RuntimeException + */ + protected function bulkSend(array $documents): void + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface | TransportException $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php new file mode 100644 index 000000000..6fd705de4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastic\Elasticsearch\Response\Elasticsearch; +use Throwable; +use RuntimeException; +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticsearchFormatter; +use InvalidArgumentException; +use Elasticsearch\Common\Exceptions\RuntimeException as ElasticsearchRuntimeException; +use Elasticsearch\Client; +use Monolog\LogRecord; +use Elastic\Elasticsearch\Exception\InvalidArgumentException as ElasticInvalidArgumentException; +use Elastic\Elasticsearch\Client as Client8; + +/** + * Elasticsearch handler + * + * @link https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html + * + * Simple usage example: + * + * $client = \Elasticsearch\ClientBuilder::create() + * ->setHosts($hosts) + * ->build(); + * + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', + * ); + * $handler = new ElasticsearchHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Avtandil Kikabidze + * @phpstan-type Options array{ + * index: string, + * type: string, + * ignore_error: bool, + * op_type: 'index'|'create' + * } + * @phpstan-type InputOptions array{ + * index?: string, + * type?: string, + * ignore_error?: bool, + * op_type?: 'index'|'create' + * } + */ +class ElasticsearchHandler extends AbstractProcessingHandler +{ + protected Client|Client8 $client; + + /** + * @var mixed[] Handler config options + * @phpstan-var Options + */ + protected array $options; + + /** + * @var bool + */ + private $needsType; + + /** + * @param Client|Client8 $client Elasticsearch Client object + * @param mixed[] $options Handler configuration + * + * @phpstan-param InputOptions $options + */ + public function __construct(Client|Client8 $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => '_doc', // Elastic document type + 'ignore_error' => false, // Suppress Elasticsearch exceptions + 'op_type' => 'index', // Elastic op_type (index or create) (https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#docs-index-api-op_type) + ], + $options + ); + + if ($client instanceof Client8 || $client::VERSION[0] === '7') { + $this->needsType = false; + // force the type to _doc for ES8/ES7 + $this->options['type'] = '_doc'; + } else { + $this->needsType = true; + } + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->bulkSend([$record->formatted]); + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticsearchFormatter) { + return parent::setFormatter($formatter); + } + + throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter'); + } + + /** + * Getter options + * + * @return mixed[] + * + * @phpstan-return Options + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticsearchFormatter($this->options['index'], $this->options['type']); + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param array> $records Records + _index/_type keys + * @throws \RuntimeException + */ + protected function bulkSend(array $records): void + { + try { + $params = [ + 'body' => [], + ]; + + foreach ($records as $record) { + $params['body'][] = [ + $this->options['op_type'] => $this->needsType ? [ + '_index' => $record['_index'], + '_type' => $record['_type'], + ] : [ + '_index' => $record['_index'], + ], + ]; + unset($record['_index'], $record['_type']); + + $params['body'][] = $record; + } + + /** @var Elasticsearch */ + $responses = $this->client->bulk($params); + + if ($responses['errors'] === true) { + throw $this->createExceptionFromResponses($responses); + } + } catch (Throwable $e) { + if (! $this->options['ignore_error']) { + throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e); + } + } + } + + /** + * Creates elasticsearch exception from responses array + * + * Only the first error is converted into an exception. + * + * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk() + */ + protected function createExceptionFromResponses($responses): Throwable + { + foreach ($responses['items'] ?? [] as $item) { + if (isset($item['index']['error'])) { + return $this->createExceptionFromError($item['index']['error']); + } + } + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.'); + } + + if (class_exists(ElasticsearchRuntimeException::class)) { + return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.'); + } + + throw new \LogicException('Unsupported elastic search client version'); + } + + /** + * Creates elasticsearch exception from error array + * + * @param mixed[] $error + */ + protected function createExceptionFromError(array $error): Throwable + { + $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null; + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous); + } + + if (class_exists(ElasticsearchRuntimeException::class)) { + return new ElasticsearchRuntimeException($error['type'].': '.$error['reason'], 0, $previous); + } + + throw new \LogicException('Unsupported elastic search client version'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 000000000..cd28ff6ac --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + public const OPERATING_SYSTEM = 0; + public const SAPI = 4; + + /** @var 0|4 */ + protected int $messageType; + protected bool $expandNewlines; + + /** + * @param 0|4 $messageType Says where the error should go. + * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries + * + * @throws \InvalidArgumentException If an unsupported message type is set + */ + public function __construct(int $messageType = self::OPERATING_SYSTEM, int|string|Level $level = Level::Debug, bool $bubble = true, bool $expandNewlines = false) + { + parent::__construct($level, $bubble); + + if (false === \in_array($messageType, self::getAvailableTypes(), true)) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + $this->expandNewlines = $expandNewlines; + } + + /** + * @return int[] With all available types + */ + public static function getAvailableTypes(): array + { + return [ + self::OPERATING_SYSTEM, + self::SAPI, + ]; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if (!$this->expandNewlines) { + error_log((string) $record->formatted, $this->messageType); + + return; + } + + $lines = preg_split('{[\r\n]+}', (string) $record->formatted); + if ($lines === false) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. preg_last_error_msg()); + } + foreach ($lines as $line) { + error_log($line, $this->messageType); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php new file mode 100644 index 000000000..58318bee7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Throwable; +use Monolog\LogRecord; + +/** + * Forwards records to at most one handler + * + * If a handler fails, the exception is suppressed and the record is forwarded to the next handler. + * + * As soon as one handler handles a record successfully, the handling stops there. + */ +class FallbackGroupHandler extends GroupHandler +{ + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + foreach ($this->handlers as $handler) { + try { + $handler->handle(clone $record); + break; + } catch (Throwable $e) { + // What throwable? + } + } + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if (\count($this->processors) > 0) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch(array_map(fn ($record) => clone $record, $records)); + break; + } catch (Throwable $e) { + // What throwable? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php new file mode 100644 index 000000000..6653fa15a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Closure; +use Monolog\Level; +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Simple handler wrapper that filters records based on a list of levels + * + * It can be configured with an exact list of levels to allow, or a min/max level. + * + * @author Hennadiy Verkh + * @author Jordi Boggiano + */ +class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * Handler or factory Closure($record, $this) + * + * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface + */ + protected Closure|HandlerInterface $handler; + + /** + * Minimum level for logs that are passed to handler + * + * @var bool[] Map of Level value => true + * @phpstan-var array, true> + */ + protected array $acceptedLevels; + + /** + * Whether the messages that are handled can bubble up the stack or not + */ + protected bool $bubble; + + /** + * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler + * + * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $filterHandler). + * @param int|string|Level|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided + * @param int|string|Level|LogLevel::* $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param value-of|value-of|Level|LogLevel::*|array|value-of|Level|LogLevel::*> $minLevelOrList + * @phpstan-param value-of|value-of|Level|LogLevel::* $maxLevel + */ + public function __construct(Closure|HandlerInterface $handler, int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency, bool $bubble = true) + { + $this->handler = $handler; + $this->bubble = $bubble; + $this->setAcceptedLevels($minLevelOrList, $maxLevel); + } + + /** + * @phpstan-return list List of levels + */ + public function getAcceptedLevels(): array + { + return array_map(fn (int $level) => Level::from($level), array_keys($this->acceptedLevels)); + } + + /** + * @param int|string|Level|LogLevel::*|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided + * @param int|string|Level|LogLevel::* $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + * @return $this + * + * @phpstan-param value-of|value-of|Level|LogLevel::*|array|value-of|Level|LogLevel::*> $minLevelOrList + * @phpstan-param value-of|value-of|Level|LogLevel::* $maxLevel + */ + public function setAcceptedLevels(int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency): self + { + if (\is_array($minLevelOrList)) { + $acceptedLevels = array_map(Logger::toMonologLevel(...), $minLevelOrList); + } else { + $minLevelOrList = Logger::toMonologLevel($minLevelOrList); + $maxLevel = Logger::toMonologLevel($maxLevel); + $acceptedLevels = array_values(array_filter(Level::cases(), fn (Level $level) => $level->value >= $minLevelOrList->value && $level->value <= $maxLevel->value)); + } + $this->acceptedLevels = []; + foreach ($acceptedLevels as $level) { + $this->acceptedLevels[$level->value] = true; + } + + return $this; + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return isset($this->acceptedLevels[$record->level->value]); + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $this->getHandler($record)->handle($record); + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $filtered = []; + foreach ($records as $record) { + if ($this->isHandling($record)) { + $filtered[] = $record; + } + } + + if (\count($filtered) > 0) { + $this->getHandler($filtered[\count($filtered) - 1])->handleBatch($filtered); + } + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory, this will trigger the handler's instantiation. + */ + public function getHandler(LogRecord|null $record = null): HandlerInterface + { + if (!$this->handler instanceof HandlerInterface) { + $handler = ($this->handler)($record, $this); + if (!$handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory Closure should return a HandlerInterface"); + } + $this->handler = $handler; + } + + return $this->handler; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.'); + } + + public function reset(): void + { + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 000000000..e8a1b0b0d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\LogRecord; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + */ + public function isHandlerActivated(LogRecord $record): bool; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 000000000..383e19af9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Level::Critical, + * array( + * 'request' => Level::Alert, + * 'sensitive' => Level::Error, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + private Level $defaultActionLevel; + + /** + * @var array + */ + private array $channelToActionLevel; + + /** + * @param int|string|Level|LogLevel::* $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $defaultActionLevel + * @phpstan-param array|value-of|Level|LogLevel::*> $channelToActionLevel + */ + public function __construct(int|string|Level $defaultActionLevel, array $channelToActionLevel = []) + { + $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); + $this->channelToActionLevel = array_map(Logger::toMonologLevel(...), $channelToActionLevel); + } + + public function isHandlerActivated(LogRecord $record): bool + { + if (isset($this->channelToActionLevel[$record->channel])) { + return $record->level->value >= $this->channelToActionLevel[$record->channel]->value; + } + + return $record->level->value >= $this->defaultActionLevel->value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 000000000..c3ca2967a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Level; +use Monolog\LogRecord; +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + private Level $actionLevel; + + /** + * @param int|string|Level $actionLevel Level or name or value + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $actionLevel + */ + public function __construct(int|string|Level $actionLevel) + { + $this->actionLevel = Logger::toMonologLevel($actionLevel); + } + + public function isHandlerActivated(LogRecord $record): bool + { + return $record->level->value >= $this->actionLevel->value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 000000000..1ce64e845 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Closure; +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Level; +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can then have a passthruLevel as well which means that at the end of the request, + * even if it did not get activated, it will still send through log records of e.g. at least a + * warning level. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * Handler or factory Closure($record, $this) + * + * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface + */ + protected Closure|HandlerInterface $handler; + + protected ActivationStrategyInterface $activationStrategy; + + protected bool $buffering = true; + + protected int $bufferSize; + + /** @var LogRecord[] */ + protected array $buffer = []; + + protected bool $stopBuffering; + + protected Level|null $passthruLevel = null; + + protected bool $bubble; + + /** + * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler + * + * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $fingersCrossedHandler). + * @param int|string|Level|LogLevel::*|null $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int|string|Level|LogLevel::*|null $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * + * @phpstan-param value-of|value-of|Level|LogLevel::*|ActivationStrategyInterface|null $activationStrategy + * @phpstan-param value-of|value-of|Level|LogLevel::*|null $passthruLevel + */ + public function __construct(Closure|HandlerInterface $handler, int|string|Level|ActivationStrategyInterface|null $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, int|string|Level|null $passthruLevel = null) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Level::Warning); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + + if ($passthruLevel !== null) { + $this->passthruLevel = Logger::toMonologLevel($passthruLevel); + } + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return true; + } + + /** + * Manually activate this logger regardless of the activation strategy + */ + public function activate(): void + { + if ($this->stopBuffering) { + $this->buffering = false; + } + + $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); + $this->buffer = []; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && \count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + $this->activate(); + } + } else { + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function close(): void + { + $this->flushBuffer(); + + $this->getHandler()->close(); + } + + public function reset(): void + { + $this->flushBuffer(); + + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear(): void + { + $this->buffer = []; + $this->reset(); + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + private function flushBuffer(): void + { + if (null !== $this->passthruLevel) { + $passthruLevel = $this->passthruLevel; + $this->buffer = array_filter($this->buffer, static function ($record) use ($passthruLevel) { + return $passthruLevel->includes($record->level); + }); + if (\count($this->buffer) > 0) { + $this->getHandler(end($this->buffer))->handleBatch($this->buffer); + } + } + + $this->buffer = []; + $this->buffering = true; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory, this will trigger the handler's instantiation. + */ + public function getHandler(LogRecord|null $record = null): HandlerInterface + { + if (!$this->handler instanceof HandlerInterface) { + $handler = ($this->handler)($record, $this); + if (!$handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory Closure should return a HandlerInterface"); + } + $this->handler = $handler; + } + + return $this->handler; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 000000000..6b9e5103a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + use WebRequestRecognizerTrait; + + /** + * WildFire JSON header message format + */ + protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + protected const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static bool $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + */ + protected static int $messageIndex = 1; + + protected static bool $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * + * @return array Complete header string ready for the client as key and message as value + * + * @phpstan-return non-empty-array + */ + protected function createHeader(array $meta, string $message): array + { + $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta)); + + return [$header => $message]; + } + + /** + * Creates message header from record + * + * @return array + * + * @phpstan-return non-empty-array + * + * @see createHeader() + */ + protected function createRecordHeader(LogRecord $record): array + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + [1, 1, 1, self::$messageIndex++], + $record->formatted + ); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * + * @return array + */ + protected function getInitHeaders(): array + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(['Protocol', 1], static::PROTOCOL_URI), + $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI), + $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + */ + protected function sendHeader(string $header, string $content): void + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + */ + protected function write(LogRecord $record): void + { + if (!self::$sendHeaders || !$this->isWebRequest()) { + return; + } + + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + } + + $header = $this->createRecordHeader($record); + if (trim(current($header)) !== '') { + $this->sendHeader(key($header), current($header)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + */ + protected function headersAccepted(): bool + { + if (isset($_SERVER['HTTP_USER_AGENT']) && 1 === preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php new file mode 100644 index 000000000..46ebfc0b7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Sends logs to Fleep.io using Webhook integrations + * + * You'll need a Fleep.io account to use this handler. + * + * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation + * @author Ando Roots + */ +class FleepHookHandler extends SocketHandler +{ + protected const FLEEP_HOST = 'fleep.io'; + + protected const FLEEP_HOOK_URI = '/hook/'; + + /** + * @var string Webhook token (specifies the conversation where logs are sent) + */ + protected string $token; + + /** + * Construct a new Fleep.io Handler. + * + * For instructions on how to create a new web hook in your conversations + * see https://fleep.io/integrations/webhooks/ + * + * @param string $token Webhook token + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct( + string $token, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if (!\extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); + } + + $this->token = $token; + + $connectionString = 'ssl://' . static::FLEEP_HOST . ':443'; + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + } + + /** + * Returns the default formatter to use with this handler + * + * Overloaded to remove empty context and extra arrays from the end of the log message. + * + * @return LineFormatter + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(null, null, true, true); + } + + /** + * Handles a log record + */ + public function write(LogRecord $record): void + { + parent::write($record); + $this->closeSocket(); + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . static::FLEEP_HOST . "\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . \strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Builds the body of API call + */ + private function buildContent(LogRecord $record): string + { + $dataArray = [ + 'message' => $record->formatted, + ]; + + return http_build_query($dataArray); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php new file mode 100644 index 000000000..27c6c1533 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\Formatter\FlowdockFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Sends notifications through the Flowdock push API + * + * This must be configured with a FlowdockFormatter instance via setFormatter() + * + * Notes: + * API token - Flowdock API token + * + * @author Dominik Liebler + * @see https://www.flowdock.com/api/push + * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 + */ +class FlowdockHandler extends SocketHandler +{ + protected string $apiToken; + + /** + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct( + string $apiToken, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if (!\extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); + } + + parent::__construct( + 'ssl://api.flowdock.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->apiToken = $apiToken; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if (!$formatter instanceof FlowdockFormatter) { + throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + return parent::setFormatter($formatter); + } + + /** + * Gets the default formatter. + */ + protected function getDefaultFormatter(): FormatterInterface + { + throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + parent::write($record); + + $this->closeSocket(); + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + */ + private function buildContent(LogRecord $record): string + { + return Utils::jsonEncode($record->formatted); + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; + $header .= "Host: api.flowdock.com\r\n"; + $header .= "Content-Type: application/json\r\n"; + $header .= "Content-Length: " . \strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 000000000..72da59e1c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface to describe loggers that have a formatter + * + * @author Jordi Boggiano + */ +interface FormattableHandlerInterface +{ + /** + * Sets the formatter. + * + * @return HandlerInterface self + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface; + + /** + * Gets the formatter. + */ + public function getFormatter(): FormatterInterface; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 000000000..c044e0786 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Helper trait for implementing FormattableInterface + * + * @author Jordi Boggiano + */ +trait FormattableHandlerTrait +{ + protected FormatterInterface|null $formatter = null; + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + if (null === $this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Gets the default formatter. + * + * Overwrite this if the LineFormatter is not a good default for your handler. + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 000000000..ba5bb975d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\PublisherInterface; +use Monolog\Level; +use Monolog\Formatter\GelfMessageFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + * @author Benjamin Zikarsky + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var PublisherInterface the publisher object that sends the message to the server + */ + protected PublisherInterface $publisher; + + /** + * @param PublisherInterface $publisher a gelf publisher object + */ + public function __construct(PublisherInterface $publisher, int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->publisher = $publisher; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->publisher->publish($record->formatted); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new GelfMessageFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 000000000..0423dc346 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\ResettableInterface; +use Monolog\LogRecord; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface +{ + use ProcessableHandlerTrait; + + /** @var HandlerInterface[] */ + protected array $handlers; + protected bool $bubble; + + /** + * @param HandlerInterface[] $handlers Array of Handlers. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @throws \InvalidArgumentException if an unsupported handler is set + */ + public function __construct(array $handlers, bool $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + foreach ($this->handlers as $handler) { + $handler->handle(clone $record); + } + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if (\count($this->processors) > 0) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + $handler->handleBatch(array_map(fn ($record) => clone $record, $records)); + } + } + + public function reset(): void + { + $this->resetProcessors(); + + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + } + + public function close(): void + { + parent::close(); + + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + foreach ($this->handlers as $handler) { + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + } + } + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Handler.php b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php new file mode 100644 index 000000000..8a4e7ab10 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing basic close() support as well as handleBatch + * + * @author Jordi Boggiano + */ +abstract class Handler implements HandlerInterface +{ + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * @inheritDoc + */ + public function close(): void + { + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable $e) { + // do nothing + } + } + + public function __serialize(): array + { + $this->close(); + + return (array) $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 000000000..93306d961 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\LogRecord; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param LogRecord $record Partial log record having only a level initialized + */ + public function isHandling(LogRecord $record): bool; + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param LogRecord $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + */ + public function handle(LogRecord $record): bool; + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle + */ + public function handleBatch(array $records): void; + + /** + * Closes the handler. + * + * Ends a log cycle and frees all resources used by the handler. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * + * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) + * and ideally handlers should be able to reopen themselves on handle() after they have been closed. + * + * This is useful at the end of a request and will be called automatically when the object + * is destroyed if you extend Monolog\Handler\Handler. + * + * If you are thinking of calling this method yourself, most likely you should be + * calling ResettableInterface::reset instead. Have a look. + */ + public function close(): void; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 000000000..541ec2541 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A custom filtering that can be applied to any handler. + * + * Inherit from this class and override handle() like this: + * + * public function handle(LogRecord $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->handle($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface +{ + protected HandlerInterface $handler; + + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return $this->handler->isHandling($record); + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + return $this->handler->handle($record); + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $this->handler->handleBatch($records); + } + + /** + * @inheritDoc + */ + public function close(): void + { + $this->handler->close(); + } + + /** + * @inheritDoc + */ + public function pushProcessor(callable $callback): HandlerInterface + { + if ($this->handler instanceof ProcessableHandlerInterface) { + $this->handler->pushProcessor($callback); + + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); + } + + /** + * @inheritDoc + */ + public function popProcessor(): callable + { + if ($this->handler instanceof ProcessableHandlerInterface) { + return $this->handler->popProcessor(); + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); + } + + public function reset(): void + { + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php new file mode 100644 index 000000000..b9c7ba84b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * IFTTTHandler uses cURL to trigger IFTTT Maker actions + * + * Register a secret key and trigger/event name at https://ifttt.com/maker + * + * value1 will be the channel from monolog's Logger constructor, + * value2 will be the level name (ERROR, WARNING, ..) + * value3 will be the log record's message + * + * @author Nehal Patel + */ +class IFTTTHandler extends AbstractProcessingHandler +{ + private string $eventName; + private string $secretKey; + + /** + * @param string $eventName The name of the IFTTT Maker event that should be triggered + * @param string $secretKey A valid IFTTT secret key + * + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct(string $eventName, string $secretKey, int|string|Level $level = Level::Error, bool $bubble = true) + { + if (!\extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the IFTTTHandler'); + } + + $this->eventName = $eventName; + $this->secretKey = $secretKey; + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + public function write(LogRecord $record): void + { + $postData = [ + "value1" => $record->channel, + "value2" => $record["level_name"], + "value3" => $record->message, + ]; + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Content-Type: application/json", + ]); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php new file mode 100644 index 000000000..4b558bd65 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Inspired on LogEntriesHandler. + * + * @author Robert Kaufmann III + * @author Gabriel Machado + */ +class InsightOpsHandler extends SocketHandler +{ + protected string $logToken; + + /** + * @param string $token Log token supplied by InsightOps + * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. + * @param bool $useSSL Whether or not SSL encryption should be used + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $region = 'us', + bool $useSSL = true, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !\extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler'); + } + + $endpoint = $useSSL + ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443' + : $region . '.data.logs.insight.rapid7.com:80'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->logToken = $token; + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + return $this->logToken . ' ' . $record->formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php new file mode 100644 index 000000000..8c12898e2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandler extends SocketHandler +{ + protected string $logToken; + + /** + * @param string $token Log token supplied by LogEntries + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param string $host Custom hostname to send the data to if needed + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + bool $useSSL = true, + $level = Level::Debug, + bool $bubble = true, + string $host = 'data.logentries.com', + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !\extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80'; + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->logToken = $token; + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + return $this->logToken . ' ' . $record->formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php new file mode 100644 index 000000000..c1ccc0ed3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogglyFormatter; +use CurlHandle; +use Monolog\LogRecord; + +/** + * Sends errors to Loggly. + * + * @author Przemek Sobstel + * @author Adam Pancutt + * @author Gregory Barchard + */ +class LogglyHandler extends AbstractProcessingHandler +{ + protected const HOST = 'logs-01.loggly.com'; + protected const ENDPOINT_SINGLE = 'inputs'; + protected const ENDPOINT_BATCH = 'bulk'; + + /** + * Caches the curl handlers for every given endpoint. + * + * @var CurlHandle[] + */ + protected array $curlHandlers = []; + + protected string $token; + + /** @var string[] */ + protected array $tag = []; + + /** + * @param string $token API token supplied by Loggly + * + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct(string $token, int|string|Level $level = Level::Debug, bool $bubble = true) + { + if (!\extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the LogglyHandler'); + } + + $this->token = $token; + + parent::__construct($level, $bubble); + } + + /** + * Loads and returns the shared curl handler for the given endpoint. + */ + protected function getCurlHandler(string $endpoint): CurlHandle + { + if (!\array_key_exists($endpoint, $this->curlHandlers)) { + $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint); + } + + return $this->curlHandlers[$endpoint]; + } + + /** + * Starts a fresh curl session for the given endpoint and returns its handler. + */ + private function loadCurlHandle(string $endpoint): CurlHandle + { + $url = sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + return $ch; + } + + /** + * @param string[]|string $tag + * @return $this + */ + public function setTag(string|array $tag): self + { + if ('' === $tag || [] === $tag) { + $this->tag = []; + } else { + $this->tag = \is_array($tag) ? $tag : [$tag]; + } + + return $this; + } + + /** + * @param string[]|string $tag + * @return $this + */ + public function addTag(string|array $tag): self + { + if ('' !== $tag) { + $tag = \is_array($tag) ? $tag : [$tag]; + $this->tag = array_unique(array_merge($this->tag, $tag)); + } + + return $this; + } + + protected function write(LogRecord $record): void + { + $this->send($record->formatted, static::ENDPOINT_SINGLE); + } + + public function handleBatch(array $records): void + { + $level = $this->level; + + $records = array_filter($records, function ($record) use ($level) { + return ($record->level->value >= $level->value); + }); + + if (\count($records) > 0) { + $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH); + } + } + + protected function send(string $data, string $endpoint): void + { + $ch = $this->getCurlHandler($endpoint); + + $headers = ['Content-Type: application/json']; + + if (\count($this->tag) > 0) { + $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + Curl\Util::execute($ch, 5); + } + + protected function getDefaultFormatter(): FormatterInterface + { + return new LogglyFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php new file mode 100644 index 000000000..6aa1b31bf --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogmaticFormatter; +use Monolog\LogRecord; + +/** + * @author Julien Breux + */ +class LogmaticHandler extends SocketHandler +{ + private string $logToken; + + private string $hostname; + + private string $appName; + + /** + * @param string $token Log token supplied by Logmatic. + * @param string $hostname Host name supplied by Logmatic. + * @param string $appName Application name supplied by Logmatic. + * @param bool $useSSL Whether or not SSL encryption should be used. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $hostname = '', + string $appName = '', + bool $useSSL = true, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !\extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); + } + + $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; + $endpoint .= '/v1/'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->logToken = $token; + $this->hostname = $hostname; + $this->appName = $appName; + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + return $this->logToken . ' ' . $record->formatted; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + $formatter = new LogmaticFormatter(); + + if ($this->hostname !== '') { + $formatter->setHostname($this->hostname); + } + if ($this->appName !== '') { + $formatter->setAppName($this->appName); + } + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 000000000..b6c822772 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\HtmlFormatter; +use Monolog\LogRecord; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $messages = []; + + foreach ($records as $record) { + if ($record->level->isLowerThan($this->level)) { + continue; + } + + $message = $this->processRecord($record); + $messages[] = $message; + } + + if (\count($messages) > 0) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + * + * @phpstan-param non-empty-array $records + */ + abstract protected function send(string $content, array $records): void; + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->send((string) $record->formatted, [$record]); + } + + /** + * @phpstan-param non-empty-array $records + */ + protected function getHighestRecord(array $records): LogRecord + { + $highestRecord = null; + foreach ($records as $record) { + if ($highestRecord === null || $record->level->isHigherThan($highestRecord->level)) { + $highestRecord = $record; + } + } + + return $highestRecord; + } + + protected function isHtmlBody(string $body): bool + { + return ($body[0] ?? null) === '<'; + } + + /** + * Gets the default formatter. + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new HtmlFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php new file mode 100644 index 000000000..477ac345a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Swift; +use Swift_Message; + +/** + * MandrillHandler uses cURL to send the emails to the Mandrill API + * + * @author Adam Nicholson + */ +class MandrillHandler extends MailHandler +{ + protected Swift_Message $message; + protected string $apiKey; + + /** + * @phpstan-param (Swift_Message|callable(): Swift_Message) $message + * + * @param string $apiKey A valid Mandrill API key + * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced + * + * @throws \InvalidArgumentException if not a Swift Message is set + */ + public function __construct(string $apiKey, callable|Swift_Message $message, int|string|Level $level = Level::Error, bool $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$message instanceof Swift_Message) { + $message = $message(); + } + if (!$message instanceof Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + $this->apiKey = $apiKey; + } + + /** + * @inheritDoc + */ + protected function send(string $content, array $records): void + { + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message = clone $this->message; + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + /** @phpstan-ignore-next-line */ + $message->setDate(time()); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'key' => $this->apiKey, + 'raw_message' => (string) $message, + 'async' => false, + ])); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 000000000..3965aeea5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Exception can be thrown if an extension for a handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 000000000..3a1c085b7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use MongoDB\Client; +use MongoDB\Collection; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Manager; +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\MongoDBFormatter; +use Monolog\LogRecord; + +/** + * Logs to a MongoDB database. + * + * Usage example: + * + * $log = new \Monolog\Logger('application'); + * $client = new \MongoDB\Client('mongodb://localhost:27017'); + * $mongodb = new \Monolog\Handler\MongoDBHandler($client, 'logs', 'prod'); + * $log->pushHandler($mongodb); + * + * The above examples uses the MongoDB PHP library's client class; however, the + * MongoDB\Driver\Manager class from ext-mongodb is also supported. + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + private Collection $collection; + + private Client|Manager $manager; + + private string|null $namespace = null; + + /** + * Constructor. + * + * @param Client|Manager $mongodb MongoDB library or driver client + * @param string $database Database name + * @param string $collection Collection name + */ + public function __construct(Client|Manager $mongodb, string $database, string $collection, int|string|Level $level = Level::Debug, bool $bubble = true) + { + if ($mongodb instanceof Client) { + $this->collection = method_exists($mongodb, 'getCollection') ? $mongodb->getCollection($database, $collection) : $mongodb->selectCollection($database, $collection); + } else { + $this->manager = $mongodb; + $this->namespace = $database . '.' . $collection; + } + + parent::__construct($level, $bubble); + } + + protected function write(LogRecord $record): void + { + if (isset($this->collection)) { + $this->collection->insertOne($record->formatted); + } + + if (isset($this->manager, $this->namespace)) { + $bulk = new BulkWrite; + $bulk->insert($record->formatted); + $this->manager->executeBulkWrite($this->namespace, $bulk); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new MongoDBFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 000000000..a5d1a9771 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\LineFormatter; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + * @author Mark Garrett + */ +class NativeMailerHandler extends MailHandler +{ + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected array $to; + + /** + * The subject of the email + */ + protected string $subject; + + /** + * Optional headers for the message + * @var string[] + */ + protected array $headers = []; + + /** + * Optional parameters for the message + * @var string[] + */ + protected array $parameters = []; + + /** + * The wordwrap length for the message + */ + protected int $maxColumnWidth; + + /** + * The Content-type for the message + */ + protected string|null $contentType = null; + + /** + * The encoding for the message + */ + protected string $encoding = 'utf-8'; + + /** + * @param string|string[] $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct(string|array $to, string $subject, string $from, int|string|Level $level = Level::Error, bool $bubble = true, int $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = (array) $to; + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * Add headers to the message + * + * @param string|string[] $headers Custom added headers + * @return $this + */ + public function addHeader($headers): self + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + + return $this; + } + + /** + * Add parameters to the message + * + * @param string|string[] $parameters Custom added parameters + * @return $this + */ + public function addParameter($parameters): self + { + $this->parameters = array_merge($this->parameters, (array) $parameters); + + return $this; + } + + /** + * @inheritDoc + */ + protected function send(string $content, array $records): void + { + $contentType = $this->getContentType() ?? ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); + + if ($contentType !== 'text/html') { + $content = wordwrap($content, $this->maxColumnWidth); + } + + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); + $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; + if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'MIME-Version: 1.0' . "\r\n"; + } + + $subjectFormatter = new LineFormatter($this->subject); + $subject = $subjectFormatter->format($this->getHighestRecord($records)); + + $parameters = implode(' ', $this->parameters); + foreach ($this->to as $to) { + $this->mail($to, $subject, $content, $headers, $parameters); + } + } + + public function getContentType(): ?string + { + return $this->contentType; + } + + public function getEncoding(): string + { + return $this->encoding; + } + + /** + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages. + * @return $this + */ + public function setContentType(string $contentType): self + { + if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { + throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); + } + + $this->contentType = $contentType; + + return $this; + } + + /** + * @return $this + */ + public function setEncoding(string $encoding): self + { + if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { + throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); + } + + $this->encoding = $encoding; + + return $this; + } + + + protected function mail(string $to, string $subject, string $content, string $headers, string $parameters): void + { + mail($to, $subject, $content, $headers, $parameters); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 000000000..4f28dd4a5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Class to record a log on a NewRelic application. + * Enabling New Relic High Security mode may prevent capture of useful information. + * + * This handler requires a NormalizerFormatter to function and expects an array in $record->formatted + * + * @see https://docs.newrelic.com/docs/agents/php-agent + * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * @inheritDoc + */ + public function __construct( + int|string|Level $level = Level::Error, + bool $bubble = true, + + /** + * Name of the New Relic application that will receive logs from this handler. + */ + protected string|null $appName = null, + + /** + * Some context and extra data is passed into the handler as arrays of values. Do we send them as is + * (useful if we are using the API), or explode them for display on the NewRelic RPM website? + */ + protected bool $explodeArrays = false, + + /** + * Name of the current transaction + */ + protected string|null $transactionName = null + ) { + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if (null !== ($appName = $this->getAppName($record->context))) { + $this->setNewRelicAppName($appName); + } + + if (null !== ($transactionName = $this->getTransactionName($record->context))) { + $this->setNewRelicTransactionName($transactionName); + unset($record->formatted['context']['transaction_name']); + } + + if (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) { + newrelic_notice_error($record->message, $record->context['exception']); + unset($record->formatted['context']['exception']); + } else { + newrelic_notice_error($record->message); + } + + if (isset($record->formatted['context']) && \is_array($record->formatted['context'])) { + foreach ($record->formatted['context'] as $key => $parameter) { + if (\is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('context_' . $key, $parameter); + } + } + } + + if (isset($record->formatted['extra']) && \is_array($record->formatted['extra'])) { + foreach ($record->formatted['extra'] as $key => $parameter) { + if (\is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('extra_' . $key, $parameter); + } + } + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + */ + protected function isNewRelicEnabled(): bool + { + return \extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in it's context. + * + * @param mixed[] $context + */ + protected function getAppName(array $context): ?string + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Returns the name of the current transaction. Each log can override the default transaction name, set in this + * handler's constructor, by providing the transaction_name in it's context + * + * @param mixed[] $context + */ + protected function getTransactionName(array $context): ?string + { + if (isset($context['transaction_name'])) { + return $context['transaction_name']; + } + + return $this->transactionName; + } + + /** + * Sets the NewRelic application that should receive this log. + */ + protected function setNewRelicAppName(string $appName): void + { + newrelic_set_appname($appName); + } + + /** + * Overwrites the name of the current transaction + */ + protected function setNewRelicTransactionName(string $transactionName): void + { + newrelic_name_transaction($transactionName); + } + + /** + * @param mixed $value + */ + protected function setNewRelicParameter(string $key, $value): void + { + if (null === $value || \is_scalar($value)) { + newrelic_add_custom_parameter($key, $value); + } else { + newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true)); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php new file mode 100644 index 000000000..d9fea180c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\LogRecord; + +/** + * No-op + * + * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. + * This can be used for testing, or to disable a handler when overriding a configuration without + * influencing the rest of the stack. + * + * @author Roel Harbers + */ +class NoopHandler extends Handler +{ + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return true; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 000000000..1aa84e4f8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Psr\Log\LogLevel; +use Monolog\Logger; +use Monolog\LogRecord; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends Handler +{ + private Level $level; + + /** + * @param string|int|Level $level The minimum logging level at which this handler will be triggered + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(string|int|Level $level = Level::Debug) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return $record->level->value >= $this->level->value; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + return $record->level->value >= $this->level->value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php new file mode 100644 index 000000000..adc0eb1f0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Handler to only pass log messages when a certain threshold of number of messages is reached. + * + * This can be useful in cases of processing a batch of data, but you're for example only interested + * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? + * + * Usage example: + * + * ``` + * $log = new Logger('application'); + * $handler = new SomeHandler(...) + * + * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 + * $overflow = new OverflowHandler($handler, [Level::Warning->value => 10, Level::Error->value => 5]); + * + * $log->pushHandler($overflow); + *``` + * + * @author Kris Buist + */ +class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface +{ + private HandlerInterface $handler; + + /** @var array */ + private array $thresholdMap = []; + + /** + * Buffer of all messages passed to the handler before the threshold was reached + * + * @var mixed[][] + */ + private array $buffer = []; + + /** + * @param array $thresholdMap Dictionary of log level value => threshold + */ + public function __construct( + HandlerInterface $handler, + array $thresholdMap = [], + $level = Level::Debug, + bool $bubble = true + ) { + $this->handler = $handler; + foreach ($thresholdMap as $thresholdLevel => $threshold) { + $this->thresholdMap[$thresholdLevel] = $threshold; + } + parent::__construct($level, $bubble); + } + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if ($record->level->isLowerThan($this->level)) { + return false; + } + + $level = $record->level->value; + + if (!isset($this->thresholdMap[$level])) { + $this->thresholdMap[$level] = 0; + } + + if ($this->thresholdMap[$level] > 0) { + // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 + $this->thresholdMap[$level]--; + $this->buffer[$level][] = $record; + + return false === $this->bubble; + } + + if ($this->thresholdMap[$level] === 0) { + // This current message is breaking the threshold. Flush the buffer and continue handling the current record + foreach ($this->buffer[$level] ?? [] as $buffered) { + $this->handler->handle($buffered); + } + $this->thresholdMap[$level]--; + unset($this->buffer[$level]); + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($this->handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($this->handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php new file mode 100644 index 000000000..b37266d59 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use PhpConsole\Connector; +use PhpConsole\Handler as VendorPhpConsoleHandler; +use PhpConsole\Helper; +use Monolog\LogRecord; +use PhpConsole\Storage; + +/** + * Monolog handler for Google Chrome extension "PHP Console" + * + * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely + * + * Usage: + * 1. Install Google Chrome extension [now dead and removed from the chrome store] + * 2. See overview https://github.com/barbushin/php-console#overview + * 3. Install PHP Console library https://github.com/barbushin/php-console#installation + * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) + * + * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); + * \Monolog\ErrorHandler::register($logger); + * echo $undefinedVar; + * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); + * PC::debug($_SERVER); // PHP Console debugger for any type of vars + * + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + * @phpstan-type Options array{ + * enabled: bool, + * classesPartialsTraceIgnore: string[], + * debugTagsKeysInContext: array, + * useOwnErrorsHandler: bool, + * useOwnExceptionsHandler: bool, + * sourcesBasePath: string|null, + * registerHelper: bool, + * serverEncoding: string|null, + * headersLimit: int|null, + * password: string|null, + * enableSslOnlyMode: bool, + * ipMasks: string[], + * enableEvalListener: bool, + * dumperDetectCallbacks: bool, + * dumperLevelLimit: int, + * dumperItemsCountLimit: int, + * dumperItemSizeLimit: int, + * dumperDumpSizeLimit: int, + * detectDumpTraceAndSource: bool, + * dataStorage: Storage|null + * } + * @phpstan-type InputOptions array{ + * enabled?: bool, + * classesPartialsTraceIgnore?: string[], + * debugTagsKeysInContext?: array, + * useOwnErrorsHandler?: bool, + * useOwnExceptionsHandler?: bool, + * sourcesBasePath?: string|null, + * registerHelper?: bool, + * serverEncoding?: string|null, + * headersLimit?: int|null, + * password?: string|null, + * enableSslOnlyMode?: bool, + * ipMasks?: string[], + * enableEvalListener?: bool, + * dumperDetectCallbacks?: bool, + * dumperLevelLimit?: int, + * dumperItemsCountLimit?: int, + * dumperItemSizeLimit?: int, + * dumperDumpSizeLimit?: int, + * detectDumpTraceAndSource?: bool, + * dataStorage?: Storage|null + * } + * + * @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4 + */ +class PHPConsoleHandler extends AbstractProcessingHandler +{ + /** + * @phpstan-var Options + */ + private array $options = [ + 'enabled' => true, // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... + 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled + 'useOwnErrorsHandler' => false, // bool Enable errors handling + 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling + 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths + 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') + 'serverEncoding' => null, // string|null Server internal encoding + 'headersLimit' => null, // int|null Set headers size limit for your web-server + 'password' => null, // string|null Protect PHP Console connection by password + 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed + 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) + 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings + 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level + 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number + 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item + 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON + 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug + 'dataStorage' => null, // \PhpConsole\Storage|null Fixes problem with custom $_SESSION handler (see https://github.com/barbushin/php-console#troubleshooting-with-_session-handler-overridden-in-some-frameworks) + ]; + + private Connector $connector; + + /** + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @throws \RuntimeException + * @phpstan-param InputOptions $options + */ + public function __construct(array $options = [], ?Connector $connector = null, int|string|Level $level = Level::Debug, bool $bubble = true) + { + if (!class_exists('PhpConsole\Connector')) { + throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + parent::__construct($level, $bubble); + $this->options = $this->initOptions($options); + $this->connector = $this->initConnector($connector); + } + + /** + * @param array $options + * @return array + * + * @phpstan-param InputOptions $options + * @phpstan-return Options + */ + private function initOptions(array $options): array + { + $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); + if (\count($wrongOptions) > 0) { + throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions)); + } + + return array_replace($this->options, $options); + } + + private function initConnector(?Connector $connector = null): Connector + { + if (null === $connector) { + if ($this->options['dataStorage'] instanceof Storage) { + Connector::setPostponeStorage($this->options['dataStorage']); + } + $connector = Connector::getInstance(); + } + + if ($this->options['registerHelper'] && !Helper::isRegistered()) { + Helper::register(); + } + + if ($this->options['enabled'] && $connector->isActiveClient()) { + if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { + $handler = VendorPhpConsoleHandler::getInstance(); + $handler->setHandleErrors($this->options['useOwnErrorsHandler']); + $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); + $handler->start(); + } + if (null !== $this->options['sourcesBasePath']) { + $connector->setSourcesBasePath($this->options['sourcesBasePath']); + } + if (null !== $this->options['serverEncoding']) { + $connector->setServerEncoding($this->options['serverEncoding']); + } + if (null !== $this->options['password']) { + $connector->setPassword($this->options['password']); + } + if ($this->options['enableSslOnlyMode']) { + $connector->enableSslOnlyMode(); + } + if (\count($this->options['ipMasks']) > 0) { + $connector->setAllowedIpMasks($this->options['ipMasks']); + } + if (null !== $this->options['headersLimit'] && $this->options['headersLimit'] > 0) { + $connector->setHeadersLimit($this->options['headersLimit']); + } + if ($this->options['detectDumpTraceAndSource']) { + $connector->getDebugDispatcher()->detectTraceAndSource = true; + } + $dumper = $connector->getDumper(); + $dumper->levelLimit = $this->options['dumperLevelLimit']; + $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; + $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; + $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; + $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; + if ($this->options['enableEvalListener']) { + $connector->startEvalRequestsListener(); + } + } + + return $connector; + } + + public function getConnector(): Connector + { + return $this->connector; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + public function handle(LogRecord $record): bool + { + if ($this->options['enabled'] && $this->connector->isActiveClient()) { + return parent::handle($record); + } + + return !$this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + */ + protected function write(LogRecord $record): void + { + if ($record->level->isLowerThan(Level::Notice)) { + $this->handleDebugRecord($record); + } elseif (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) { + $this->handleExceptionRecord($record); + } else { + $this->handleErrorRecord($record); + } + } + + private function handleDebugRecord(LogRecord $record): void + { + [$tags, $filteredContext] = $this->getRecordTags($record); + $message = $record->message; + if (\count($filteredContext) > 0) { + $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($filteredContext)), null, true); + } + $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); + } + + private function handleExceptionRecord(LogRecord $record): void + { + $this->connector->getErrorsDispatcher()->dispatchException($record->context['exception']); + } + + private function handleErrorRecord(LogRecord $record): void + { + $context = $record->context; + + $this->connector->getErrorsDispatcher()->dispatchError( + $context['code'] ?? null, + $context['message'] ?? $record->message, + $context['file'] ?? null, + $context['line'] ?? null, + $this->options['classesPartialsTraceIgnore'] + ); + } + + /** + * @return array{string, mixed[]} + */ + private function getRecordTags(LogRecord $record): array + { + $tags = null; + $filteredContext = []; + if ($record->context !== []) { + $filteredContext = $record->context; + foreach ($this->options['debugTagsKeysInContext'] as $key) { + if (isset($filteredContext[$key])) { + $tags = $filteredContext[$key]; + if ($key === 0) { + array_shift($filteredContext); + } else { + unset($filteredContext[$key]); + } + break; + } + } + } + + return [$tags ?? $record->level->toPsrLogLevel(), $filteredContext]; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('%message%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php new file mode 100644 index 000000000..2855a9f7c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Stores to STDIN of any process, specified by a command. + * + * Usage example: + *
+ * $log = new Logger('myLogger');
+ * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
+ * 
+ * + * @author Kolja Zuelsdorf + */ +class ProcessHandler extends AbstractProcessingHandler +{ + /** + * Holds the process to receive data on its STDIN. + * + * @var resource|bool|null + */ + private $process; + + private string $command; + + private ?string $cwd; + + /** + * @var resource[] + */ + private array $pipes = []; + + private float $timeout; + + /** + * @var array> + */ + protected const DESCRIPTOR_SPEC = [ + 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from + 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to + 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors + ]; + + /** + * @param string $command Command for the process to start. Absolute paths are recommended, + * especially if you do not use the $cwd parameter. + * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. + * @param float $timeout The maximum timeout (in seconds) for the stream_select() function. + * @throws \InvalidArgumentException + */ + public function __construct(string $command, int|string|Level $level = Level::Debug, bool $bubble = true, ?string $cwd = null, float $timeout = 1.0) + { + if ($command === '') { + throw new \InvalidArgumentException('The command argument must be a non-empty string.'); + } + if ($cwd === '') { + throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); + } + + parent::__construct($level, $bubble); + + $this->command = $command; + $this->cwd = $cwd; + $this->timeout = $timeout; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @throws \UnexpectedValueException + */ + protected function write(LogRecord $record): void + { + $this->ensureProcessIsStarted(); + + $this->writeProcessInput($record->formatted); + + $errors = $this->readProcessErrors(); + if ($errors !== '') { + throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors)); + } + } + + /** + * Makes sure that the process is actually started, and if not, starts it, + * assigns the stream pipes, and handles startup errors, if any. + */ + private function ensureProcessIsStarted(): void + { + if (\is_resource($this->process) === false) { + $this->startProcess(); + + $this->handleStartupErrors(); + } + } + + /** + * Starts the actual process and sets all streams to non-blocking. + */ + private function startProcess(): void + { + $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + } + + /** + * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. + * + * @throws \UnexpectedValueException + */ + private function handleStartupErrors(): void + { + $selected = $this->selectErrorStream(); + if (false === $selected) { + throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); + } + + $errors = $this->readProcessErrors(); + + if (\is_resource($this->process) === false || $errors !== '') { + throw new \UnexpectedValueException( + sprintf('The process "%s" could not be opened: ' . $errors, $this->command) + ); + } + } + + /** + * Selects the STDERR stream. + * + * @return int|bool + */ + protected function selectErrorStream() + { + $empty = []; + $errorPipes = [$this->pipes[2]]; + + $seconds = (int) $this->timeout; + return stream_select($errorPipes, $empty, $empty, $seconds, (int) (($this->timeout - $seconds) * 1000000)); + } + + /** + * Reads the errors of the process, if there are any. + * + * @codeCoverageIgnore + * @return string Empty string if there are no errors. + */ + protected function readProcessErrors(): string + { + return (string) stream_get_contents($this->pipes[2]); + } + + /** + * Writes to the input stream of the opened process. + * + * @codeCoverageIgnore + */ + protected function writeProcessInput(string $string): void + { + fwrite($this->pipes[0], $string); + } + + /** + * @inheritDoc + */ + public function close(): void + { + if (\is_resource($this->process)) { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + proc_close($this->process); + $this->process = null; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 000000000..9fb290faa --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Processor\ProcessorInterface; +use Monolog\LogRecord; + +/** + * Interface to describe loggers that have processors + * + * @author Jordi Boggiano + */ +interface ProcessableHandlerInterface +{ + /** + * Adds a processor in the stack. + * + * @phpstan-param ProcessorInterface|(callable(LogRecord): LogRecord) $callback + * + * @param ProcessorInterface|callable $callback + * @return HandlerInterface self + */ + public function pushProcessor(callable $callback): HandlerInterface; + + /** + * Removes the processor on top of the stack and returns it. + * + * @phpstan-return ProcessorInterface|(callable(LogRecord): LogRecord) $callback + * + * @throws \LogicException In case the processor stack is empty + * @return callable|ProcessorInterface + */ + public function popProcessor(): callable; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 000000000..74eeddddc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Processor\ProcessorInterface; +use Monolog\LogRecord; + +/** + * Helper trait for implementing ProcessableInterface + * + * @author Jordi Boggiano + */ +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + * @phpstan-var array<(callable(LogRecord): LogRecord)|ProcessorInterface> + */ + protected array $processors = []; + + /** + * @inheritDoc + */ + public function pushProcessor(callable $callback): HandlerInterface + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * @inheritDoc + */ + public function popProcessor(): callable + { + if (\count($this->processors) === 0) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + protected function processRecord(LogRecord $record): LogRecord + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + + return $record; + } + + protected function resetProcessors(): void + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php new file mode 100644 index 000000000..100e8e4a6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Psr\Log\LoggerInterface; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Proxies log messages to an existing PSR-3 compliant logger. + * + * If a formatter is configured, the formatter's output MUST be a string and the + * formatted message will be fed to the wrapped PSR logger instead of the original + * log record's message. + * + * @author Michael Moussa + */ +class PsrHandler extends AbstractHandler implements FormattableHandlerInterface +{ + /** + * PSR-3 compliant logger + */ + protected LoggerInterface $logger; + + protected FormatterInterface|null $formatter = null; + private bool $includeExtra; + + /** + * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied + */ + public function __construct(LoggerInterface $logger, int|string|Level $level = Level::Debug, bool $bubble = true, bool $includeExtra = false) + { + parent::__construct($level, $bubble); + + $this->logger = $logger; + $this->includeExtra = $includeExtra; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + $message = $this->formatter !== null + ? (string) $this->formatter->format($record) + : $record->message; + + $context = $this->includeExtra + ? [...$record->extra, ...$record->context] + : $record->context; + + $this->logger->log($record->level->toPsrLogLevel(), $message, $context); + + return false === $this->bubble; + } + + /** + * Sets the formatter. + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Gets the formatter. + */ + public function getFormatter(): FormatterInterface + { + if ($this->formatter === null) { + throw new \LogicException('No formatter has been set and this handler does not have a default formatter'); + } + + return $this->formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 000000000..b2a78955a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Logger; +use Monolog\Utils; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandler extends SocketHandler +{ + private string $token; + + /** @var array */ + private array $users; + + private string $title; + + private string|int|null $user = null; + + private int $retry; + + private int $expire; + + private Level $highPriorityLevel; + + private Level $emergencyLevel; + + private bool $useFormattedMessage = false; + + /** + * All parameters that can be sent to Pushover + * @see https://pushover.net/api + * @var array + */ + private array $parameterNames = [ + 'token' => true, + 'user' => true, + 'message' => true, + 'device' => true, + 'title' => true, + 'url' => true, + 'url_title' => true, + 'priority' => true, + 'timestamp' => true, + 'sound' => true, + 'retry' => true, + 'expire' => true, + 'callback' => true, + ]; + + /** + * Sounds the api supports by default + * @see https://pushover.net/api#sounds + * @var string[] + */ + private array $sounds = [ + 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', + 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', + 'persistent', 'echo', 'updown', 'none', + ]; + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string|null $title Title sent to the Pushover API + * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will + * send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue + * to be retried for (every retry seconds). + * + * @param int|string|Level|LogLevel::* $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param int|string|Level|LogLevel::* $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * + * + * @phpstan-param string|array $users + * @phpstan-param value-of|value-of|Level|LogLevel::* $highPriorityLevel + * @phpstan-param value-of|value-of|Level|LogLevel::* $emergencyLevel + */ + public function __construct( + string $token, + $users, + ?string $title = null, + int|string|Level $level = Level::Critical, + bool $bubble = true, + bool $useSSL = true, + int|string|Level $highPriorityLevel = Level::Critical, + int|string|Level $emergencyLevel = Level::Emergency, + int $retry = 30, + int $expire = 25200, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?? (string) gethostname(); + $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); + $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream(LogRecord $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + private function buildContent(LogRecord $record): string + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - \strlen($this->title); + + $message = ($this->useFormattedMessage) ? $record->formatted : $record->message; + $message = Utils::substr($message, 0, $maxMessageLength); + + $timestamp = $record->datetime->getTimestamp(); + + $dataArray = [ + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp, + ]; + + if ($record->level->value >= $this->emergencyLevel->value) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif ($record->level->value >= $this->highPriorityLevel->value) { + $dataArray['priority'] = 1; + } + + // First determine the available parameters + $context = array_intersect_key($record->context, $this->parameterNames); + $extra = array_intersect_key($record->extra, $this->parameterNames); + + // Least important info should be merged with subsequent info + $dataArray = array_merge($extra, $context, $dataArray); + + // Only pass sounds that are supported by the API + if (isset($dataArray['sound']) && !\in_array($dataArray['sound'], $this->sounds, true)) { + unset($dataArray['sound']); + } + + return http_build_query($dataArray); + } + + private function buildHeader(string $content): string + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . \strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + protected function write(LogRecord $record): void + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + /** + * @param int|string|Level|LogLevel::* $level + * @return $this + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function setHighPriorityLevel(int|string|Level $level): self + { + $this->highPriorityLevel = Logger::toMonologLevel($level); + + return $this; + } + + /** + * @param int|string|Level|LogLevel::* $level + * @return $this + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function setEmergencyLevel(int|string|Level $level): self + { + $this->emergencyLevel = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Use the formatted message? + * + * @return $this + */ + public function useFormattedMessage(bool $useFormattedMessage): self + { + $this->useFormattedMessage = $useFormattedMessage; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 000000000..c40d97c61 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\LogRecord; +use Predis\Client as Predis; +use Redis; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + */ +class RedisHandler extends AbstractProcessingHandler +{ + /** @var Predis|Redis */ + private Predis|Redis $redisClient; + private string $redisKey; + protected int $capSize; + + /** + * @param Predis|Redis $redis The redis instance + * @param string $key The key name to push records to + * @param int $capSize Number of entries to limit list size to, 0 = unlimited + */ + public function __construct(Predis|Redis $redis, string $key, int|string|Level $level = Level::Debug, bool $bubble = true, int $capSize = 0) + { + $this->redisClient = $redis; + $this->redisKey = $key; + $this->capSize = $capSize; + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if ($this->capSize > 0) { + $this->writeCapped($record); + } else { + $this->redisClient->rpush($this->redisKey, $record->formatted); + } + } + + /** + * Write and cap the collection + * Writes the record to the redis list and caps its + */ + protected function writeCapped(LogRecord $record): void + { + if ($this->redisClient instanceof Redis) { + $mode = \defined('Redis::MULTI') ? Redis::MULTI : 1; + $this->redisClient->multi($mode) + ->rPush($this->redisKey, $record->formatted) + ->ltrim($this->redisKey, -$this->capSize, -1) + ->exec(); + } else { + $redisKey = $this->redisKey; + $capSize = $this->capSize; + $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { + $tx->rpush($redisKey, $record->formatted); + $tx->ltrim($redisKey, -$capSize, -1); + }); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php new file mode 100644 index 000000000..fa8e9e9ff --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\LogRecord; +use Predis\Client as Predis; +use Redis; + +/** + * Sends the message to a Redis Pub/Sub channel using PUBLISH + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Level::Warning); + * $log->pushHandler($redis); + * + * @author Gaëtan Faugère + */ +class RedisPubSubHandler extends AbstractProcessingHandler +{ + /** @var Predis|Redis */ + private Predis|Redis $redisClient; + private string $channelKey; + + /** + * @param Predis|Redis $redis The redis instance + * @param string $key The channel key to publish records to + */ + public function __construct(Predis|Redis $redis, string $key, int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->redisClient = $redis; + $this->channelKey = $key; + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->redisClient->publish($this->channelKey, $record->formatted); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php new file mode 100644 index 000000000..6b9943265 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Rollbar\RollbarLogger; +use Throwable; +use Monolog\LogRecord; + +/** + * Sends errors to Rollbar + * + * If the context data contains a `payload` key, that is used as an array + * of payload options to RollbarLogger's log method. + * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * + * @author Paul Statezny + */ +class RollbarHandler extends AbstractProcessingHandler +{ + protected RollbarLogger $rollbarLogger; + + /** + * Records whether any log records have been added since the last flush of the rollbar notifier + */ + private bool $hasRecords = false; + + protected bool $initialized = false; + + /** + * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token + */ + public function __construct(RollbarLogger $rollbarLogger, int|string|Level $level = Level::Error, bool $bubble = true) + { + $this->rollbarLogger = $rollbarLogger; + + parent::__construct($level, $bubble); + } + + /** + * Translates Monolog log levels to Rollbar levels. + * + * @return 'debug'|'info'|'warning'|'error'|'critical' + */ + protected function toRollbarLevel(Level $level): string + { + return match ($level) { + Level::Debug => 'debug', + Level::Info => 'info', + Level::Notice => 'info', + Level::Warning => 'warning', + Level::Error => 'error', + Level::Critical => 'critical', + Level::Alert => 'critical', + Level::Emergency => 'critical', + }; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function([$this, 'close']); + $this->initialized = true; + } + + $context = $record->context; + $context = array_merge($context, $record->extra, [ + 'level' => $this->toRollbarLevel($record->level), + 'monolog_level' => $record->level->getName(), + 'channel' => $record->channel, + 'datetime' => $record->datetime->format('U'), + ]); + + if (isset($context['exception']) && $context['exception'] instanceof Throwable) { + $exception = $context['exception']; + unset($context['exception']); + $toLog = $exception; + } else { + $toLog = $record->message; + } + + $this->rollbarLogger->log($context['level'], $toLog, $context); + + $this->hasRecords = true; + } + + public function flush(): void + { + if ($this->hasRecords) { + $this->rollbarLogger->flush(); + $this->hasRecords = false; + } + } + + /** + * @inheritDoc + */ + public function close(): void + { + $this->flush(); + } + + /** + * @inheritDoc + */ + public function reset(): void + { + $this->flush(); + + parent::reset(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 000000000..401c10895 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use DateTimeZone; +use InvalidArgumentException; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + public const FILE_PER_DAY = 'Y-m-d'; + public const FILE_PER_MONTH = 'Y-m'; + public const FILE_PER_YEAR = 'Y'; + + protected string $filename; + protected int $maxFiles; + protected bool|null $mustRotate = null; + protected \DateTimeImmutable $nextRotation; + protected string $filenameFormat; + protected string $dateFormat; + protected DateTimeZone|null $timezone = null; + + /** + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + */ + public function __construct(string $filename, int $maxFiles = 0, int|string|Level $level = Level::Debug, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false, string $dateFormat = self::FILE_PER_DAY, string $filenameFormat = '{filename}-{date}', DateTimeZone|null $timezone = null) + { + $this->filename = Utils::canonicalizePath($filename); + $this->maxFiles = $maxFiles; + $this->setFilenameFormat($filenameFormat, $dateFormat); + $this->nextRotation = $this->getNextRotation(); + $this->timezone = $timezone; + + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + /** + * @inheritDoc + */ + public function close(): void + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + /** + * @inheritDoc + */ + public function reset(): void + { + parent::reset(); + } + + /** + * @return $this + */ + public function setFilenameFormat(string $filenameFormat, string $dateFormat): self + { + $this->setDateFormat($dateFormat); + if (substr_count($filenameFormat, '{date}') === 0) { + throw new InvalidArgumentException( + 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.' + ); + } + $this->filenameFormat = $filenameFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + + return $this; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + // on the first record written, if the log is new, we rotate (once per day) after the log has been written so that the new file exists + if (null === $this->mustRotate) { + $this->mustRotate = null === $this->url || !file_exists($this->url); + } + + // if the next rotation is expired, then we rotate immediately + if ($this->nextRotation <= $record->datetime) { + $this->mustRotate = true; + $this->close(); // triggers rotation + } + + parent::write($record); + + if (true === $this->mustRotate) { + $this->close(); // triggers rotation + } + } + + /** + * Rotates the files. + */ + protected function rotate(): void + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = $this->getNextRotation(); + + $this->mustRotate = false; + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $logFiles = glob($this->getGlobPattern()); + if (false === $logFiles) { + // failed to glob + return; + } + + if ($this->maxFiles >= \count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + + $basePath = dirname($this->filename); + + foreach (\array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { + return true; + }); + unlink($file); + + $dir = dirname($file); + while ($dir !== $basePath) { + $entries = scandir($dir); + if ($entries === false || \count(array_diff($entries, ['.', '..'])) > 0) { + break; + } + + rmdir($dir); + $dir = dirname($dir); + } + restore_error_handler(); + } + } + } + + protected function getTimedFilename(): string + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace( + ['{filename}', '{date}'], + [$fileInfo['filename'], (new \DateTimeImmutable(timezone: $this->timezone))->format($this->dateFormat)], + ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat + ); + + if (isset($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } + + protected function getGlobPattern(): string + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace( + ['{filename}', '{date}'], + [$fileInfo['filename'], str_replace( + ['Y', 'y', 'm', 'd'], + ['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'], + $this->dateFormat + )], + ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat + ); + if (isset($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + + return $glob; + } + + protected function setDateFormat(string $dateFormat): void + { + if (0 === preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + throw new InvalidArgumentException( + 'Invalid date format - format must be one of '. + 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. + 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. + 'date formats using slashes, underscores and/or dots instead of dashes.' + ); + } + $this->dateFormat = $dateFormat; + } + + protected function getNextRotation(): \DateTimeImmutable + { + return match (str_replace(['/','_','.'], '-', $this->dateFormat)) { + self::FILE_PER_MONTH => (new \DateTimeImmutable('first day of next month'))->setTime(0, 0, 0), + self::FILE_PER_YEAR => (new \DateTimeImmutable('first day of January next year'))->setTime(0, 0, 0), + default => (new \DateTimeImmutable('tomorrow'))->setTime(0, 0, 0), + }; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php new file mode 100644 index 000000000..1b10580f2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Closure; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Sampling handler + * + * A sampled event stream can be useful for logging high frequency events in + * a production environment where you only need an idea of what is happening + * and are not concerned with capturing every occurrence. Since the decision to + * handle or not handle a particular event is determined randomly, the + * resulting sampled log is not guaranteed to contain 1/N of the events that + * occurred in the application, but based on the Law of large numbers, it will + * tend to be close to this ratio with a large number of attempts. + * + * @author Bryan Davis + * @author Kunal Mehta + */ +class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * Handler or factory Closure($record, $this) + * + * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface + */ + protected Closure|HandlerInterface $handler; + + protected int $factor; + + /** + * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler + * + * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $samplingHandler). + * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) + */ + public function __construct(Closure|HandlerInterface $handler, int $factor) + { + parent::__construct(); + $this->handler = $handler; + $this->factor = $factor; + } + + public function isHandling(LogRecord $record): bool + { + return $this->getHandler($record)->isHandling($record); + } + + public function handle(LogRecord $record): bool + { + if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory, this will trigger the handler's instantiation. + */ + public function getHandler(LogRecord|null $record = null): HandlerInterface + { + if (!$this->handler instanceof HandlerInterface) { + $handler = ($this->handler)($record, $this); + if (!$handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory Closure should return a HandlerInterface"); + } + $this->handler = $handler; + } + + return $this->handler; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php new file mode 100644 index 000000000..5847a585c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; + +/** + * SendGridHandler uses the SendGrid API v3 function to send Log emails, more information in https://www.twilio.com/docs/sendgrid/for-developers/sending-email/api-getting-started + * + * @author Ricardo Fontanelli + */ +class SendGridHandler extends MailHandler +{ + /** + * The SendGrid API User + * @deprecated this is not used anymore as of SendGrid API v3 + */ + protected string $apiUser; + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected array $to; + + /** + * @param string|null $apiUser Unused user as of SendGrid API v3, you can pass null or any string + * @param list|string $to + * @param non-empty-string $apiHost Allows you to use another endpoint (e.g. api.eu.sendgrid.com) + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct( + string|null $apiUser, + protected string $apiKey, + protected string $from, + array|string $to, + protected string $subject, + int|string|Level $level = Level::Error, + bool $bubble = true, + /** @var non-empty-string */ + private readonly string $apiHost = 'api.sendgrid.com', + ) { + if (!\extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler'); + } + + $this->to = (array) $to; + // @phpstan-ignore property.deprecated + $this->apiUser = $apiUser ?? ''; + parent::__construct($level, $bubble); + } + + protected function send(string $content, array $records): void + { + $body = []; + $body['personalizations'] = []; + $body['from']['email'] = $this->from; + foreach ($this->to as $recipient) { + $body['personalizations'][]['to'][]['email'] = $recipient; + } + $body['subject'] = $this->subject; + + if ($this->isHtmlBody($content)) { + $body['content'][] = [ + 'type' => 'text/html', + 'value' => $content, + ]; + } else { + $body['content'][] = [ + 'type' => 'text/plain', + 'value' => $content, + ]; + } + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Authorization: Bearer '.$this->apiKey, + ]); + curl_setopt($ch, CURLOPT_URL, 'https://'.$this->apiHost.'/v3/mail/send'); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, Utils::jsonEncode($body)); + + Curl\Util::execute($ch, 2); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 000000000..0e46c2d2f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -0,0 +1,381 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + */ +class SlackRecord +{ + public const COLOR_DANGER = 'danger'; + + public const COLOR_WARNING = 'warning'; + + public const COLOR_GOOD = 'good'; + + public const COLOR_DEFAULT = '#e3e4e6'; + + /** + * Slack channel (encoded ID or name) + */ + private string|null $channel; + + /** + * Name of a bot + */ + private string|null $username; + + /** + * User icon e.g. 'ghost', 'http://example.com/user.png' + */ + private string|null $userIcon; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + */ + private bool $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + */ + private bool $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + */ + private bool $includeContextAndExtra; + + /** + * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @var string[] + */ + private array $excludeFields; + + private FormatterInterface|null $formatter; + + private NormalizerFormatter $normalizerFormatter; + + /** + * @param string[] $excludeFields + */ + public function __construct( + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $userIcon = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = [], + FormatterInterface|null $formatter = null + ) { + $this + ->setChannel($channel) + ->setUsername($username) + ->useAttachment($useAttachment) + ->setUserIcon($userIcon) + ->useShortAttachment($useShortAttachment) + ->includeContextAndExtra($includeContextAndExtra) + ->excludeFields($excludeFields) + ->setFormatter($formatter); + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + } + + /** + * Returns required data in format that Slack + * is expecting. + * + * @phpstan-return mixed[] + */ + public function getSlackData(LogRecord $record): array + { + $dataArray = []; + + if ($this->username !== null) { + $dataArray['username'] = $this->username; + } + + if ($this->channel !== null) { + $dataArray['channel'] = $this->channel; + } + + if ($this->formatter !== null && !$this->useAttachment) { + $message = $this->formatter->format($record); + } else { + $message = $record->message; + } + + $recordData = $this->removeExcludedFields($record); + + if ($this->useAttachment) { + $attachment = [ + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record->level), + 'fields' => [], + 'mrkdwn_in' => ['fields'], + 'ts' => $recordData['datetime']->getTimestamp(), + 'footer' => $this->username, + 'footer_icon' => $this->userIcon, + ]; + + if ($this->useShortAttachment) { + $attachment['title'] = $recordData['level_name']; + } else { + $attachment['title'] = 'Message'; + $attachment['fields'][] = $this->generateAttachmentField('Level', $recordData['level_name']); + } + + if ($this->includeContextAndExtra) { + foreach (['extra', 'context'] as $key) { + if (!isset($recordData[$key]) || \count($recordData[$key]) === 0) { + continue; + } + + if ($this->useShortAttachment) { + $attachment['fields'][] = $this->generateAttachmentField( + $key, + $recordData[$key] + ); + } else { + // Add all extra fields as individual fields in attachment + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($recordData[$key]) + ); + } + } + } + + $dataArray['attachments'] = [$attachment]; + } else { + $dataArray['text'] = $message; + } + + if ($this->userIcon !== null) { + if (false !== ($iconUrl = filter_var($this->userIcon, FILTER_VALIDATE_URL))) { + $dataArray['icon_url'] = $iconUrl; + } else { + $dataArray['icon_emoji'] = ":{$this->userIcon}:"; + } + } + + return $dataArray; + } + + /** + * Returns a Slack message attachment color associated with + * provided level. + */ + public function getAttachmentColor(Level $level): string + { + return match ($level) { + Level::Error, Level::Critical, Level::Alert, Level::Emergency => static::COLOR_DANGER, + Level::Warning => static::COLOR_WARNING, + Level::Info, Level::Notice => static::COLOR_GOOD, + Level::Debug => static::COLOR_DEFAULT + }; + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param mixed[] $fields + */ + public function stringify(array $fields): string + { + /** @var array|bool|float|int|string|null> $normalized */ + $normalized = $this->normalizerFormatter->normalizeValue($fields); + + $hasSecondDimension = \count(array_filter($normalized, 'is_array')) > 0; + $hasOnlyNonNumericKeys = \count(array_filter(array_keys($normalized), 'is_numeric')) === 0; + + return $hasSecondDimension || $hasOnlyNonNumericKeys + ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS) + : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); + } + + /** + * Channel used by the bot when posting + * + * @param ?string $channel + * @return $this + */ + public function setChannel(?string $channel = null): self + { + $this->channel = $channel; + + return $this; + } + + /** + * Username used by the bot when posting + * + * @param ?string $username + * @return $this + */ + public function setUsername(?string $username = null): self + { + $this->username = $username; + + return $this; + } + + /** + * @return $this + */ + public function useAttachment(bool $useAttachment = true): self + { + $this->useAttachment = $useAttachment; + + return $this; + } + + /** + * @return $this + */ + public function setUserIcon(?string $userIcon = null): self + { + $this->userIcon = $userIcon; + + if (\is_string($userIcon)) { + $this->userIcon = trim($userIcon, ':'); + } + + return $this; + } + + /** + * @return $this + */ + public function useShortAttachment(bool $useShortAttachment = false): self + { + $this->useShortAttachment = $useShortAttachment; + + return $this; + } + + /** + * @return $this + */ + public function includeContextAndExtra(bool $includeContextAndExtra = false): self + { + $this->includeContextAndExtra = $includeContextAndExtra; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + + return $this; + } + + /** + * @param string[] $excludeFields + * @return $this + */ + public function excludeFields(array $excludeFields = []): self + { + $this->excludeFields = $excludeFields; + + return $this; + } + + /** + * @return $this + */ + public function setFormatter(?FormatterInterface $formatter = null): self + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Generates attachment field + * + * @param string|mixed[] $value + * + * @return array{title: string, value: string, short: false} + */ + private function generateAttachmentField(string $title, $value): array + { + $value = \is_array($value) + ? sprintf('```%s```', substr($this->stringify($value), 0, 1990)) + : $value; + + return [ + 'title' => ucfirst($title), + 'value' => $value, + 'short' => false, + ]; + } + + /** + * Generates a collection of attachment fields from array + * + * @param mixed[] $data + * + * @return array + */ + private function generateAttachmentFields(array $data): array + { + /** @var array|string> $normalized */ + $normalized = $this->normalizerFormatter->normalizeValue($data); + + $fields = []; + foreach ($normalized as $key => $value) { + $fields[] = $this->generateAttachmentField((string) $key, $value); + } + + return $fields; + } + + /** + * Get a copy of record with fields excluded according to $this->excludeFields + * + * @return mixed[] + */ + private function removeExcludedFields(LogRecord $record): array + { + $recordData = $record->toArray(); + foreach ($this->excludeFields as $field) { + $keys = explode('.', $field); + $node = &$recordData; + $lastKey = end($keys); + foreach ($keys as $key) { + if (!isset($node[$key])) { + break; + } + if ($lastKey === $key) { + unset($node[$key]); + break; + } + $node = &$node[$key]; + } + } + + return $recordData; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php new file mode 100644 index 000000000..2a34dda2d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -0,0 +1,267 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; +use Monolog\LogRecord; + +/** + * Sends notifications through Slack API + * + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandler extends SocketHandler +{ + /** + * Slack API token + */ + private string $token; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + */ + private SlackRecord $slackRecord; + + /** + * @param string $token Slack API token + * @param string $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @throws MissingExtensionException If no OpenSSL PHP extension configured + */ + public function __construct( + string $token, + string $channel, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + $level = Level::Critical, + bool $bubble = true, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = [], + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if (!\extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + } + + parent::__construct( + 'ssl://slack.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields + ); + + $this->token = $token; + } + + public function getSlackRecord(): SlackRecord + { + return $this->slackRecord; + } + + public function getToken(): string + { + return $this->token; + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + */ + private function buildContent(LogRecord $record): string + { + $dataArray = $this->prepareContentData($record); + + return http_build_query($dataArray); + } + + /** + * @return string[] + */ + protected function prepareContentData(LogRecord $record): array + { + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; + + if (isset($dataArray['attachments']) && \is_array($dataArray['attachments']) && \count($dataArray['attachments']) > 0) { + $dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']); + } + + return $dataArray; + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; + $header .= "Host: slack.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . \strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + parent::write($record); + $this->finalizeWrite(); + } + + /** + * Finalizes the request by reading some bytes and then closing the socket + * + * If we do not read some but close the socket too early, slack sometimes + * drops the request entirely. + */ + protected function finalizeWrite(): void + { + $res = $this->getResource(); + if (\is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } + + /** + * Channel used by the bot when posting + * + * @return $this + */ + public function setChannel(string $channel): self + { + $this->slackRecord->setChannel($channel); + + return $this; + } + + /** + * Username used by the bot when posting + * + * @return $this + */ + public function setUsername(string $username): self + { + $this->slackRecord->setUsername($username); + + return $this; + } + + /** + * @return $this + */ + public function useAttachment(bool $useAttachment): self + { + $this->slackRecord->useAttachment($useAttachment); + + return $this; + } + + /** + * @return $this + */ + public function setIconEmoji(string $iconEmoji): self + { + $this->slackRecord->setUserIcon($iconEmoji); + + return $this; + } + + /** + * @return $this + */ + public function useShortAttachment(bool $useShortAttachment): self + { + $this->slackRecord->useShortAttachment($useShortAttachment); + + return $this; + } + + /** + * @return $this + */ + public function includeContextAndExtra(bool $includeContextAndExtra): self + { + $this->slackRecord->includeContextAndExtra($includeContextAndExtra); + + return $this; + } + + /** + * @param string[] $excludeFields + * @return $this + */ + public function excludeFields(array $excludeFields): self + { + $this->slackRecord->excludeFields($excludeFields); + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 000000000..f265d80c9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; +use Monolog\LogRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + * + * @var non-empty-string + */ + private string $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + */ + private SlackRecord $slackRecord; + + /** + * @param non-empty-string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct( + string $webhookUrl, + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + $level = Level::Critical, + bool $bubble = true, + array $excludeFields = [] + ) { + if (!\extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SlackWebhookHandler'); + } + + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields + ); + } + + public function getSlackRecord(): SlackRecord + { + return $this->slackRecord; + } + + public function getWebhookUrl(): string + { + return $this->webhookUrl; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $postData = $this->slackRecord->getSlackData($record); + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + $options = [ + CURLOPT_URL => $this->webhookUrl, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => ['Content-type: application/json'], + CURLOPT_POSTFIELDS => $postString, + ]; + + curl_setopt_array($ch, $options); + + Curl\Util::execute($ch); + } + + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 000000000..36d46bf9d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,436 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + */ +class SocketHandler extends AbstractProcessingHandler +{ + private string $connectionString; + private float $connectionTimeout; + /** @var resource|null */ + private $resource; + private float $timeout; + private float $writingTimeout; + private int|null $lastSentBytes = null; + private int|null $chunkSize; + private bool $persistent; + private int|null $errno = null; + private string|null $errstr = null; + private float|null $lastWritingAt = null; + + /** + * @param string $connectionString Socket connection string + * @param bool $persistent Flag to enable/disable persistent connections + * @param float $timeout Socket timeout to wait until the request is being aborted + * @param float $writingTimeout Socket timeout to wait until the request should've been sent/written + * @param float|null $connectionTimeout Socket connect timeout to wait until the connection should've been + * established + * @param int|null $chunkSize Sets the chunk size. Only has effect during connection in the writing cycle + * + * @throws \InvalidArgumentException If an invalid timeout value (less than 0) is passed. + */ + public function __construct( + string $connectionString, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + + if ($connectionTimeout !== null) { + $this->validateTimeout($connectionTimeout); + } + + $this->connectionTimeout = $connectionTimeout ?? (float) \ini_get('default_socket_timeout'); + $this->persistent = $persistent; + $this->validateTimeout($timeout); + $this->timeout = $timeout; + $this->validateTimeout($writingTimeout); + $this->writingTimeout = $writingTimeout; + $this->chunkSize = $chunkSize; + } + + /** + * Connect (if necessary) and write to the socket + * + * @inheritDoc + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + protected function write(LogRecord $record): void + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close(): void + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket(): void + { + if (\is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to be persistent. It only has effect before the connection is initiated. + * + * @return $this + */ + public function setPersistent(bool $persistent): self + { + $this->persistent = $persistent; + + return $this; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @see http://php.net/manual/en/function.fsockopen.php + * @return $this + */ + public function setConnectionTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->connectionTimeout = $seconds; + + return $this; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + * @return $this + */ + public function setTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->timeout = $seconds; + + return $this; + } + + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + * @return $this + */ + public function setWritingTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->writingTimeout = $seconds; + + return $this; + } + + /** + * Set chunk size. Only has effect during connection in the writing cycle. + * + * @return $this + */ + public function setChunkSize(int $bytes): self + { + $this->chunkSize = $bytes; + + return $this; + } + + /** + * Get current connection string + */ + public function getConnectionString(): string + { + return $this->connectionString; + } + + /** + * Get persistent setting + */ + public function isPersistent(): bool + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + */ + public function getConnectionTimeout(): float + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + */ + public function getTimeout(): float + { + return $this->timeout; + } + + /** + * Get current local writing timeout + */ + public function getWritingTimeout(): float + { + return $this->writingTimeout; + } + + /** + * Get current chunk size + */ + public function getChunkSize(): ?int + { + return $this->chunkSize; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + */ + public function isConnected(): bool + { + return \is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + * + * @return resource|false + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @return resource|false + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + protected function streamSetTimeout(): bool + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds) * 1e6); + + if (!\is_resource($this->resource)) { + throw new \LogicException('streamSetTimeout called but $this->resource is not a resource'); + } + + return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-chunk-size.php + * + * @return int|false + */ + protected function streamSetChunkSize(): int|bool + { + if (!\is_resource($this->resource)) { + throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); + } + + if (null === $this->chunkSize) { + throw new \LogicException('streamSetChunkSize called but $this->chunkSize is not set'); + } + + return stream_set_chunk_size($this->resource, $this->chunkSize); + } + + /** + * Wrapper to allow mocking + * + * @return int|false + */ + protected function fwrite(string $data): int|bool + { + if (!\is_resource($this->resource)) { + throw new \LogicException('fwrite called but $this->resource is not a resource'); + } + + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + * + * @return mixed[]|bool + */ + protected function streamGetMetadata(): array|bool + { + if (!\is_resource($this->resource)) { + throw new \LogicException('streamGetMetadata called but $this->resource is not a resource'); + } + + return stream_get_meta_data($this->resource); + } + + private function validateTimeout(float $value): void + { + if ($value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected(): void + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + protected function generateDataStream(LogRecord $record): string + { + return (string) $record->formatted; + } + + /** + * @return resource|null + */ + protected function getResource() + { + return $this->resource; + } + + private function connect(): void + { + $this->createSocketResource(); + $this->setSocketTimeout(); + $this->setStreamChunkSize(); + } + + private function createSocketResource(): void + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (\is_bool($resource)) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout(): void + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function setStreamChunkSize(): void + { + if (null !== $this->chunkSize && false === $this->streamSetChunkSize()) { + throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); + } + } + + private function writeToSocket(string $data): void + { + $length = \strlen($data); + $sent = 0; + $this->lastSentBytes = $sent; + while ($this->isConnected() && $sent < $length) { + if (0 === $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if (\is_array($socketInfo) && (bool) $socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + + private function writingIsTimedOut(int $sent): bool + { + // convert to ms + if (0.0 === $this->writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = microtime(true); + $this->lastSentBytes = $sent; + + return false; + } else { + usleep(100); + } + + if ((microtime(true) - (float) $this->lastWritingAt) >= $this->writingTimeout) { + $this->closeSocket(); + + return true; + } + + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php new file mode 100644 index 000000000..1d28b65d4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sqs\SqsClient; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Writes to any sqs queue. + * + * @author Martijn van Calker + */ +class SqsHandler extends AbstractProcessingHandler +{ + /** 256 KB in bytes - maximum message size in SQS */ + protected const MAX_MESSAGE_SIZE = 262144; + /** 100 KB in bytes - head message size for new error log */ + protected const HEAD_MESSAGE_SIZE = 102400; + + private SqsClient $client; + private string $queueUrl; + + public function __construct(SqsClient $sqsClient, string $queueUrl, int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->client = $sqsClient; + $this->queueUrl = $queueUrl; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if (!isset($record->formatted) || 'string' !== \gettype($record->formatted)) { + throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string' . Utils::getRecordMessageForException($record)); + } + + $messageBody = $record->formatted; + if (\strlen($messageBody) >= static::MAX_MESSAGE_SIZE) { + $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE); + } + + $this->client->sendMessage([ + 'QueueUrl' => $this->queueUrl, + 'MessageBody' => $messageBody, + ]); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 000000000..c8a648c43 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,280 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected const MAX_CHUNK_SIZE = 2147483647; + /** 10MB */ + protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; + protected int $streamChunkSize; + /** @var resource|null */ + protected $stream; + protected string|null $url = null; + private string|null $errorMessage = null; + protected int|null $filePermission; + protected bool $useLocking; + protected string $fileOpenMode; + /** @var true|null */ + private bool|null $dirCreated = null; + private bool $retrying = false; + private int|null $inodeUrl = null; + + /** + * @param resource|string $stream If a missing path can't be created, an UnexpectedValueException will be thrown on first write + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + * @param string $fileOpenMode The fopen() mode used when opening a file, if $stream is a file path + * + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, int|string|Level $level = Level::Debug, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false, string $fileOpenMode = 'a') + { + parent::__construct($level, $bubble); + + if (($phpMemoryLimit = Utils::expandIniShorthandBytes(\ini_get('memory_limit'))) !== false) { + if ($phpMemoryLimit > 0) { + // use max 10% of allowed memory for the chunk size, and at least 100KB + $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024)); + } else { + // memory is unlimited, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + } else { + // no memory limit information, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + + if (\is_resource($stream)) { + $this->stream = $stream; + + stream_set_chunk_size($this->stream, $this->streamChunkSize); + } elseif (\is_string($stream)) { + $this->url = Utils::canonicalizePath($stream); + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->fileOpenMode = $fileOpenMode; + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * @inheritDoc + */ + public function reset(): void + { + parent::reset(); + + // auto-close on reset to make sure we periodically close the file in long running processes + // as long as they correctly call reset() between jobs + if ($this->url !== null && $this->url !== 'php://memory') { + $this->close(); + } + } + + /** + * @inheritDoc + */ + public function close(): void + { + if (null !== $this->url && \is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + $this->dirCreated = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + */ + public function getUrl(): ?string + { + return $this->url; + } + + public function getStreamChunkSize(): int + { + return $this->streamChunkSize; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if ($this->hasUrlInodeWasChanged()) { + $this->close(); + $this->write($record); + + return; + } + + if (!\is_resource($this->stream)) { + $url = $this->url; + if (null === $url || '' === $url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().' . Utils::getRecordMessageForException($record)); + } + $this->createDir($url); + $this->errorMessage = null; + set_error_handler($this->customErrorHandler(...)); + + try { + $stream = fopen($url, $this->fileOpenMode); + if ($this->filePermission !== null) { + @chmod($url, $this->filePermission); + } + } finally { + restore_error_handler(); + } + if (!\is_resource($stream)) { + $this->stream = null; + + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url) . Utils::getRecordMessageForException($record)); + } + stream_set_chunk_size($stream, $this->streamChunkSize); + $this->stream = $stream; + $this->inodeUrl = $this->getInodeFromUrl(); + } + + $stream = $this->stream; + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($stream, LOCK_EX); + } + + $this->errorMessage = null; + set_error_handler($this->customErrorHandler(...)); + try { + $this->streamWrite($stream, $record); + } finally { + restore_error_handler(); + } + if ($this->errorMessage !== null) { + $error = $this->errorMessage; + // close the resource if possible to reopen it, and retry the failed write + if (!$this->retrying && $this->url !== null && $this->url !== 'php://memory') { + $this->retrying = true; + $this->close(); + $this->write($record); + + return; + } + + throw new \UnexpectedValueException('Writing to the log file failed: '.$error . Utils::getRecordMessageForException($record)); + } + + $this->retrying = false; + if ($this->useLocking) { + flock($stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + */ + protected function streamWrite($stream, LogRecord $record): void + { + fwrite($stream, (string) $record->formatted); + } + + /** + * @return true + */ + private function customErrorHandler(int $code, string $msg): bool + { + $this->errorMessage = preg_replace('{^(fopen|mkdir|fwrite)\(.*?\): }', '', $msg); + + return true; + } + + private function getDirFromStream(string $stream): ?string + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return \dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return \dirname(substr($stream, 7)); + } + + return null; + } + + private function createDir(string $url): void + { + // Do not try to create dir if it has already been tried. + if (true === $this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler(function (...$args) { + return $this->customErrorHandler(...$args); + }); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } + + private function getInodeFromUrl(): ?int + { + if ($this->url === null || str_starts_with($this->url, 'php://')) { + return null; + } + + $inode = @fileinode($this->url); + + return $inode === false ? null : $inode; + } + + private function hasUrlInodeWasChanged(): bool + { + if ($this->inodeUrl === null || $this->retrying || $this->inodeUrl === $this->getInodeFromUrl()) { + return false; + } + + $this->retrying = true; + + return true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php new file mode 100644 index 000000000..33aa4fd7b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Closure; +use Monolog\Level; +use Monolog\LogRecord; +use Monolog\Utils; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mime\Email; + +/** + * SymfonyMailerHandler uses Symfony's Mailer component to send the emails + * + * @author Jordi Boggiano + */ +class SymfonyMailerHandler extends MailHandler +{ + protected MailerInterface|TransportInterface $mailer; + /** @var Email|Closure(string, LogRecord[]): Email */ + private Email|Closure $emailTemplate; + + /** + * @phpstan-param Email|Closure(string, LogRecord[]): Email $email + * + * @param MailerInterface|TransportInterface $mailer The mailer to use + * @param Closure|Email $email An email template, the subject/body will be replaced + */ + public function __construct($mailer, Email|Closure $email, int|string|Level $level = Level::Error, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->emailTemplate = $email; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string|null $format The format of the subject + */ + protected function getSubjectFormatter(?string $format): FormatterInterface + { + return new LineFormatter($format); + } + + /** + * Creates instance of Email to be sent + * + * @param string $content formatted email body to be sent + * @param LogRecord[] $records Log records that formed the content + */ + protected function buildMessage(string $content, array $records): Email + { + $message = null; + if ($this->emailTemplate instanceof Email) { + $message = clone $this->emailTemplate; + } elseif (\is_callable($this->emailTemplate)) { + $message = ($this->emailTemplate)($content, $records); + } + + if (!$message instanceof Email) { + $record = reset($records); + + throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record instanceof LogRecord ? Utils::getRecordMessageForException($record) : '')); + } + + if (\count($records) > 0) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->subject($subjectFormatter->format($this->getHighestRecord($records))); + } + + if ($this->isHtmlBody($content)) { + if (null !== ($charset = $message->getHtmlCharset())) { + $message->html($content, $charset); + } else { + $message->html($content); + } + } else { + if (null !== ($charset = $message->getTextCharset())) { + $message->text($content, $charset); + } else { + $message->text($content); + } + } + + return $message->date(new \DateTimeImmutable()); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 000000000..f3d7674c2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractSyslogHandler +{ + protected string $ident; + protected int $logopts; + + /** + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct(string $ident, string|int $facility = LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true, int $logopts = LOG_PID) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->logopts = $logopts; + } + + /** + * @inheritDoc + */ + public function close(): void + { + closelog(); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + openlog($this->ident, $this->logopts, $this->facility); + syslog($this->toSyslogPriority($record->level), (string) $record->formatted); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php new file mode 100644 index 000000000..3ff0bce5f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\SyslogUdp; + +use Monolog\Utils; +use Socket; + +class UdpSocket +{ + protected const DATAGRAM_MAX_LENGTH = 65023; + + protected string $ip; + protected int $port; + protected ?Socket $socket = null; + + public function __construct(string $ip, int $port = 514) + { + $this->ip = $ip; + $this->port = $port; + } + + public function write(string $line, string $header = ""): void + { + $this->send($this->assembleMessage($line, $header)); + } + + public function close(): void + { + if ($this->socket instanceof Socket) { + socket_close($this->socket); + $this->socket = null; + } + } + + protected function getSocket(): Socket + { + if (null !== $this->socket) { + return $this->socket; + } + + $domain = AF_INET; + $protocol = SOL_UDP; + // Check if we are using unix sockets. + if ($this->port === 0) { + $domain = AF_UNIX; + $protocol = IPPROTO_IP; + } + + $socket = socket_create($domain, SOCK_DGRAM, $protocol); + if ($socket instanceof Socket) { + return $this->socket = $socket; + } + + throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' could not be opened via socket_create'); + } + + protected function send(string $chunk): void + { + socket_sendto($this->getSocket(), $chunk, \strlen($chunk), $flags = 0, $this->ip, $this->port); + } + + protected function assembleMessage(string $line, string $header): string + { + $chunkSize = static::DATAGRAM_MAX_LENGTH - \strlen($header); + + return $header . Utils::substr($line, 0, $chunkSize); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php new file mode 100644 index 000000000..b5e467417 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use DateTimeInterface; +use Monolog\Handler\SyslogUdp\UdpSocket; +use Monolog\Level; +use Monolog\LogRecord; +use Monolog\Utils; + +/** + * A Handler for logging to a remote syslogd server. + * + * @author Jesper Skovgaard Nielsen + * @author Dominik Kukacka + */ +class SyslogUdpHandler extends AbstractSyslogHandler +{ + const RFC3164 = 0; + const RFC5424 = 1; + const RFC5424e = 2; + + /** @var array */ + private array $dateFormats = [ + self::RFC3164 => 'M d H:i:s', + self::RFC5424 => \DateTime::RFC3339, + self::RFC5424e => \DateTime::RFC3339_EXTENDED, + ]; + + protected UdpSocket $socket; + protected string $ident; + /** @var self::RFC* */ + protected int $rfc; + + /** + * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) + * @param int $port Port number, or 0 if $host is a unix socket + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $ident Program name or tag for each log message. + * @param int $rfc RFC to format the message for. + * @throws MissingExtensionException when there is no socket extension + * + * @phpstan-param self::RFC* $rfc + */ + public function __construct(string $host, int $port = 514, string|int $facility = LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424) + { + if (!\extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); + } + + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->rfc = $rfc; + + $this->socket = new UdpSocket($host, $port); + } + + protected function write(LogRecord $record): void + { + $lines = $this->splitMessageIntoLines($record->formatted); + + $header = $this->makeCommonSyslogHeader($this->toSyslogPriority($record->level), $record->datetime); + + foreach ($lines as $line) { + $this->socket->write($line, $header); + } + } + + public function close(): void + { + $this->socket->close(); + } + + /** + * @param string|string[] $message + * @return string[] + */ + private function splitMessageIntoLines($message): array + { + if (\is_array($message)) { + $message = implode("\n", $message); + } + + $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); + if (false === $lines) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . preg_last_error_msg()); + } + + return $lines; + } + + /** + * Make common syslog header (see rfc5424 or rfc3164) + */ + protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string + { + $priority = $severity + $this->facility; + + $pid = getmypid(); + if (false === $pid) { + $pid = '-'; + } + + $hostname = gethostname(); + if (false === $hostname) { + $hostname = '-'; + } + + if ($this->rfc === self::RFC3164) { + // see https://github.com/phpstan/phpstan/issues/5348 + // @phpstan-ignore-next-line + $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); + $date = $dateNew->format($this->dateFormats[$this->rfc]); + + return "<$priority>" . + $date . " " . + $hostname . " " . + $this->ident . "[" . $pid . "]: "; + } + + $date = $datetime->format($this->dateFormats[$this->rfc]); + + return "<$priority>1 " . + $date . " " . + $hostname . " " . + $this->ident . " " . + $pid . " - - "; + } + + /** + * Inject your own socket, mainly used for testing + * + * @return $this + */ + public function setSocket(UdpSocket $socket): self + { + $this->socket = $socket; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php new file mode 100644 index 000000000..e6b23bc48 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RuntimeException; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Handler sends logs to Telegram using Telegram Bot API. + * + * How to use: + * 1) Create a Telegram bot with https://telegram.me/BotFather; + * 2) Create a Telegram channel or a group where logs will be recorded; + * 3) Add the created bot from step 1 to the created channel/group from step 2. + * + * In order to create an instance of TelegramBotHandler use + * 1. The Telegram bot API key from step 1 + * 2. The channel name with the `@` prefix if you created a public channel (e.g. `@my_public_channel`), + * or the channel ID with the `-100` prefix if you created a private channel (e.g. `-1001234567890`), + * or the group ID from step 2 (e.g. `-1234567890`). + * + * @link https://core.telegram.org/bots/api + * + * @author Mazur Alexandr + */ +class TelegramBotHandler extends AbstractProcessingHandler +{ + private const BOT_API = 'https://api.telegram.org/bot'; + + /** + * The available values of parseMode according to the Telegram api documentation + */ + private const AVAILABLE_PARSE_MODES = [ + 'HTML', + 'MarkdownV2', + 'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead + ]; + + /** + * The maximum number of characters allowed in a message according to the Telegram api documentation + */ + private const MAX_MESSAGE_LENGTH = 4096; + + /** + * Telegram bot access token provided by BotFather. + * Create telegram bot with https://telegram.me/BotFather and use access token from it. + */ + private string $apiKey; + + /** + * Telegram channel name. + * Since to start with '@' symbol as prefix. + */ + private string $channel; + + /** + * The kind of formatting that is used for the message. + * See available options at https://core.telegram.org/bots/api#formatting-options + * or in AVAILABLE_PARSE_MODES + */ + private string|null $parseMode; + + /** + * Disables link previews for links in the message. + */ + private bool|null $disableWebPagePreview; + + /** + * Sends the message silently. Users will receive a notification with no sound. + */ + private bool|null $disableNotification; + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + */ + private bool $splitLongMessages; + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + */ + private bool $delayBetweenMessages; + + /** + * Telegram message thread id, unique identifier for the target message thread (topic) of the forum; for forum supergroups only + * See how to get the `message_thread_id` https://stackoverflow.com/a/75178418 + */ + private int|null $topic; + + /** + * @param string $apiKey Telegram bot access token provided by BotFather + * @param string $channel Telegram channel name + * @param bool $splitLongMessages Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages + * @param bool $delayBetweenMessages Adds delay between sending a split message according to Telegram API + * @param int $topic Telegram message thread id, unique identifier for the target message thread (topic) of the forum + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct( + string $apiKey, + string $channel, + $level = Level::Debug, + bool $bubble = true, + ?string $parseMode = null, + ?bool $disableWebPagePreview = null, + ?bool $disableNotification = null, + bool $splitLongMessages = false, + bool $delayBetweenMessages = false, + ?int $topic = null + ) { + if (!\extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler'); + } + + parent::__construct($level, $bubble); + + $this->apiKey = $apiKey; + $this->channel = $channel; + $this->setParseMode($parseMode); + $this->disableWebPagePreview($disableWebPagePreview); + $this->disableNotification($disableNotification); + $this->splitLongMessages($splitLongMessages); + $this->delayBetweenMessages($delayBetweenMessages); + $this->setTopic($topic); + } + + /** + * @return $this + */ + public function setParseMode(string|null $parseMode = null): self + { + if ($parseMode !== null && !\in_array($parseMode, self::AVAILABLE_PARSE_MODES, true)) { + throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.'); + } + + $this->parseMode = $parseMode; + + return $this; + } + + /** + * @return $this + */ + public function disableWebPagePreview(bool|null $disableWebPagePreview = null): self + { + $this->disableWebPagePreview = $disableWebPagePreview; + + return $this; + } + + /** + * @return $this + */ + public function disableNotification(bool|null $disableNotification = null): self + { + $this->disableNotification = $disableNotification; + + return $this; + } + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * + * @return $this + */ + public function splitLongMessages(bool $splitLongMessages = false): self + { + $this->splitLongMessages = $splitLongMessages; + + return $this; + } + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * + * @return $this + */ + public function delayBetweenMessages(bool $delayBetweenMessages = false): self + { + $this->delayBetweenMessages = $delayBetweenMessages; + + return $this; + } + + /** + * @return $this + */ + public function setTopic(?int $topic = null): self + { + $this->topic = $topic; + + return $this; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $messages = []; + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $messages[] = $record; + } + + if (\count($messages) > 0) { + $this->send((string) $this->getFormatter()->formatBatch($messages)); + } + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->send($record->formatted); + } + + /** + * Send request to @link https://api.telegram.org/bot on SendMessage action. + */ + protected function send(string $message): void + { + $messages = $this->handleMessageLength($message); + + foreach ($messages as $key => $msg) { + if ($this->delayBetweenMessages && $key > 0) { + sleep(1); + } + + $this->sendCurl($msg); + } + } + + protected function sendCurl(string $message): void + { + if ('' === trim($message)) { + return; + } + + $ch = curl_init(); + $url = self::BOT_API . $this->apiKey . '/SendMessage'; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + $params = [ + 'text' => $message, + 'chat_id' => $this->channel, + 'parse_mode' => $this->parseMode, + 'disable_web_page_preview' => $this->disableWebPagePreview, + 'disable_notification' => $this->disableNotification, + ]; + if ($this->topic !== null) { + $params['message_thread_id'] = $this->topic; + } + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); + + $result = Curl\Util::execute($ch); + if (!\is_string($result)) { + throw new RuntimeException('Telegram API error. Description: No response'); + } + $result = json_decode($result, true); + + if ($result['ok'] === false) { + throw new RuntimeException('Telegram API error. Description: ' . $result['description']); + } + } + + /** + * Handle a message that is too long: truncates or splits into several + * @return string[] + */ + private function handleMessageLength(string $message): array + { + $truncatedMarker = ' (…truncated)'; + if (!$this->splitLongMessages && \strlen($message) > self::MAX_MESSAGE_LENGTH) { + return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - \strlen($truncatedMarker)) . $truncatedMarker]; + } + + return str_split($message, self::MAX_MESSAGE_LENGTH); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 000000000..cb95e79c0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; +use NoDiscard; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + * + * @method bool hasEmergency(array{message: string, context?: mixed[]}|string $recordAssertions) + * @method bool hasAlert(array{message: string, context?: mixed[]}|string $recordAssertions) + * @method bool hasCritical(array{message: string, context?: mixed[]}|string $recordAssertions) + * @method bool hasError(array{message: string, context?: mixed[]}|string $recordAssertions) + * @method bool hasWarning(array{message: string, context?: mixed[]}|string $recordAssertions) + * @method bool hasNotice(array{message: string, context?: mixed[]}|string $recordAssertions) + * @method bool hasInfo(array{message: string, context?: mixed[]}|string $recordAssertions) + * @method bool hasDebug(array{message: string, context?: mixed[]}|string $recordAssertions) + * + * @method bool hasEmergencyRecords() + * @method bool hasAlertRecords() + * @method bool hasCriticalRecords() + * @method bool hasErrorRecords() + * @method bool hasWarningRecords() + * @method bool hasNoticeRecords() + * @method bool hasInfoRecords() + * @method bool hasDebugRecords() + * + * @method bool hasEmergencyThatContains(string $message) + * @method bool hasAlertThatContains(string $message) + * @method bool hasCriticalThatContains(string $message) + * @method bool hasErrorThatContains(string $message) + * @method bool hasWarningThatContains(string $message) + * @method bool hasNoticeThatContains(string $message) + * @method bool hasInfoThatContains(string $message) + * @method bool hasDebugThatContains(string $message) + * + * @method bool hasEmergencyThatMatches(string $regex) + * @method bool hasAlertThatMatches(string $regex) + * @method bool hasCriticalThatMatches(string $regex) + * @method bool hasErrorThatMatches(string $regex) + * @method bool hasWarningThatMatches(string $regex) + * @method bool hasNoticeThatMatches(string $regex) + * @method bool hasInfoThatMatches(string $regex) + * @method bool hasDebugThatMatches(string $regex) + * + * @method bool hasEmergencyThatPasses(callable $predicate) + * @method bool hasAlertThatPasses(callable $predicate) + * @method bool hasCriticalThatPasses(callable $predicate) + * @method bool hasErrorThatPasses(callable $predicate) + * @method bool hasWarningThatPasses(callable $predicate) + * @method bool hasNoticeThatPasses(callable $predicate) + * @method bool hasInfoThatPasses(callable $predicate) + * @method bool hasDebugThatPasses(callable $predicate) + */ +class TestHandler extends AbstractProcessingHandler +{ + /** @var LogRecord[] */ + protected array $records = []; + /** @phpstan-var array, LogRecord[]> */ + protected array $recordsByLevel = []; + private bool $skipReset = false; + + /** + * @return array + */ + #[NoDiscard] + public function getRecords(): array + { + return $this->records; + } + + public function clear(): void + { + $this->records = []; + $this->recordsByLevel = []; + } + + public function reset(): void + { + if (!$this->skipReset) { + $this->clear(); + } + } + + public function setSkipReset(bool $skipReset): void + { + $this->skipReset = $skipReset; + } + + /** + * @param int|string|Level|LogLevel::* $level Logging level value or name + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + #[NoDiscard] + public function hasRecords(int|string|Level $level): bool + { + return isset($this->recordsByLevel[Logger::toMonologLevel($level)->value]); + } + + /** + * @param string|array $recordAssertions Either a message string or an array containing message and optionally context keys that will be checked against all records + * + * @phpstan-param array{message: string, context?: mixed[]}|string $recordAssertions + */ + #[NoDiscard] + public function hasRecord(string|array $recordAssertions, Level $level): bool + { + if (\is_string($recordAssertions)) { + $recordAssertions = ['message' => $recordAssertions]; + } + + return $this->hasRecordThatPasses(function (LogRecord $rec) use ($recordAssertions) { + if ($rec->message !== $recordAssertions['message']) { + return false; + } + if (isset($recordAssertions['context']) && $rec->context !== $recordAssertions['context']) { + return false; + } + + return true; + }, $level); + } + + #[NoDiscard] + public function hasRecordThatContains(string $message, Level $level): bool + { + return $this->hasRecordThatPasses(fn (LogRecord $rec) => str_contains($rec->message, $message), $level); + } + + #[NoDiscard] + public function hasRecordThatMatches(string $regex, Level $level): bool + { + return $this->hasRecordThatPasses(fn (LogRecord $rec) => preg_match($regex, $rec->message) > 0, $level); + } + + /** + * @phpstan-param callable(LogRecord, int): mixed $predicate + */ + #[NoDiscard] + public function hasRecordThatPasses(callable $predicate, Level $level): bool + { + $level = Logger::toMonologLevel($level); + + if (!isset($this->recordsByLevel[$level->value])) { + return false; + } + + foreach ($this->recordsByLevel[$level->value] as $i => $rec) { + if ((bool) $predicate($rec, $i)) { + return true; + } + } + + return false; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->recordsByLevel[$record->level->value][] = $record; + $this->records[] = $record; + } + + /** + * @param mixed[] $args + */ + #[NoDiscard] + public function __call(string $method, array $args): bool + { + if ((bool) preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches)) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = \constant(Level::class.'::' . $matches[2]); + $callback = [$this, $genericMethod]; + if (\is_callable($callback)) { + $args[] = $level; + + return \call_user_func_array($callback, $args); + } + } + + throw new \BadMethodCallException('Call to undefined method ' . \get_class($this) . '::' . $method . '()'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php new file mode 100644 index 000000000..9c12c3d56 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +trait WebRequestRecognizerTrait +{ + /** + * Checks if PHP's serving a web request + */ + protected function isWebRequest(): bool + { + return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php new file mode 100644 index 000000000..932fa70e7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\LogRecord; +use Throwable; + +/** + * Forwards records to multiple handlers suppressing failures of each handler + * and continuing through to give every handler a chance to succeed. + * + * @author Craig D'Amelio + */ +class WhatFailureGroupHandler extends GroupHandler +{ + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + foreach ($this->handlers as $handler) { + try { + $handler->handle(clone $record); + } catch (Throwable) { + // What failure? + } + } + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if (\count($this->processors) > 0) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch(array_map(fn ($record) => clone $record, $records)); + } catch (Throwable) { + // What failure? + } + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + foreach ($this->handlers as $handler) { + try { + $handler->close(); + } catch (\Throwable $e) { + // What failure? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 000000000..8841f2f14 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + * @author Jason Davis + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * @throws MissingExtensionException + */ + public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) + { + if (!\function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException( + 'You must have Zend Server installed with Zend Monitor enabled in order to use this handler' + ); + } + + parent::__construct($level, $bubble); + } + + /** + * Translates Monolog log levels to ZendMonitor levels. + */ + protected function toZendMonitorLevel(Level $level): int + { + return match ($level) { + Level::Debug => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Level::Info => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Level::Notice => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Level::Warning => \ZEND_MONITOR_EVENT_SEVERITY_WARNING, + Level::Error => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Level::Critical => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Level::Alert => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Level::Emergency => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + }; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->writeZendMonitorCustomEvent( + $record->level->getName(), + $record->message, + $record->formatted, + $this->toZendMonitorLevel($record->level) + ); + } + + /** + * Write to Zend Monitor Events + * @param string $type Text displayed in "Class Name (custom)" field + * @param string $message Text displayed in "Error String" + * @param array $formatted Displayed in Custom Variables tab + * @param int $severity Set the event severity level (-1,0,1) + */ + protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity): void + { + zend_monitor_custom_event($type, $message, $formatted, $severity); + } + + /** + * @inheritDoc + */ + public function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/JsonSerializableDateTimeImmutable.php b/vendor/monolog/monolog/src/Monolog/JsonSerializableDateTimeImmutable.php new file mode 100644 index 000000000..de2cc5e96 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/JsonSerializableDateTimeImmutable.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use DateTimeZone; + +/** + * Overrides default json encoding of date time objects + * + * @author Menno Holtkamp + * @author Jordi Boggiano + */ +class JsonSerializableDateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable +{ + private bool $useMicroseconds; + + public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null) + { + $this->useMicroseconds = $useMicroseconds; + + // if you like to use a custom time to pass to Logger::addRecord directly, + // call modify() or setTimestamp() on this instance to change the date after creating it + parent::__construct('now', $timezone); + } + + public function jsonSerialize(): string + { + if ($this->useMicroseconds) { + return $this->format('Y-m-d\TH:i:s.uP'); + } + + return $this->format('Y-m-d\TH:i:sP'); + } + + public function __toString(): string + { + return $this->jsonSerialize(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Level.php b/vendor/monolog/monolog/src/Monolog/Level.php new file mode 100644 index 000000000..38a74fb8f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Level.php @@ -0,0 +1,209 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LogLevel; + +/** + * Represents the log levels + * + * Monolog supports the logging levels described by RFC 5424 {@see https://datatracker.ietf.org/doc/html/rfc5424} + * but due to BC the severity values used internally are not 0-7. + * + * To get the level name/value out of a Level there are several options: + * + * - Use ->getName() to get the standard Monolog name which is full uppercased (e.g. "DEBUG") + * - Use ->toPsrLogLevel() to get the standard PSR-3 name which is full lowercased (e.g. "debug") + * - Use ->toRFC5424Level() to get the standard RFC 5424 value (e.g. 7 for debug, 0 for emergency) + * - Use ->name to get the enum case's name which is capitalized (e.g. "Debug") + * + * To get the internal value for filtering, if the includes/isLowerThan/isHigherThan methods are + * not enough, you can use ->value to get the enum case's integer value. + */ +enum Level: int +{ + /** + * Detailed debug information + */ + case Debug = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + case Info = 200; + + /** + * Uncommon events + */ + case Notice = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + case Warning = 300; + + /** + * Runtime errors + */ + case Error = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + case Critical = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + case Alert = 550; + + /** + * Urgent alert. + */ + case Emergency = 600; + + /** + * @param value-of|LogLevel::*|'Debug'|'Info'|'Notice'|'Warning'|'Error'|'Critical'|'Alert'|'Emergency' $name + * @return static + */ + public static function fromName(string $name): self + { + return match (strtolower($name)) { + 'debug' => self::Debug, + 'info' => self::Info, + 'notice' => self::Notice, + 'warning' => self::Warning, + 'error' => self::Error, + 'critical' => self::Critical, + 'alert' => self::Alert, + 'emergency' => self::Emergency, + }; + } + + /** + * @param value-of $value + * @return static + */ + public static function fromValue(int $value): self + { + return self::from($value); + } + + /** + * Returns true if the passed $level is higher or equal to $this + */ + public function includes(Level $level): bool + { + return $this->value <= $level->value; + } + + public function isHigherThan(Level $level): bool + { + return $this->value > $level->value; + } + + public function isLowerThan(Level $level): bool + { + return $this->value < $level->value; + } + + /** + * Returns the monolog standardized all-capitals name of the level + * + * Use this instead of $level->name which returns the enum case name (e.g. Debug vs DEBUG if you use getName()) + * + * @return value-of + */ + public function getName(): string + { + return match ($this) { + self::Debug => 'DEBUG', + self::Info => 'INFO', + self::Notice => 'NOTICE', + self::Warning => 'WARNING', + self::Error => 'ERROR', + self::Critical => 'CRITICAL', + self::Alert => 'ALERT', + self::Emergency => 'EMERGENCY', + }; + } + + /** + * Returns the PSR-3 level matching this instance + * + * @phpstan-return \Psr\Log\LogLevel::* + */ + public function toPsrLogLevel(): string + { + return match ($this) { + self::Debug => LogLevel::DEBUG, + self::Info => LogLevel::INFO, + self::Notice => LogLevel::NOTICE, + self::Warning => LogLevel::WARNING, + self::Error => LogLevel::ERROR, + self::Critical => LogLevel::CRITICAL, + self::Alert => LogLevel::ALERT, + self::Emergency => LogLevel::EMERGENCY, + }; + } + + /** + * Returns the RFC 5424 level matching this instance + * + * @phpstan-return int<0, 7> + */ + public function toRFC5424Level(): int + { + return match ($this) { + self::Debug => 7, + self::Info => 6, + self::Notice => 5, + self::Warning => 4, + self::Error => 3, + self::Critical => 2, + self::Alert => 1, + self::Emergency => 0, + }; + } + + public const VALUES = [ + 100, + 200, + 250, + 300, + 400, + 500, + 550, + 600, + ]; + + public const NAMES = [ + 'DEBUG', + 'INFO', + 'NOTICE', + 'WARNING', + 'ERROR', + 'CRITICAL', + 'ALERT', + 'EMERGENCY', + ]; +} diff --git a/vendor/monolog/monolog/src/Monolog/LogRecord.php b/vendor/monolog/monolog/src/Monolog/LogRecord.php new file mode 100644 index 000000000..14c82f304 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/LogRecord.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use ArrayAccess; + +/** + * Monolog log record + * + * @author Jordi Boggiano + * @template-implements ArrayAccess<'message'|'level'|'context'|'level_name'|'channel'|'datetime'|'extra'|'formatted', int|string|\DateTimeImmutable|array> + */ +class LogRecord implements ArrayAccess +{ + private const MODIFIABLE_FIELDS = [ + 'extra' => true, + 'formatted' => true, + ]; + + public function __construct( + public readonly \DateTimeImmutable $datetime, + public readonly string $channel, + public readonly Level $level, + public readonly string $message, + /** @var array */ + public readonly array $context = [], + /** @var array */ + public array $extra = [], + public mixed $formatted = null, + ) { + } + + public function offsetSet(mixed $offset, mixed $value): void + { + if ($offset === 'extra') { + if (!\is_array($value)) { + throw new \InvalidArgumentException('extra must be an array'); + } + + $this->extra = $value; + + return; + } + + if ($offset === 'formatted') { + $this->formatted = $value; + + return; + } + + throw new \LogicException('Unsupported operation: setting '.$offset); + } + + public function offsetExists(mixed $offset): bool + { + if ($offset === 'level_name') { + return true; + } + + return isset($this->{$offset}); + } + + public function offsetUnset(mixed $offset): void + { + throw new \LogicException('Unsupported operation'); + } + + public function &offsetGet(mixed $offset): mixed + { + // handle special cases for the level enum + if ($offset === 'level_name') { + // avoid returning readonly props by ref as this is illegal + $copy = $this->level->getName(); + + return $copy; + } + if ($offset === 'level') { + // avoid returning readonly props by ref as this is illegal + $copy = $this->level->value; + + return $copy; + } + + if (isset(self::MODIFIABLE_FIELDS[$offset])) { + return $this->{$offset}; + } + + // avoid returning readonly props by ref as this is illegal + $copy = $this->{$offset}; + + return $copy; + } + + /** + * @phpstan-return array{message: string, context: mixed[], level: value-of, level_name: value-of, channel: string, datetime: \DateTimeImmutable, extra: mixed[]} + */ + public function toArray(): array + { + return [ + 'message' => $this->message, + 'context' => $this->context, + 'level' => $this->level->value, + 'level_name' => $this->level->getName(), + 'channel' => $this->channel, + 'datetime' => $this->datetime, + 'extra' => $this->extra, + ]; + } + + public function with(mixed ...$args): self + { + foreach (['message', 'context', 'level', 'channel', 'datetime', 'extra'] as $prop) { + $args[$prop] ??= $this->{$prop}; + } + + return new self(...$args); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 000000000..e545c4487 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,751 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Closure; +use DateTimeZone; +use Fiber; +use Monolog\Handler\HandlerInterface; +use Monolog\Processor\ProcessorInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Throwable; +use Stringable; +use WeakMap; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + * @final + */ +class Logger implements LoggerInterface, ResettableInterface +{ + /** + * Detailed debug information + * + * @deprecated Use \Monolog\Level::Debug + */ + public const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + * + * @deprecated Use \Monolog\Level::Info + */ + public const INFO = 200; + + /** + * Uncommon events + * + * @deprecated Use \Monolog\Level::Notice + */ + public const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + * + * @deprecated Use \Monolog\Level::Warning + */ + public const WARNING = 300; + + /** + * Runtime errors + * + * @deprecated Use \Monolog\Level::Error + */ + public const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + * + * @deprecated Use \Monolog\Level::Critical + */ + public const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + * + * @deprecated Use \Monolog\Level::Alert + */ + public const ALERT = 550; + + /** + * Urgent alert. + * + * @deprecated Use \Monolog\Level::Emergency + */ + public const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + */ + public const API = 3; + + /** + * Mapping between levels numbers defined in RFC 5424 and Monolog ones + * + * @phpstan-var array $rfc_5424_levels + */ + private const RFC_5424_LEVELS = [ + 7 => Level::Debug, + 6 => Level::Info, + 5 => Level::Notice, + 4 => Level::Warning, + 3 => Level::Error, + 2 => Level::Critical, + 1 => Level::Alert, + 0 => Level::Emergency, + ]; + + protected string $name; + + /** + * The handler stack + * + * @var list + */ + protected array $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var array<(callable(LogRecord): LogRecord)|ProcessorInterface> + */ + protected array $processors; + + protected bool $microsecondTimestamps = true; + + protected DateTimeZone $timezone; + + protected Closure|null $exceptionHandler = null; + + /** + * Keeps track of depth to prevent infinite logging loops + */ + private int $logDepth = 0; + + /** + * @var WeakMap, int> Keeps track of depth inside fibers to prevent infinite logging loops + */ + private WeakMap $fiberLogDepth; + + /** + * Whether to detect infinite logging loops + * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this + */ + private bool $detectCycles = true; + + /** + * @param string $name The logging channel, a simple descriptive name that is attached to all log records + * @param list $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + * @param DateTimeZone|null $timezone Optional timezone, if not provided date_default_timezone_get() will be used + * + * @phpstan-param array<(callable(LogRecord): LogRecord)|ProcessorInterface> $processors + */ + public function __construct(string $name, array $handlers = [], array $processors = [], DateTimeZone|null $timezone = null) + { + $this->name = $name; + $this->setHandlers($handlers); + $this->processors = $processors; + $this->timezone = $timezone ?? new DateTimeZone(date_default_timezone_get()); + $this->fiberLogDepth = new \WeakMap(); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + * + * @return static + */ + public function withName(string $name): self + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + * + * @return $this + */ + public function pushHandler(HandlerInterface $handler): self + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @throws \LogicException If empty handler stack + */ + public function popHandler(): HandlerInterface + { + if (0 === \count($this->handlers)) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param list $handlers + * @return $this + */ + public function setHandlers(array $handlers): self + { + $this->handlers = []; + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return list + */ + public function getHandlers(): array + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + * + * @phpstan-param ProcessorInterface|(callable(LogRecord): LogRecord) $callback + * @return $this + */ + public function pushProcessor(ProcessorInterface|callable $callback): self + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @phpstan-return ProcessorInterface|(callable(LogRecord): LogRecord) + * @throws \LogicException If empty processor stack + */ + public function popProcessor(): callable + { + if (0 === \count($this->processors)) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + * @phpstan-return array + */ + public function getProcessors(): array + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * As of PHP7.1 microseconds are always included by the engine, so + * there is no performance penalty and Monolog 2 enabled microseconds + * by default. This function lets you disable them though in case you want + * to suppress microseconds from the output. + * + * @param bool $micro True to use microtime() to create timestamps + * @return $this + */ + public function useMicrosecondTimestamps(bool $micro): self + { + $this->microsecondTimestamps = $micro; + + return $this; + } + + /** + * @return $this + */ + public function useLoggingLoopDetection(bool $detectCycles): self + { + $this->detectCycles = $detectCycles; + + return $this; + } + + /** + * Adds a log record. + * + * @param int $level The logging level (a Monolog or RFC 5424 level) + * @param string $message The log message + * @param mixed[] $context The log context + * @param JsonSerializableDateTimeImmutable|null $datetime Optional log date to log into the past or future + * + * @return bool Whether the record has been processed + * + * @phpstan-param value-of|Level $level + */ + public function addRecord(int|Level $level, string $message, array $context = [], JsonSerializableDateTimeImmutable|null $datetime = null): bool + { + if (\is_int($level) && isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; + } + + if ($this->detectCycles) { + if (null !== ($fiber = Fiber::getCurrent())) { + $logDepth = $this->fiberLogDepth[$fiber] = ($this->fiberLogDepth[$fiber] ?? 0) + 1; + } else { + $logDepth = ++$this->logDepth; + } + } else { + $logDepth = 0; + } + + if ($logDepth === 3) { + $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.'); + + return false; + } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above + return false; + } + + try { + $recordInitialized = \count($this->processors) === 0; + + $record = new LogRecord( + datetime: $datetime ?? new JsonSerializableDateTimeImmutable($this->microsecondTimestamps, $this->timezone), + channel: $this->name, + level: self::toMonologLevel($level), + message: $message, + context: $context, + extra: [], + ); + $handled = false; + + foreach ($this->handlers as $handler) { + if (false === $recordInitialized) { + // skip initializing the record as long as no handler is going to handle it + if (!$handler->isHandling($record)) { + continue; + } + + try { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + $recordInitialized = true; + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + + // once the record is initialized, send it to all handlers as long as the bubbling chain is not interrupted + try { + $handled = true; + if (true === $handler->handle(clone $record)) { + break; + } + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + + return $handled; + } finally { + if ($this->detectCycles) { + if (isset($fiber)) { + $this->fiberLogDepth[$fiber]--; + } else { + $this->logDepth--; + } + } + } + } + + /** + * Ends a log cycle and frees all resources used by handlers. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * Handlers that have been closed should be able to accept log records again and re-open + * themselves on demand, but this may not always be possible depending on implementation. + * + * This is useful at the end of a request and will be called automatically on every handler + * when they get destructed. + */ + public function close(): void + { + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + + /** + * Ends a log cycle and resets all handlers and processors to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + */ + public function reset(): void + { + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Gets the name of the logging level as a string. + * + * This still returns a string instead of a Level for BC, but new code should not rely on this method. + * + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param value-of|Level $level + * @phpstan-return value-of + * + * @deprecated Since 3.0, use {@see toMonologLevel} or {@see \Monolog\Level->getName()} instead + */ + public static function getLevelName(int|Level $level): string + { + return self::toMonologLevel($level)->getName(); + } + + /** + * Converts PSR-3 levels to Monolog ones if necessary + * + * @param int|string|Level|LogLevel::* $level Level number (monolog) or name (PSR-3) + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public static function toMonologLevel(string|int|Level $level): Level + { + if ($level instanceof Level) { + return $level; + } + + if (\is_string($level)) { + if (is_numeric($level)) { + $levelEnum = Level::tryFrom((int) $level); + if ($levelEnum === null) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES)); + } + + return $levelEnum; + } + + // Contains first char of all log levels and avoids using strtoupper() which may have + // strange results depending on locale (for example, "i" will become "İ" in Turkish locale) + $upper = strtr(substr($level, 0, 1), 'dinweca', 'DINWECA') . strtolower(substr($level, 1)); + if (\defined(Level::class.'::'.$upper)) { + return \constant(Level::class . '::' . $upper); + } + + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES)); + } + + $levelEnum = Level::tryFrom($level); + if ($levelEnum === null) { + throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES)); + } + + return $levelEnum; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function isHandling(int|string|Level $level): bool + { + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable($this->microsecondTimestamps, $this->timezone), + channel: $this->name, + message: '', + level: self::toMonologLevel($level), + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Set a custom exception handler that will be called if adding a new record fails + * + * The Closure will receive an exception object and the record that failed to be logged + * + * @return $this + */ + public function setExceptionHandler(Closure|null $callback): self + { + $this->exceptionHandler = $callback; + + return $this; + } + + public function getExceptionHandler(): Closure|null + { + return $this->exceptionHandler; + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level (a Monolog, PSR-3 or RFC 5424 level) + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + * + * @phpstan-param Level|LogLevel::* $level + */ + public function log($level, string|\Stringable $message, array $context = []): void + { + if (!$level instanceof Level) { + if (!\is_string($level) && !\is_int($level)) { + throw new \InvalidArgumentException('$level is expected to be a string, int or '.Level::class.' instance'); + } + + if (isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; + } + + $level = static::toMonologLevel($level); + } + + $this->addRecord($level, (string) $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function debug(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Debug, (string) $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function info(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Info, (string) $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function notice(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Notice, (string) $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function warning(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Warning, (string) $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function error(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Error, (string) $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function critical(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Critical, (string) $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function alert(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Alert, (string) $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function emergency(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Emergency, (string) $message, $context); + } + + /** + * Sets the timezone to be used for the timestamp of log records. + * + * @return $this + */ + public function setTimezone(DateTimeZone $tz): self + { + $this->timezone = $tz; + + return $this; + } + + /** + * Returns the timezone to be used for the timestamp of log records. + */ + public function getTimezone(): DateTimeZone + { + return $this->timezone; + } + + /** + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. + */ + protected function handleException(Throwable $e, LogRecord $record): void + { + if (null === $this->exceptionHandler) { + throw $e; + } + + ($this->exceptionHandler)($e, $record); + } + + /** + * @return array + */ + public function __serialize(): array + { + return [ + 'name' => $this->name, + 'handlers' => $this->handlers, + 'processors' => $this->processors, + 'microsecondTimestamps' => $this->microsecondTimestamps, + 'timezone' => $this->timezone, + 'exceptionHandler' => $this->exceptionHandler, + 'logDepth' => $this->logDepth, + 'detectCycles' => $this->detectCycles, + ]; + } + + /** + * @param array $data + */ + public function __unserialize(array $data): void + { + foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) { + if (isset($data[$property])) { + $this->$property = $data[$property]; + } + } + + $this->fiberLogDepth = new \WeakMap(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php new file mode 100644 index 000000000..514b35478 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Generates a context from a Closure if the Closure is the only value + * in the context + * + * It helps reduce the performance impact of debug logs if they do + * need to create lots of context information. If this processor is added + * on the correct handler the context data will only be generated + * when the logs are actually logged to that handler, which is useful when + * using FingersCrossedHandler or other filtering handlers to conditionally + * log records. + */ +class ClosureContextProcessor implements ProcessorInterface +{ + public function __invoke(LogRecord $record): LogRecord + { + $context = $record->context; + if (isset($context[0]) && 1 === \count($context) && $context[0] instanceof \Closure) { + try { + $context = $context[0](); + } catch (\Throwable $e) { + $context = [ + 'error_on_context_generation' => $e->getMessage(), + 'exception' => $e, + ]; + } + + if (!\is_array($context)) { + $context = [$context]; + } + + $record = $record->with(context: $context); + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php new file mode 100644 index 000000000..6b25505de --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Injects Git branch and Git commit SHA in all records + * + * @author Nick Otter + * @author Jordi Boggiano + */ +class GitProcessor implements ProcessorInterface +{ + private Level $level; + /** @var array{branch: string, commit: string}|array|null */ + private static $cache = null; + + /** + * @param int|string|Level|LogLevel::* $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(int|string|Level $level = Level::Debug) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + // return if the level is not high enough + if ($record->level->isLowerThan($this->level)) { + return $record; + } + + $record->extra['git'] = self::getGitInfo(); + + return $record; + } + + /** + * @return array{branch: string, commit: string}|array + */ + private static function getGitInfo(): array + { + if (self::$cache !== null) { + return self::$cache; + } + + $branches = shell_exec('git branch -v --no-abbrev'); + if (\is_string($branches) && 1 === preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = [ + 'branch' => $matches[1], + 'commit' => $matches[2], + ]; + } + + return self::$cache = []; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php new file mode 100644 index 000000000..cba6e0963 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Injects value of gethostname in all records + */ +class HostnameProcessor implements ProcessorInterface +{ + private static string $host; + + public function __construct() + { + self::$host = (string) gethostname(); + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $record->extra['hostname'] = self::$host; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 000000000..b942dc055 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor implements ProcessorInterface +{ + protected Level $level; + + /** @var string[] */ + protected array $skipClassesPartials; + + protected int $skipStackFramesCount; + + protected const SKIP_FUNCTIONS = [ + 'call_user_func', + 'call_user_func_array', + ]; + + protected const SKIP_CLASSES = [ + 'Monolog\\', + ]; + + /** + * @param string|int|Level $level The minimum logging level at which this Processor will be triggered + * @param string[] $skipClassesPartials + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(int|string|Level $level = Level::Debug, array $skipClassesPartials = [], int $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(static::SKIP_CLASSES, $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + // return if the level is not high enough + if ($record->level->isLowerThan($this->level)) { + return $record; + } + + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + + continue 2; + } + } + } elseif (\in_array($trace[$i]['function'], self::SKIP_FUNCTIONS, true)) { + $i++; + + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record->extra = array_merge( + $record->extra, + [ + 'file' => $trace[$i - 1]['file'] ?? null, + 'line' => $trace[$i - 1]['line'] ?? null, + 'class' => $trace[$i]['class'] ?? null, + 'callType' => $trace[$i]['type'] ?? null, + 'function' => $trace[$i]['function'] ?? null, + ] + ); + + return $record; + } + + /** + * @param array $trace + */ + private function isTraceClassOrSkippedFunction(array $trace, int $index): bool + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || \in_array($trace[$index]['function'], self::SKIP_FUNCTIONS, true); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php new file mode 100644 index 000000000..762ed9142 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Injects sys_getloadavg in all records @see https://www.php.net/manual/en/function.sys-getloadavg.php + * + * @author Johan Vlaar + */ +class LoadAverageProcessor implements ProcessorInterface +{ + public const LOAD_1_MINUTE = 0; + public const LOAD_5_MINUTE = 1; + public const LOAD_15_MINUTE = 2; + + private const AVAILABLE_LOAD = [ + self::LOAD_1_MINUTE, + self::LOAD_5_MINUTE, + self::LOAD_15_MINUTE, + ]; + + /** + * @var int + */ + protected $avgSystemLoad; + + /** + * @param self::LOAD_* $avgSystemLoad + */ + public function __construct(int $avgSystemLoad = self::LOAD_1_MINUTE) + { + if (!\in_array($avgSystemLoad, self::AVAILABLE_LOAD, true)) { + throw new \InvalidArgumentException(sprintf('Invalid average system load: `%s`', $avgSystemLoad)); + } + $this->avgSystemLoad = $avgSystemLoad; + } + + /** + * {@inheritDoc} + */ + public function __invoke(LogRecord $record): LogRecord + { + if (!\function_exists('sys_getloadavg')) { + return $record; + } + $usage = sys_getloadavg(); + if (false === $usage) { + return $record; + } + + $record->extra['load_average'] = $usage[$this->avgSystemLoad]; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 000000000..adc32c65d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $usage = memory_get_peak_usage($this->realUsage); + + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record->extra['memory_peak_usage'] = $usage; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 000000000..f808e51b4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor implements ProcessorInterface +{ + /** + * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. + */ + protected bool $realUsage; + + /** + * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + protected bool $useFormatting; + + /** + * @param bool $realUsage Set this to true to get the real size of memory allocated from system. + * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + public function __construct(bool $realUsage = true, bool $useFormatting = true) + { + $this->realUsage = $realUsage; + $this->useFormatting = $useFormatting; + } + + /** + * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is + * + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int + */ + protected function formatBytes(int $bytes) + { + if (!$this->useFormatting) { + return $bytes; + } + + if ($bytes > 1024 * 1024) { + return round($bytes / 1024 / 1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes / 1024, 2).' KB'; + } + + return $bytes . ' B'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 000000000..a814b1df3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $usage = memory_get_usage($this->realUsage); + + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record->extra['memory_usage'] = $usage; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php new file mode 100644 index 000000000..3076a3d8c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder + */ +class MercurialProcessor implements ProcessorInterface +{ + private Level $level; + /** @var array{branch: string, revision: string}|array|null */ + private static $cache = null; + + /** + * @param int|string|Level $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(int|string|Level $level = Level::Debug) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + // return if the level is not high enough + if ($record->level->isLowerThan($this->level)) { + return $record; + } + + $record->extra['hg'] = self::getMercurialInfo(); + + return $record; + } + + /** + * @return array{branch: string, revision: string}|array + */ + private static function getMercurialInfo(): array + { + if (self::$cache !== null) { + return self::$cache; + } + + $result = explode(' ', trim((string) shell_exec('hg id -nb'))); + if (\count($result) >= 3) { + return self::$cache = [ + 'branch' => $result[1], + 'revision' => $result[2], + ]; + } + if (\count($result) === 2) { + return self::$cache = [ + 'branch' => $result[1], + 'revision' => $result[0], + ]; + } + + return self::$cache = []; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 000000000..bb9a52243 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor implements ProcessorInterface +{ + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $record->extra['process_id'] = getmypid(); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php new file mode 100644 index 000000000..ebe41fc20 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * An optional interface to allow labelling Monolog processors. + * + * @author Nicolas Grekas + */ +interface ProcessorInterface +{ + /** + * @return LogRecord The processed record + */ + public function __invoke(LogRecord $record); +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 000000000..291361d2f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor implements ProcessorInterface +{ + public const SIMPLE_DATE = "Y-m-d\TH:i:s.uP"; + + private ?string $dateFormat; + + private bool $removeUsedContextFields; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset + */ + public function __construct(?string $dateFormat = null, bool $removeUsedContextFields = false) + { + $this->dateFormat = $dateFormat; + $this->removeUsedContextFields = $removeUsedContextFields; + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + if (false === strpos($record->message, '{')) { + return $record; + } + + $replacements = []; + $context = $record->context; + + foreach ($context as $key => $val) { + $placeholder = '{' . $key . '}'; + if (strpos($record->message, $placeholder) === false) { + continue; + } + + if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, "__toString"))) { + $replacements[$placeholder] = $val; + } elseif ($val instanceof \DateTimeInterface) { + if (null === $this->dateFormat && $val instanceof \Monolog\JsonSerializableDateTimeImmutable) { + // handle monolog dates using __toString if no specific dateFormat was asked for + // so that it follows the useMicroseconds flag + $replacements[$placeholder] = (string) $val; + } else { + $replacements[$placeholder] = $val->format($this->dateFormat ?? static::SIMPLE_DATE); + } + } elseif ($val instanceof \UnitEnum) { + $replacements[$placeholder] = $val instanceof \BackedEnum ? $val->value : $val->name; + } elseif (\is_object($val)) { + $replacements[$placeholder] = '[object '.Utils::getClass($val).']'; + } elseif (\is_array($val)) { + $replacements[$placeholder] = 'array'.Utils::jsonEncode($val, null, true); + } else { + $replacements[$placeholder] = '['.\gettype($val).']'; + } + + if ($this->removeUsedContextFields) { + unset($context[$key]); + } + } + + return $record->with(message: strtr($record->message, $replacements), context: $context); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php new file mode 100644 index 000000000..f4e41ce20 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Adds a tags array into record + * + * @author Martijn Riemers + */ +class TagProcessor implements ProcessorInterface +{ + /** @var string[] */ + private array $tags; + + /** + * @param string[] $tags + */ + public function __construct(array $tags = []) + { + $this->setTags($tags); + } + + /** + * @param string[] $tags + * @return $this + */ + public function addTags(array $tags = []): self + { + $this->tags = array_merge($this->tags, $tags); + + return $this; + } + + /** + * @param string[] $tags + * @return $this + */ + public function setTags(array $tags = []): self + { + $this->tags = $tags; + + return $this; + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $record->extra['tags'] = $this->tags; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 000000000..261e65389 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\ResettableInterface; +use Monolog\LogRecord; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor implements ProcessorInterface, ResettableInterface +{ + /** @var non-empty-string */ + private string $uid; + + /** + * @param int<1, 32> $length + */ + public function __construct(int $length = 7) + { + if ($length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + $this->uid = $this->generateUid($length); + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $record->extra['uid'] = $this->uid; + + return $record; + } + + public function getUid(): string + { + return $this->uid; + } + + public function reset(): void + { + $this->uid = $this->generateUid(\strlen($this->uid)); + } + + /** + * @param positive-int $length + * @return non-empty-string + */ + private function generateUid(int $length): string + { + return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 000000000..b78385e0c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use ArrayAccess; +use Monolog\LogRecord; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor implements ProcessorInterface +{ + /** + * @var array|ArrayAccess + */ + protected array|ArrayAccess $serverData; + + /** + * Default fields + * + * Array is structured as [key in record.extra => key in $serverData] + * + * @var array + */ + protected array $extraFields = [ + 'url' => 'REQUEST_URI', + 'ip' => 'REMOTE_ADDR', + 'http_method' => 'REQUEST_METHOD', + 'server' => 'SERVER_NAME', + 'referrer' => 'HTTP_REFERER', + 'user_agent' => 'HTTP_USER_AGENT', + ]; + + /** + * @param array|ArrayAccess|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|array|null $extraFields Field names and the related key inside $serverData to be added (or just a list of field names to use the default configured $serverData mapping). If not provided it defaults to: [url, ip, http_method, server, referrer] + unique_id if present in server data + */ + public function __construct(array|ArrayAccess|null $serverData = null, array|null $extraFields = null) + { + if (null === $serverData) { + $this->serverData = &$_SERVER; + } else { + $this->serverData = $serverData; + } + + $defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer']; + if (isset($this->serverData['UNIQUE_ID'])) { + $this->extraFields['unique_id'] = 'UNIQUE_ID'; + $defaultEnabled[] = 'unique_id'; + } + + if (null === $extraFields) { + $extraFields = $defaultEnabled; + } + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!\in_array($fieldName, $extraFields, true)) { + unset($this->extraFields[$fieldName]); + } + } + } else { + $this->extraFields = $extraFields; + } + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record->extra = $this->appendExtraFields($record->extra); + + return $record; + } + + /** + * @return $this + */ + public function addExtraField(string $extraName, string $serverName): self + { + $this->extraFields[$extraName] = $serverName; + + return $this; + } + + /** + * @param mixed[] $extra + * @return mixed[] + */ + private function appendExtraFields(array $extra): array + { + foreach ($this->extraFields as $extraName => $serverName) { + $extra[$extraName] = $this->serverData[$serverName] ?? null; + } + + return $extra; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Registry.php b/vendor/monolog/monolog/src/Monolog/Registry.php new file mode 100644 index 000000000..2ef2edceb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Registry.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use InvalidArgumentException; + +/** + * Monolog log registry + * + * Allows to get `Logger` instances in the global scope + * via static method calls on this class. + * + * + * $application = new Monolog\Logger('application'); + * $api = new Monolog\Logger('api'); + * + * Monolog\Registry::addLogger($application); + * Monolog\Registry::addLogger($api); + * + * function testLogger() + * { + * Monolog\Registry::api()->error('Sent to $api Logger instance'); + * Monolog\Registry::application()->error('Sent to $application Logger instance'); + * } + * + * + * @author Tomas Tatarko + */ +class Registry +{ + /** + * List of all loggers in the registry (by named indexes) + * + * @var Logger[] + */ + private static array $loggers = []; + + /** + * Adds new logging channel to the registry + * + * @param Logger $logger Instance of the logging channel + * @param string|null $name Name of the logging channel ($logger->getName() by default) + * @param bool $overwrite Overwrite instance in the registry if the given name already exists? + * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + */ + public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = false): void + { + $name = $name ?? $logger->getName(); + + if (isset(self::$loggers[$name]) && !$overwrite) { + throw new InvalidArgumentException('Logger with the given name already exists'); + } + + self::$loggers[$name] = $logger; + } + + /** + * Checks if such logging channel exists by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function hasLogger($logger): bool + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } + + return isset(self::$loggers[$logger]); + } + + /** + * Removes instance from registry by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function removeLogger($logger): void + { + if ($logger instanceof Logger) { + if (false !== ($idx = array_search($logger, self::$loggers, true))) { + unset(self::$loggers[$idx]); + } + } else { + unset(self::$loggers[$logger]); + } + } + + /** + * Clears the registry + */ + public static function clear(): void + { + self::$loggers = []; + } + + /** + * Gets Logger instance from the registry + * + * @param string $name Name of the requested Logger instance + * @throws \InvalidArgumentException If named Logger instance is not in the registry + */ + public static function getInstance(string $name): Logger + { + if (!isset(self::$loggers[$name])) { + throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); + } + + return self::$loggers[$name]; + } + + /** + * Gets Logger instance from the registry via static method call + * + * @param string $name Name of the requested Logger instance + * @param mixed[] $arguments Arguments passed to static method call + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function __callStatic(string $name, array $arguments): Logger + { + return self::getInstance($name); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php new file mode 100644 index 000000000..4983a6b35 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +/** + * Handler or Processor implementing this interface will be reset when Logger::reset() is called. + * + * Resetting ends a log cycle gets them back to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + * + * @author Grégoire Pineau + */ +interface ResettableInterface +{ + public function reset(): void; +} diff --git a/vendor/monolog/monolog/src/Monolog/SignalHandler.php b/vendor/monolog/monolog/src/Monolog/SignalHandler.php new file mode 100644 index 000000000..b6a69fca0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/SignalHandler.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use ReflectionExtension; + +/** + * Monolog POSIX signal handler + * + * @author Robert Gust-Bardon + */ +class SignalHandler +{ + private LoggerInterface $logger; + + /** @var array SIG_DFL, SIG_IGN or previous callable */ + private array $previousSignalHandler = []; + /** @var array */ + private array $signalLevelMap = []; + /** @var array */ + private array $signalRestartSyscalls = []; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * @param int|string|Level $level Level or level name + * @return $this + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function registerSignalHandler(int $signo, int|string|Level $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self + { + if (!\extension_loaded('pcntl') || !\function_exists('pcntl_signal')) { + return $this; + } + + $level = Logger::toMonologLevel($level)->toPsrLogLevel(); + + if ($callPrevious) { + $handler = pcntl_signal_get_handler($signo); + $this->previousSignalHandler[$signo] = $handler; + } else { + unset($this->previousSignalHandler[$signo]); + } + $this->signalLevelMap[$signo] = $level; + $this->signalRestartSyscalls[$signo] = $restartSyscalls; + + if ($async !== null) { + pcntl_async_signals($async); + } + + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + + return $this; + } + + /** + * @param mixed $siginfo + */ + public function handleSignal(int $signo, $siginfo = null): void + { + /** @var array $signals */ + static $signals = []; + + if (\count($signals) === 0 && \extension_loaded('pcntl')) { + $pcntl = new ReflectionExtension('pcntl'); + foreach ($pcntl->getConstants() as $name => $value) { + if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && \is_int($value)) { + $signals[$value] = $name; + } + } + } + + $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; + $signal = $signals[$signo] ?? $signo; + $context = $siginfo ?? []; + $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); + + if (!isset($this->previousSignalHandler[$signo])) { + return; + } + + if ($this->previousSignalHandler[$signo] === SIG_DFL) { + if (\extension_loaded('pcntl') && \function_exists('pcntl_signal') && \function_exists('pcntl_sigprocmask') && \function_exists('pcntl_signal_dispatch') + && \extension_loaded('posix') && \function_exists('posix_getpid') && \function_exists('posix_kill') + ) { + $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + } + } elseif (\is_callable($this->previousSignalHandler[$signo])) { + $this->previousSignalHandler[$signo]($signo, $siginfo); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Test/MonologTestCase.php b/vendor/monolog/monolog/src/Monolog/Test/MonologTestCase.php new file mode 100644 index 000000000..34c7724ea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Test/MonologTestCase.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Test; + +use Monolog\Level; +use Monolog\Logger; +use Monolog\LogRecord; +use Monolog\JsonSerializableDateTimeImmutable; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; + +/** + * Lets you easily generate log records and a dummy formatter for testing purposes + * + * @author Jordi Boggiano + */ +class MonologTestCase extends \PHPUnit\Framework\TestCase +{ + /** + * @param array $context + * @param array $extra + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + protected function getRecord(int|string|Level $level = Level::Warning, string|\Stringable $message = 'test', array $context = [], string $channel = 'test', \DateTimeImmutable $datetime = new JsonSerializableDateTimeImmutable(true), array $extra = []): LogRecord + { + return new LogRecord( + message: (string) $message, + context: $context, + level: Logger::toMonologLevel($level), + channel: $channel, + datetime: $datetime, + extra: $extra, + ); + } + + /** + * @phpstan-return list + */ + protected function getMultipleRecords(): array + { + return [ + $this->getRecord(Level::Debug, 'debug message 1'), + $this->getRecord(Level::Debug, 'debug message 2'), + $this->getRecord(Level::Info, 'information'), + $this->getRecord(Level::Warning, 'warning'), + $this->getRecord(Level::Error, 'error'), + ]; + } + + protected function getIdentityFormatter(): FormatterInterface + { + $formatter = $this->createMock(FormatterInterface::class); + $formatter->expects(self::any()) + ->method('format') + ->willReturnCallback(function ($record) { + return $record->message; + }); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Test/TestCase.php b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php new file mode 100644 index 000000000..bf40b31fc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Test; + +/** + * Lets you easily generate log records and a dummy formatter for testing purposes + * + * @author Jordi Boggiano + * + * @deprecated use MonologTestCase instead. + */ +class TestCase extends MonologTestCase +{ +} diff --git a/vendor/monolog/monolog/src/Monolog/Utils.php b/vendor/monolog/monolog/src/Monolog/Utils.php new file mode 100644 index 000000000..153801553 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Utils.php @@ -0,0 +1,257 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +final class Utils +{ + const DEFAULT_JSON_FLAGS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR; + + public static function getClass(object $object): string + { + $class = \get_class($object); + + if (false === ($pos = strpos($class, "@anonymous\0"))) { + return $class; + } + + if (false === ($parent = get_parent_class($class))) { + return substr($class, 0, $pos + 10); + } + + return $parent . '@anonymous'; + } + + public static function substr(string $string, int $start, ?int $length = null): string + { + if (\extension_loaded('mbstring')) { + return mb_strcut($string, $start, $length); + } + + return substr($string, $start, (null === $length) ? \strlen($string) : $length); + } + + /** + * Makes sure if a relative path is passed in it is turned into an absolute path + * + * @param string $streamUrl stream URL or path without protocol + */ + public static function canonicalizePath(string $streamUrl): string + { + $prefix = ''; + if ('file://' === substr($streamUrl, 0, 7)) { + $streamUrl = substr($streamUrl, 7); + $prefix = 'file://'; + } + + // other type of stream, not supported + if (false !== strpos($streamUrl, '://')) { + return $streamUrl; + } + + // already absolute + if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\') { + return $prefix.$streamUrl; + } + + $streamUrl = getcwd() . '/' . $streamUrl; + + return $prefix.$streamUrl; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param int $encodeFlags flags to pass to json encode, defaults to DEFAULT_JSON_FLAGS + * @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string when errors are ignored and the encoding fails, "null" is returned which is valid json for null + */ + public static function jsonEncode($data, ?int $encodeFlags = null, bool $ignoreErrors = false): string + { + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + if ($ignoreErrors) { + $json = @json_encode($data, $encodeFlags); + if (false === $json) { + return 'null'; + } + + return $json; + } + + $json = json_encode($data, $encodeFlags); + if (false === $json) { + $json = self::handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * initial error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + public static function handleJsonError(int $code, $data, ?int $encodeFlags = null): string + { + if ($code !== JSON_ERROR_UTF8) { + self::throwEncodeError($code, $data); + } + + if (\is_string($data)) { + self::detectAndCleanUtf8($data); + } elseif (\is_array($data)) { + array_walk_recursive($data, ['Monolog\Utils', 'detectAndCleanUtf8']); + } else { + self::throwEncodeError($code, $data); + } + + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + $json = json_encode($data, $encodeFlags); + + if ($json === false) { + self::throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + */ + private static function throwEncodeError(int $code, $data): never + { + $msg = match ($code) { + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + default => 'Unknown error', + }; + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed $data Input to check and convert if needed, passed by ref + */ + private static function detectAndCleanUtf8(&$data): void + { + if (\is_string($data) && preg_match('//u', $data) !== 1) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function (array $m): string { + return \function_exists('mb_convert_encoding') + ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') + : (\function_exists('utf8_encode') ? utf8_encode($m[0]) : ''); + }, + $data + ); + if (!\is_string($data)) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . preg_last_error_msg()); + } + $data = str_replace( + ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], + ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], + $data + ); + } + } + + /** + * Converts a string with a valid 'memory_limit' format, to bytes. + * + * @param string|false $val + * @return int|false Returns an integer representing bytes. Returns FALSE in case of error. + */ + public static function expandIniShorthandBytes($val) + { + if (!\is_string($val)) { + return false; + } + + // support -1 + if ((int) $val < 0) { + return (int) $val; + } + + if (!(bool) preg_match('/^\s*(?\d+)(?:\.\d+)?\s*(?[gmk]?)\s*$/i', $val, $match)) { + return false; + } + + $val = (int) $match['val']; + switch (strtolower($match['unit'])) { + case 'g': + $val *= 1024; + // no break + case 'm': + $val *= 1024; + // no break + case 'k': + $val *= 1024; + } + + return $val; + } + + public static function getRecordMessageForException(LogRecord $record): string + { + $context = ''; + $extra = ''; + + try { + if (\count($record->context) > 0) { + $context = "\nContext: " . json_encode($record->context, JSON_THROW_ON_ERROR); + } + if (\count($record->extra) > 0) { + $extra = "\nExtra: " . json_encode($record->extra, JSON_THROW_ON_ERROR); + } + } catch (\Throwable $e) { + // noop + } + + return "\nThe exception occurred while attempting to log: " . $record->message . $context . $extra; + } +} diff --git a/vendor/myclabs/php-enum/LICENSE b/vendor/myclabs/php-enum/LICENSE new file mode 100644 index 000000000..2a8cf22ec --- /dev/null +++ b/vendor/myclabs/php-enum/LICENSE @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Copyright (c) 2015 My C-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/myclabs/php-enum/README.md b/vendor/myclabs/php-enum/README.md new file mode 100644 index 000000000..948f37458 --- /dev/null +++ b/vendor/myclabs/php-enum/README.md @@ -0,0 +1,196 @@ +# PHP Enum implementation inspired from SplEnum + +[![GitHub Actions][GA Image]][GA Link] +[![Latest Stable Version](https://poser.pugx.org/myclabs/php-enum/version.png)](https://packagist.org/packages/myclabs/php-enum) +[![Total Downloads](https://poser.pugx.org/myclabs/php-enum/downloads.png)](https://packagist.org/packages/myclabs/php-enum) +[![Psalm Shepherd][Shepherd Image]][Shepherd Link] + +Maintenance for this project is [supported via Tidelift](https://tidelift.com/subscription/pkg/packagist-myclabs-php-enum?utm_source=packagist-myclabs-php-enum&utm_medium=referral&utm_campaign=readme). + +## Why? + +First, and mainly, `SplEnum` is not integrated to PHP, you have to install the extension separately. + +Using an enum instead of class constants provides the following advantages: + +- You can use an enum as a parameter type: `function setAction(Action $action) {` +- You can use an enum as a return type: `function getAction() : Action {` +- You can enrich the enum with methods (e.g. `format`, `parse`, …) +- You can extend the enum to add new values (make your enum `final` to prevent it) +- You can get a list of all the possible values (see below) + +This Enum class is not intended to replace class constants, but only to be used when it makes sense. + +## Installation + +``` +composer require myclabs/php-enum +``` + +## Declaration + +```php +use MyCLabs\Enum\Enum; + +/** + * Action enum + * + * @extends Enum + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Usage + +```php +$action = Action::VIEW(); + +// or with a dynamic key: +$action = Action::$key(); +// or with a dynamic value: +$action = Action::from($value); +// or +$action = new Action($value); +``` + +As you can see, static methods are automatically implemented to provide quick access to an enum value. + +One advantage over using class constants is to be able to use an enum as a parameter type: + +```php +function setAction(Action $action) { + // ... +} +``` + +## Documentation + +- `__construct()` The constructor checks that the value exist in the enum +- `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant) +- `getValue()` Returns the current value of the enum +- `getKey()` Returns the key of the current value on Enum +- `equals()` Tests whether enum instances are equal (returns `true` if enum values are equal, `false` otherwise) + +Static methods: + +- `from()` Creates an Enum instance, checking that the value exist in the enum +- `toArray()` method Returns all possible values as an array (constant name in key, constant value in value) +- `keys()` Returns the names (keys) of all constants in the Enum class +- `values()` Returns instances of the Enum class of all Enum constants (constant name in key, Enum instance in value) +- `isValid()` Check if tested value is valid on enum set +- `isValidKey()` Check if tested key is valid on enum set +- `assertValidValue()` Assert the value is valid on enum set, throwing exception otherwise +- `search()` Return key for searched value + +### Static methods + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} + +// Static method: +$action = Action::VIEW(); +$action = Action::EDIT(); +``` + +Static method helpers are implemented using [`__callStatic()`](http://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic). + +If you care about IDE autocompletion, you can either implement the static methods yourself: + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + + /** + * @return Action + */ + public static function VIEW() { + return new Action(self::VIEW); + } +} +``` + +or you can use phpdoc (this is supported in PhpStorm for example): + +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Native enums and migration +Native enum arrived to PHP in version 8.1: https://www.php.net/enumerations +If your project is running PHP 8.1+ or your library has it as a minimum requirement you should use it instead of this library. + +When migrating from `myclabs/php-enum`, the effort should be small if the usage was in the recommended way: +- private constants +- final classes +- no method overridden + +Changes for migration: +- Class definition should be changed from +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + to +```php +enum Action: string +{ + case VIEW = 'view'; + case EDIT = 'edit'; +} +``` +All places where the class was used as a type will continue to work. + +Usages and the change needed: + +| Operation | myclabs/php-enum | native enum | +|----------------------------------------------------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Obtain an instance will change from | `$enumCase = Action::VIEW()` | `$enumCase = Action::VIEW` | +| Create an enum from a backed value | `$enumCase = new Action('view')` | `$enumCase = Action::from('view')` | +| Get the backed value of the enum instance | `$enumCase->getValue()` | `$enumCase->value` | +| Compare two enum instances | `$enumCase1 == $enumCase2`
or
`$enumCase1->equals($enumCase2)` | `$enumCase1 === $enumCase2` | +| Get the key/name of the enum instance | `$enumCase->getKey()` | `$enumCase->name` | +| Get a list of all the possible instances of the enum | `Action::values()` | `Action::cases()` | +| Get a map of possible instances of the enum mapped by name | `Action::values()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), Action::cases())`
or
`(new ReflectionEnum(Action::class))->getConstants()` | +| Get a list of all possible names of the enum | `Action::keys()` | `array_map(fn($case) => $case->name, Action::cases())` | +| Get a list of all possible backed values of the enum | `Action::toArray()` | `array_map(fn($case) => $case->value, Action::cases())` | +| Get a map of possible backed values of the enum mapped by name | `Action::toArray()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), array_map(fn($case) => $case->value, Action::cases()))`
or
`array_map(fn($case) => $case->value, (new ReflectionEnum(Action::class))->getConstants()))` | + +## Related projects + +- [PHP 8.1+ native enum](https://www.php.net/enumerations) +- [Doctrine enum mapping](https://github.com/acelaya/doctrine-enum-type) +- [Symfony ParamConverter integration](https://github.com/Ex3v/MyCLabsEnumParamConverter) +- [PHPStan integration](https://github.com/timeweb/phpstan-enum) + + +[GA Image]: https://github.com/myclabs/php-enum/workflows/CI/badge.svg + +[GA Link]: https://github.com/myclabs/php-enum/actions?query=workflow%3A%22CI%22+branch%3Amaster + +[Shepherd Image]: https://shepherd.dev/github/myclabs/php-enum/coverage.svg + +[Shepherd Link]: https://shepherd.dev/github/myclabs/php-enum diff --git a/vendor/myclabs/php-enum/SECURITY.md b/vendor/myclabs/php-enum/SECURITY.md new file mode 100644 index 000000000..84fd4e320 --- /dev/null +++ b/vendor/myclabs/php-enum/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +Only the latest stable release is supported. + +## Reporting a Vulnerability + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). + +Tidelift will coordinate the fix and disclosure. diff --git a/vendor/myclabs/php-enum/composer.json b/vendor/myclabs/php-enum/composer.json new file mode 100644 index 000000000..eab626353 --- /dev/null +++ b/vendor/myclabs/php-enum/composer.json @@ -0,0 +1,36 @@ +{ + "name": "myclabs/php-enum", + "type": "library", + "description": "PHP Enum implementation", + "keywords": ["enum"], + "homepage": "https://github.com/myclabs/php-enum", + "license": "MIT", + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "autoload-dev": { + "psr-4": { + "MyCLabs\\Tests\\Enum\\": "tests/" + } + }, + "require": { + "php": "^7.3 || ^8.0", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + } +} diff --git a/vendor/myclabs/php-enum/src/Enum.php b/vendor/myclabs/php-enum/src/Enum.php new file mode 100644 index 000000000..1bd559213 --- /dev/null +++ b/vendor/myclabs/php-enum/src/Enum.php @@ -0,0 +1,319 @@ + + * @author Daniel Costa + * @author Mirosław Filip + * + * @psalm-template T + * @psalm-immutable + * @psalm-consistent-constructor + */ +abstract class Enum implements \JsonSerializable, \Stringable +{ + /** + * Enum value + * + * @var mixed + * @psalm-var T + */ + protected $value; + + /** + * Enum key, the constant name + * + * @var string + */ + private $key; + + /** + * Store existing constants in a static cache per object. + * + * + * @var array + * @psalm-var array> + */ + protected static $cache = []; + + /** + * Cache of instances of the Enum class + * + * @var array + * @psalm-var array> + */ + protected static $instances = []; + + /** + * Creates a new value of some type + * + * @psalm-pure + * @param mixed $value + * + * @psalm-param T $value + * @throws \UnexpectedValueException if incompatible type is given. + */ + public function __construct($value) + { + if ($value instanceof static) { + /** @psalm-var T */ + $value = $value->getValue(); + } + + /** @psalm-suppress ImplicitToStringCast assertValidValueReturningKey returns always a string but psalm has currently an issue here */ + $this->key = static::assertValidValueReturningKey($value); + + /** @psalm-var T */ + $this->value = $value; + } + + /** + * This method exists only for the compatibility reason when deserializing a previously serialized version + * that didn't had the key property + */ + public function __wakeup() + { + /** @psalm-suppress DocblockTypeContradiction key can be null when deserializing an enum without the key */ + if ($this->key === null) { + /** + * @psalm-suppress InaccessibleProperty key is not readonly as marked by psalm + * @psalm-suppress PossiblyFalsePropertyAssignmentValue deserializing a case that was removed + */ + $this->key = static::search($this->value); + } + } + + /** + * @param mixed $value + * @return static + */ + public static function from($value): self + { + $key = static::assertValidValueReturningKey($value); + + return self::__callStatic($key, []); + } + + /** + * @psalm-pure + * @return mixed + * @psalm-return T + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the enum key (i.e. the constant name). + * + * @psalm-pure + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @psalm-pure + * @psalm-suppress InvalidCast + * @return string + */ + public function __toString() + { + return (string)$this->value; + } + + /** + * Determines if Enum should be considered equal with the variable passed as a parameter. + * Returns false if an argument is an object of different class or not an object. + * + * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 + * + * @psalm-pure + * @psalm-param mixed $variable + * @return bool + */ + final public function equals($variable = null): bool + { + return $variable instanceof self + && $this->getValue() === $variable->getValue() + && static::class === \get_class($variable); + } + + /** + * Returns the names (keys) of all constants in the Enum class + * + * @psalm-pure + * @psalm-return list + * @return array + */ + public static function keys() + { + return \array_keys(static::toArray()); + } + + /** + * Returns instances of the Enum class of all Enum constants + * + * @psalm-pure + * @psalm-return array + * @return static[] Constant name in key, Enum instance in value + */ + public static function values() + { + $values = array(); + + /** @psalm-var T $value */ + foreach (static::toArray() as $key => $value) { + /** @psalm-suppress UnsafeGenericInstantiation */ + $values[$key] = new static($value); + } + + return $values; + } + + /** + * Returns all possible values as an array + * + * @psalm-pure + * @psalm-suppress ImpureStaticProperty + * + * @psalm-return array + * @return array Constant name in key, constant value in value + */ + public static function toArray() + { + $class = static::class; + + if (!isset(static::$cache[$class])) { + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + $reflection = new \ReflectionClass($class); + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + static::$cache[$class] = $reflection->getConstants(); + } + + return static::$cache[$class]; + } + + /** + * Check if is valid enum value + * + * @param $value + * @psalm-param mixed $value + * @psalm-pure + * @psalm-assert-if-true T $value + * @return bool + */ + public static function isValid($value) + { + return \in_array($value, static::toArray(), true); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + */ + public static function assertValidValue($value): void + { + self::assertValidValueReturningKey($value); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + * @return string + */ + private static function assertValidValueReturningKey($value): string + { + if (false === ($key = static::search($value))) { + throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); + } + + return $key; + } + + /** + * Check if is valid enum key + * + * @param $key + * @psalm-param string $key + * @psalm-pure + * @return bool + */ + public static function isValidKey($key) + { + $array = static::toArray(); + + return isset($array[$key]) || \array_key_exists($key, $array); + } + + /** + * Return key for value + * + * @param mixed $value + * + * @psalm-param mixed $value + * @psalm-pure + * @return string|false + */ + public static function search($value) + { + return \array_search($value, static::toArray(), true); + } + + /** + * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant + * + * @param string $name + * @param array $arguments + * + * @return static + * @throws \BadMethodCallException + * + * @psalm-pure + */ + public static function __callStatic($name, $arguments) + { + $class = static::class; + if (!isset(self::$instances[$class][$name])) { + $array = static::toArray(); + if (!isset($array[$name]) && !\array_key_exists($name, $array)) { + $message = "No static method or enum constant '$name' in class " . static::class; + throw new \BadMethodCallException($message); + } + /** @psalm-suppress UnsafeGenericInstantiation */ + return self::$instances[$class][$name] = new static($array[$name]); + } + return clone self::$instances[$class][$name]; + } + + /** + * Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode() + * natively. + * + * @return mixed + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getValue(); + } +} diff --git a/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php new file mode 100644 index 000000000..7c65e4e6c --- /dev/null +++ b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php @@ -0,0 +1,54 @@ +register(new \MyCLabs\Enum\PHPUnit\Comparator()); + */ +final class Comparator extends \SebastianBergmann\Comparator\Comparator +{ + public function accepts($expected, $actual) + { + return $expected instanceof Enum && ( + $actual instanceof Enum || $actual === null + ); + } + + /** + * @param Enum $expected + * @param Enum|null $actual + * + * @return void + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) + { + if ($expected->equals($actual)) { + return; + } + + throw new ComparisonFailure( + $expected, + $actual, + $this->formatEnum($expected), + $this->formatEnum($actual), + false, + 'Failed asserting that two Enums are equal.' + ); + } + + private function formatEnum(?Enum $enum = null) + { + if ($enum === null) { + return "null"; + } + + return get_class($enum)."::{$enum->getKey()}()"; + } +} diff --git a/vendor/myclabs/php-enum/stubs/Stringable.php b/vendor/myclabs/php-enum/stubs/Stringable.php new file mode 100644 index 000000000..4811af700 --- /dev/null +++ b/vendor/myclabs/php-enum/stubs/Stringable.php @@ -0,0 +1,11 @@ += 7.4; for parsing PHP 7.0 to PHP 8.4, with limited support for parsing PHP 5.x). + +[Documentation for version 4.x][doc_4_x] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3). + +Features +-------- + +The main features provided by this library are: + + * Parsing PHP 7, and PHP 8 code into an abstract syntax tree (AST). + * Invalid code can be parsed into a partial AST. + * The AST contains accurate location information. + * Dumping the AST in human-readable form. + * Converting an AST back to PHP code. + * Formatting can be preserved for partially changed ASTs. + * Infrastructure to traverse and modify ASTs. + * Resolution of namespaced names. + * Evaluation of constant expressions. + * Builders to simplify AST construction for code generation. + * Converting an AST into JSON and back. + +Quick Start +----------- + +Install the library using [composer](https://getcomposer.org): + + php composer.phar require nikic/php-parser + +Parse some PHP code into an AST and dump the result in human-readable form: + +```php +createForNewestSupportedVersion(); +try { + $ast = $parser->parse($code); +} catch (Error $error) { + echo "Parse error: {$error->getMessage()}\n"; + return; +} + +$dumper = new NodeDumper; +echo $dumper->dump($ast) . "\n"; +``` + +This dumps an AST looking something like this: + +``` +array( + 0: Stmt_Function( + attrGroups: array( + ) + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + attrGroups: array( + ) + flags: 0 + type: null + byRef: false + variadic: false + var: Expr_Variable( + name: foo + ) + default: null + ) + ) + returnType: null + stmts: array( + 0: Stmt_Expression( + expr: Expr_FuncCall( + name: Name( + name: var_dump + ) + args: array( + 0: Arg( + name: null + value: Expr_Variable( + name: foo + ) + byRef: false + unpack: false + ) + ) + ) + ) + ) + ) +) +``` + +Let's traverse the AST and perform some kind of modification. For example, drop all function bodies: + +```php +use PhpParser\Node; +use PhpParser\Node\Stmt\Function_; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; + +$traverser = new NodeTraverser(); +$traverser->addVisitor(new class extends NodeVisitorAbstract { + public function enterNode(Node $node) { + if ($node instanceof Function_) { + // Clean out the function body + $node->stmts = []; + } + } +}); + +$ast = $traverser->traverse($ast); +echo $dumper->dump($ast) . "\n"; +``` + +This gives us an AST where the `Function_::$stmts` are empty: + +``` +array( + 0: Stmt_Function( + attrGroups: array( + ) + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + attrGroups: array( + ) + type: null + byRef: false + variadic: false + var: Expr_Variable( + name: foo + ) + default: null + ) + ) + returnType: null + stmts: array( + ) + ) +) +``` + +Finally, we can convert the new AST back to PHP code: + +```php +use PhpParser\PrettyPrinter; + +$prettyPrinter = new PrettyPrinter\Standard; +echo $prettyPrinter->prettyPrintFile($ast); +``` + +This gives us our original code, minus the `var_dump()` call inside the function: + +```php +createForVersion($attributes['version']); +$dumper = new PhpParser\NodeDumper([ + 'dumpComments' => true, + 'dumpPositions' => $attributes['with-positions'], +]); +$prettyPrinter = new PhpParser\PrettyPrinter\Standard; + +$traverser = new PhpParser\NodeTraverser(); +$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); + +foreach ($files as $file) { + if ($file === '-') { + $code = file_get_contents('php://stdin'); + fwrite(STDERR, "====> Stdin:\n"); + } else if (strpos($file, ' Code $code\n"); + } else { + if (!file_exists($file)) { + fwrite(STDERR, "File $file does not exist.\n"); + exit(1); + } + + $code = file_get_contents($file); + fwrite(STDERR, "====> File $file:\n"); + } + + if ($attributes['with-recovery']) { + $errorHandler = new PhpParser\ErrorHandler\Collecting; + $stmts = $parser->parse($code, $errorHandler); + foreach ($errorHandler->getErrors() as $error) { + $message = formatErrorMessage($error, $code, $attributes['with-column-info']); + fwrite(STDERR, $message . "\n"); + } + if (null === $stmts) { + continue; + } + } else { + try { + $stmts = $parser->parse($code); + } catch (PhpParser\Error $error) { + $message = formatErrorMessage($error, $code, $attributes['with-column-info']); + fwrite(STDERR, $message . "\n"); + exit(1); + } + } + + foreach ($operations as $operation) { + if ('dump' === $operation) { + fwrite(STDERR, "==> Node dump:\n"); + echo $dumper->dump($stmts, $code), "\n"; + } elseif ('pretty-print' === $operation) { + fwrite(STDERR, "==> Pretty print:\n"); + echo $prettyPrinter->prettyPrintFile($stmts), "\n"; + } elseif ('json-dump' === $operation) { + fwrite(STDERR, "==> JSON dump:\n"); + echo json_encode($stmts, JSON_PRETTY_PRINT), "\n"; + } elseif ('var-dump' === $operation) { + fwrite(STDERR, "==> var_dump():\n"); + var_dump($stmts); + } elseif ('resolve-names' === $operation) { + fwrite(STDERR, "==> Resolved names.\n"); + $stmts = $traverser->traverse($stmts); + } + } +} + +function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) { + if ($withColumnInfo && $e->hasColumnInfo()) { + return $e->getMessageWithColumnInfo($code); + } else { + return $e->getMessage(); + } +} + +function showHelp($error = '') { + if ($error) { + fwrite(STDERR, $error . "\n\n"); + } + fwrite($error ? STDERR : STDOUT, <<<'OUTPUT' +Usage: php-parse [operations] file1.php [file2.php ...] + or: php-parse [operations] " false, + 'with-positions' => false, + 'with-recovery' => false, + 'version' => PhpParser\PhpVersion::getNewestSupported(), + ]; + + array_shift($args); + $parseOptions = true; + foreach ($args as $arg) { + if (!$parseOptions) { + $files[] = $arg; + continue; + } + + switch ($arg) { + case '--dump': + case '-d': + $operations[] = 'dump'; + break; + case '--pretty-print': + case '-p': + $operations[] = 'pretty-print'; + break; + case '--json-dump': + case '-j': + $operations[] = 'json-dump'; + break; + case '--var-dump': + $operations[] = 'var-dump'; + break; + case '--resolve-names': + case '-N': + $operations[] = 'resolve-names'; + break; + case '--with-column-info': + case '-c': + $attributes['with-column-info'] = true; + break; + case '--with-positions': + case '-P': + $attributes['with-positions'] = true; + break; + case '--with-recovery': + case '-r': + $attributes['with-recovery'] = true; + break; + case '--help': + case '-h': + showHelp(); + break; + case '--': + $parseOptions = false; + break; + default: + if (preg_match('/^--version=(.*)$/', $arg, $matches)) { + $attributes['version'] = PhpParser\PhpVersion::fromString($matches[1]); + } elseif ($arg[0] === '-' && \strlen($arg[0]) > 1) { + showHelp("Invalid operation $arg."); + } else { + $files[] = $arg; + } + } + } + + return [$operations, $files, $attributes]; +} diff --git a/vendor/nikic/php-parser/composer.json b/vendor/nikic/php-parser/composer.json new file mode 100644 index 000000000..7a8591d48 --- /dev/null +++ b/vendor/nikic/php-parser/composer.json @@ -0,0 +1,43 @@ +{ + "name": "nikic/php-parser", + "type": "library", + "description": "A PHP parser written in PHP", + "keywords": [ + "php", + "parser" + ], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Nikita Popov" + } + ], + "require": { + "php": ">=7.4", + "ext-tokenizer": "*", + "ext-json": "*", + "ext-ctype": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "ircmaxell/php-yacc": "^0.0.7" + }, + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "autoload-dev": { + "psr-4": { + "PhpParser\\": "test/PhpParser/" + } + }, + "bin": [ + "bin/php-parse" + ] +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder.php b/vendor/nikic/php-parser/lib/PhpParser/Builder.php new file mode 100644 index 000000000..d6aa124c0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder.php @@ -0,0 +1,12 @@ + */ + protected array $attributes = []; + /** @var list */ + protected array $constants = []; + + /** @var list */ + protected array $attributeGroups = []; + /** @var Identifier|Node\Name|Node\ComplexType|null */ + protected ?Node $type = null; + + /** + * Creates a class constant builder + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value + */ + public function __construct($name, $value) { + $this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))]; + } + + /** + * Add another constant to const group + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value + * + * @return $this The builder instance (for fluid interface) + */ + public function addConst($name, $value) { + $this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value)); + + return $this; + } + + /** + * Makes the constant public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the constant protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the constant private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the constant final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Sets doc comment for the constant. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Sets the constant type. + * + * @param string|Node\Name|Identifier|Node\ComplexType $type + * + * @return $this + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\ClassConst The built constant node + */ + public function getNode(): PhpParser\Node { + return new Stmt\ClassConst( + $this->constants, + $this->flags, + $this->attributes, + $this->attributeGroups, + $this->type + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php new file mode 100644 index 000000000..6f394315e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php @@ -0,0 +1,151 @@ + */ + protected array $implements = []; + protected int $flags = 0; + /** @var list */ + protected array $uses = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $properties = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a class builder. + * + * @param string $name Name of the class + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Extends a class. + * + * @param Name|string $class Name of class to extend + * + * @return $this The builder instance (for fluid interface) + */ + public function extend($class) { + $this->extends = BuilderHelpers::normalizeName($class); + + return $this; + } + + /** + * Implements one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) + */ + public function implement(...$interfaces) { + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Makes the class abstract. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeAbstract() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::ABSTRACT); + + return $this; + } + + /** + * Makes the class final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Makes the class readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::READONLY); + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\Property) { + $this->properties[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Class_ The built class node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Class_($this->name, [ + 'flags' => $this->flags, + 'extends' => $this->extends, + 'implements' => $this->implements, + 'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php new file mode 100644 index 000000000..488b72131 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php @@ -0,0 +1,50 @@ + */ + protected array $attributes = []; + + /** + * Adds a statement. + * + * @param PhpParser\Node\Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + abstract public function addStmt($stmt); + + /** + * Adds multiple statements. + * + * @param (PhpParser\Node\Stmt|PhpParser\Builder)[] $stmts The statements to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmts(array $stmts) { + foreach ($stmts as $stmt) { + $this->addStmt($stmt); + } + + return $this; + } + + /** + * Sets doc comment for the declaration. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes['comments'] = [ + BuilderHelpers::normalizeDocComment($docComment) + ]; + + return $this; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php new file mode 100644 index 000000000..c766321ba --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php @@ -0,0 +1,86 @@ + */ + protected array $attributes = []; + + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an enum case builder. + * + * @param string|Identifier $name Name + */ + public function __construct($name) { + $this->name = $name; + } + + /** + * Sets the value. + * + * @param Node\Expr|string|int $value + * + * @return $this + */ + public function setValue($value) { + $this->value = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets doc comment for the constant. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built enum case node. + * + * @return Stmt\EnumCase The built constant node + */ + public function getNode(): PhpParser\Node { + return new Stmt\EnumCase( + $this->name, + $this->value, + $this->attributeGroups, + $this->attributes + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php new file mode 100644 index 000000000..c00df03f5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php @@ -0,0 +1,116 @@ + */ + protected array $implements = []; + /** @var list */ + protected array $uses = []; + /** @var list */ + protected array $enumCases = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an enum builder. + * + * @param string $name Name of the enum + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Sets the scalar type. + * + * @param string|Identifier $scalarType + * + * @return $this + */ + public function setScalarType($scalarType) { + $this->scalarType = BuilderHelpers::normalizeType($scalarType); + + return $this; + } + + /** + * Implements one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) + */ + public function implement(...$interfaces) { + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\EnumCase) { + $this->enumCases[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Enum_ The built enum node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Enum_($this->name, [ + 'scalarType' => $this->scalarType, + 'implements' => $this->implements, + 'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php new file mode 100644 index 000000000..ff79cb6b4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php @@ -0,0 +1,73 @@ +returnByRef = true; + + return $this; + } + + /** + * Adds a parameter. + * + * @param Node\Param|Param $param The parameter to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addParam($param) { + $param = BuilderHelpers::normalizeNode($param); + + if (!$param instanceof Node\Param) { + throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType())); + } + + $this->params[] = $param; + + return $this; + } + + /** + * Adds multiple parameters. + * + * @param (Node\Param|Param)[] $params The parameters to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addParams(array $params) { + foreach ($params as $param) { + $this->addParam($param); + } + + return $this; + } + + /** + * Sets the return type for PHP 7. + * + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type + * + * @return $this The builder instance (for fluid interface) + */ + public function setReturnType($type) { + $this->returnType = BuilderHelpers::normalizeType($type); + + return $this; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php new file mode 100644 index 000000000..48f5f6931 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php @@ -0,0 +1,67 @@ + */ + protected array $stmts = []; + + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a function builder. + * + * @param string $name Name of the function + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built function node. + * + * @return Stmt\Function_ The built function node + */ + public function getNode(): Node { + return new Stmt\Function_($this->name, [ + 'byRef' => $this->returnByRef, + 'params' => $this->params, + 'returnType' => $this->returnType, + 'stmts' => $this->stmts, + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php new file mode 100644 index 000000000..13dd3f7f9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php @@ -0,0 +1,94 @@ + */ + protected array $extends = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Extends one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to extend + * + * @return $this The builder instance (for fluid interface) + */ + public function extend(...$interfaces) { + foreach ($interfaces as $interface) { + $this->extends[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + // we erase all statements in the body of an interface method + $stmt->stmts = null; + $this->methods[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built interface node. + * + * @return Stmt\Interface_ The built interface node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Interface_($this->name, [ + 'extends' => $this->extends, + 'stmts' => array_merge($this->constants, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php new file mode 100644 index 000000000..8358dbe38 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php @@ -0,0 +1,147 @@ +|null */ + protected ?array $stmts = []; + + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a method builder. + * + * @param string $name Name of the method + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Makes the method public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the method static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::STATIC); + + return $this; + } + + /** + * Makes the method abstract. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeAbstract() { + if (!empty($this->stmts)) { + throw new \LogicException('Cannot make method with statements abstract'); + } + + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT); + $this->stmts = null; // abstract methods don't have statements + + return $this; + } + + /** + * Makes the method final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + if (null === $this->stmts) { + throw new \LogicException('Cannot add statements to an abstract method'); + } + + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built method node. + * + * @return Stmt\ClassMethod The built method node + */ + public function getNode(): Node { + return new Stmt\ClassMethod($this->name, [ + 'flags' => $this->flags, + 'byRef' => $this->returnByRef, + 'params' => $this->params, + 'returnType' => $this->returnType, + 'stmts' => $this->stmts, + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php new file mode 100644 index 000000000..80fe6f846 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php @@ -0,0 +1,45 @@ +name = null !== $name ? BuilderHelpers::normalizeName($name) : null; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Returns the built node. + * + * @return Stmt\Namespace_ The built node + */ + public function getNode(): Node { + return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php new file mode 100644 index 000000000..324a32b05 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php @@ -0,0 +1,171 @@ + */ + protected array $attributeGroups = []; + + /** + * Creates a parameter builder. + * + * @param string $name Name of the parameter + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Sets default value for the parameter. + * + * @param mixed $value Default value to use + * + * @return $this The builder instance (for fluid interface) + */ + public function setDefault($value) { + $this->default = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets type for the parameter. + * + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type + * + * @return $this The builder instance (for fluid interface) + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + if ($this->type == 'void') { + throw new \LogicException('Parameter type cannot be void'); + } + + return $this; + } + + /** + * Make the parameter accept the value by reference. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeByRef() { + $this->byRef = true; + + return $this; + } + + /** + * Make the parameter variadic + * + * @return $this The builder instance (for fluid interface) + */ + public function makeVariadic() { + $this->variadic = true; + + return $this; + } + + /** + * Makes the (promoted) parameter public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the (promoted) parameter protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the (promoted) parameter private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the (promoted) parameter readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY); + + return $this; + } + + /** + * Gives the promoted property private(set) visibility. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivateSet() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET); + + return $this; + } + + /** + * Gives the promoted property protected(set) visibility. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtectedSet() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built parameter node. + * + * @return Node\Param The built parameter node + */ + public function getNode(): Node { + return new Node\Param( + new Node\Expr\Variable($this->name), + $this->default, $this->type, $this->byRef, $this->variadic, [], $this->flags, $this->attributeGroups + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php new file mode 100644 index 000000000..c80fe481b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php @@ -0,0 +1,223 @@ + */ + protected array $attributes = []; + /** @var null|Identifier|Name|ComplexType */ + protected ?Node $type = null; + /** @var list */ + protected array $attributeGroups = []; + /** @var list */ + protected array $hooks = []; + + /** + * Creates a property builder. + * + * @param string $name Name of the property + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Makes the property public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the property protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the property private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the property static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::STATIC); + + return $this; + } + + /** + * Makes the property readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY); + + return $this; + } + + /** + * Makes the property abstract. Requires at least one property hook to be specified as well. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeAbstract() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT); + + return $this; + } + + /** + * Makes the property final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Gives the property private(set) visibility. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivateSet() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET); + + return $this; + } + + /** + * Gives the property protected(set) visibility. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtectedSet() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET); + + return $this; + } + + /** + * Sets default value for the property. + * + * @param mixed $value Default value to use + * + * @return $this The builder instance (for fluid interface) + */ + public function setDefault($value) { + $this->default = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets doc comment for the property. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Sets the property type for PHP 7.4+. + * + * @param string|Name|Identifier|ComplexType $type + * + * @return $this + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Adds a property hook. + * + * @return $this The builder instance (for fluid interface) + */ + public function addHook(Node\PropertyHook $hook) { + $this->hooks[] = $hook; + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Property The built property node + */ + public function getNode(): PhpParser\Node { + if ($this->flags & Modifiers::ABSTRACT && !$this->hooks) { + throw new PhpParser\Error('Only hooked properties may be declared abstract'); + } + + return new Stmt\Property( + $this->flags !== 0 ? $this->flags : Modifiers::PUBLIC, + [ + new Node\PropertyItem($this->name, $this->default) + ], + $this->attributes, + $this->type, + $this->attributeGroups, + $this->hooks + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php new file mode 100644 index 000000000..cf21c821a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php @@ -0,0 +1,65 @@ +and($trait); + } + } + + /** + * Adds used trait. + * + * @param Node\Name|string $trait Trait name + * + * @return $this The builder instance (for fluid interface) + */ + public function and($trait) { + $this->traits[] = BuilderHelpers::normalizeName($trait); + return $this; + } + + /** + * Adds trait adaptation. + * + * @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation + * + * @return $this The builder instance (for fluid interface) + */ + public function with($adaptation) { + $adaptation = BuilderHelpers::normalizeNode($adaptation); + + if (!$adaptation instanceof Stmt\TraitUseAdaptation) { + throw new \LogicException('Adaptation must have type TraitUseAdaptation'); + } + + $this->adaptations[] = $adaptation; + return $this; + } + + /** + * Returns the built node. + * + * @return Node The built node + */ + public function getNode(): Node { + return new Stmt\TraitUse($this->traits, $this->adaptations); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php new file mode 100644 index 000000000..fee09583a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php @@ -0,0 +1,145 @@ +type = self::TYPE_UNDEFINED; + + $this->trait = is_null($trait) ? null : BuilderHelpers::normalizeName($trait); + $this->method = BuilderHelpers::normalizeIdentifier($method); + } + + /** + * Sets alias of method. + * + * @param Node\Identifier|string $alias Alias for adapted method + * + * @return $this The builder instance (for fluid interface) + */ + public function as($alias) { + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set alias for not alias adaptation buider'); + } + + $this->alias = BuilderHelpers::normalizeIdentifier($alias); + return $this; + } + + /** + * Sets adapted method public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->setModifier(Modifiers::PUBLIC); + return $this; + } + + /** + * Sets adapted method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->setModifier(Modifiers::PROTECTED); + return $this; + } + + /** + * Sets adapted method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->setModifier(Modifiers::PRIVATE); + return $this; + } + + /** + * Adds overwritten traits. + * + * @param Node\Name|string ...$traits Traits for overwrite + * + * @return $this The builder instance (for fluid interface) + */ + public function insteadof(...$traits) { + if ($this->type === self::TYPE_UNDEFINED) { + if (is_null($this->trait)) { + throw new \LogicException('Precedence adaptation must have trait'); + } + + $this->type = self::TYPE_PRECEDENCE; + } + + if ($this->type !== self::TYPE_PRECEDENCE) { + throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider'); + } + + foreach ($traits as $trait) { + $this->insteadof[] = BuilderHelpers::normalizeName($trait); + } + + return $this; + } + + protected function setModifier(int $modifier): void { + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set access modifier for not alias adaptation buider'); + } + + if (is_null($this->modifier)) { + $this->modifier = $modifier; + } else { + throw new \LogicException('Multiple access type modifiers are not allowed'); + } + } + + /** + * Returns the built node. + * + * @return Node The built node + */ + public function getNode(): Node { + switch ($this->type) { + case self::TYPE_ALIAS: + return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias); + case self::TYPE_PRECEDENCE: + return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof); + default: + throw new \LogicException('Type of adaptation is not defined'); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php new file mode 100644 index 000000000..ffa1bd5cc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php @@ -0,0 +1,83 @@ + */ + protected array $uses = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $properties = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\Property) { + $this->properties[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built trait node. + * + * @return Stmt\Trait_ The built interface node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Trait_( + $this->name, [ + 'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php new file mode 100644 index 000000000..b82cf1396 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php @@ -0,0 +1,49 @@ +name = BuilderHelpers::normalizeName($name); + $this->type = $type; + } + + /** + * Sets alias for used name. + * + * @param string $alias Alias to use (last component of full name by default) + * + * @return $this The builder instance (for fluid interface) + */ + public function as(string $alias) { + $this->alias = $alias; + return $this; + } + + /** + * Returns the built node. + * + * @return Stmt\Use_ The built node + */ + public function getNode(): Node { + return new Stmt\Use_([ + new Node\UseItem($this->name, $this->alias) + ], $this->type); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php b/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php new file mode 100644 index 000000000..07642f92f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php @@ -0,0 +1,375 @@ +args($args) + ); + } + + /** + * Creates a namespace builder. + * + * @param null|string|Node\Name $name Name of the namespace + * + * @return Builder\Namespace_ The created namespace builder + */ + public function namespace($name): Builder\Namespace_ { + return new Builder\Namespace_($name); + } + + /** + * Creates a class builder. + * + * @param string $name Name of the class + * + * @return Builder\Class_ The created class builder + */ + public function class(string $name): Builder\Class_ { + return new Builder\Class_($name); + } + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + * + * @return Builder\Interface_ The created interface builder + */ + public function interface(string $name): Builder\Interface_ { + return new Builder\Interface_($name); + } + + /** + * Creates a trait builder. + * + * @param string $name Name of the trait + * + * @return Builder\Trait_ The created trait builder + */ + public function trait(string $name): Builder\Trait_ { + return new Builder\Trait_($name); + } + + /** + * Creates an enum builder. + * + * @param string $name Name of the enum + * + * @return Builder\Enum_ The created enum builder + */ + public function enum(string $name): Builder\Enum_ { + return new Builder\Enum_($name); + } + + /** + * Creates a trait use builder. + * + * @param Node\Name|string ...$traits Trait names + * + * @return Builder\TraitUse The created trait use builder + */ + public function useTrait(...$traits): Builder\TraitUse { + return new Builder\TraitUse(...$traits); + } + + /** + * Creates a trait use adaptation builder. + * + * @param Node\Name|string|null $trait Trait name + * @param Node\Identifier|string $method Method name + * + * @return Builder\TraitUseAdaptation The created trait use adaptation builder + */ + public function traitUseAdaptation($trait, $method = null): Builder\TraitUseAdaptation { + if ($method === null) { + $method = $trait; + $trait = null; + } + + return new Builder\TraitUseAdaptation($trait, $method); + } + + /** + * Creates a method builder. + * + * @param string $name Name of the method + * + * @return Builder\Method The created method builder + */ + public function method(string $name): Builder\Method { + return new Builder\Method($name); + } + + /** + * Creates a parameter builder. + * + * @param string $name Name of the parameter + * + * @return Builder\Param The created parameter builder + */ + public function param(string $name): Builder\Param { + return new Builder\Param($name); + } + + /** + * Creates a property builder. + * + * @param string $name Name of the property + * + * @return Builder\Property The created property builder + */ + public function property(string $name): Builder\Property { + return new Builder\Property($name); + } + + /** + * Creates a function builder. + * + * @param string $name Name of the function + * + * @return Builder\Function_ The created function builder + */ + public function function(string $name): Builder\Function_ { + return new Builder\Function_($name); + } + + /** + * Creates a namespace/class use builder. + * + * @param Node\Name|string $name Name of the entity (namespace or class) to alias + * + * @return Builder\Use_ The created use builder + */ + public function use($name): Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_NORMAL); + } + + /** + * Creates a function use builder. + * + * @param Node\Name|string $name Name of the function to alias + * + * @return Builder\Use_ The created use function builder + */ + public function useFunction($name): Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_FUNCTION); + } + + /** + * Creates a constant use builder. + * + * @param Node\Name|string $name Name of the const to alias + * + * @return Builder\Use_ The created use const builder + */ + public function useConst($name): Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_CONSTANT); + } + + /** + * Creates a class constant builder. + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value + * + * @return Builder\ClassConst The created use const builder + */ + public function classConst($name, $value): Builder\ClassConst { + return new Builder\ClassConst($name, $value); + } + + /** + * Creates an enum case builder. + * + * @param string|Identifier $name Name + * + * @return Builder\EnumCase The created use const builder + */ + public function enumCase($name): Builder\EnumCase { + return new Builder\EnumCase($name); + } + + /** + * Creates node a for a literal value. + * + * @param Expr|bool|null|int|float|string|array|\UnitEnum $value $value + */ + public function val($value): Expr { + return BuilderHelpers::normalizeValue($value); + } + + /** + * Creates variable node. + * + * @param string|Expr $name Name + */ + public function var($name): Expr\Variable { + if (!\is_string($name) && !$name instanceof Expr) { + throw new \LogicException('Variable name must be string or Expr'); + } + + return new Expr\Variable($name); + } + + /** + * Normalizes an argument list. + * + * Creates Arg nodes for all arguments and converts literal values to expressions. + * + * @param array $args List of arguments to normalize + * + * @return list + */ + public function args(array $args): array { + $normalizedArgs = []; + foreach ($args as $key => $arg) { + if (!($arg instanceof Arg)) { + $arg = new Arg(BuilderHelpers::normalizeValue($arg)); + } + if (\is_string($key)) { + $arg->name = BuilderHelpers::normalizeIdentifier($key); + } + $normalizedArgs[] = $arg; + } + return $normalizedArgs; + } + + /** + * Creates a function call node. + * + * @param string|Name|Expr $name Function name + * @param array $args Function arguments + */ + public function funcCall($name, array $args = []): Expr\FuncCall { + return new Expr\FuncCall( + BuilderHelpers::normalizeNameOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates a method call node. + * + * @param Expr $var Variable the method is called on + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments + */ + public function methodCall(Expr $var, $name, array $args = []): Expr\MethodCall { + return new Expr\MethodCall( + $var, + BuilderHelpers::normalizeIdentifierOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates a static method call node. + * + * @param string|Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments + */ + public function staticCall($class, $name, array $args = []): Expr\StaticCall { + return new Expr\StaticCall( + BuilderHelpers::normalizeNameOrExpr($class), + BuilderHelpers::normalizeIdentifierOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates an object creation node. + * + * @param string|Name|Expr $class Class name + * @param array $args Constructor arguments + */ + public function new($class, array $args = []): Expr\New_ { + return new Expr\New_( + BuilderHelpers::normalizeNameOrExpr($class), + $this->args($args) + ); + } + + /** + * Creates a constant fetch node. + * + * @param string|Name $name Constant name + */ + public function constFetch($name): Expr\ConstFetch { + return new Expr\ConstFetch(BuilderHelpers::normalizeName($name)); + } + + /** + * Creates a property fetch node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Property name + */ + public function propertyFetch(Expr $var, $name): Expr\PropertyFetch { + return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name)); + } + + /** + * Creates a class constant fetch node. + * + * @param string|Name|Expr $class Class name + * @param string|Identifier|Expr $name Constant name + */ + public function classConstFetch($class, $name): Expr\ClassConstFetch { + return new Expr\ClassConstFetch( + BuilderHelpers::normalizeNameOrExpr($class), + BuilderHelpers::normalizeIdentifierOrExpr($name) + ); + } + + /** + * Creates nested Concat nodes from a list of expressions. + * + * @param Expr|string ...$exprs Expressions or literal strings + */ + public function concat(...$exprs): Concat { + $numExprs = count($exprs); + if ($numExprs < 2) { + throw new \LogicException('Expected at least two expressions'); + } + + $lastConcat = $this->normalizeStringExpr($exprs[0]); + for ($i = 1; $i < $numExprs; $i++) { + $lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i])); + } + return $lastConcat; + } + + /** + * @param string|Expr $expr + */ + private function normalizeStringExpr($expr): Expr { + if ($expr instanceof Expr) { + return $expr; + } + + if (\is_string($expr)) { + return new String_($expr); + } + + throw new \LogicException('Expected string or Expr'); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php b/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php new file mode 100644 index 000000000..f29a69153 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php @@ -0,0 +1,338 @@ +getNode(); + } + + if ($node instanceof Node) { + return $node; + } + + throw new \LogicException('Expected node or builder object'); + } + + /** + * Normalizes a node to a statement. + * + * Expressions are wrapped in a Stmt\Expression node. + * + * @param Node|Builder $node The node to normalize + * + * @return Stmt The normalized statement node + */ + public static function normalizeStmt($node): Stmt { + $node = self::normalizeNode($node); + if ($node instanceof Stmt) { + return $node; + } + + if ($node instanceof Expr) { + return new Stmt\Expression($node); + } + + throw new \LogicException('Expected statement or expression node'); + } + + /** + * Normalizes strings to Identifier. + * + * @param string|Identifier $name The identifier to normalize + * + * @return Identifier The normalized identifier + */ + public static function normalizeIdentifier($name): Identifier { + if ($name instanceof Identifier) { + return $name; + } + + if (\is_string($name)) { + return new Identifier($name); + } + + throw new \LogicException('Expected string or instance of Node\Identifier'); + } + + /** + * Normalizes strings to Identifier, also allowing expressions. + * + * @param string|Identifier|Expr $name The identifier to normalize + * + * @return Identifier|Expr The normalized identifier or expression + */ + public static function normalizeIdentifierOrExpr($name) { + if ($name instanceof Identifier || $name instanceof Expr) { + return $name; + } + + if (\is_string($name)) { + return new Identifier($name); + } + + throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr'); + } + + /** + * Normalizes a name: Converts string names to Name nodes. + * + * @param Name|string $name The name to normalize + * + * @return Name The normalized name + */ + public static function normalizeName($name): Name { + if ($name instanceof Name) { + return $name; + } + + if (is_string($name)) { + if (!$name) { + throw new \LogicException('Name cannot be empty'); + } + + if ($name[0] === '\\') { + return new Name\FullyQualified(substr($name, 1)); + } + + if (0 === strpos($name, 'namespace\\')) { + return new Name\Relative(substr($name, strlen('namespace\\'))); + } + + return new Name($name); + } + + throw new \LogicException('Name must be a string or an instance of Node\Name'); + } + + /** + * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. + * + * @param Expr|Name|string $name The name to normalize + * + * @return Name|Expr The normalized name or expression + */ + public static function normalizeNameOrExpr($name) { + if ($name instanceof Expr) { + return $name; + } + + if (!is_string($name) && !($name instanceof Name)) { + throw new \LogicException( + 'Name must be a string or an instance of Node\Name or Node\Expr' + ); + } + + return self::normalizeName($name); + } + + /** + * Normalizes a type: Converts plain-text type names into proper AST representation. + * + * In particular, builtin types become Identifiers, custom types become Names and nullables + * are wrapped in NullableType nodes. + * + * @param string|Name|Identifier|ComplexType $type The type to normalize + * + * @return Name|Identifier|ComplexType The normalized type + */ + public static function normalizeType($type) { + if (!is_string($type)) { + if ( + !$type instanceof Name && !$type instanceof Identifier && + !$type instanceof ComplexType + ) { + throw new \LogicException( + 'Type must be a string, or an instance of Name, Identifier or ComplexType' + ); + } + return $type; + } + + $nullable = false; + if (strlen($type) > 0 && $type[0] === '?') { + $nullable = true; + $type = substr($type, 1); + } + + $builtinTypes = [ + 'array', + 'callable', + 'bool', + 'int', + 'float', + 'string', + 'iterable', + 'void', + 'object', + 'null', + 'false', + 'mixed', + 'never', + 'true', + ]; + + $lowerType = strtolower($type); + if (in_array($lowerType, $builtinTypes)) { + $type = new Identifier($lowerType); + } else { + $type = self::normalizeName($type); + } + + $notNullableTypes = [ + 'void', 'mixed', 'never', + ]; + if ($nullable && in_array((string) $type, $notNullableTypes)) { + throw new \LogicException(sprintf('%s type cannot be nullable', $type)); + } + + return $nullable ? new NullableType($type) : $type; + } + + /** + * Normalizes a value: Converts nulls, booleans, integers, + * floats, strings and arrays into their respective nodes + * + * @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value The value to normalize + * + * @return Expr The normalized value + */ + public static function normalizeValue($value): Expr { + if ($value instanceof Node\Expr) { + return $value; + } + + if (is_null($value)) { + return new Expr\ConstFetch( + new Name('null') + ); + } + + if (is_bool($value)) { + return new Expr\ConstFetch( + new Name($value ? 'true' : 'false') + ); + } + + if (is_int($value)) { + return new Scalar\Int_($value); + } + + if (is_float($value)) { + return new Scalar\Float_($value); + } + + if (is_string($value)) { + return new Scalar\String_($value); + } + + if (is_array($value)) { + $items = []; + $lastKey = -1; + foreach ($value as $itemKey => $itemValue) { + // for consecutive, numeric keys don't generate keys + if (null !== $lastKey && ++$lastKey === $itemKey) { + $items[] = new Node\ArrayItem( + self::normalizeValue($itemValue) + ); + } else { + $lastKey = null; + $items[] = new Node\ArrayItem( + self::normalizeValue($itemValue), + self::normalizeValue($itemKey) + ); + } + } + + return new Expr\Array_($items); + } + + if ($value instanceof \UnitEnum) { + return new Expr\ClassConstFetch(new FullyQualified(\get_class($value)), new Identifier($value->name)); + } + + throw new \LogicException('Invalid value'); + } + + /** + * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc. + * + * @param Comment\Doc|string $docComment The doc comment to normalize + * + * @return Comment\Doc The normalized doc comment + */ + public static function normalizeDocComment($docComment): Comment\Doc { + if ($docComment instanceof Comment\Doc) { + return $docComment; + } + + if (is_string($docComment)) { + return new Comment\Doc($docComment); + } + + throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc'); + } + + /** + * Normalizes a attribute: Converts attribute to the Attribute Group if needed. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return Node\AttributeGroup The Attribute Group + */ + public static function normalizeAttribute($attribute): Node\AttributeGroup { + if ($attribute instanceof Node\AttributeGroup) { + return $attribute; + } + + if (!($attribute instanceof Node\Attribute)) { + throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup'); + } + + return new Node\AttributeGroup([$attribute]); + } + + /** + * Adds a modifier and returns new modifier bitmask. + * + * @param int $modifiers Existing modifiers + * @param int $modifier Modifier to set + * + * @return int New modifiers + */ + public static function addModifier(int $modifiers, int $modifier): int { + Modifiers::verifyModifier($modifiers, $modifier); + return $modifiers | $modifier; + } + + /** + * Adds a modifier and returns new modifier bitmask. + * @return int New modifiers + */ + public static function addClassModifier(int $existingModifiers, int $modifierToSet): int { + Modifiers::verifyClassModifier($existingModifiers, $modifierToSet); + return $existingModifiers | $modifierToSet; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Comment.php b/vendor/nikic/php-parser/lib/PhpParser/Comment.php new file mode 100644 index 000000000..01b341e43 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Comment.php @@ -0,0 +1,209 @@ +text = $text; + $this->startLine = $startLine; + $this->startFilePos = $startFilePos; + $this->startTokenPos = $startTokenPos; + $this->endLine = $endLine; + $this->endFilePos = $endFilePos; + $this->endTokenPos = $endTokenPos; + } + + /** + * Gets the comment text. + * + * @return string The comment text (including comment delimiters like /*) + */ + public function getText(): string { + return $this->text; + } + + /** + * Gets the line number the comment started on. + * + * @return int Line number (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getStartLine(): int { + return $this->startLine; + } + + /** + * Gets the file offset the comment started on. + * + * @return int File offset (or -1 if not available) + */ + public function getStartFilePos(): int { + return $this->startFilePos; + } + + /** + * Gets the token offset the comment started on. + * + * @return int Token offset (or -1 if not available) + */ + public function getStartTokenPos(): int { + return $this->startTokenPos; + } + + /** + * Gets the line number the comment ends on. + * + * @return int Line number (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getEndLine(): int { + return $this->endLine; + } + + /** + * Gets the file offset the comment ends on. + * + * @return int File offset (or -1 if not available) + */ + public function getEndFilePos(): int { + return $this->endFilePos; + } + + /** + * Gets the token offset the comment ends on. + * + * @return int Token offset (or -1 if not available) + */ + public function getEndTokenPos(): int { + return $this->endTokenPos; + } + + /** + * Gets the comment text. + * + * @return string The comment text (including comment delimiters like /*) + */ + public function __toString(): string { + return $this->text; + } + + /** + * Gets the reformatted comment text. + * + * "Reformatted" here means that we try to clean up the whitespace at the + * starts of the lines. This is necessary because we receive the comments + * without leading whitespace on the first line, but with leading whitespace + * on all subsequent lines. + * + * Additionally, this normalizes CRLF newlines to LF newlines. + */ + public function getReformattedText(): string { + $text = str_replace("\r\n", "\n", $this->text); + $newlinePos = strpos($text, "\n"); + if (false === $newlinePos) { + // Single line comments don't need further processing + return $text; + } + if (preg_match('(^.*(?:\n\s+\*.*)+$)', $text)) { + // Multi line comment of the type + // + // /* + // * Some text. + // * Some more text. + // */ + // + // is handled by replacing the whitespace sequences before the * by a single space + return preg_replace('(^\s+\*)m', ' *', $text); + } + if (preg_match('(^/\*\*?\s*\n)', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) { + // Multi line comment of the type + // + // /* + // Some text. + // Some more text. + // */ + // + // is handled by removing the whitespace sequence on the line before the closing + // */ on all lines. So if the last line is " */", then " " is removed at the + // start of all lines. + return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text); + } + if (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) { + // Multi line comment of the type + // + // /* Some text. + // Some more text. + // Indented text. + // Even more text. */ + // + // is handled by removing the difference between the shortest whitespace prefix on all + // lines and the length of the "/* " opening sequence. + $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1)); + $removeLen = $prefixLen - strlen($matches[0]); + return preg_replace('(^\s{' . $removeLen . '})m', '', $text); + } + + // No idea how to format this comment, so simply return as is + return $text; + } + + /** + * Get length of shortest whitespace prefix (at the start of a line). + * + * If there is a line with no prefix whitespace, 0 is a valid return value. + * + * @param string $str String to check + * @return int Length in characters. Tabs count as single characters. + */ + private function getShortestWhitespacePrefixLen(string $str): int { + $lines = explode("\n", $str); + $shortestPrefixLen = \PHP_INT_MAX; + foreach ($lines as $line) { + preg_match('(^\s*)', $line, $matches); + $prefixLen = strlen($matches[0]); + if ($prefixLen < $shortestPrefixLen) { + $shortestPrefixLen = $prefixLen; + } + } + return $shortestPrefixLen; + } + + /** + * @return array{nodeType:string, text:mixed, line:mixed, filePos:mixed} + */ + public function jsonSerialize(): array { + // Technically not a node, but we make it look like one anyway + $type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment'; + return [ + 'nodeType' => $type, + 'text' => $this->text, + // TODO: Rename these to include "start". + 'line' => $this->startLine, + 'filePos' => $this->startFilePos, + 'tokenPos' => $this->startTokenPos, + 'endLine' => $this->endLine, + 'endFilePos' => $this->endFilePos, + 'endTokenPos' => $this->endTokenPos, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php b/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php new file mode 100644 index 000000000..bb3e9146a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php @@ -0,0 +1,6 @@ +fallbackEvaluator = $fallbackEvaluator ?? function (Expr $expr) { + throw new ConstExprEvaluationException( + "Expression of type {$expr->getType()} cannot be evaluated" + ); + }; + } + + /** + * Silently evaluates a constant expression into a PHP value. + * + * Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException. + * The original source of the exception is available through getPrevious(). + * + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. + * + * See class doc comment for caveats and limitations. + * + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation + * + * @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred + */ + public function evaluateSilently(Expr $expr) { + set_error_handler(function ($num, $str, $file, $line) { + throw new \ErrorException($str, 0, $num, $file, $line); + }); + + try { + return $this->evaluate($expr); + } catch (\Throwable $e) { + if (!$e instanceof ConstExprEvaluationException) { + $e = new ConstExprEvaluationException( + "An error occurred during constant expression evaluation", 0, $e); + } + throw $e; + } finally { + restore_error_handler(); + } + } + + /** + * Directly evaluates a constant expression into a PHP value. + * + * May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these + * into a ConstExprEvaluationException. + * + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. + * + * See class doc comment for caveats and limitations. + * + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation + * + * @throws ConstExprEvaluationException if the expression cannot be evaluated + */ + public function evaluateDirectly(Expr $expr) { + return $this->evaluate($expr); + } + + /** @return mixed */ + private function evaluate(Expr $expr) { + if ($expr instanceof Scalar\Int_ + || $expr instanceof Scalar\Float_ + || $expr instanceof Scalar\String_ + ) { + return $expr->value; + } + + if ($expr instanceof Expr\Array_) { + return $this->evaluateArray($expr); + } + + // Unary operators + if ($expr instanceof Expr\UnaryPlus) { + return +$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\UnaryMinus) { + return -$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BooleanNot) { + return !$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BitwiseNot) { + return ~$this->evaluate($expr->expr); + } + + if ($expr instanceof Expr\BinaryOp) { + return $this->evaluateBinaryOp($expr); + } + + if ($expr instanceof Expr\Ternary) { + return $this->evaluateTernary($expr); + } + + if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) { + return $this->evaluate($expr->var)[$this->evaluate($expr->dim)]; + } + + if ($expr instanceof Expr\ConstFetch) { + return $this->evaluateConstFetch($expr); + } + + return ($this->fallbackEvaluator)($expr); + } + + private function evaluateArray(Expr\Array_ $expr): array { + $array = []; + foreach ($expr->items as $item) { + if (null !== $item->key) { + $array[$this->evaluate($item->key)] = $this->evaluate($item->value); + } elseif ($item->unpack) { + $array = array_merge($array, $this->evaluate($item->value)); + } else { + $array[] = $this->evaluate($item->value); + } + } + return $array; + } + + /** @return mixed */ + private function evaluateTernary(Expr\Ternary $expr) { + if (null === $expr->if) { + return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else); + } + + return $this->evaluate($expr->cond) + ? $this->evaluate($expr->if) + : $this->evaluate($expr->else); + } + + /** @return mixed */ + private function evaluateBinaryOp(Expr\BinaryOp $expr) { + if ($expr instanceof Expr\BinaryOp\Coalesce + && $expr->left instanceof Expr\ArrayDimFetch + ) { + // This needs to be special cased to respect BP_VAR_IS fetch semantics + return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] + ?? $this->evaluate($expr->right); + } + + // The evaluate() calls are repeated in each branch, because some of the operators are + // short-circuiting and evaluating the RHS in advance may be illegal in that case + $l = $expr->left; + $r = $expr->right; + switch ($expr->getOperatorSigil()) { + case '&': return $this->evaluate($l) & $this->evaluate($r); + case '|': return $this->evaluate($l) | $this->evaluate($r); + case '^': return $this->evaluate($l) ^ $this->evaluate($r); + case '&&': return $this->evaluate($l) && $this->evaluate($r); + case '||': return $this->evaluate($l) || $this->evaluate($r); + case '??': return $this->evaluate($l) ?? $this->evaluate($r); + case '.': return $this->evaluate($l) . $this->evaluate($r); + case '/': return $this->evaluate($l) / $this->evaluate($r); + case '==': return $this->evaluate($l) == $this->evaluate($r); + case '>': return $this->evaluate($l) > $this->evaluate($r); + case '>=': return $this->evaluate($l) >= $this->evaluate($r); + case '===': return $this->evaluate($l) === $this->evaluate($r); + case 'and': return $this->evaluate($l) and $this->evaluate($r); + case 'or': return $this->evaluate($l) or $this->evaluate($r); + case 'xor': return $this->evaluate($l) xor $this->evaluate($r); + case '-': return $this->evaluate($l) - $this->evaluate($r); + case '%': return $this->evaluate($l) % $this->evaluate($r); + case '*': return $this->evaluate($l) * $this->evaluate($r); + case '!=': return $this->evaluate($l) != $this->evaluate($r); + case '!==': return $this->evaluate($l) !== $this->evaluate($r); + case '+': return $this->evaluate($l) + $this->evaluate($r); + case '**': return $this->evaluate($l) ** $this->evaluate($r); + case '<<': return $this->evaluate($l) << $this->evaluate($r); + case '>>': return $this->evaluate($l) >> $this->evaluate($r); + case '<': return $this->evaluate($l) < $this->evaluate($r); + case '<=': return $this->evaluate($l) <= $this->evaluate($r); + case '<=>': return $this->evaluate($l) <=> $this->evaluate($r); + case '|>': + $lval = $this->evaluate($l); + return $this->evaluate($r)($lval); + } + + throw new \Exception('Should not happen'); + } + + /** @return mixed */ + private function evaluateConstFetch(Expr\ConstFetch $expr) { + $name = $expr->name->toLowerString(); + switch ($name) { + case 'null': return null; + case 'false': return false; + case 'true': return true; + } + + return ($this->fallbackEvaluator)($expr); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Error.php b/vendor/nikic/php-parser/lib/PhpParser/Error.php new file mode 100644 index 000000000..f81f0c420 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Error.php @@ -0,0 +1,173 @@ + */ + protected array $attributes; + + /** + * Creates an Exception signifying a parse error. + * + * @param string $message Error message + * @param array $attributes Attributes of node/token where error occurred + */ + public function __construct(string $message, array $attributes = []) { + $this->rawMessage = $message; + $this->attributes = $attributes; + $this->updateMessage(); + } + + /** + * Gets the error message + * + * @return string Error message + */ + public function getRawMessage(): string { + return $this->rawMessage; + } + + /** + * Gets the line the error starts in. + * + * @return int Error start line + * @phpstan-return -1|positive-int + */ + public function getStartLine(): int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets the line the error ends in. + * + * @return int Error end line + * @phpstan-return -1|positive-int + */ + public function getEndLine(): int { + return $this->attributes['endLine'] ?? -1; + } + + /** + * Gets the attributes of the node/token the error occurred at. + * + * @return array + */ + public function getAttributes(): array { + return $this->attributes; + } + + /** + * Sets the attributes of the node/token the error occurred at. + * + * @param array $attributes + */ + public function setAttributes(array $attributes): void { + $this->attributes = $attributes; + $this->updateMessage(); + } + + /** + * Sets the line of the PHP file the error occurred in. + * + * @param string $message Error message + */ + public function setRawMessage(string $message): void { + $this->rawMessage = $message; + $this->updateMessage(); + } + + /** + * Sets the line the error starts in. + * + * @param int $line Error start line + */ + public function setStartLine(int $line): void { + $this->attributes['startLine'] = $line; + $this->updateMessage(); + } + + /** + * Returns whether the error has start and end column information. + * + * For column information enable the startFilePos and endFilePos in the lexer options. + */ + public function hasColumnInfo(): bool { + return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']); + } + + /** + * Gets the start column (1-based) into the line where the error started. + * + * @param string $code Source code of the file + */ + public function getStartColumn(string $code): int { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + + return $this->toColumn($code, $this->attributes['startFilePos']); + } + + /** + * Gets the end column (1-based) into the line where the error ended. + * + * @param string $code Source code of the file + */ + public function getEndColumn(string $code): int { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + + return $this->toColumn($code, $this->attributes['endFilePos']); + } + + /** + * Formats message including line and column information. + * + * @param string $code Source code associated with the error, for calculation of the columns + * + * @return string Formatted message + */ + public function getMessageWithColumnInfo(string $code): string { + return sprintf( + '%s from %d:%d to %d:%d', $this->getRawMessage(), + $this->getStartLine(), $this->getStartColumn($code), + $this->getEndLine(), $this->getEndColumn($code) + ); + } + + /** + * Converts a file offset into a column. + * + * @param string $code Source code that $pos indexes into + * @param int $pos 0-based position in $code + * + * @return int 1-based column (relative to start of line) + */ + private function toColumn(string $code, int $pos): int { + if ($pos > strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); + if (false === $lineStartPos) { + $lineStartPos = -1; + } + + return $pos - $lineStartPos; + } + + /** + * Updates the exception message after a change to rawMessage or rawLine. + */ + protected function updateMessage(): void { + $this->message = $this->rawMessage; + + if (-1 === $this->getStartLine()) { + $this->message .= ' on unknown line'; + } else { + $this->message .= ' on line ' . $this->getStartLine(); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php new file mode 100644 index 000000000..51ad730c4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php @@ -0,0 +1,12 @@ +errors[] = $error; + } + + /** + * Get collected errors. + * + * @return Error[] + */ + public function getErrors(): array { + return $this->errors; + } + + /** + * Check whether there are any errors. + */ + public function hasErrors(): bool { + return !empty($this->errors); + } + + /** + * Reset/clear collected errors. + */ + public function clearErrors(): void { + $this->errors = []; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php new file mode 100644 index 000000000..dff33dd02 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php @@ -0,0 +1,17 @@ +type = $type; + $this->old = $old; + $this->new = $new; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php new file mode 100644 index 000000000..253e17574 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php @@ -0,0 +1,178 @@ +isEqual = $isEqual; + } + + /** + * Calculate diff (edit script) from $old to $new. + * + * @param T[] $old Original array + * @param T[] $new New array + * + * @return DiffElem[] Diff (edit script) + */ + public function diff(array $old, array $new): array { + $old = \array_values($old); + $new = \array_values($new); + list($trace, $x, $y) = $this->calculateTrace($old, $new); + return $this->extractDiff($trace, $x, $y, $old, $new); + } + + /** + * Calculate diff, including "replace" operations. + * + * If a sequence of remove operations is followed by the same number of add operations, these + * will be coalesced into replace operations. + * + * @param T[] $old Original array + * @param T[] $new New array + * + * @return DiffElem[] Diff (edit script), including replace operations + */ + public function diffWithReplacements(array $old, array $new): array { + return $this->coalesceReplacements($this->diff($old, $new)); + } + + /** + * @param T[] $old + * @param T[] $new + * @return array{array>, int, int} + */ + private function calculateTrace(array $old, array $new): array { + $n = \count($old); + $m = \count($new); + $max = $n + $m; + $v = [1 => 0]; + $trace = []; + for ($d = 0; $d <= $max; $d++) { + $trace[] = $v; + for ($k = -$d; $k <= $d; $k += 2) { + if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { + $x = $v[$k + 1]; + } else { + $x = $v[$k - 1] + 1; + } + + $y = $x - $k; + while ($x < $n && $y < $m && ($this->isEqual)($old[$x], $new[$y])) { + $x++; + $y++; + } + + $v[$k] = $x; + if ($x >= $n && $y >= $m) { + return [$trace, $x, $y]; + } + } + } + throw new \Exception('Should not happen'); + } + + /** + * @param array> $trace + * @param T[] $old + * @param T[] $new + * @return DiffElem[] + */ + private function extractDiff(array $trace, int $x, int $y, array $old, array $new): array { + $result = []; + for ($d = \count($trace) - 1; $d >= 0; $d--) { + $v = $trace[$d]; + $k = $x - $y; + + if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { + $prevK = $k + 1; + } else { + $prevK = $k - 1; + } + + $prevX = $v[$prevK]; + $prevY = $prevX - $prevK; + + while ($x > $prevX && $y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_KEEP, $old[$x - 1], $new[$y - 1]); + $x--; + $y--; + } + + if ($d === 0) { + break; + } + + while ($x > $prevX) { + $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $old[$x - 1], null); + $x--; + } + + while ($y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $new[$y - 1]); + $y--; + } + } + return array_reverse($result); + } + + /** + * Coalesce equal-length sequences of remove+add into a replace operation. + * + * @param DiffElem[] $diff + * @return DiffElem[] + */ + private function coalesceReplacements(array $diff): array { + $newDiff = []; + $c = \count($diff); + for ($i = 0; $i < $c; $i++) { + $diffType = $diff[$i]->type; + if ($diffType !== DiffElem::TYPE_REMOVE) { + $newDiff[] = $diff[$i]; + continue; + } + + $j = $i; + while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) { + $j++; + } + + $k = $j; + while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) { + $k++; + } + + if ($j - $i === $k - $j) { + $len = $j - $i; + for ($n = 0; $n < $len; $n++) { + $newDiff[] = new DiffElem( + DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new + ); + } + } else { + for (; $i < $k; $i++) { + $newDiff[] = $diff[$i]; + } + } + $i = $k - 1; + } + return $newDiff; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php new file mode 100644 index 000000000..b30a99a14 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php @@ -0,0 +1,71 @@ + $attributes Attributes + */ + public function __construct( + array $attrGroups, int $flags, array $args, ?Node\Name $extends, array $implements, + array $stmts, array $attributes + ) { + parent::__construct($attributes); + $this->attrGroups = $attrGroups; + $this->flags = $flags; + $this->args = $args; + $this->extends = $extends; + $this->implements = $implements; + $this->stmts = $stmts; + } + + public static function fromNewNode(Expr\New_ $newNode): self { + $class = $newNode->class; + assert($class instanceof Node\Stmt\Class_); + // We don't assert that $class->name is null here, to allow consumers to assign unique names + // to anonymous classes for their own purposes. We simplify ignore the name here. + return new self( + $class->attrGroups, $class->flags, $newNode->args, $class->extends, $class->implements, + $class->stmts, $newNode->getAttributes() + ); + } + + public function getType(): string { + return 'Expr_PrintableNewAnonClass'; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'args', 'extends', 'implements', 'stmts']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenPolyfill.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenPolyfill.php new file mode 100644 index 000000000..36022d09a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenPolyfill.php @@ -0,0 +1,237 @@ += 80000) { + class TokenPolyfill extends \PhpToken { + } + return; +} + +/** + * This is a polyfill for the PhpToken class introduced in PHP 8.0. We do not actually polyfill + * PhpToken, because composer might end up picking a different polyfill implementation, which does + * not meet our requirements. + * + * @internal + */ +class TokenPolyfill { + /** @var int The ID of the token. Either a T_* constant of a character code < 256. */ + public int $id; + /** @var string The textual content of the token. */ + public string $text; + /** @var int The 1-based starting line of the token (or -1 if unknown). */ + public int $line; + /** @var int The 0-based starting position of the token (or -1 if unknown). */ + public int $pos; + + /** @var array Tokens ignored by the PHP parser. */ + private const IGNORABLE_TOKENS = [ + \T_WHITESPACE => true, + \T_COMMENT => true, + \T_DOC_COMMENT => true, + \T_OPEN_TAG => true, + ]; + + /** @var array Tokens that may be part of a T_NAME_* identifier. */ + private static array $identifierTokens; + + /** + * Create a Token with the given ID and text, as well optional line and position information. + */ + final public function __construct(int $id, string $text, int $line = -1, int $pos = -1) { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $pos; + } + + /** + * Get the name of the token. For single-char tokens this will be the token character. + * Otherwise it will be a T_* style name, or null if the token ID is unknown. + */ + public function getTokenName(): ?string { + if ($this->id < 256) { + return \chr($this->id); + } + + $name = token_name($this->id); + return $name === 'UNKNOWN' ? null : $name; + } + + /** + * Check whether the token is of the given kind. The kind may be either an integer that matches + * the token ID, a string that matches the token text, or an array of integers/strings. In the + * latter case, the function returns true if any of the kinds in the array match. + * + * @param int|string|(int|string)[] $kind + */ + public function is($kind): bool { + if (\is_int($kind)) { + return $this->id === $kind; + } + if (\is_string($kind)) { + return $this->text === $kind; + } + if (\is_array($kind)) { + foreach ($kind as $entry) { + if (\is_int($entry)) { + if ($this->id === $entry) { + return true; + } + } elseif (\is_string($entry)) { + if ($this->text === $entry) { + return true; + } + } else { + throw new \TypeError( + 'Argument #1 ($kind) must only have elements of type string|int, ' . + gettype($entry) . ' given'); + } + } + return false; + } + throw new \TypeError( + 'Argument #1 ($kind) must be of type string|int|array, ' .gettype($kind) . ' given'); + } + + /** + * Check whether this token would be ignored by the PHP parser. Returns true for T_WHITESPACE, + * T_COMMENT, T_DOC_COMMENT and T_OPEN_TAG, and false for everything else. + */ + public function isIgnorable(): bool { + return isset(self::IGNORABLE_TOKENS[$this->id]); + } + + /** + * Return the textual content of the token. + */ + public function __toString(): string { + return $this->text; + } + + /** + * Tokenize the given source code and return an array of tokens. + * + * This performs certain canonicalizations to match the PHP 8.0 token format: + * * Bad characters are represented using T_BAD_CHARACTER rather than omitted. + * * T_COMMENT does not include trailing newlines, instead the newline is part of a following + * T_WHITESPACE token. + * * Namespaced names are represented using T_NAME_* tokens. + * + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array { + self::init(); + + $tokens = []; + $line = 1; + $pos = 0; + $origTokens = \token_get_all($code, $flags); + + $numTokens = \count($origTokens); + for ($i = 0; $i < $numTokens; $i++) { + $token = $origTokens[$i]; + if (\is_string($token)) { + if (\strlen($token) === 2) { + // b" and B" are tokenized as single-char tokens, even though they aren't. + $tokens[] = new static(\ord('"'), $token, $line, $pos); + $pos += 2; + } else { + $tokens[] = new static(\ord($token), $token, $line, $pos); + $pos++; + } + } else { + $id = $token[0]; + $text = $token[1]; + + // Emulate PHP 8.0 comment format, which does not include trailing whitespace anymore. + if ($id === \T_COMMENT && \substr($text, 0, 2) !== '/*' && + \preg_match('/(\r\n|\n|\r)$/D', $text, $matches) + ) { + $trailingNewline = $matches[0]; + $text = \substr($text, 0, -\strlen($trailingNewline)); + $tokens[] = new static($id, $text, $line, $pos); + $pos += \strlen($text); + + if ($i + 1 < $numTokens && $origTokens[$i + 1][0] === \T_WHITESPACE) { + // Move trailing newline into following T_WHITESPACE token, if it already exists. + $origTokens[$i + 1][1] = $trailingNewline . $origTokens[$i + 1][1]; + $origTokens[$i + 1][2]--; + } else { + // Otherwise, we need to create a new T_WHITESPACE token. + $tokens[] = new static(\T_WHITESPACE, $trailingNewline, $line, $pos); + $line++; + $pos += \strlen($trailingNewline); + } + continue; + } + + // Emulate PHP 8.0 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and + // T_STRING into a single token. + if (($id === \T_NS_SEPARATOR || isset(self::$identifierTokens[$id]))) { + $newText = $text; + $lastWasSeparator = $id === \T_NS_SEPARATOR; + for ($j = $i + 1; $j < $numTokens; $j++) { + if ($lastWasSeparator) { + if (!isset(self::$identifierTokens[$origTokens[$j][0]])) { + break; + } + $lastWasSeparator = false; + } else { + if ($origTokens[$j][0] !== \T_NS_SEPARATOR) { + break; + } + $lastWasSeparator = true; + } + $newText .= $origTokens[$j][1]; + } + if ($lastWasSeparator) { + // Trailing separator is not part of the name. + $j--; + $newText = \substr($newText, 0, -1); + } + if ($j > $i + 1) { + if ($id === \T_NS_SEPARATOR) { + $id = \T_NAME_FULLY_QUALIFIED; + } elseif ($id === \T_NAMESPACE) { + $id = \T_NAME_RELATIVE; + } else { + $id = \T_NAME_QUALIFIED; + } + $tokens[] = new static($id, $newText, $line, $pos); + $pos += \strlen($newText); + $i = $j - 1; + continue; + } + } + + $tokens[] = new static($id, $text, $line, $pos); + $line += \substr_count($text, "\n"); + $pos += \strlen($text); + } + } + return $tokens; + } + + /** Initialize private static state needed by tokenize(). */ + private static function init(): void { + if (isset(self::$identifierTokens)) { + return; + } + + // Based on semi_reserved production. + self::$identifierTokens = \array_fill_keys([ + \T_STRING, + \T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY, + \T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND, + \T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE, + \T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH, + \T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO, + \T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT, + \T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS, + \T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN, + \T_MATCH, + ], true); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php new file mode 100644 index 000000000..cdbe2bdcc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php @@ -0,0 +1,282 @@ +tokens = $tokens; + $this->indentMap = $this->calcIndentMap($tabWidth); + } + + /** + * Whether the given position is immediately surrounded by parenthesis. + * + * @param int $startPos Start position + * @param int $endPos End position + */ + public function haveParens(int $startPos, int $endPos): bool { + return $this->haveTokenImmediatelyBefore($startPos, '(') + && $this->haveTokenImmediatelyAfter($endPos, ')'); + } + + /** + * Whether the given position is immediately surrounded by braces. + * + * @param int $startPos Start position + * @param int $endPos End position + */ + public function haveBraces(int $startPos, int $endPos): bool { + return ($this->haveTokenImmediatelyBefore($startPos, '{') + || $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN)) + && $this->haveTokenImmediatelyAfter($endPos, '}'); + } + + /** + * Check whether the position is directly preceded by a certain token type. + * + * During this check whitespace and comments are skipped. + * + * @param int $pos Position before which the token should occur + * @param int|string $expectedTokenType Token to check for + * + * @return bool Whether the expected token was found + */ + public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType): bool { + $tokens = $this->tokens; + $pos--; + for (; $pos >= 0; $pos--) { + $token = $tokens[$pos]; + if ($token->is($expectedTokenType)) { + return true; + } + if (!$token->isIgnorable()) { + break; + } + } + return false; + } + + /** + * Check whether the position is directly followed by a certain token type. + * + * During this check whitespace and comments are skipped. + * + * @param int $pos Position after which the token should occur + * @param int|string $expectedTokenType Token to check for + * + * @return bool Whether the expected token was found + */ + public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType): bool { + $tokens = $this->tokens; + $pos++; + for ($c = \count($tokens); $pos < $c; $pos++) { + $token = $tokens[$pos]; + if ($token->is($expectedTokenType)) { + return true; + } + if (!$token->isIgnorable()) { + break; + } + } + return false; + } + + /** @param int|string|(int|string)[] $skipTokenType */ + public function skipLeft(int $pos, $skipTokenType): int { + $tokens = $this->tokens; + + $pos = $this->skipLeftWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if (!$tokens[$pos]->is($skipTokenType)) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos--; + + return $this->skipLeftWhitespace($pos); + } + + /** @param int|string|(int|string)[] $skipTokenType */ + public function skipRight(int $pos, $skipTokenType): int { + $tokens = $this->tokens; + + $pos = $this->skipRightWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if (!$tokens[$pos]->is($skipTokenType)) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos++; + + return $this->skipRightWhitespace($pos); + } + + /** + * Return first non-whitespace token position smaller or equal to passed position. + * + * @param int $pos Token position + * @return int Non-whitespace token position + */ + public function skipLeftWhitespace(int $pos): int { + $tokens = $this->tokens; + for (; $pos >= 0; $pos--) { + if (!$tokens[$pos]->isIgnorable()) { + break; + } + } + return $pos; + } + + /** + * Return first non-whitespace position greater or equal to passed position. + * + * @param int $pos Token position + * @return int Non-whitespace token position + */ + public function skipRightWhitespace(int $pos): int { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + if (!$tokens[$pos]->isIgnorable()) { + break; + } + } + return $pos; + } + + /** @param int|string|(int|string)[] $findTokenType */ + public function findRight(int $pos, $findTokenType): int { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + if ($tokens[$pos]->is($findTokenType)) { + return $pos; + } + } + return -1; + } + + /** + * Whether the given position range contains a certain token type. + * + * @param int $startPos Starting position (inclusive) + * @param int $endPos Ending position (exclusive) + * @param int|string $tokenType Token type to look for + * @return bool Whether the token occurs in the given range + */ + public function haveTokenInRange(int $startPos, int $endPos, $tokenType): bool { + $tokens = $this->tokens; + for ($pos = $startPos; $pos < $endPos; $pos++) { + if ($tokens[$pos]->is($tokenType)) { + return true; + } + } + return false; + } + + public function haveTagInRange(int $startPos, int $endPos): bool { + return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG) + || $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG); + } + + /** + * Get indentation before token position. + * + * @param int $pos Token position + * + * @return int Indentation depth (in spaces) + */ + public function getIndentationBefore(int $pos): int { + return $this->indentMap[$pos]; + } + + /** + * Get the code corresponding to a token offset range, optionally adjusted for indentation. + * + * @param int $from Token start position (inclusive) + * @param int $to Token end position (exclusive) + * @param int $indent By how much the code should be indented (can be negative as well) + * + * @return string Code corresponding to token range, adjusted for indentation + */ + public function getTokenCode(int $from, int $to, int $indent): string { + $tokens = $this->tokens; + $result = ''; + for ($pos = $from; $pos < $to; $pos++) { + $token = $tokens[$pos]; + $id = $token->id; + $text = $token->text; + if ($id === \T_CONSTANT_ENCAPSED_STRING || $id === \T_ENCAPSED_AND_WHITESPACE) { + $result .= $text; + } else { + // TODO Handle non-space indentation + if ($indent < 0) { + $result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $text); + } elseif ($indent > 0) { + $result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $text); + } else { + $result .= $text; + } + } + } + return $result; + } + + /** + * Precalculate the indentation at every token position. + * + * @return int[] Token position to indentation map + */ + private function calcIndentMap(int $tabWidth): array { + $indentMap = []; + $indent = 0; + foreach ($this->tokens as $i => $token) { + $indentMap[] = $indent; + + if ($token->id === \T_WHITESPACE) { + $content = $token->text; + $newlinePos = \strrpos($content, "\n"); + if (false !== $newlinePos) { + $indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth); + } elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG && + $this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") { + // Special case: Newline at the end of opening tag followed by whitespace. + $indent = $this->getIndent($content, $tabWidth); + } + } + } + + // Add a sentinel for one past end of the file + $indentMap[] = $indent; + + return $indentMap; + } + + private function getIndent(string $ws, int $tabWidth): int { + $spaces = \substr_count($ws, " "); + $tabs = \substr_count($ws, "\t"); + assert(\strlen($ws) === $spaces + $tabs); + return $spaces + $tabs * $tabWidth; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php b/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php new file mode 100644 index 000000000..7be41426e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php @@ -0,0 +1,108 @@ +[] Node type to reflection class map */ + private array $reflectionClassCache; + + /** @return mixed */ + public function decode(string $json) { + $value = json_decode($json, true); + if (json_last_error()) { + throw new \RuntimeException('JSON decoding error: ' . json_last_error_msg()); + } + + return $this->decodeRecursive($value); + } + + /** + * @param mixed $value + * @return mixed + */ + private function decodeRecursive($value) { + if (\is_array($value)) { + if (isset($value['nodeType'])) { + if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') { + return $this->decodeComment($value); + } + return $this->decodeNode($value); + } + return $this->decodeArray($value); + } + return $value; + } + + private function decodeArray(array $array): array { + $decodedArray = []; + foreach ($array as $key => $value) { + $decodedArray[$key] = $this->decodeRecursive($value); + } + return $decodedArray; + } + + private function decodeNode(array $value): Node { + $nodeType = $value['nodeType']; + if (!\is_string($nodeType)) { + throw new \RuntimeException('Node type must be a string'); + } + + $reflectionClass = $this->reflectionClassFromNodeType($nodeType); + $node = $reflectionClass->newInstanceWithoutConstructor(); + + if (isset($value['attributes'])) { + if (!\is_array($value['attributes'])) { + throw new \RuntimeException('Attributes must be an array'); + } + + $node->setAttributes($this->decodeArray($value['attributes'])); + } + + foreach ($value as $name => $subNode) { + if ($name === 'nodeType' || $name === 'attributes') { + continue; + } + + $node->$name = $this->decodeRecursive($subNode); + } + + return $node; + } + + private function decodeComment(array $value): Comment { + $className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class; + if (!isset($value['text'])) { + throw new \RuntimeException('Comment must have text'); + } + + return new $className( + $value['text'], + $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1, + $value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1 + ); + } + + /** @return \ReflectionClass */ + private function reflectionClassFromNodeType(string $nodeType): \ReflectionClass { + if (!isset($this->reflectionClassCache[$nodeType])) { + $className = $this->classNameFromNodeType($nodeType); + $this->reflectionClassCache[$nodeType] = new \ReflectionClass($className); + } + return $this->reflectionClassCache[$nodeType]; + } + + /** @return class-string */ + private function classNameFromNodeType(string $nodeType): string { + $className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\'); + if (class_exists($className)) { + return $className; + } + + $className .= '_'; + if (class_exists($className)) { + return $className; + } + + throw new \RuntimeException("Unknown node type \"$nodeType\""); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer.php new file mode 100644 index 000000000..5e2ece961 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer.php @@ -0,0 +1,116 @@ +postprocessTokens($tokens, $errorHandler); + + if (false !== $scream) { + ini_set('xdebug.scream', $scream); + } + + return $tokens; + } + + private function handleInvalidCharacter(Token $token, ErrorHandler $errorHandler): void { + $chr = $token->text; + if ($chr === "\0") { + // PHP cuts error message after null byte, so need special case + $errorMsg = 'Unexpected null byte'; + } else { + $errorMsg = sprintf( + 'Unexpected character "%s" (ASCII %d)', $chr, ord($chr) + ); + } + + $errorHandler->handleError(new Error($errorMsg, [ + 'startLine' => $token->line, + 'endLine' => $token->line, + 'startFilePos' => $token->pos, + 'endFilePos' => $token->pos, + ])); + } + + private function isUnterminatedComment(Token $token): bool { + return $token->is([\T_COMMENT, \T_DOC_COMMENT]) + && substr($token->text, 0, 2) === '/*' + && substr($token->text, -2) !== '*/'; + } + + /** + * @param list $tokens + */ + protected function postprocessTokens(array &$tokens, ErrorHandler $errorHandler): void { + // This function reports errors (bad characters and unterminated comments) in the token + // array, and performs certain canonicalizations: + // * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and + // T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types. + // * Add a sentinel token with ID 0. + + $numTokens = \count($tokens); + if ($numTokens === 0) { + // Empty input edge case: Just add the sentinel token. + $tokens[] = new Token(0, "\0", 1, 0); + return; + } + + for ($i = 0; $i < $numTokens; $i++) { + $token = $tokens[$i]; + if ($token->id === \T_BAD_CHARACTER) { + $this->handleInvalidCharacter($token, $errorHandler); + } + + if ($token->id === \ord('&')) { + $next = $i + 1; + while (isset($tokens[$next]) && $tokens[$next]->id === \T_WHITESPACE) { + $next++; + } + $followedByVarOrVarArg = isset($tokens[$next]) && + $tokens[$next]->is([\T_VARIABLE, \T_ELLIPSIS]); + $token->id = $followedByVarOrVarArg + ? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG + : \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG; + } + } + + // Check for unterminated comment + $lastToken = $tokens[$numTokens - 1]; + if ($this->isUnterminatedComment($lastToken)) { + $errorHandler->handleError(new Error('Unterminated comment', [ + 'startLine' => $lastToken->line, + 'endLine' => $lastToken->getEndLine(), + 'startFilePos' => $lastToken->pos, + 'endFilePos' => $lastToken->getEndPos(), + ])); + } + + // Add sentinel token. + $tokens[] = new Token(0, "\0", $lastToken->getEndLine(), $lastToken->getEndPos()); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php new file mode 100644 index 000000000..3185e808e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php @@ -0,0 +1,230 @@ + */ + private array $emulators = []; + + private PhpVersion $targetPhpVersion; + + private PhpVersion $hostPhpVersion; + + /** + * @param PhpVersion|null $phpVersion PHP version to emulate. Defaults to newest supported. + */ + public function __construct(?PhpVersion $phpVersion = null) { + $this->targetPhpVersion = $phpVersion ?? PhpVersion::getNewestSupported(); + $this->hostPhpVersion = PhpVersion::getHostVersion(); + + $emulators = [ + new MatchTokenEmulator(), + new NullsafeTokenEmulator(), + new AttributeEmulator(), + new EnumTokenEmulator(), + new ReadonlyTokenEmulator(), + new ExplicitOctalEmulator(), + new ReadonlyFunctionTokenEmulator(), + new PropertyTokenEmulator(), + new AsymmetricVisibilityTokenEmulator(), + new PipeOperatorEmulator(), + new VoidCastEmulator(), + ]; + + // Collect emulators that are relevant for the PHP version we're running + // and the PHP version we're targeting for emulation. + foreach ($emulators as $emulator) { + $emulatorPhpVersion = $emulator->getPhpVersion(); + if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = $emulator; + } elseif ($this->isReverseEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = new ReverseEmulator($emulator); + } + } + } + + public function tokenize(string $code, ?ErrorHandler $errorHandler = null): array { + $emulators = array_filter($this->emulators, function ($emulator) use ($code) { + return $emulator->isEmulationNeeded($code); + }); + + if (empty($emulators)) { + // Nothing to emulate, yay + return parent::tokenize($code, $errorHandler); + } + + if ($errorHandler === null) { + $errorHandler = new ErrorHandler\Throwing(); + } + + $this->patches = []; + foreach ($emulators as $emulator) { + $code = $emulator->preprocessCode($code, $this->patches); + } + + $collector = new ErrorHandler\Collecting(); + $tokens = parent::tokenize($code, $collector); + $this->sortPatches(); + $tokens = $this->fixupTokens($tokens); + + $errors = $collector->getErrors(); + if (!empty($errors)) { + $this->fixupErrors($errors); + foreach ($errors as $error) { + $errorHandler->handleError($error); + } + } + + foreach ($emulators as $emulator) { + $tokens = $emulator->emulate($code, $tokens); + } + + return $tokens; + } + + private function isForwardEmulationNeeded(PhpVersion $emulatorPhpVersion): bool { + return $this->hostPhpVersion->older($emulatorPhpVersion) + && $this->targetPhpVersion->newerOrEqual($emulatorPhpVersion); + } + + private function isReverseEmulationNeeded(PhpVersion $emulatorPhpVersion): bool { + return $this->hostPhpVersion->newerOrEqual($emulatorPhpVersion) + && $this->targetPhpVersion->older($emulatorPhpVersion); + } + + private function sortPatches(): void { + // Patches may be contributed by different emulators. + // Make sure they are sorted by increasing patch position. + usort($this->patches, function ($p1, $p2) { + return $p1[0] <=> $p2[0]; + }); + } + + /** + * @param list $tokens + * @return list + */ + private function fixupTokens(array $tokens): array { + if (\count($this->patches) === 0) { + return $tokens; + } + + // Load first patch + $patchIdx = 0; + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + + // We use a manual loop over the tokens, because we modify the array on the fly + $posDelta = 0; + $lineDelta = 0; + for ($i = 0, $c = \count($tokens); $i < $c; $i++) { + $token = $tokens[$i]; + $pos = $token->pos; + $token->pos += $posDelta; + $token->line += $lineDelta; + $localPosDelta = 0; + $len = \strlen($token->text); + while ($patchPos >= $pos && $patchPos < $pos + $len) { + $patchTextLen = \strlen($patchText); + if ($patchType === 'remove') { + if ($patchPos === $pos && $patchTextLen === $len) { + // Remove token entirely + array_splice($tokens, $i, 1, []); + $i--; + $c--; + } else { + // Remove from token string + $token->text = substr_replace( + $token->text, '', $patchPos - $pos + $localPosDelta, $patchTextLen + ); + $localPosDelta -= $patchTextLen; + } + $lineDelta -= \substr_count($patchText, "\n"); + } elseif ($patchType === 'add') { + // Insert into the token string + $token->text = substr_replace( + $token->text, $patchText, $patchPos - $pos + $localPosDelta, 0 + ); + $localPosDelta += $patchTextLen; + $lineDelta += \substr_count($patchText, "\n"); + } elseif ($patchType === 'replace') { + // Replace inside the token string + $token->text = substr_replace( + $token->text, $patchText, $patchPos - $pos + $localPosDelta, $patchTextLen + ); + } else { + assert(false); + } + + // Fetch the next patch + $patchIdx++; + if ($patchIdx >= \count($this->patches)) { + // No more patches. However, we still need to adjust position. + $patchPos = \PHP_INT_MAX; + break; + } + + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + } + + $posDelta += $localPosDelta; + } + return $tokens; + } + + /** + * Fixup line and position information in errors. + * + * @param Error[] $errors + */ + private function fixupErrors(array $errors): void { + foreach ($errors as $error) { + $attrs = $error->getAttributes(); + + $posDelta = 0; + $lineDelta = 0; + foreach ($this->patches as $patch) { + list($patchPos, $patchType, $patchText) = $patch; + if ($patchPos >= $attrs['startFilePos']) { + // No longer relevant + break; + } + + if ($patchType === 'add') { + $posDelta += strlen($patchText); + $lineDelta += substr_count($patchText, "\n"); + } elseif ($patchType === 'remove') { + $posDelta -= strlen($patchText); + $lineDelta -= substr_count($patchText, "\n"); + } + } + + $attrs['startFilePos'] += $posDelta; + $attrs['endFilePos'] += $posDelta; + $attrs['startLine'] += $lineDelta; + $attrs['endLine'] += $lineDelta; + $error->setAttributes($attrs); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php new file mode 100644 index 000000000..084bb75dc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php @@ -0,0 +1,93 @@ + \T_PUBLIC_SET, + \T_PROTECTED => \T_PROTECTED_SET, + \T_PRIVATE => \T_PRIVATE_SET, + ]; + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + if (isset($map[$token->id]) && $i + 3 < $c && $tokens[$i + 1]->text === '(' && + $tokens[$i + 2]->id === \T_STRING && \strtolower($tokens[$i + 2]->text) === 'set' && + $tokens[$i + 3]->text === ')' && + $this->isKeywordContext($tokens, $i) + ) { + array_splice($tokens, $i, 4, [ + new Token( + $map[$token->id], $token->text . '(' . $tokens[$i + 2]->text . ')', + $token->line, $token->pos), + ]); + $c -= 3; + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array { + $reverseMap = [ + \T_PUBLIC_SET => \T_PUBLIC, + \T_PROTECTED_SET => \T_PROTECTED, + \T_PRIVATE_SET => \T_PRIVATE, + ]; + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + if (isset($reverseMap[$token->id]) && + \preg_match('/(public|protected|private)\((set)\)/i', $token->text, $matches) + ) { + [, $modifier, $set] = $matches; + $modifierLen = \strlen($modifier); + array_splice($tokens, $i, 1, [ + new Token($reverseMap[$token->id], $modifier, $token->line, $token->pos), + new Token(\ord('('), '(', $token->line, $token->pos + $modifierLen), + new Token(\T_STRING, $set, $token->line, $token->pos + $modifierLen + 1), + new Token(\ord(')'), ')', $token->line, $token->pos + $modifierLen + 4), + ]); + $i += 3; + $c += 3; + } + } + + return $tokens; + } + + /** @param Token[] $tokens */ + protected function isKeywordContext(array $tokens, int $pos): bool { + $prevToken = $this->getPreviousNonSpaceToken($tokens, $pos); + if ($prevToken === null) { + return false; + } + return $prevToken->id !== \T_OBJECT_OPERATOR + && $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR; + } + + /** @param Token[] $tokens */ + private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token { + for ($i = $start - 1; $i >= 0; --$i) { + if ($tokens[$i]->id === T_WHITESPACE) { + continue; + } + + return $tokens[$i]; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php new file mode 100644 index 000000000..2c12f33ae --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php @@ -0,0 +1,49 @@ +text === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1]->text === '[') { + array_splice($tokens, $i, 2, [ + new Token(\T_ATTRIBUTE, '#[', $token->line, $token->pos), + ]); + $c--; + continue; + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array { + // TODO + return $tokens; + } + + public function preprocessCode(string $code, array &$patches): string { + $pos = 0; + while (false !== $pos = strpos($code, '#[', $pos)) { + // Replace #[ with %[ + $code[$pos] = '%'; + $patches[] = [$pos, 'replace', '#']; + $pos += 2; + } + return $code; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php new file mode 100644 index 000000000..5418f52c0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php @@ -0,0 +1,26 @@ +id === \T_WHITESPACE + && $tokens[$pos + 2]->id === \T_STRING; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php new file mode 100644 index 000000000..9cadf420c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php @@ -0,0 +1,45 @@ +id == \T_LNUMBER && $token->text === '0' && + isset($tokens[$i + 1]) && $tokens[$i + 1]->id == \T_STRING && + preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1]->text) + ) { + $tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1]->text); + array_splice($tokens, $i, 2, [ + new Token($tokenKind, '0' . $tokens[$i + 1]->text, $token->line, $token->pos), + ]); + $c--; + } + } + return $tokens; + } + + private function resolveIntegerOrFloatToken(string $str): int { + $str = substr($str, 1); + $str = str_replace('_', '', $str); + $num = octdec($str); + return is_float($num) ? \T_DNUMBER : \T_LNUMBER; + } + + public function reverseEmulate(string $code, array $tokens): array { + // Explicit octals were not legal code previously, don't bother. + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php new file mode 100644 index 000000000..066e7cd85 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php @@ -0,0 +1,60 @@ +getKeywordString()) !== false; + } + + /** @param Token[] $tokens */ + protected function isKeywordContext(array $tokens, int $pos): bool { + $prevToken = $this->getPreviousNonSpaceToken($tokens, $pos); + if ($prevToken === null) { + return false; + } + return $prevToken->id !== \T_OBJECT_OPERATOR + && $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR; + } + + public function emulate(string $code, array $tokens): array { + $keywordString = $this->getKeywordString(); + foreach ($tokens as $i => $token) { + if ($token->id === T_STRING && strtolower($token->text) === $keywordString + && $this->isKeywordContext($tokens, $i)) { + $token->id = $this->getKeywordToken(); + } + } + + return $tokens; + } + + /** @param Token[] $tokens */ + private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token { + for ($i = $start - 1; $i >= 0; --$i) { + if ($tokens[$i]->id === T_WHITESPACE) { + continue; + } + + return $tokens[$i]; + } + + return null; + } + + public function reverseEmulate(string $code, array $tokens): array { + $keywordToken = $this->getKeywordToken(); + foreach ($tokens as $token) { + if ($token->id === $keywordToken) { + $token->id = \T_STRING; + } + } + + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php new file mode 100644 index 000000000..0fa5fbc2f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php @@ -0,0 +1,19 @@ +') !== false; + } + + public function emulate(string $code, array $tokens): array { + // We need to manually iterate and manage a count because we'll change + // the tokens array on the way + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + if ($token->text === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1]->id === \T_OBJECT_OPERATOR) { + array_splice($tokens, $i, 2, [ + new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos), + ]); + $c--; + continue; + } + + // Handle ?-> inside encapsed string. + if ($token->id === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1]) + && $tokens[$i - 1]->id === \T_VARIABLE + && preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $token->text, $matches) + ) { + $replacement = [ + new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos), + new Token(\T_STRING, $matches[1], $token->line, $token->pos + 3), + ]; + $matchLen = \strlen($matches[0]); + if ($matchLen !== \strlen($token->text)) { + $replacement[] = new Token( + \T_ENCAPSED_AND_WHITESPACE, + \substr($token->text, $matchLen), + $token->line, $token->pos + $matchLen + ); + } + array_splice($tokens, $i, 1, $replacement); + $c += \count($replacement) - 1; + continue; + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array { + // ?-> was not valid code previously, don't bother. + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/PipeOperatorEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/PipeOperatorEmulator.php new file mode 100644 index 000000000..b56169239 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/PipeOperatorEmulator.php @@ -0,0 +1,45 @@ +') !== false; + } + + public function emulate(string $code, array $tokens): array { + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + if ($token->text === '|' && isset($tokens[$i + 1]) && $tokens[$i + 1]->text === '>') { + array_splice($tokens, $i, 2, [ + new Token(\T_PIPE, '|>', $token->line, $token->pos), + ]); + $c--; + } + } + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array { + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + if ($token->id === \T_PIPE) { + array_splice($tokens, $i, 1, [ + new Token(\ord('|'), '|', $token->line, $token->pos), + new Token(\ord('>'), '>', $token->line, $token->pos + 1), + ]); + $i++; + $c++; + } + } + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/PropertyTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/PropertyTokenEmulator.php new file mode 100644 index 000000000..71b7fc232 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/PropertyTokenEmulator.php @@ -0,0 +1,19 @@ +text === '(' || + ($tokens[$pos + 1]->id === \T_WHITESPACE && + isset($tokens[$pos + 2]) && + $tokens[$pos + 2]->text === '('))); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php new file mode 100644 index 000000000..851b5c4ac --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php @@ -0,0 +1,37 @@ +emulator = $emulator; + } + + public function getPhpVersion(): PhpVersion { + return $this->emulator->getPhpVersion(); + } + + public function isEmulationNeeded(string $code): bool { + return $this->emulator->isEmulationNeeded($code); + } + + public function emulate(string $code, array $tokens): array { + return $this->emulator->reverseEmulate($code, $tokens); + } + + public function reverseEmulate(string $code, array $tokens): array { + return $this->emulator->emulate($code, $tokens); + } + + public function preprocessCode(string $code, array &$patches): string { + return $code; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php new file mode 100644 index 000000000..fec2f19f4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php @@ -0,0 +1,30 @@ +text !== '(') { + continue; + } + + $numTokens = 1; + $text = '('; + $j = $i + 1; + if ($j < $c && $tokens[$j]->id === \T_WHITESPACE && preg_match('/[ \t]+/', $tokens[$j]->text)) { + $text .= $tokens[$j]->text; + $numTokens++; + $j++; + } + + if ($j >= $c || $tokens[$j]->id !== \T_STRING || \strtolower($tokens[$j]->text) !== 'void') { + continue; + } + + $text .= $tokens[$j]->text; + $numTokens++; + $k = $j + 1; + if ($k < $c && $tokens[$k]->id === \T_WHITESPACE && preg_match('/[ \t]+/', $tokens[$k]->text)) { + $text .= $tokens[$k]->text; + $numTokens++; + $k++; + } + + if ($k >= $c || $tokens[$k]->text !== ')') { + continue; + } + + $text .= ')'; + $numTokens++; + array_splice($tokens, $i, $numTokens, [ + new Token(\T_VOID_CAST, $text, $token->line, $token->pos), + ]); + $c -= $numTokens - 1; + } + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array { + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + if ($token->id !== \T_VOID_CAST) { + continue; + } + + if (!preg_match('/^\(([ \t]*)(void)([ \t]*)\)$/i', $token->text, $match)) { + throw new \LogicException('Unexpected T_VOID_CAST contents'); + } + + $newTokens = []; + $pos = $token->pos; + + $newTokens[] = new Token(\ord('('), '(', $token->line, $pos); + $pos++; + + if ($match[1] !== '') { + $newTokens[] = new Token(\T_WHITESPACE, $match[1], $token->line, $pos); + $pos += \strlen($match[1]); + } + + $newTokens[] = new Token(\T_STRING, $match[2], $token->line, $pos); + $pos += \strlen($match[2]); + + if ($match[3] !== '') { + $newTokens[] = new Token(\T_WHITESPACE, $match[3], $token->line, $pos); + $pos += \strlen($match[3]); + } + + $newTokens[] = new Token(\ord(')'), ')', $token->line, $pos); + + array_splice($tokens, $i, 1, $newTokens); + $i += \count($newTokens) - 1; + $c += \count($newTokens) - 1; + } + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Modifiers.php b/vendor/nikic/php-parser/lib/PhpParser/Modifiers.php new file mode 100644 index 000000000..0f0f22d6b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Modifiers.php @@ -0,0 +1,85 @@ + 'public', + self::PROTECTED => 'protected', + self::PRIVATE => 'private', + self::STATIC => 'static', + self::ABSTRACT => 'abstract', + self::FINAL => 'final', + self::READONLY => 'readonly', + self::PUBLIC_SET => 'public(set)', + self::PROTECTED_SET => 'protected(set)', + self::PRIVATE_SET => 'private(set)', + ]; + + public static function toString(int $modifier): string { + if (!isset(self::TO_STRING_MAP[$modifier])) { + throw new \InvalidArgumentException("Unknown modifier $modifier"); + } + return self::TO_STRING_MAP[$modifier]; + } + + private static function isValidModifier(int $modifier): bool { + $isPow2 = ($modifier & ($modifier - 1)) == 0 && $modifier != 0; + return $isPow2 && $modifier <= self::PRIVATE_SET; + } + + /** + * @internal + */ + public static function verifyClassModifier(int $a, int $b): void { + assert(self::isValidModifier($b)); + if (($a & $b) != 0) { + throw new Error( + 'Multiple ' . self::toString($b) . ' modifiers are not allowed'); + } + + if ($a & 48 && $b & 48) { + throw new Error('Cannot use the final modifier on an abstract class'); + } + } + + /** + * @internal + */ + public static function verifyModifier(int $a, int $b): void { + assert(self::isValidModifier($b)); + if (($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) || + ($a & Modifiers::VISIBILITY_SET_MASK && $b & Modifiers::VISIBILITY_SET_MASK) + ) { + throw new Error('Multiple access type modifiers are not allowed'); + } + + if (($a & $b) != 0) { + throw new Error( + 'Multiple ' . self::toString($b) . ' modifiers are not allowed'); + } + + if ($a & 48 && $b & 48) { + throw new Error('Cannot use the final modifier on an abstract class member'); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NameContext.php b/vendor/nikic/php-parser/lib/PhpParser/NameContext.php new file mode 100644 index 000000000..2265ecce8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NameContext.php @@ -0,0 +1,284 @@ + [aliasName => originalName]] */ + protected array $aliases = []; + + /** @var Name[][] Same as $aliases but preserving original case */ + protected array $origAliases = []; + + /** @var ErrorHandler Error handler */ + protected ErrorHandler $errorHandler; + + /** + * Create a name context. + * + * @param ErrorHandler $errorHandler Error handling used to report errors + */ + public function __construct(ErrorHandler $errorHandler) { + $this->errorHandler = $errorHandler; + } + + /** + * Start a new namespace. + * + * This also resets the alias table. + * + * @param Name|null $namespace Null is the global namespace + */ + public function startNamespace(?Name $namespace = null): void { + $this->namespace = $namespace; + $this->origAliases = $this->aliases = [ + Stmt\Use_::TYPE_NORMAL => [], + Stmt\Use_::TYPE_FUNCTION => [], + Stmt\Use_::TYPE_CONSTANT => [], + ]; + } + + /** + * Add an alias / import. + * + * @param Name $name Original name + * @param string $aliasName Aliased name + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * @param array $errorAttrs Attributes to use to report an error + */ + public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []): void { + // Constant names are case sensitive, everything else case insensitive + if ($type === Stmt\Use_::TYPE_CONSTANT) { + $aliasLookupName = $aliasName; + } else { + $aliasLookupName = strtolower($aliasName); + } + + if (isset($this->aliases[$type][$aliasLookupName])) { + $typeStringMap = [ + Stmt\Use_::TYPE_NORMAL => '', + Stmt\Use_::TYPE_FUNCTION => 'function ', + Stmt\Use_::TYPE_CONSTANT => 'const ', + ]; + + $this->errorHandler->handleError(new Error( + sprintf( + 'Cannot use %s%s as %s because the name is already in use', + $typeStringMap[$type], $name, $aliasName + ), + $errorAttrs + )); + return; + } + + $this->aliases[$type][$aliasLookupName] = $name; + $this->origAliases[$type][$aliasName] = $name; + } + + /** + * Get current namespace. + * + * @return null|Name Namespace (or null if global namespace) + */ + public function getNamespace(): ?Name { + return $this->namespace; + } + + /** + * Get resolved name. + * + * @param Name $name Name to resolve + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT} + * + * @return null|Name Resolved name, or null if static resolution is not possible + */ + public function getResolvedName(Name $name, int $type): ?Name { + // don't resolve special class names + if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) { + if (!$name->isUnqualified()) { + $this->errorHandler->handleError(new Error( + sprintf("'\\%s' is an invalid class name", $name->toString()), + $name->getAttributes() + )); + } + return $name; + } + + // fully qualified names are already resolved + if ($name->isFullyQualified()) { + return $name; + } + + // Try to resolve aliases + if (null !== $resolvedName = $this->resolveAlias($name, $type)) { + return $resolvedName; + } + + if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) { + if (null === $this->namespace) { + // outside of a namespace unaliased unqualified is same as fully qualified + return new FullyQualified($name, $name->getAttributes()); + } + + // Cannot resolve statically + return null; + } + + // if no alias exists prepend current namespace + return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); + } + + /** + * Get resolved class name. + * + * @param Name $name Class ame to resolve + * + * @return Name Resolved name + */ + public function getResolvedClassName(Name $name): Name { + return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL); + } + + /** + * Get possible ways of writing a fully qualified name (e.g., by making use of aliases). + * + * @param string $name Fully-qualified name (without leading namespace separator) + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * + * @return Name[] Possible representations of the name + */ + public function getPossibleNames(string $name, int $type): array { + $lcName = strtolower($name); + + if ($type === Stmt\Use_::TYPE_NORMAL) { + // self, parent and static must always be unqualified + if ($lcName === "self" || $lcName === "parent" || $lcName === "static") { + return [new Name($name)]; + } + } + + // Collect possible ways to write this name, starting with the fully-qualified name + $possibleNames = [new FullyQualified($name)]; + + if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) { + // Make sure there is no alias that makes the normally namespace-relative name + // into something else + if (null === $this->resolveAlias($nsRelativeName, $type)) { + $possibleNames[] = $nsRelativeName; + } + } + + // Check for relevant namespace use statements + foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) { + $lcOrig = $orig->toLowerString(); + if (0 === strpos($lcName, $lcOrig . '\\')) { + $possibleNames[] = new Name($alias . substr($name, strlen($lcOrig))); + } + } + + // Check for relevant type-specific use statements + foreach ($this->origAliases[$type] as $alias => $orig) { + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // Constants are complicated-sensitive + $normalizedOrig = $this->normalizeConstName($orig->toString()); + if ($normalizedOrig === $this->normalizeConstName($name)) { + $possibleNames[] = new Name($alias); + } + } else { + // Everything else is case-insensitive + if ($orig->toLowerString() === $lcName) { + $possibleNames[] = new Name($alias); + } + } + } + + return $possibleNames; + } + + /** + * Get shortest representation of this fully-qualified name. + * + * @param string $name Fully-qualified name (without leading namespace separator) + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * + * @return Name Shortest representation + */ + public function getShortName(string $name, int $type): Name { + $possibleNames = $this->getPossibleNames($name, $type); + + // Find shortest name + $shortestName = null; + $shortestLength = \INF; + foreach ($possibleNames as $possibleName) { + $length = strlen($possibleName->toCodeString()); + if ($length < $shortestLength) { + $shortestName = $possibleName; + $shortestLength = $length; + } + } + + return $shortestName; + } + + private function resolveAlias(Name $name, int $type): ?FullyQualified { + $firstPart = $name->getFirst(); + + if ($name->isQualified()) { + // resolve aliases for qualified names, always against class alias table + $checkName = strtolower($firstPart); + if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) { + $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName]; + return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); + } + } elseif ($name->isUnqualified()) { + // constant aliases are case-sensitive, function aliases case-insensitive + $checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : strtolower($firstPart); + if (isset($this->aliases[$type][$checkName])) { + // resolve unqualified aliases + return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes()); + } + } + + // No applicable aliases + return null; + } + + private function getNamespaceRelativeName(string $name, string $lcName, int $type): ?Name { + if (null === $this->namespace) { + return new Name($name); + } + + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // The constants true/false/null always resolve to the global symbols, even inside a + // namespace, so they may be used without qualification + if ($lcName === "true" || $lcName === "false" || $lcName === "null") { + return new Name($name); + } + } + + $namespacePrefix = strtolower($this->namespace . '\\'); + if (0 === strpos($lcName, $namespacePrefix)) { + return new Name(substr($name, strlen($namespacePrefix))); + } + + return null; + } + + private function normalizeConstName(string $name): string { + $nsSep = strrpos($name, '\\'); + if (false === $nsSep) { + return $name; + } + + // Constants have case-insensitive namespace and case-sensitive short-name + $ns = substr($name, 0, $nsSep); + $shortName = substr($name, $nsSep + 1); + return strtolower($ns) . '\\' . $shortName; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node.php b/vendor/nikic/php-parser/lib/PhpParser/Node.php new file mode 100644 index 000000000..fd2a9b724 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node.php @@ -0,0 +1,150 @@ + + */ + public function getAttributes(): array; + + /** + * Replaces all the attributes of this node. + * + * @param array $attributes + */ + public function setAttributes(array $attributes): void; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php new file mode 100644 index 000000000..6680efac9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php @@ -0,0 +1,44 @@ + $attributes Additional attributes + * @param Identifier|null $name Parameter name (for named parameters) + */ + public function __construct( + Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = [], + ?Identifier $name = null + ) { + $this->attributes = $attributes; + $this->name = $name; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; + } + + public function getSubNodeNames(): array { + return ['name', 'value', 'byRef', 'unpack']; + } + + public function getType(): string { + return 'Arg'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ArrayItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ArrayItem.php new file mode 100644 index 000000000..fa1cff527 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ArrayItem.php @@ -0,0 +1,43 @@ + $attributes Additional attributes + */ + public function __construct(Expr $value, ?Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) { + $this->attributes = $attributes; + $this->key = $key; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; + } + + public function getSubNodeNames(): array { + return ['key', 'value', 'byRef', 'unpack']; + } + + public function getType(): string { + return 'ArrayItem'; + } +} + +// @deprecated compatibility alias +class_alias(ArrayItem::class, Expr\ArrayItem::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php new file mode 100644 index 000000000..9d892436a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php @@ -0,0 +1,33 @@ + Attribute arguments */ + public array $args; + + /** + * @param Node\Name $name Attribute name + * @param list $args Attribute arguments + * @param array $attributes Additional node attributes + */ + public function __construct(Name $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['name', 'args']; + } + + public function getType(): string { + return 'Attribute'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php b/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php new file mode 100644 index 000000000..b9eb588d0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php @@ -0,0 +1,27 @@ + $attributes Additional node attributes + */ + public function __construct(array $attrs, array $attributes = []) { + $this->attributes = $attributes; + $this->attrs = $attrs; + } + + public function getSubNodeNames(): array { + return ['attrs']; + } + + public function getType(): string { + return 'AttributeGroup'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ClosureUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ClosureUse.php new file mode 100644 index 000000000..e313280b6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ClosureUse.php @@ -0,0 +1,36 @@ + $attributes Additional attributes + */ + public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->byRef = $byRef; + } + + public function getSubNodeNames(): array { + return ['var', 'byRef']; + } + + public function getType(): string { + return 'ClosureUse'; + } +} + +// @deprecated compatibility alias +class_alias(ClosureUse::class, Expr\ClosureUse::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php new file mode 100644 index 000000000..05a5e5eef --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php @@ -0,0 +1,13 @@ + $attributes Additional attributes + */ + public function __construct($name, Expr $value, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['name', 'value']; + } + + public function getType(): string { + return 'Const'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/DeclareItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/DeclareItem.php new file mode 100644 index 000000000..55c1fe4f1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/DeclareItem.php @@ -0,0 +1,37 @@ +value pair node. + * + * @param string|Node\Identifier $key Key + * @param Node\Expr $value Value + * @param array $attributes Additional attributes + */ + public function __construct($key, Node\Expr $value, array $attributes = []) { + $this->attributes = $attributes; + $this->key = \is_string($key) ? new Node\Identifier($key) : $key; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['key', 'value']; + } + + public function getType(): string { + return 'DeclareItem'; + } +} + +// @deprecated compatibility alias +class_alias(DeclareItem::class, Stmt\DeclareDeclare::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php new file mode 100644 index 000000000..8b7dbb6ca --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php @@ -0,0 +1,8 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, ?Expr $dim = null, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->dim = $dim; + } + + public function getSubNodeNames(): array { + return ['var', 'dim']; + } + + public function getType(): string { + return 'Expr_ArrayDimFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php new file mode 100644 index 000000000..55ef16350 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php @@ -0,0 +1,15 @@ + $attributes Additional attributes + */ + public function __construct(array $items = [], array $attributes = []) { + $this->attributes = $attributes; + $this->items = $items; + } + + public function getSubNodeNames(): array { + return ['items']; + } + + public function getType(): string { + return 'Expr_Array'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php new file mode 100644 index 000000000..0e98ce9f6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php @@ -0,0 +1,84 @@ + false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes, array $attributes = []) { + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? false; + $this->byRef = $subNodes['byRef'] ?? false; + $this->params = $subNodes['params'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->expr = $subNodes['expr']; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + /** + * @return Node\Stmt\Return_[] + */ + public function getStmts(): array { + return [new Node\Stmt\Return_($this->expr)]; + } + + public function getType(): string { + return 'Expr_ArrowFunction'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php new file mode 100644 index 000000000..dcbf84dd4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['var', 'expr']; + } + + public function getType(): string { + return 'Expr_Assign'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php new file mode 100644 index 000000000..5209a64b1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['var', 'expr']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php new file mode 100644 index 000000000..4f3623fb6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php @@ -0,0 +1,11 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['var', 'expr']; + } + + public function getType(): string { + return 'Expr_AssignRef'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php new file mode 100644 index 000000000..1b92bd4f5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(Expr $left, Expr $right, array $attributes = []) { + $this->attributes = $attributes; + $this->left = $left; + $this->right = $right; + } + + public function getSubNodeNames(): array { + return ['left', 'right']; + } + + /** + * Get the operator sigil for this binary operation. + * + * In the case there are multiple possible sigils for an operator, this method does not + * necessarily return the one used in the parsed code. + */ + abstract public function getOperatorSigil(): string; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php new file mode 100644 index 000000000..5930c5413 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php @@ -0,0 +1,15 @@ +'; + } + + public function getType(): string { + return 'Expr_BinaryOp_Greater'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php new file mode 100644 index 000000000..4d440b100 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php @@ -0,0 +1,15 @@ +='; + } + + public function getType(): string { + return 'Expr_BinaryOp_GreaterOrEqual'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php new file mode 100644 index 000000000..e25d17cd9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php @@ -0,0 +1,15 @@ +'; + } + + public function getType(): string { + return 'Expr_BinaryOp_Pipe'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php new file mode 100644 index 000000000..fe34b84c9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php @@ -0,0 +1,15 @@ +>'; + } + + public function getType(): string { + return 'Expr_BinaryOp_ShiftRight'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php new file mode 100644 index 000000000..01e9b2310 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php @@ -0,0 +1,15 @@ +'; + } + + public function getType(): string { + return 'Expr_BinaryOp_Spaceship'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php new file mode 100644 index 000000000..b7175a7ae --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_BitwiseNot'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php new file mode 100644 index 000000000..c66d23326 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_BooleanNot'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php new file mode 100644 index 000000000..86e781c10 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php @@ -0,0 +1,60 @@ + + */ + abstract public function getRawArgs(): array; + + /** + * Returns whether this call expression is actually a first class callable. + */ + public function isFirstClassCallable(): bool { + $rawArgs = $this->getRawArgs(); + return count($rawArgs) === 1 && current($rawArgs) instanceof VariadicPlaceholder; + } + + /** + * Assert that this is not a first-class callable and return only ordinary Args. + * + * @return Arg[] + */ + public function getArgs(): array { + assert(!$this->isFirstClassCallable()); + return $this->getRawArgs(); + } + + /** + * Retrieves a specific argument from the raw arguments. + * + * Returns the named argument that matches the given `$name`, or the + * positional (unnamed) argument that exists at the given `$position`, + * otherwise, returns `null` for first-class callables or if no match is found. + */ + public function getArg(string $name, int $position): ?Arg { + if ($this->isFirstClassCallable()) { + return null; + } + foreach ($this->getRawArgs() as $i => $arg) { + if ($arg->unpack) { + continue; + } + if ( + ($arg->name !== null && $arg->name->toString() === $name) + || ($arg->name === null && $i === $position) + ) { + return $arg; + } + } + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php new file mode 100644 index 000000000..c2751de47 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php @@ -0,0 +1,25 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php new file mode 100644 index 000000000..471cb824a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php @@ -0,0 +1,11 @@ + $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['class', 'name']; + } + + public function getType(): string { + return 'Expr_ClassConstFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php new file mode 100644 index 000000000..d85bc9ab4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Clone'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php new file mode 100644 index 000000000..0680446f3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php @@ -0,0 +1,86 @@ + false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array(): Parameters + * 'uses' => array(): use()s + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attributes groups + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? false; + $this->byRef = $subNodes['byRef'] ?? false; + $this->params = $subNodes['params'] ?? []; + $this->uses = $subNodes['uses'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + /** @return Node\Stmt[] */ + public function getStmts(): array { + return $this->stmts; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + public function getType(): string { + return 'Expr_Closure'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php new file mode 100644 index 000000000..279aa26ad --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php @@ -0,0 +1,15 @@ + $attributes Additional attributes + */ + public function __construct(Name $name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Expr_ConstFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php new file mode 100644 index 000000000..d2f30506b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Empty'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php new file mode 100644 index 000000000..43010ac45 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + public function getSubNodeNames(): array { + return []; + } + + public function getType(): string { + return 'Expr_Error'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php new file mode 100644 index 000000000..32625a233 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_ErrorSuppress'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php new file mode 100644 index 000000000..5120b1b4f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Eval'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php new file mode 100644 index 000000000..cf0024669 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(?Expr $expr = null, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Exit'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php new file mode 100644 index 000000000..0b85840d8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php @@ -0,0 +1,38 @@ + Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr $name Function name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['name', 'args']; + } + + public function getType(): string { + return 'Expr_FuncCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php new file mode 100644 index 000000000..e1187b194 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php @@ -0,0 +1,38 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, int $type, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->type = $type; + } + + public function getSubNodeNames(): array { + return ['expr', 'type']; + } + + public function getType(): string { + return 'Expr_Include'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php new file mode 100644 index 000000000..a2783cb3a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php @@ -0,0 +1,35 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, Node $class, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->class = $class; + } + + public function getSubNodeNames(): array { + return ['expr', 'class']; + } + + public function getType(): string { + return 'Expr_Instanceof'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php new file mode 100644 index 000000000..4f80fff72 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Expr_Isset'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php new file mode 100644 index 000000000..496b7b385 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php @@ -0,0 +1,34 @@ + $attributes Additional attributes + */ + public function __construct(array $items, array $attributes = []) { + $this->attributes = $attributes; + $this->items = $items; + } + + public function getSubNodeNames(): array { + return ['items']; + } + + public function getType(): string { + return 'Expr_List'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php new file mode 100644 index 000000000..cd028a2da --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php @@ -0,0 +1,32 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->arms = $arms; + } + + public function getSubNodeNames(): array { + return ['cond', 'arms']; + } + + public function getType(): string { + return 'Expr_Match'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php new file mode 100644 index 000000000..2703c75d8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php @@ -0,0 +1,45 @@ + Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['var', 'name', 'args']; + } + + public function getType(): string { + return 'Expr_MethodCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php new file mode 100644 index 000000000..eedaaa1e3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php @@ -0,0 +1,40 @@ + Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes) + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $class, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['class', 'args']; + } + + public function getType(): string { + return 'Expr_New'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php new file mode 100644 index 000000000..a151f7152 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php @@ -0,0 +1,45 @@ + Arguments */ + public array $args; + + /** + * Constructs a nullsafe method call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['var', 'name', 'args']; + } + + public function getType(): string { + return 'Expr_NullsafeMethodCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php new file mode 100644 index 000000000..6f73a16d7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php @@ -0,0 +1,35 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['var', 'name']; + } + + public function getType(): string { + return 'Expr_NullsafePropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php new file mode 100644 index 000000000..3dca8fdc5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PostDec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php new file mode 100644 index 000000000..bc990c303 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PostInc'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php new file mode 100644 index 000000000..2f1687301 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PreDec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php new file mode 100644 index 000000000..fd455f55b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PreInc'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php new file mode 100644 index 000000000..605747604 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Print'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php new file mode 100644 index 000000000..8c416a8c4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php @@ -0,0 +1,35 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['var', 'name']; + } + + public function getType(): string { + return 'Expr_PropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php new file mode 100644 index 000000000..e40035127 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct(array $parts, array $attributes = []) { + $this->attributes = $attributes; + $this->parts = $parts; + } + + public function getSubNodeNames(): array { + return ['parts']; + } + + public function getType(): string { + return 'Expr_ShellExec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php new file mode 100644 index 000000000..707f34b66 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php @@ -0,0 +1,45 @@ + Arguments */ + public array $args; + + /** + * Constructs a static method call node. + * + * @param Node\Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['class', 'name', 'args']; + } + + public function getType(): string { + return 'Expr_StaticCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php new file mode 100644 index 000000000..4836a65b2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php @@ -0,0 +1,36 @@ + $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['class', 'name']; + } + + public function getType(): string { + return 'Expr_StaticPropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php new file mode 100644 index 000000000..d4837e640 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(Expr $cond, ?Expr $if, Expr $else, array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->if = $if; + $this->else = $else; + } + + public function getSubNodeNames(): array { + return ['cond', 'if', 'else']; + } + + public function getType(): string { + return 'Expr_Ternary'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php new file mode 100644 index 000000000..ee49f835f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Throw'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php new file mode 100644 index 000000000..cd06f74ba --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_UnaryMinus'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php new file mode 100644 index 000000000..1b44f7b3e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_UnaryPlus'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php new file mode 100644 index 000000000..bab74920a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Expr_Variable'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php new file mode 100644 index 000000000..5cff88f86 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_YieldFrom'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php new file mode 100644 index 000000000..bd81e69b3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(?Expr $value = null, ?Expr $key = null, array $attributes = []) { + $this->attributes = $attributes; + $this->key = $key; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['key', 'value']; + } + + public function getType(): string { + return 'Expr_Yield'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php new file mode 100644 index 000000000..58f653a89 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php @@ -0,0 +1,40 @@ + */ + private static array $specialClassNames = [ + 'self' => true, + 'parent' => true, + 'static' => true, + ]; + + /** + * Constructs an identifier node. + * + * @param string $name Identifier as string + * @param array $attributes Additional attributes + */ + public function __construct(string $name, array $attributes = []) { + if ($name === '') { + throw new \InvalidArgumentException('Identifier name cannot be empty'); + } + + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + /** + * Get identifier as string. + * + * @psalm-return non-empty-string + * @return string Identifier as string. + */ + public function toString(): string { + return $this->name; + } + + /** + * Get lowercased identifier as string. + * + * @psalm-return non-empty-string&lowercase-string + * @return string Lowercased identifier as string + */ + public function toLowerString(): string { + return strtolower($this->name); + } + + /** + * Checks whether the identifier is a special class name (self, parent or static). + * + * @return bool Whether identifier is a special class name + */ + public function isSpecialClassName(): bool { + return isset(self::$specialClassNames[strtolower($this->name)]); + } + + /** + * Get identifier as string. + * + * @psalm-return non-empty-string + * @return string Identifier as string + */ + public function __toString(): string { + return $this->name; + } + + public function getType(): string { + return 'Identifier'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/InterpolatedStringPart.php b/vendor/nikic/php-parser/lib/PhpParser/Node/InterpolatedStringPart.php new file mode 100644 index 000000000..576dac46f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/InterpolatedStringPart.php @@ -0,0 +1,32 @@ + $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + public function getType(): string { + return 'InterpolatedStringPart'; + } +} + +// @deprecated compatibility alias +class_alias(InterpolatedStringPart::class, Scalar\EncapsedStringPart::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php new file mode 100644 index 000000000..3b39cf105 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php @@ -0,0 +1,27 @@ + $attributes Additional attributes + */ + public function __construct(array $types, array $attributes = []) { + $this->attributes = $attributes; + $this->types = $types; + } + + public function getSubNodeNames(): array { + return ['types']; + } + + public function getType(): string { + return 'IntersectionType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php b/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php new file mode 100644 index 000000000..192216dfb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php @@ -0,0 +1,29 @@ + */ + public ?array $conds; + public Expr $body; + + /** + * @param null|list $conds + */ + public function __construct(?array $conds, Node\Expr $body, array $attributes = []) { + $this->conds = $conds; + $this->body = $body; + $this->attributes = $attributes; + } + + public function getSubNodeNames(): array { + return ['conds', 'body']; + } + + public function getType(): string { + return 'MatchArm'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php new file mode 100644 index 000000000..932080b54 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php @@ -0,0 +1,278 @@ + */ + private static array $specialClassNames = [ + 'self' => true, + 'parent' => true, + 'static' => true, + ]; + + /** + * Constructs a name node. + * + * @param string|string[]|self $name Name as string, part array or Name instance (copy ctor) + * @param array $attributes Additional attributes + */ + final public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = self::prepareName($name); + } + + public function getSubNodeNames(): array { + return ['name']; + } + + /** + * Get parts of name (split by the namespace separator). + * + * @psalm-return non-empty-list + * @return string[] Parts of name + */ + public function getParts(): array { + return \explode('\\', $this->name); + } + + /** + * Gets the first part of the name, i.e. everything before the first namespace separator. + * + * @return string First part of the name + */ + public function getFirst(): string { + if (false !== $pos = \strpos($this->name, '\\')) { + return \substr($this->name, 0, $pos); + } + return $this->name; + } + + /** + * Gets the last part of the name, i.e. everything after the last namespace separator. + * + * @return string Last part of the name + */ + public function getLast(): string { + if (false !== $pos = \strrpos($this->name, '\\')) { + return \substr($this->name, $pos + 1); + } + return $this->name; + } + + /** + * Checks whether the name is unqualified. (E.g. Name) + * + * @return bool Whether the name is unqualified + */ + public function isUnqualified(): bool { + return false === \strpos($this->name, '\\'); + } + + /** + * Checks whether the name is qualified. (E.g. Name\Name) + * + * @return bool Whether the name is qualified + */ + public function isQualified(): bool { + return false !== \strpos($this->name, '\\'); + } + + /** + * Checks whether the name is fully qualified. (E.g. \Name) + * + * @return bool Whether the name is fully qualified + */ + public function isFullyQualified(): bool { + return false; + } + + /** + * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name) + * + * @return bool Whether the name is relative + */ + public function isRelative(): bool { + return false; + } + + /** + * Returns a string representation of the name itself, without taking the name type into + * account (e.g., not including a leading backslash for fully qualified names). + * + * @psalm-return non-empty-string + * @return string String representation + */ + public function toString(): string { + return $this->name; + } + + /** + * Returns a string representation of the name as it would occur in code (e.g., including + * leading backslash for fully qualified names. + * + * @psalm-return non-empty-string + * @return string String representation + */ + public function toCodeString(): string { + return $this->toString(); + } + + /** + * Returns lowercased string representation of the name, without taking the name type into + * account (e.g., no leading backslash for fully qualified names). + * + * @psalm-return non-empty-string&lowercase-string + * @return string Lowercased string representation + */ + public function toLowerString(): string { + return strtolower($this->name); + } + + /** + * Checks whether the identifier is a special class name (self, parent or static). + * + * @return bool Whether identifier is a special class name + */ + public function isSpecialClassName(): bool { + return isset(self::$specialClassNames[strtolower($this->name)]); + } + + /** + * Returns a string representation of the name by imploding the namespace parts with the + * namespace separator. + * + * @psalm-return non-empty-string + * @return string String representation + */ + public function __toString(): string { + return $this->name; + } + + /** + * Gets a slice of a name (similar to array_slice). + * + * This method returns a new instance of the same type as the original and with the same + * attributes. + * + * If the slice is empty, null is returned. The null value will be correctly handled in + * concatenations using concat(). + * + * Offset and length have the same meaning as in array_slice(). + * + * @param int $offset Offset to start the slice at (may be negative) + * @param int|null $length Length of the slice (may be negative) + * + * @return static|null Sliced name + */ + public function slice(int $offset, ?int $length = null) { + if ($offset === 1 && $length === null) { + // Short-circuit the common case. + if (false !== $pos = \strpos($this->name, '\\')) { + return new static(\substr($this->name, $pos + 1)); + } + return null; + } + + $parts = \explode('\\', $this->name); + $numParts = \count($parts); + + $realOffset = $offset < 0 ? $offset + $numParts : $offset; + if ($realOffset < 0 || $realOffset > $numParts) { + throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset)); + } + + if (null === $length) { + $realLength = $numParts - $realOffset; + } else { + $realLength = $length < 0 ? $length + $numParts - $realOffset : $length; + if ($realLength < 0 || $realLength > $numParts - $realOffset) { + throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length)); + } + } + + if ($realLength === 0) { + // Empty slice is represented as null + return null; + } + + return new static(array_slice($parts, $realOffset, $realLength), $this->attributes); + } + + /** + * Concatenate two names, yielding a new Name instance. + * + * The type of the generated instance depends on which class this method is called on, for + * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance. + * + * If one of the arguments is null, a new instance of the other name will be returned. If both + * arguments are null, null will be returned. As such, writing + * Name::concat($namespace, $shortName) + * where $namespace is a Name node or null will work as expected. + * + * @param string|string[]|self|null $name1 The first name + * @param string|string[]|self|null $name2 The second name + * @param array $attributes Attributes to assign to concatenated name + * + * @return static|null Concatenated name + */ + public static function concat($name1, $name2, array $attributes = []) { + if (null === $name1 && null === $name2) { + return null; + } + if (null === $name1) { + return new static($name2, $attributes); + } + if (null === $name2) { + return new static($name1, $attributes); + } else { + return new static( + self::prepareName($name1) . '\\' . self::prepareName($name2), $attributes + ); + } + } + + /** + * Prepares a (string, array or Name node) name for use in name changing methods by converting + * it to a string. + * + * @param string|string[]|self $name Name to prepare + * + * @psalm-return non-empty-string + * @return string Prepared name + */ + private static function prepareName($name): string { + if (\is_string($name)) { + if ('' === $name) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return $name; + } + if (\is_array($name)) { + if (empty($name)) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return implode('\\', $name); + } + if ($name instanceof self) { + return $name->name; + } + + throw new \InvalidArgumentException( + 'Expected string, array of parts or Name instance' + ); + } + + public function getType(): string { + return 'Name'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php new file mode 100644 index 000000000..21183786b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php @@ -0,0 +1,49 @@ +toString(); + } + + public function getType(): string { + return 'Name_FullyQualified'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php new file mode 100644 index 000000000..0226a4e48 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php @@ -0,0 +1,49 @@ +toString(); + } + + public function getType(): string { + return 'Name_Relative'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php new file mode 100644 index 000000000..b99acd135 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Node $type, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + } + + public function getSubNodeNames(): array { + return ['type']; + } + + public function getType(): string { + return 'NullableType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php new file mode 100644 index 000000000..6cbb84c9d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php @@ -0,0 +1,123 @@ + $attributes Additional attributes + * @param int $flags Optional visibility flags + * @param list $attrGroups PHP attribute groups + * @param PropertyHook[] $hooks Property hooks for promoted properties + */ + public function __construct( + Expr $var, ?Expr $default = null, ?Node $type = null, + bool $byRef = false, bool $variadic = false, + array $attributes = [], + int $flags = 0, + array $attrGroups = [], + array $hooks = [] + ) { + $this->attributes = $attributes; + $this->type = $type; + $this->byRef = $byRef; + $this->variadic = $variadic; + $this->var = $var; + $this->default = $default; + $this->flags = $flags; + $this->attrGroups = $attrGroups; + $this->hooks = $hooks; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default', 'hooks']; + } + + public function getType(): string { + return 'Param'; + } + + /** + * Whether this parameter uses constructor property promotion. + */ + public function isPromoted(): bool { + return $this->flags !== 0 || $this->hooks !== []; + } + + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + public function isPublic(): bool { + $public = (bool) ($this->flags & Modifiers::PUBLIC); + if ($public) { + return true; + } + + if (!$this->isPromoted()) { + return false; + } + + return ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + public function isReadonly(): bool { + return (bool) ($this->flags & Modifiers::READONLY); + } + + /** + * Whether the promoted property has explicit public(set) visibility. + */ + public function isPublicSet(): bool { + return (bool) ($this->flags & Modifiers::PUBLIC_SET); + } + + /** + * Whether the promoted property has explicit protected(set) visibility. + */ + public function isProtectedSet(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED_SET); + } + + /** + * Whether the promoted property has explicit private(set) visibility. + */ + public function isPrivateSet(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE_SET); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyHook.php b/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyHook.php new file mode 100644 index 000000000..349b9cef4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyHook.php @@ -0,0 +1,105 @@ + 0 : Flags + * 'byRef' => false : Whether hook returns by reference + * 'params' => array(): Parameters + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, $body, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->body = $body; + $this->flags = $subNodes['flags'] ?? 0; + $this->byRef = $subNodes['byRef'] ?? false; + $this->params = $subNodes['params'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return null; + } + + /** + * Whether the property hook is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + public function getStmts(): ?array { + if ($this->body instanceof Expr) { + $name = $this->name->toLowerString(); + if ($name === 'get') { + return [new Return_($this->body)]; + } + if ($name === 'set') { + if (!$this->hasAttribute('propertyName')) { + throw new \LogicException( + 'Can only use getStmts() on a "set" hook if the "propertyName" attribute is set'); + } + + $propName = $this->getAttribute('propertyName'); + $prop = new PropertyFetch(new Variable('this'), (string) $propName); + return [new Expression(new Assign($prop, $this->body))]; + } + throw new \LogicException('Unknown property hook "' . $name . '"'); + } + return $this->body; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + public function getType(): string { + return 'PropertyHook'; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'body']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyItem.php new file mode 100644 index 000000000..101611e6b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyItem.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct($name, ?Node\Expr $default = null, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\VarLikeIdentifier($name) : $name; + $this->default = $default; + } + + public function getSubNodeNames(): array { + return ['name', 'default']; + } + + public function getType(): string { + return 'PropertyItem'; + } +} + +// @deprecated compatibility alias +class_alias(PropertyItem::class, Stmt\PropertyProperty::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php new file mode 100644 index 000000000..3df257216 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php @@ -0,0 +1,6 @@ + $attributes Additional attributes + */ + public function __construct(float $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + /** + * @param mixed[] $attributes + */ + public static function fromString(string $str, array $attributes = []): Float_ { + $attributes['rawValue'] = $str; + $float = self::parse($str); + + return new Float_($float, $attributes); + } + + /** + * @internal + * + * Parses a DNUMBER token like PHP would. + * + * @param string $str A string number + * + * @return float The parsed number + */ + public static function parse(string $str): float { + $str = str_replace('_', '', $str); + + // Check whether this is one of the special integer notations. + if ('0' === $str[0]) { + // hex + if ('x' === $str[1] || 'X' === $str[1]) { + return hexdec($str); + } + + // bin + if ('b' === $str[1] || 'B' === $str[1]) { + return bindec($str); + } + + // oct, but only if the string does not contain any of '.eE'. + if (false === strpbrk($str, '.eE')) { + // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit + // (8 or 9) so that only the digits before that are used. + return octdec(substr($str, 0, strcspn($str, '89'))); + } + } + + // dec + return (float) $str; + } + + public function getType(): string { + return 'Scalar_Float'; + } +} + +// @deprecated compatibility alias +class_alias(Float_::class, DNumber::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Int_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Int_.php new file mode 100644 index 000000000..bcc257a6a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Int_.php @@ -0,0 +1,82 @@ + $attributes Additional attributes + */ + public function __construct(int $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + /** + * Constructs an Int node from a string number literal. + * + * @param string $str String number literal (decimal, octal, hex or binary) + * @param array $attributes Additional attributes + * @param bool $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5) + * + * @return Int_ The constructed LNumber, including kind attribute + */ + public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false): Int_ { + $attributes['rawValue'] = $str; + + $str = str_replace('_', '', $str); + + if ('0' !== $str[0] || '0' === $str) { + $attributes['kind'] = Int_::KIND_DEC; + return new Int_((int) $str, $attributes); + } + + if ('x' === $str[1] || 'X' === $str[1]) { + $attributes['kind'] = Int_::KIND_HEX; + return new Int_(hexdec($str), $attributes); + } + + if ('b' === $str[1] || 'B' === $str[1]) { + $attributes['kind'] = Int_::KIND_BIN; + return new Int_(bindec($str), $attributes); + } + + if (!$allowInvalidOctal && strpbrk($str, '89')) { + throw new Error('Invalid numeric literal', $attributes); + } + + // Strip optional explicit octal prefix. + if ('o' === $str[1] || 'O' === $str[1]) { + $str = substr($str, 2); + } + + // use intval instead of octdec to get proper cutting behavior with malformed numbers + $attributes['kind'] = Int_::KIND_OCT; + return new Int_(intval($str, 8), $attributes); + } + + public function getType(): string { + return 'Scalar_Int'; + } +} + +// @deprecated compatibility alias +class_alias(Int_::class, LNumber::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/InterpolatedString.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/InterpolatedString.php new file mode 100644 index 000000000..9336dfe4d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/InterpolatedString.php @@ -0,0 +1,34 @@ + $attributes Additional attributes + */ + public function __construct(array $parts, array $attributes = []) { + $this->attributes = $attributes; + $this->parts = $parts; + } + + public function getSubNodeNames(): array { + return ['parts']; + } + + public function getType(): string { + return 'Scalar_InterpolatedString'; + } +} + +// @deprecated compatibility alias +class_alias(InterpolatedString::class, Encapsed::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php new file mode 100644 index 000000000..868d78f86 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php @@ -0,0 +1,15 @@ + $attributes Additional attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + public function getSubNodeNames(): array { + return []; + } + + /** + * Get name of magic constant. + * + * @return string Name of magic constant + */ + abstract public function getName(): string; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php new file mode 100644 index 000000000..732ed140f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php @@ -0,0 +1,15 @@ + Escaped character to its decoded value */ + protected static array $replacements = [ + '\\' => '\\', + '$' => '$', + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'f' => "\f", + 'v' => "\v", + 'e' => "\x1B", + ]; + + /** + * Constructs a string scalar node. + * + * @param string $value Value of the string + * @param array $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + /** + * @param array $attributes + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + */ + public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self { + $attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B'))) + ? Scalar\String_::KIND_SINGLE_QUOTED + : Scalar\String_::KIND_DOUBLE_QUOTED; + + $attributes['rawValue'] = $str; + + $string = self::parse($str, $parseUnicodeEscape); + + return new self($string, $attributes); + } + + /** + * @internal + * + * Parses a string token. + * + * @param string $str String token content + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string The parsed string + */ + public static function parse(string $str, bool $parseUnicodeEscape = true): string { + $bLength = 0; + if ('b' === $str[0] || 'B' === $str[0]) { + $bLength = 1; + } + + if ('\'' === $str[$bLength]) { + return str_replace( + ['\\\\', '\\\''], + ['\\', '\''], + substr($str, $bLength + 1, -1) + ); + } else { + return self::parseEscapeSequences( + substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape + ); + } + } + + /** + * @internal + * + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param null|string $quote Quote type + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string String with escape sequences parsed + */ + public static function parseEscapeSequences(string $str, ?string $quote, bool $parseUnicodeEscape = true): string { + if (null !== $quote) { + $str = str_replace('\\' . $quote, $quote, $str); + } + + $extra = ''; + if ($parseUnicodeEscape) { + $extra = '|u\{([0-9a-fA-F]+)\}'; + } + + return preg_replace_callback( + '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~', + function ($matches) { + $str = $matches[1]; + + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } + if ('x' === $str[0] || 'X' === $str[0]) { + return chr(hexdec(substr($str, 1))); + } + if ('u' === $str[0]) { + $dec = hexdec($matches[2]); + // If it overflowed to float, treat as INT_MAX, it will throw an error anyway. + return self::codePointToUtf8(\is_int($dec) ? $dec : \PHP_INT_MAX); + } else { + return chr(octdec($str) & 255); + } + }, + $str + ); + } + + /** + * Converts a Unicode code point to its UTF-8 encoded representation. + * + * @param int $num Code point + * + * @return string UTF-8 representation of code point + */ + private static function codePointToUtf8(int $num): string { + if ($num <= 0x7F) { + return chr($num); + } + if ($num <= 0x7FF) { + return chr(($num >> 6) + 0xC0) . chr(($num & 0x3F) + 0x80); + } + if ($num <= 0xFFFF) { + return chr(($num >> 12) + 0xE0) . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); + } + if ($num <= 0x1FFFFF) { + return chr(($num >> 18) + 0xF0) . chr((($num >> 12) & 0x3F) + 0x80) + . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); + } + throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large'); + } + + public function getType(): string { + return 'Scalar_String'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/StaticVar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/StaticVar.php new file mode 100644 index 000000000..517c0eddd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/StaticVar.php @@ -0,0 +1,39 @@ + $attributes Additional attributes + */ + public function __construct( + Expr\Variable $var, ?Node\Expr $default = null, array $attributes = [] + ) { + $this->attributes = $attributes; + $this->var = $var; + $this->default = $default; + } + + public function getSubNodeNames(): array { + return ['var', 'default']; + } + + public function getType(): string { + return 'StaticVar'; + } +} + +// @deprecated compatibility alias +class_alias(StaticVar::class, Stmt\StaticVar::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php new file mode 100644 index 000000000..481d31a93 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php @@ -0,0 +1,8 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts, array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + } + + public function getType(): string { + return 'Stmt_Block'; + } + + public function getSubNodeNames(): array { + return ['stmts']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php new file mode 100644 index 000000000..d2bcc5eb2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $num = null, array $attributes = []) { + $this->attributes = $attributes; + $this->num = $num; + } + + public function getSubNodeNames(): array { + return ['num']; + } + + public function getType(): string { + return 'Stmt_Break'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php new file mode 100644 index 000000000..a06ca1832 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Case'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php new file mode 100644 index 000000000..e8d39c9cc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php @@ -0,0 +1,40 @@ + $attributes Additional attributes + */ + public function __construct( + array $types, ?Expr\Variable $var = null, array $stmts = [], array $attributes = [] + ) { + $this->attributes = $attributes; + $this->types = $types; + $this->var = $var; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['types', 'var', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Catch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php new file mode 100644 index 000000000..9bdce1f1d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php @@ -0,0 +1,77 @@ + $attributes Additional attributes + * @param list $attrGroups PHP attribute groups + * @param null|Node\Identifier|Node\Name|Node\ComplexType $type Type declaration + */ + public function __construct( + array $consts, + int $flags = 0, + array $attributes = [], + array $attrGroups = [], + ?Node $type = null + ) { + $this->attributes = $attributes; + $this->flags = $flags; + $this->consts = $consts; + $this->attrGroups = $attrGroups; + $this->type = $type; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'type', 'consts']; + } + + /** + * Whether constant is explicitly or implicitly public. + */ + public function isPublic(): bool { + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + /** + * Whether constant is protected. + */ + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + /** + * Whether constant is private. + */ + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + /** + * Whether constant is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + public function getType(): string { + return 'Stmt_ClassConst'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php new file mode 100644 index 000000000..e652177c4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php @@ -0,0 +1,109 @@ + + */ + public function getTraitUses(): array { + $traitUses = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof TraitUse) { + $traitUses[] = $stmt; + } + } + return $traitUses; + } + + /** + * @return list + */ + public function getConstants(): array { + $constants = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassConst) { + $constants[] = $stmt; + } + } + return $constants; + } + + /** + * @return list + */ + public function getProperties(): array { + $properties = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + $properties[] = $stmt; + } + } + return $properties; + } + + /** + * Gets property with the given name defined directly in this class/interface/trait. + * + * @param string $name Name of the property + * + * @return Property|null Property node or null if the property does not exist + */ + public function getProperty(string $name): ?Property { + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + foreach ($stmt->props as $prop) { + if ($prop instanceof PropertyItem && $name === $prop->name->toString()) { + return $stmt; + } + } + } + } + return null; + } + + /** + * Gets all methods defined directly in this class/interface/trait + * + * @return list + */ + public function getMethods(): array { + $methods = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod) { + $methods[] = $stmt; + } + } + return $methods; + } + + /** + * Gets method with the given name defined directly in this class/interface/trait. + * + * @param string $name Name of the method (compared case-insensitively) + * + * @return ClassMethod|null Method node or null if the method does not exist + */ + public function getMethod(string $name): ?ClassMethod { + $lowerName = strtolower($name); + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod && $lowerName === $stmt->name->toLowerString()) { + return $stmt; + } + } + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php new file mode 100644 index 000000000..59c0519ea --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php @@ -0,0 +1,154 @@ + */ + private static array $magicNames = [ + '__construct' => true, + '__destruct' => true, + '__call' => true, + '__callstatic' => true, + '__get' => true, + '__set' => true, + '__isset' => true, + '__unset' => true, + '__sleep' => true, + '__wakeup' => true, + '__tostring' => true, + '__set_state' => true, + '__clone' => true, + '__invoke' => true, + '__debuginfo' => true, + '__serialize' => true, + '__unserialize' => true, + ]; + + /** + * Constructs a class method node. + * + * @param string|Node\Identifier $name Name + * @param array{ + * flags?: int, + * byRef?: bool, + * params?: Node\Param[], + * returnType?: null|Node\Identifier|Node\Name|Node\ComplexType, + * stmts?: Node\Stmt[]|null, + * attrGroups?: Node\AttributeGroup[], + * } $subNodes Array of the following optional subnodes: + * 'flags => 0 : Flags + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->byRef = $subNodes['byRef'] ?? false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'returnType', 'stmts']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getStmts(): ?array { + return $this->stmts; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + /** + * Whether the method is explicitly or implicitly public. + */ + public function isPublic(): bool { + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + /** + * Whether the method is protected. + */ + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + /** + * Whether the method is private. + */ + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + /** + * Whether the method is abstract. + */ + public function isAbstract(): bool { + return (bool) ($this->flags & Modifiers::ABSTRACT); + } + + /** + * Whether the method is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + /** + * Whether the method is static. + */ + public function isStatic(): bool { + return (bool) ($this->flags & Modifiers::STATIC); + } + + /** + * Whether the method is magic. + */ + public function isMagic(): bool { + return isset(self::$magicNames[$this->name->toLowerString()]); + } + + public function getType(): string { + return 'Stmt_ClassMethod'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php new file mode 100644 index 000000000..3f492b7bb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php @@ -0,0 +1,94 @@ + 0 : Flags + * 'extends' => null : Name of extended class + * 'implements' => array(): Names of implemented interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'name', 'extends', 'implements', 'stmts']; + } + + /** + * Whether the class is explicitly abstract. + */ + public function isAbstract(): bool { + return (bool) ($this->flags & Modifiers::ABSTRACT); + } + + /** + * Whether the class is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + public function isReadonly(): bool { + return (bool) ($this->flags & Modifiers::READONLY); + } + + /** + * Whether the class is anonymous. + */ + public function isAnonymous(): bool { + return null === $this->name; + } + + public function getType(): string { + return 'Stmt_Class'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php new file mode 100644 index 000000000..c54d6780e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + * @param list $attrGroups PHP attribute groups + */ + public function __construct( + array $consts, + array $attributes = [], + array $attrGroups = [] + ) { + $this->attributes = $attributes; + $this->attrGroups = $attrGroups; + $this->consts = $consts; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'consts']; + } + + public function getType(): string { + return 'Stmt_Const'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php new file mode 100644 index 000000000..54e979dda --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $num = null, array $attributes = []) { + $this->attributes = $attributes; + $this->num = $num; + } + + public function getSubNodeNames(): array { + return ['num']; + } + + public function getType(): string { + return 'Stmt_Continue'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php new file mode 100644 index 000000000..c18613438 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php @@ -0,0 +1,17 @@ + $attributes Additional attributes + */ + public function __construct(array $declares, ?array $stmts = null, array $attributes = []) { + $this->attributes = $attributes; + $this->declares = $declares; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['declares', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Declare'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php new file mode 100644 index 000000000..612444288 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['stmts', 'cond']; + } + + public function getType(): string { + return 'Stmt_Do'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php new file mode 100644 index 000000000..4d4245235 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $exprs, array $attributes = []) { + $this->attributes = $attributes; + $this->exprs = $exprs; + } + + public function getSubNodeNames(): array { + return ['exprs']; + } + + public function getType(): string { + return 'Stmt_Echo'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php new file mode 100644 index 000000000..b26d59ce5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts']; + } + + public function getType(): string { + return 'Stmt_ElseIf'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php new file mode 100644 index 000000000..3d2b066ec --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['stmts']; + } + + public function getType(): string { + return 'Stmt_Else'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php new file mode 100644 index 000000000..c071a0af1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php @@ -0,0 +1,36 @@ + $attrGroups PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, ?Node\Expr $expr = null, array $attrGroups = [], array $attributes = []) { + parent::__construct($attributes); + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->expr = $expr; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'expr']; + } + + public function getType(): string { + return 'Stmt_EnumCase'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php new file mode 100644 index 000000000..7eea6a699 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php @@ -0,0 +1,44 @@ + null : Scalar type + * 'implements' => array() : Names of implemented interfaces + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->scalarType = $subNodes['scalarType'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + + parent::__construct($attributes); + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'scalarType', 'implements', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Enum'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php new file mode 100644 index 000000000..89751fa2d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php @@ -0,0 +1,32 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Stmt_Expression'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php new file mode 100644 index 000000000..69ecf2537 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['stmts']; + } + + public function getType(): string { + return 'Stmt_Finally'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php new file mode 100644 index 000000000..6f2fbb9e3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php @@ -0,0 +1,47 @@ + array(): Init expressions + * 'cond' => array(): Loop conditions + * 'loop' => array(): Loop expressions + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->init = $subNodes['init'] ?? []; + $this->cond = $subNodes['cond'] ?? []; + $this->loop = $subNodes['loop'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + } + + public function getSubNodeNames(): array { + return ['init', 'cond', 'loop', 'stmts']; + } + + public function getType(): string { + return 'Stmt_For'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php new file mode 100644 index 000000000..c5d9a8b13 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php @@ -0,0 +1,50 @@ + null : Variable to assign key to + * 'byRef' => false : Whether to assign value by reference + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->keyVar = $subNodes['keyVar'] ?? null; + $this->byRef = $subNodes['byRef'] ?? false; + $this->valueVar = $valueVar; + $this->stmts = $subNodes['stmts'] ?? []; + } + + public function getSubNodeNames(): array { + return ['expr', 'keyVar', 'byRef', 'valueVar', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Foreach'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php new file mode 100644 index 000000000..2111bab74 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php @@ -0,0 +1,81 @@ + false : Whether to return by reference + * 'params' => array(): Parameters + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->byRef = $subNodes['byRef'] ?? false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'byRef', 'name', 'params', 'returnType', 'stmts']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + /** @return Node\Stmt[] */ + public function getStmts(): array { + return $this->stmts; + } + + public function getType(): string { + return 'Stmt_Function'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php new file mode 100644 index 000000000..d3ab12fc2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Stmt_Global'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php new file mode 100644 index 000000000..26a0d01ea --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Stmt_Goto'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php new file mode 100644 index 000000000..0ec8e9d42 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php @@ -0,0 +1,41 @@ + $attributes Additional attributes + */ + public function __construct(Name $prefix, array $uses, int $type = Use_::TYPE_NORMAL, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + $this->prefix = $prefix; + $this->uses = $uses; + } + + public function getSubNodeNames(): array { + return ['type', 'prefix', 'uses']; + } + + public function getType(): string { + return 'Stmt_GroupUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php new file mode 100644 index 000000000..665bacdee --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(string $remaining, array $attributes = []) { + $this->attributes = $attributes; + $this->remaining = $remaining; + } + + public function getSubNodeNames(): array { + return ['remaining']; + } + + public function getType(): string { + return 'Stmt_HaltCompiler'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php new file mode 100644 index 000000000..544390ff5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php @@ -0,0 +1,46 @@ + array(): Statements + * 'elseifs' => array(): Elseif clauses + * 'else' => null : Else clause + * @param array $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $subNodes['stmts'] ?? []; + $this->elseifs = $subNodes['elseifs'] ?? []; + $this->else = $subNodes['else'] ?? null; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts', 'elseifs', 'else']; + } + + public function getType(): string { + return 'Stmt_If'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php new file mode 100644 index 000000000..0515d0205 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + public function getType(): string { + return 'Stmt_InlineHTML'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php new file mode 100644 index 000000000..9359064f8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php @@ -0,0 +1,40 @@ + array(): Name of extended interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'extends', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Interface'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php new file mode 100644 index 000000000..658468d2f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Stmt_Label'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php new file mode 100644 index 000000000..f5b59ad6e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Name $name = null, ?array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['name', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Namespace'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php new file mode 100644 index 000000000..3acfa46fb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php @@ -0,0 +1,16 @@ + $attributes Additional attributes + * @param null|Identifier|Name|ComplexType $type Type declaration + * @param Node\AttributeGroup[] $attrGroups PHP attribute groups + * @param Node\PropertyHook[] $hooks Property hooks + */ + public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = [], array $hooks = []) { + $this->attributes = $attributes; + $this->flags = $flags; + $this->props = $props; + $this->type = $type; + $this->attrGroups = $attrGroups; + $this->hooks = $hooks; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'type', 'props', 'hooks']; + } + + /** + * Whether the property is explicitly or implicitly public. + */ + public function isPublic(): bool { + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + /** + * Whether the property is protected. + */ + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + /** + * Whether the property is private. + */ + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + /** + * Whether the property is static. + */ + public function isStatic(): bool { + return (bool) ($this->flags & Modifiers::STATIC); + } + + /** + * Whether the property is readonly. + */ + public function isReadonly(): bool { + return (bool) ($this->flags & Modifiers::READONLY); + } + + /** + * Whether the property is abstract. + */ + public function isAbstract(): bool { + return (bool) ($this->flags & Modifiers::ABSTRACT); + } + + /** + * Whether the property is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + /** + * Whether the property has explicit public(set) visibility. + */ + public function isPublicSet(): bool { + return (bool) ($this->flags & Modifiers::PUBLIC_SET); + } + + /** + * Whether the property has explicit protected(set) visibility. + */ + public function isProtectedSet(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED_SET); + } + + /** + * Whether the property has explicit private(set) visibility. + */ + public function isPrivateSet(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE_SET); + } + + public function getType(): string { + return 'Stmt_Property'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php new file mode 100644 index 000000000..62556e709 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php @@ -0,0 +1,17 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $expr = null, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Stmt_Return'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php new file mode 100644 index 000000000..a3c5fa6e6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php @@ -0,0 +1,15 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Stmt_Static'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php new file mode 100644 index 000000000..21e5efa56 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $cases, array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->cases = $cases; + } + + public function getSubNodeNames(): array { + return ['cond', 'cases']; + } + + public function getType(): string { + return 'Stmt_Switch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php new file mode 100644 index 000000000..7705a5705 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(array $traits, array $adaptations = [], array $attributes = []) { + $this->attributes = $attributes; + $this->traits = $traits; + $this->adaptations = $adaptations; + } + + public function getSubNodeNames(): array { + return ['traits', 'adaptations']; + } + + public function getType(): string { + return 'Stmt_TraitUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php new file mode 100644 index 000000000..987bc88ed --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php @@ -0,0 +1,12 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Name $trait, $method, ?int $newModifier, $newName, array $attributes = []) { + $this->attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->newModifier = $newModifier; + $this->newName = \is_string($newName) ? new Node\Identifier($newName) : $newName; + } + + public function getSubNodeNames(): array { + return ['trait', 'method', 'newModifier', 'newName']; + } + + public function getType(): string { + return 'Stmt_TraitUseAdaptation_Alias'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php new file mode 100644 index 000000000..7bc408376 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Name $trait, $method, array $insteadof, array $attributes = []) { + $this->attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->insteadof = $insteadof; + } + + public function getSubNodeNames(): array { + return ['trait', 'method', 'insteadof']; + } + + public function getType(): string { + return 'Stmt_TraitUseAdaptation_Precedence'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php new file mode 100644 index 000000000..5f2b33070 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php @@ -0,0 +1,34 @@ + array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Trait'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php new file mode 100644 index 000000000..6414c46c9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts, array $catches, ?Finally_ $finally = null, array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + $this->catches = $catches; + $this->finally = $finally; + } + + public function getSubNodeNames(): array { + return ['stmts', 'catches', 'finally']; + } + + public function getType(): string { + return 'Stmt_TryCatch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php new file mode 100644 index 000000000..c211beb0c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Stmt_Unset'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php new file mode 100644 index 000000000..9e504f892 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php @@ -0,0 +1,17 @@ + $attributes Additional attributes + */ + public function __construct(array $uses, int $type = self::TYPE_NORMAL, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + $this->uses = $uses; + } + + public function getSubNodeNames(): array { + return ['type', 'uses']; + } + + public function getType(): string { + return 'Stmt_Use'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php new file mode 100644 index 000000000..2f7aed234 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts']; + } + + public function getType(): string { + return 'Stmt_While'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php new file mode 100644 index 000000000..bad88d2b8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php @@ -0,0 +1,27 @@ + $attributes Additional attributes + */ + public function __construct(array $types, array $attributes = []) { + $this->attributes = $attributes; + $this->types = $types; + } + + public function getSubNodeNames(): array { + return ['types']; + } + + public function getType(): string { + return 'UnionType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/UseItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/UseItem.php new file mode 100644 index 000000000..a7d9fc447 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/UseItem.php @@ -0,0 +1,55 @@ + $attributes Additional attributes + */ + public function __construct(Node\Name $name, $alias = null, int $type = Use_::TYPE_UNKNOWN, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + $this->name = $name; + $this->alias = \is_string($alias) ? new Identifier($alias) : $alias; + } + + public function getSubNodeNames(): array { + return ['type', 'name', 'alias']; + } + + /** + * Get alias. If not explicitly given this is the last component of the used name. + */ + public function getAlias(): Identifier { + if (null !== $this->alias) { + return $this->alias; + } + + return new Identifier($this->name->getLast()); + } + + public function getType(): string { + return 'UseItem'; + } +} + +// @deprecated compatibility alias +class_alias(UseItem::class, Stmt\UseUse::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php b/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php new file mode 100644 index 000000000..9baa6fe0b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php @@ -0,0 +1,16 @@ + $attributes Additional attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + public function getType(): string { + return 'VariadicPlaceholder'; + } + + public function getSubNodeNames(): array { + return []; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php new file mode 100644 index 000000000..a6a50aea0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php @@ -0,0 +1,181 @@ + Attributes */ + protected array $attributes; + + /** + * Creates a Node. + * + * @param array $attributes Array of attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + /** + * Gets line the node started in (alias of getStartLine). + * + * @return int Start line (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getLine(): int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets line the node started in. + * + * Requires the 'startLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int Start line (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getStartLine(): int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets the line the node ended in. + * + * Requires the 'endLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int End line (or -1 if not available) + * @phpstan-return -1|positive-int + */ + public function getEndLine(): int { + return $this->attributes['endLine'] ?? -1; + } + + /** + * Gets the token offset of the first token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token start position (or -1 if not available) + */ + public function getStartTokenPos(): int { + return $this->attributes['startTokenPos'] ?? -1; + } + + /** + * Gets the token offset of the last token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token end position (or -1 if not available) + */ + public function getEndTokenPos(): int { + return $this->attributes['endTokenPos'] ?? -1; + } + + /** + * Gets the file offset of the first character that is part of this node. + * + * Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File start position (or -1 if not available) + */ + public function getStartFilePos(): int { + return $this->attributes['startFilePos'] ?? -1; + } + + /** + * Gets the file offset of the last character that is part of this node. + * + * Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File end position (or -1 if not available) + */ + public function getEndFilePos(): int { + return $this->attributes['endFilePos'] ?? -1; + } + + /** + * Gets all comments directly preceding this node. + * + * The comments are also available through the "comments" attribute. + * + * @return Comment[] + */ + public function getComments(): array { + return $this->attributes['comments'] ?? []; + } + + /** + * Gets the doc comment of the node. + * + * @return null|Comment\Doc Doc comment object or null + */ + public function getDocComment(): ?Comment\Doc { + $comments = $this->getComments(); + for ($i = count($comments) - 1; $i >= 0; $i--) { + $comment = $comments[$i]; + if ($comment instanceof Comment\Doc) { + return $comment; + } + } + + return null; + } + + /** + * Sets the doc comment of the node. + * + * This will either replace an existing doc comment or add it to the comments array. + * + * @param Comment\Doc $docComment Doc comment to set + */ + public function setDocComment(Comment\Doc $docComment): void { + $comments = $this->getComments(); + for ($i = count($comments) - 1; $i >= 0; $i--) { + if ($comments[$i] instanceof Comment\Doc) { + // Replace existing doc comment. + $comments[$i] = $docComment; + $this->setAttribute('comments', $comments); + return; + } + } + + // Append new doc comment. + $comments[] = $docComment; + $this->setAttribute('comments', $comments); + } + + public function setAttribute(string $key, $value): void { + $this->attributes[$key] = $value; + } + + public function hasAttribute(string $key): bool { + return array_key_exists($key, $this->attributes); + } + + public function getAttribute(string $key, $default = null) { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + + return $default; + } + + public function getAttributes(): array { + return $this->attributes; + } + + public function setAttributes(array $attributes): void { + $this->attributes = $attributes; + } + + /** + * @return array + */ + public function jsonSerialize(): array { + return ['nodeType' => $this->getType()] + get_object_vars($this); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php b/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php new file mode 100644 index 000000000..7d62d038d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php @@ -0,0 +1,299 @@ + true, + 'startLine' => true, + 'endLine' => true, + 'startFilePos' => true, + 'endFilePos' => true, + 'startTokenPos' => true, + 'endTokenPos' => true, + ]; + + /** + * Constructs a NodeDumper. + * + * Supported options: + * * bool dumpComments: Whether comments should be dumped. + * * bool dumpPositions: Whether line/offset information should be dumped. To dump offset + * information, the code needs to be passed to dump(). + * * bool dumpOtherAttributes: Whether non-comment, non-position attributes should be dumped. + * + * @param array $options Options (see description) + */ + public function __construct(array $options = []) { + $this->dumpComments = !empty($options['dumpComments']); + $this->dumpPositions = !empty($options['dumpPositions']); + $this->dumpOtherAttributes = !empty($options['dumpOtherAttributes']); + } + + /** + * Dumps a node or array. + * + * @param array|Node $node Node or array to dump + * @param string|null $code Code corresponding to dumped AST. This only needs to be passed if + * the dumpPositions option is enabled and the dumping of node offsets + * is desired. + * + * @return string Dumped value + */ + public function dump($node, ?string $code = null): string { + $this->code = $code; + $this->res = ''; + $this->nl = "\n"; + $this->dumpRecursive($node, false); + return $this->res; + } + + /** @param mixed $node */ + protected function dumpRecursive($node, bool $indent = true): void { + if ($indent) { + $this->nl .= " "; + } + if ($node instanceof Node) { + $this->res .= $node->getType(); + if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) { + $this->res .= $p; + } + $this->res .= '('; + + foreach ($node->getSubNodeNames() as $key) { + $this->res .= "$this->nl " . $key . ': '; + + $value = $node->$key; + if (\is_int($value)) { + if ('flags' === $key || 'newModifier' === $key) { + $this->res .= $this->dumpFlags($value); + continue; + } + if ('type' === $key && $node instanceof Include_) { + $this->res .= $this->dumpIncludeType($value); + continue; + } + if ('type' === $key + && ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) { + $this->res .= $this->dumpUseType($value); + continue; + } + } + $this->dumpRecursive($value); + } + + if ($this->dumpComments && $comments = $node->getComments()) { + $this->res .= "$this->nl comments: "; + $this->dumpRecursive($comments); + } + + if ($this->dumpOtherAttributes) { + foreach ($node->getAttributes() as $key => $value) { + if (isset(self::IGNORE_ATTRIBUTES[$key])) { + continue; + } + + $this->res .= "$this->nl $key: "; + if (\is_int($value)) { + if ('kind' === $key) { + if ($node instanceof Int_) { + $this->res .= $this->dumpIntKind($value); + continue; + } + if ($node instanceof String_ || $node instanceof InterpolatedString) { + $this->res .= $this->dumpStringKind($value); + continue; + } + if ($node instanceof Array_) { + $this->res .= $this->dumpArrayKind($value); + continue; + } + if ($node instanceof List_) { + $this->res .= $this->dumpListKind($value); + continue; + } + } + } + $this->dumpRecursive($value); + } + } + $this->res .= "$this->nl)"; + } elseif (\is_array($node)) { + $this->res .= 'array('; + foreach ($node as $key => $value) { + $this->res .= "$this->nl " . $key . ': '; + $this->dumpRecursive($value); + } + $this->res .= "$this->nl)"; + } elseif ($node instanceof Comment) { + $this->res .= \str_replace("\n", $this->nl, $node->getReformattedText()); + } elseif (\is_string($node)) { + $this->res .= \str_replace("\n", $this->nl, $node); + } elseif (\is_int($node) || \is_float($node)) { + $this->res .= $node; + } elseif (null === $node) { + $this->res .= 'null'; + } elseif (false === $node) { + $this->res .= 'false'; + } elseif (true === $node) { + $this->res .= 'true'; + } else { + throw new \InvalidArgumentException('Can only dump nodes and arrays.'); + } + if ($indent) { + $this->nl = \substr($this->nl, 0, -4); + } + } + + protected function dumpFlags(int $flags): string { + $strs = []; + if ($flags & Modifiers::PUBLIC) { + $strs[] = 'PUBLIC'; + } + if ($flags & Modifiers::PROTECTED) { + $strs[] = 'PROTECTED'; + } + if ($flags & Modifiers::PRIVATE) { + $strs[] = 'PRIVATE'; + } + if ($flags & Modifiers::ABSTRACT) { + $strs[] = 'ABSTRACT'; + } + if ($flags & Modifiers::STATIC) { + $strs[] = 'STATIC'; + } + if ($flags & Modifiers::FINAL) { + $strs[] = 'FINAL'; + } + if ($flags & Modifiers::READONLY) { + $strs[] = 'READONLY'; + } + if ($flags & Modifiers::PUBLIC_SET) { + $strs[] = 'PUBLIC_SET'; + } + if ($flags & Modifiers::PROTECTED_SET) { + $strs[] = 'PROTECTED_SET'; + } + if ($flags & Modifiers::PRIVATE_SET) { + $strs[] = 'PRIVATE_SET'; + } + + if ($strs) { + return implode(' | ', $strs) . ' (' . $flags . ')'; + } else { + return (string) $flags; + } + } + + /** @param array $map */ + private function dumpEnum(int $value, array $map): string { + if (!isset($map[$value])) { + return (string) $value; + } + return $map[$value] . ' (' . $value . ')'; + } + + private function dumpIncludeType(int $type): string { + return $this->dumpEnum($type, [ + Include_::TYPE_INCLUDE => 'TYPE_INCLUDE', + Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE', + Include_::TYPE_REQUIRE => 'TYPE_REQUIRE', + Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE', + ]); + } + + private function dumpUseType(int $type): string { + return $this->dumpEnum($type, [ + Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN', + Use_::TYPE_NORMAL => 'TYPE_NORMAL', + Use_::TYPE_FUNCTION => 'TYPE_FUNCTION', + Use_::TYPE_CONSTANT => 'TYPE_CONSTANT', + ]); + } + + private function dumpIntKind(int $kind): string { + return $this->dumpEnum($kind, [ + Int_::KIND_BIN => 'KIND_BIN', + Int_::KIND_OCT => 'KIND_OCT', + Int_::KIND_DEC => 'KIND_DEC', + Int_::KIND_HEX => 'KIND_HEX', + ]); + } + + private function dumpStringKind(int $kind): string { + return $this->dumpEnum($kind, [ + String_::KIND_SINGLE_QUOTED => 'KIND_SINGLE_QUOTED', + String_::KIND_DOUBLE_QUOTED => 'KIND_DOUBLE_QUOTED', + String_::KIND_HEREDOC => 'KIND_HEREDOC', + String_::KIND_NOWDOC => 'KIND_NOWDOC', + ]); + } + + private function dumpArrayKind(int $kind): string { + return $this->dumpEnum($kind, [ + Array_::KIND_LONG => 'KIND_LONG', + Array_::KIND_SHORT => 'KIND_SHORT', + ]); + } + + private function dumpListKind(int $kind): string { + return $this->dumpEnum($kind, [ + List_::KIND_LIST => 'KIND_LIST', + List_::KIND_ARRAY => 'KIND_ARRAY', + ]); + } + + /** + * Dump node position, if possible. + * + * @param Node $node Node for which to dump position + * + * @return string|null Dump of position, or null if position information not available + */ + protected function dumpPosition(Node $node): ?string { + if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) { + return null; + } + + $start = $node->getStartLine(); + $end = $node->getEndLine(); + if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos') + && null !== $this->code + ) { + $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos()); + $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos()); + } + return "[$start - $end]"; + } + + // Copied from Error class + private function toColumn(string $code, int $pos): int { + if ($pos > strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); + if (false === $lineStartPos) { + $lineStartPos = -1; + } + + return $pos - $lineStartPos; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php b/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php new file mode 100644 index 000000000..96c845263 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php @@ -0,0 +1,90 @@ +traverse($nodes); + + return $visitor->getFoundNodes(); + } + + /** + * Find all nodes that are instances of a certain class. + + * @template TNode as Node + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param class-string $class Class name + * + * @return TNode[] Found nodes (all instances of $class) + */ + public function findInstanceOf($nodes, string $class): array { + return $this->find($nodes, function ($node) use ($class) { + return $node instanceof $class; + }); + } + + /** + * Find first node satisfying a filter callback. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param callable $filter Filter callback: function(Node $node) : bool + * + * @return null|Node Found node (or null if none found) + */ + public function findFirst($nodes, callable $filter): ?Node { + if ($nodes === []) { + return null; + } + + if (!is_array($nodes)) { + $nodes = [$nodes]; + } + + $visitor = new FirstFindingVisitor($filter); + + $traverser = new NodeTraverser($visitor); + $traverser->traverse($nodes); + + return $visitor->getFoundNode(); + } + + /** + * Find first node that is an instance of a certain class. + * + * @template TNode as Node + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param class-string $class Class name + * + * @return null|TNode Found node, which is an instance of $class (or null if none found) + */ + public function findFirstInstanceOf($nodes, string $class): ?Node { + return $this->findFirst($nodes, function ($node) use ($class) { + return $node instanceof $class; + }); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php new file mode 100644 index 000000000..6a5c2ad7c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php @@ -0,0 +1,287 @@ + Visitors */ + protected array $visitors = []; + + /** @var bool Whether traversal should be stopped */ + protected bool $stopTraversal; + + /** + * Create a traverser with the given visitors. + * + * @param NodeVisitor ...$visitors Node visitors + */ + public function __construct(NodeVisitor ...$visitors) { + $this->visitors = $visitors; + } + + /** + * Adds a visitor. + * + * @param NodeVisitor $visitor Visitor to add + */ + public function addVisitor(NodeVisitor $visitor): void { + $this->visitors[] = $visitor; + } + + /** + * Removes an added visitor. + */ + public function removeVisitor(NodeVisitor $visitor): void { + $index = array_search($visitor, $this->visitors); + if ($index !== false) { + array_splice($this->visitors, $index, 1, []); + } + } + + /** + * Traverses an array of nodes using the registered visitors. + * + * @param Node[] $nodes Array of nodes + * + * @return Node[] Traversed array of nodes + */ + public function traverse(array $nodes): array { + $this->stopTraversal = false; + + foreach ($this->visitors as $visitor) { + if (null !== $return = $visitor->beforeTraverse($nodes)) { + $nodes = $return; + } + } + + $nodes = $this->traverseArray($nodes); + + for ($i = \count($this->visitors) - 1; $i >= 0; --$i) { + $visitor = $this->visitors[$i]; + if (null !== $return = $visitor->afterTraverse($nodes)) { + $nodes = $return; + } + } + + return $nodes; + } + + /** + * Recursively traverse a node. + * + * @param Node $node Node to traverse. + */ + protected function traverseNode(Node $node): void { + foreach ($node->getSubNodeNames() as $name) { + $subNode = $node->$name; + + if (\is_array($subNode)) { + $node->$name = $this->traverseArray($subNode); + if ($this->stopTraversal) { + break; + } + + continue; + } + + if (!$subNode instanceof Node) { + continue; + } + + $traverseChildren = true; + $visitorIndex = -1; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($subNode); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $node->$name = $return; + } elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + break; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + $node->$name = null; + continue 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $this->traverseNode($subNode); + if ($this->stopTraversal) { + break; + } + } + + for (; $visitorIndex >= 0; --$visitorIndex) { + $visitor = $this->visitors[$visitorIndex]; + $return = $visitor->leaveNode($subNode); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $node->$name = $return; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + $node->$name = null; + break; + } elseif (\is_array($return)) { + throw new \LogicException( + 'leaveNode() may only return an array ' . + 'if the parent structure is an array' + ); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + } + } + + /** + * Recursively traverse array (usually of nodes). + * + * @param Node[] $nodes Array to traverse + * + * @return Node[] Result of traversal (may be original array or changed one) + */ + protected function traverseArray(array $nodes): array { + $doNodes = []; + + foreach ($nodes as $i => $node) { + if (!$node instanceof Node) { + if (\is_array($node)) { + throw new \LogicException('Invalid node structure: Contains nested arrays'); + } + continue; + } + + $traverseChildren = true; + $visitorIndex = -1; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($node); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $nodes[$i] = $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + continue 2; + } elseif (NodeVisitor::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + continue 2; + } elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + break; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + throw new \LogicException( + 'REPLACE_WITH_NULL can not be used if the parent structure is an array'); + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $this->traverseNode($node); + if ($this->stopTraversal) { + break; + } + } + + for (; $visitorIndex >= 0; --$visitorIndex) { + $visitor = $this->visitors[$visitorIndex]; + $return = $visitor->leaveNode($node); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $nodes[$i] = $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + break; + } elseif (NodeVisitor::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + break; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + throw new \LogicException( + 'REPLACE_WITH_NULL can not be used if the parent structure is an array'); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + } + + if (!empty($doNodes)) { + while (list($i, $replace) = array_pop($doNodes)) { + array_splice($nodes, $i, 1, $replace); + } + } + + return $nodes; + } + + private function ensureReplacementReasonable(Node $old, Node $new): void { + if ($old instanceof Node\Stmt && $new instanceof Node\Expr) { + throw new \LogicException( + "Trying to replace statement ({$old->getType()}) " . + "with expression ({$new->getType()}). Are you missing a " . + "Stmt_Expression wrapper?" + ); + } + + if ($old instanceof Node\Expr && $new instanceof Node\Stmt) { + throw new \LogicException( + "Trying to replace expression ({$old->getType()}) " . + "with statement ({$new->getType()})" + ); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php new file mode 100644 index 000000000..c3992b3dc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php @@ -0,0 +1,26 @@ + $node stays as-is + * * array (of Nodes) + * => The return value is merged into the parent array (at the position of the $node) + * * NodeVisitor::REMOVE_NODE + * => $node is removed from the parent array + * * NodeVisitor::REPLACE_WITH_NULL + * => $node is replaced with null + * * NodeVisitor::DONT_TRAVERSE_CHILDREN + * => Children of $node are not traversed. $node stays as-is + * * NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN + * => Further visitors for the current node are skipped, and its children are not + * traversed. $node stays as-is. + * * NodeVisitor::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node|Node[] Replacement node (or special return value) + */ + public function enterNode(Node $node); + + /** + * Called when leaving a node. + * + * Return value semantics: + * * null + * => $node stays as-is + * * NodeVisitor::REMOVE_NODE + * => $node is removed from the parent array + * * NodeVisitor::REPLACE_WITH_NULL + * => $node is replaced with null + * * NodeVisitor::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * array (of Nodes) + * => The return value is merged into the parent array (at the position of the $node) + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node|Node[] Replacement node (or special return value) + */ + public function leaveNode(Node $node); + + /** + * Called once after traversal. + * + * Return value semantics: + * * null: $nodes stays as-is + * * otherwise: $nodes is set to the return value + * + * @param Node[] $nodes Array of nodes + * + * @return null|Node[] Array of nodes + */ + public function afterTraverse(array $nodes); +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php new file mode 100644 index 000000000..cba924998 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php @@ -0,0 +1,19 @@ +setAttribute('origNode', $origNode); + return $node; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php new file mode 100644 index 000000000..5e2aed313 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php @@ -0,0 +1,82 @@ + Token positions of comments */ + private array $commentPositions = []; + + /** + * Create a comment annotation visitor. + * + * @param Token[] $tokens Token array + */ + public function __construct(array $tokens) { + $this->tokens = $tokens; + + // Collect positions of comments. We use this to avoid traversing parts of the AST where + // there are no comments. + foreach ($tokens as $i => $token) { + if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) { + $this->commentPositions[] = $i; + } + } + } + + public function enterNode(Node $node) { + $nextCommentPos = current($this->commentPositions); + if ($nextCommentPos === false) { + // No more comments. + return self::STOP_TRAVERSAL; + } + + $oldPos = $this->pos; + $this->pos = $pos = $node->getStartTokenPos(); + if ($nextCommentPos > $oldPos && $nextCommentPos < $pos) { + $comments = []; + while (--$pos >= $oldPos) { + $token = $this->tokens[$pos]; + if ($token->id === \T_DOC_COMMENT) { + $comments[] = new Comment\Doc( + $token->text, $token->line, $token->pos, $pos, + $token->getEndLine(), $token->getEndPos() - 1, $pos); + continue; + } + if ($token->id === \T_COMMENT) { + $comments[] = new Comment( + $token->text, $token->line, $token->pos, $pos, + $token->getEndLine(), $token->getEndPos() - 1, $pos); + continue; + } + if ($token->id !== \T_WHITESPACE) { + break; + } + } + if (!empty($comments)) { + $node->setAttribute('comments', array_reverse($comments)); + } + + do { + $nextCommentPos = next($this->commentPositions); + } while ($nextCommentPos !== false && $nextCommentPos < $this->pos); + } + + $endPos = $node->getEndTokenPos(); + if ($nextCommentPos > $endPos) { + // Skip children if there are no comments located inside this node. + $this->pos = $endPos; + return self::DONT_TRAVERSE_CHILDREN; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php new file mode 100644 index 000000000..65a1bd3f1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php @@ -0,0 +1,47 @@ + Found nodes */ + protected array $foundNodes; + + public function __construct(callable $filterCallback) { + $this->filterCallback = $filterCallback; + } + + /** + * Get found nodes satisfying the filter callback. + * + * Nodes are returned in pre-order. + * + * @return list Found nodes + */ + public function getFoundNodes(): array { + return $this->foundNodes; + } + + public function beforeTraverse(array $nodes): ?array { + $this->foundNodes = []; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNodes[] = $node; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php new file mode 100644 index 000000000..05deed597 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php @@ -0,0 +1,49 @@ +filterCallback = $filterCallback; + } + + /** + * Get found node satisfying the filter callback. + * + * Returns null if no node satisfies the filter callback. + * + * @return null|Node Found node (or null if not found) + */ + public function getFoundNode(): ?Node { + return $this->foundNode; + } + + public function beforeTraverse(array $nodes): ?array { + $this->foundNode = null; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNode = $node; + return NodeVisitor::STOP_TRAVERSAL; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php new file mode 100644 index 000000000..e0066f2d0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php @@ -0,0 +1,269 @@ +nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing()); + $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false; + $this->replaceNodes = $options['replaceNodes'] ?? true; + } + + /** + * Get name resolution context. + */ + public function getNameContext(): NameContext { + return $this->nameContext; + } + + public function beforeTraverse(array $nodes): ?array { + $this->nameContext->startNamespace(); + return null; + } + + public function enterNode(Node $node) { + if ($node instanceof Stmt\Namespace_) { + $this->nameContext->startNamespace($node->name); + } elseif ($node instanceof Stmt\Use_) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, null); + } + } elseif ($node instanceof Stmt\GroupUse) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, $node->prefix); + } + } elseif ($node instanceof Stmt\Class_) { + if (null !== $node->extends) { + $node->extends = $this->resolveClassName($node->extends); + } + + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + if (null !== $node->name) { + $this->addNamespacedName($node); + } else { + $node->namespacedName = null; + } + } elseif ($node instanceof Stmt\Interface_) { + foreach ($node->extends as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Enum_) { + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Trait_) { + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Function_) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\ClassMethod + || $node instanceof Expr\Closure + || $node instanceof Expr\ArrowFunction + ) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Property) { + if (null !== $node->type) { + $node->type = $this->resolveType($node->type); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Node\PropertyHook) { + foreach ($node->params as $param) { + $param->type = $this->resolveType($param->type); + $this->resolveAttrGroups($param); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Const_) { + foreach ($node->consts as $const) { + $this->addNamespacedName($const); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\ClassConst) { + if (null !== $node->type) { + $node->type = $this->resolveType($node->type); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\EnumCase) { + $this->resolveAttrGroups($node); + } elseif ($node instanceof Expr\StaticCall + || $node instanceof Expr\StaticPropertyFetch + || $node instanceof Expr\ClassConstFetch + || $node instanceof Expr\New_ + || $node instanceof Expr\Instanceof_ + ) { + if ($node->class instanceof Name) { + $node->class = $this->resolveClassName($node->class); + } + } elseif ($node instanceof Stmt\Catch_) { + foreach ($node->types as &$type) { + $type = $this->resolveClassName($type); + } + } elseif ($node instanceof Expr\FuncCall) { + if ($node->name instanceof Name) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION); + } + } elseif ($node instanceof Expr\ConstFetch) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT); + } elseif ($node instanceof Stmt\TraitUse) { + foreach ($node->traits as &$trait) { + $trait = $this->resolveClassName($trait); + } + + foreach ($node->adaptations as $adaptation) { + if (null !== $adaptation->trait) { + $adaptation->trait = $this->resolveClassName($adaptation->trait); + } + + if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) { + foreach ($adaptation->insteadof as &$insteadof) { + $insteadof = $this->resolveClassName($insteadof); + } + } + } + } + + return null; + } + + /** @param Stmt\Use_::TYPE_* $type */ + private function addAlias(Node\UseItem $use, int $type, ?Name $prefix = null): void { + // Add prefix for group uses + $name = $prefix ? Name::concat($prefix, $use->name) : $use->name; + // Type is determined either by individual element or whole use declaration + $type |= $use->type; + + $this->nameContext->addAlias( + $name, (string) $use->getAlias(), $type, $use->getAttributes() + ); + } + + /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure|Expr\ArrowFunction $node */ + private function resolveSignature($node): void { + foreach ($node->params as $param) { + $param->type = $this->resolveType($param->type); + $this->resolveAttrGroups($param); + } + $node->returnType = $this->resolveType($node->returnType); + } + + /** + * @template T of Node\Identifier|Name|Node\ComplexType|null + * @param T $node + * @return T + */ + private function resolveType(?Node $node): ?Node { + if ($node instanceof Name) { + return $this->resolveClassName($node); + } + if ($node instanceof Node\NullableType) { + $node->type = $this->resolveType($node->type); + return $node; + } + if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { + foreach ($node->types as &$type) { + $type = $this->resolveType($type); + } + return $node; + } + return $node; + } + + /** + * Resolve name, according to name resolver options. + * + * @param Name $name Function or constant name to resolve + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * + * @return Name Resolved name, or original name with attribute + */ + protected function resolveName(Name $name, int $type): Name { + if (!$this->replaceNodes) { + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + $name->setAttribute('resolvedName', $resolvedName); + } else { + $name->setAttribute('namespacedName', FullyQualified::concat( + $this->nameContext->getNamespace(), $name, $name->getAttributes())); + } + return $name; + } + + if ($this->preserveOriginalNames) { + // Save the original name + $originalName = $name; + $name = clone $originalName; + $name->setAttribute('originalName', $originalName); + } + + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + return $resolvedName; + } + + // unqualified names inside a namespace cannot be resolved at compile-time + // add the namespaced version of the name as an attribute + $name->setAttribute('namespacedName', FullyQualified::concat( + $this->nameContext->getNamespace(), $name, $name->getAttributes())); + return $name; + } + + protected function resolveClassName(Name $name): Name { + return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL); + } + + protected function addNamespacedName(Node $node): void { + $node->namespacedName = Name::concat( + $this->nameContext->getNamespace(), (string) $node->name); + } + + protected function resolveAttrGroups(Node $node): void { + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $attr->name = $this->resolveClassName($attr->name); + } + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php new file mode 100644 index 000000000..70e051e2d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php @@ -0,0 +1,73 @@ +$weakReferences=false on the child node, the parent node can be accessed through + * $node->getAttribute('parent'), the previous + * node can be accessed through $node->getAttribute('previous'), + * and the next node can be accessed through $node->getAttribute('next'). + * + * With $weakReferences=true attribute names are prefixed by "weak_", e.g. "weak_parent". + */ +final class NodeConnectingVisitor extends NodeVisitorAbstract { + /** + * @var Node[] + */ + private array $stack = []; + + /** + * @var ?Node + */ + private $previous; + + private bool $weakReferences; + + public function __construct(bool $weakReferences = false) { + $this->weakReferences = $weakReferences; + } + + public function beforeTraverse(array $nodes) { + $this->stack = []; + $this->previous = null; + } + + public function enterNode(Node $node) { + if (!empty($this->stack)) { + $parent = $this->stack[count($this->stack) - 1]; + if ($this->weakReferences) { + $node->setAttribute('weak_parent', \WeakReference::create($parent)); + } else { + $node->setAttribute('parent', $parent); + } + } + + if ($this->previous !== null) { + if ( + $this->weakReferences + ) { + if ($this->previous->getAttribute('weak_parent') === $node->getAttribute('weak_parent')) { + $node->setAttribute('weak_previous', \WeakReference::create($this->previous)); + $this->previous->setAttribute('weak_next', \WeakReference::create($node)); + } + } elseif ($this->previous->getAttribute('parent') === $node->getAttribute('parent')) { + $node->setAttribute('previous', $this->previous); + $this->previous->setAttribute('next', $node); + } + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) { + $this->previous = $node; + + array_pop($this->stack); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php new file mode 100644 index 000000000..abf6e37d2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php @@ -0,0 +1,51 @@ +$weakReferences=false on the child node, the parent node can be accessed through + * $node->getAttribute('parent'). + * + * With $weakReferences=true the attribute name is "weak_parent" instead. + */ +final class ParentConnectingVisitor extends NodeVisitorAbstract { + /** + * @var Node[] + */ + private array $stack = []; + + private bool $weakReferences; + + public function __construct(bool $weakReferences = false) { + $this->weakReferences = $weakReferences; + } + + public function beforeTraverse(array $nodes) { + $this->stack = []; + } + + public function enterNode(Node $node) { + if (!empty($this->stack)) { + $parent = $this->stack[count($this->stack) - 1]; + if ($this->weakReferences) { + $node->setAttribute('weak_parent', \WeakReference::create($parent)); + } else { + $node->setAttribute('parent', $parent); + } + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) { + array_pop($this->stack); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php new file mode 100644 index 000000000..6fb15cca4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php @@ -0,0 +1,24 @@ +'", + "T_IS_GREATER_OR_EQUAL", + "T_SL", + "T_SR", + "'+'", + "'-'", + "'.'", + "'*'", + "'/'", + "'%'", + "'!'", + "T_INSTANCEOF", + "'~'", + "T_INC", + "T_DEC", + "T_INT_CAST", + "T_DOUBLE_CAST", + "T_STRING_CAST", + "T_ARRAY_CAST", + "T_OBJECT_CAST", + "T_BOOL_CAST", + "T_UNSET_CAST", + "'@'", + "T_POW", + "'['", + "T_NEW", + "T_CLONE", + "T_EXIT", + "T_IF", + "T_ELSEIF", + "T_ELSE", + "T_ENDIF", + "T_LNUMBER", + "T_DNUMBER", + "T_STRING", + "T_STRING_VARNAME", + "T_VARIABLE", + "T_NUM_STRING", + "T_INLINE_HTML", + "T_ENCAPSED_AND_WHITESPACE", + "T_CONSTANT_ENCAPSED_STRING", + "T_ECHO", + "T_DO", + "T_WHILE", + "T_ENDWHILE", + "T_FOR", + "T_ENDFOR", + "T_FOREACH", + "T_ENDFOREACH", + "T_DECLARE", + "T_ENDDECLARE", + "T_AS", + "T_SWITCH", + "T_MATCH", + "T_ENDSWITCH", + "T_CASE", + "T_DEFAULT", + "T_BREAK", + "T_CONTINUE", + "T_GOTO", + "T_FUNCTION", + "T_FN", + "T_CONST", + "T_RETURN", + "T_TRY", + "T_CATCH", + "T_FINALLY", + "T_USE", + "T_INSTEADOF", + "T_GLOBAL", + "T_STATIC", + "T_ABSTRACT", + "T_FINAL", + "T_PRIVATE", + "T_PROTECTED", + "T_PUBLIC", + "T_READONLY", + "T_PUBLIC_SET", + "T_PROTECTED_SET", + "T_PRIVATE_SET", + "T_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "T_ENUM", + "T_EXTENDS", + "T_IMPLEMENTS", + "T_OBJECT_OPERATOR", + "T_NULLSAFE_OBJECT_OPERATOR", + "T_LIST", + "T_ARRAY", + "T_CALLABLE", + "T_CLASS_C", + "T_TRAIT_C", + "T_METHOD_C", + "T_FUNC_C", + "T_PROPERTY_C", + "T_LINE", + "T_FILE", + "T_START_HEREDOC", + "T_END_HEREDOC", + "T_DOLLAR_OPEN_CURLY_BRACES", + "T_CURLY_OPEN", + "T_PAAMAYIM_NEKUDOTAYIM", + "T_NAMESPACE", + "T_NS_C", + "T_DIR", + "T_NS_SEPARATOR", + "T_ELLIPSIS", + "T_NAME_FULLY_QUALIFIED", + "T_NAME_QUALIFIED", + "T_NAME_RELATIVE", + "T_ATTRIBUTE", + "';'", + "']'", + "'('", + "')'", + "'{'", + "'}'", + "'`'", + "'\"'", + "'$'" + ); + + protected array $tokenToSymbol = array( + 0, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 57, 171, 173, 172, 56, 173, 173, + 166, 167, 54, 51, 9, 52, 53, 55, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 32, 164, + 45, 17, 47, 31, 69, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 71, 173, 165, 37, 173, 170, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 168, 36, 169, 59, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 1, 2, 3, 4, + 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, + 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 33, 34, 35, 38, 39, 40, + 41, 42, 43, 44, 46, 48, 49, 50, 58, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 70, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, + 163 + ); + + protected array $action = array( + 133, 134, 135, 575, 136, 137, 1049, 766, 767, 768, + 138, 41, 850, -341, 495, 1390,-32766,-32766,-32766, 1008, + 841, 1145, 1146, 1147, 1141, 1140, 1139, 1148, 1142, 1143, + 1144,-32766,-32766,-32766, -195, 760, 759,-32766, -194,-32766, + -32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767,-32767,-32767, + -32767, 0,-32766, 3, 4, 769, 1145, 1146, 1147, 1141, + 1140, 1139, 1148, 1142, 1143, 1144, 388, 389, 448, 272, + 53, 391, 773, 774, 775, 776, 433, 5, 434, 571, + 337, 39, 254, 29, 298, 830, 777, 778, 779, 780, + 781, 782, 783, 784, 785, 786, 806, 576, 807, 808, + 809, 810, 798, 799, 353, 354, 801, 802, 787, 788, + 789, 791, 792, 793, 364, 833, 834, 835, 836, 837, + 577, -382, 306, -382, 794, 795, 578, 579, 244, 818, + 816, 817, 829, 813, 814, 1313, 38, 580, 581, 812, + 582, 583, 584, 585, 1325, 586, 587, 481, 482, -628, + 496, 1009, 815, 588, 589, 140, 139, -628, 133, 134, + 135, 575, 136, 137, 1085, 766, 767, 768, 138, 41, + -32766, -341, 1046, 1041, 1040, 1039, 1045, 1042, 1043, 1044, + -32766,-32766,-32766,-32767,-32767,-32767,-32767, 106, 107, 108, + 109, 110, -195, 760, 759, 1058, -194,-32766,-32766,-32766, + 149,-32766, 852,-32766,-32766,-32766,-32766,-32766,-32766,-32766, + 936, 303, 257, 769,-32766,-32766,-32766, 850,-32766, 297, + -32766,-32766,-32766,-32766,-32766, 1371, 1355, 272, 53, 391, + 773, 774, 775, 776, -625,-32766, 434,-32766,-32766,-32766, + -32766, 730, -625, 830, 777, 778, 779, 780, 781, 782, + 783, 784, 785, 786, 806, 576, 807, 808, 809, 810, + 798, 799, 353, 354, 801, 802, 787, 788, 789, 791, + 792, 793, 364, 833, 834, 835, 836, 837, 577, -579, + -275, 317, 794, 795, 578, 579, -577, 818, 816, 817, + 829, 813, 814, 957, 926, 580, 581, 812, 582, 583, + 584, 585, 144, 586, 587, 841, 336,-32766,-32766,-32766, + 815, 588, 589, -628, 139, -628, 133, 134, 135, 575, + 136, 137, 1082, 766, 767, 768, 138, 41,-32766, 1375, + -32766,-32766,-32766,-32766,-32766,-32766,-32766, 1374, 629, 388, + 389,-32766,-32766,-32766,-32766,-32766, -579, -579, 1081, 433, + 321, 760, 759, -577, -577,-32766, 1293,-32766,-32766, 111, + 112, 113, -579, 282, 843, 851, 623, 1400, 936, -577, + 1401, 769, 333, 938, -585, 114, -579, 725, 294, 298, + 1119, -584, 349, -577, 752, 272, 53, 391, 773, 774, + 775, 776, 145, 86, 434, 306, 336, 336, -625, 731, + -625, 830, 777, 778, 779, 780, 781, 782, 783, 784, + 785, 786, 806, 576, 807, 808, 809, 810, 798, 799, + 353, 354, 801, 802, 787, 788, 789, 791, 792, 793, + 364, 833, 834, 835, 836, 837, 577, -576, 850, -578, + 794, 795, 578, 579, 845, 818, 816, 817, 829, 813, + 814, 727, 926, 580, 581, 812, 582, 583, 584, 585, + 740, 586, 587, 243, 1055,-32766,-32766, -85, 815, 588, + 589, 878, 152, 879, 133, 134, 135, 575, 136, 137, + 1087, 766, 767, 768, 138, 41, 350, 961, 960, 1058, + 1058, 1058,-32766,-32766,-32766, 841,-32766, 131, 977, 978, + 400, 1055, 10, 979, -576, -576, -578, -578, 378, 760, + 759, 936, 973, 290, 297, 297,-32766, 846, 936, 154, + -576, 79, -578, 382, 849, 936, 1058, 336, 878, 769, + 879, 938, -583, -85, -576, 725, -578, 959, 108, 109, + 110, 1058, 732, 272, 53, 391, 773, 774, 775, 776, + 290, 155, 434, 470, 471, 472, 735, 760, 759, 830, + 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, + 806, 576, 807, 808, 809, 810, 798, 799, 353, 354, + 801, 802, 787, 788, 789, 791, 792, 793, 364, 833, + 834, 835, 836, 837, 577, 926, 434, 847, 794, 795, + 578, 579, 926, 818, 816, 817, 829, 813, 814, 926, + 398, 580, 581, 812, 582, 583, 584, 585, 452, 586, + 587, 157, 87, 88, 89, 453, 815, 588, 589, 454, + 152, 790, 761, 762, 763, 764, 765, 158, 766, 767, + 768, 803, 804, 40, 27, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 1134, + 282, 1055, 455,-32766, 994, 1288, 1287, 1289, 725, 390, + 389, 938, 114, 856, 1120, 725, 769, 159, 938, 433, + 672, 23, 725, 1118, 691, 692, 1058,-32766, 153, 416, + 770, 771, 772, 773, 774, 775, 776, -78, -619, 839, + -619, -581, 386, 387, 392, 393, 830, 777, 778, 779, + 780, 781, 782, 783, 784, 785, 786, 806, 828, 807, + 808, 809, 810, 798, 799, 800, 827, 801, 802, 787, + 788, 789, 791, 792, 793, 832, 833, 834, 835, 836, + 837, 838, 161, 663, 664, 794, 795, 796, 797, 36, + 818, 816, 817, 829, 813, 814, -58, -57, 805, 811, + 812, 819, 820, 822, 821, -87, 823, 824, -581, -581, + 128, 129, 141, 815, 826, 825, 54, 55, 56, 57, + 527, 58, 59, 142, -110, 148, 162, 60, 61, -110, + 62, -110, 936, 163, 164, 165, 313, 166, -581, -110, + -110, -110, -110, -110, -110, -110, -110, -110, -110, -110, + 1293, -84, 953, -78, -73, -72, -71, -70, -69, -68, + -67, -66, -65, 742, -46, 63, 64, -18, -575, 1286, + 146, 65, 51, 66, 251, 252, 67, 68, 69, 70, + 71, 72, 73, 74, 281, 31, 273, 47, 450, 528, + 291, -357, 741, 1319, 1320, 529, 744, 850, 935, 151, + 295, 1317, 45, 22, 530, 1284, 531, -309, 532, -305, + 533, 286, 936, 534, 535, 287, 926, 292, 48, 49, + 456, 385, 384, 293, 50, 536, 342, 296, 282, 1057, + 376, 348, 850, 299, 300, -575, -575, 1279, 114, 307, + 308, 701, 538, 539, 540, 150, 841,-32766, 1288, 1287, + 1289, -575, 850, 294, 542, 543, 1402, 1305, 1306, 1307, + 1308, 1310, 1302, 1303, 305, -575, 716, -110, -110, 130, + 1309, 1304, -110, 593, 1288, 1287, 1289, 306, 13, 673, + 75, -110, 1152, 678, 331, 332, 336, -154, -154, -154, + -32766, 718, 694, -4, 936, 938, 926, 314, 478, 725, + 506, 1324, -154, 705, -154, 679, -154, 695, -154, 974, + 1326, -541, 306, 312, 311, 79, 849, 661, 383, 43, + 320, 336, 37, 1252, 0, 0, 52, 0, 0, 977, + 978, 0, 760, 759, 537,-32766, 0, 0, 0, 706, + 0, 0, 912, 973, -110, -110, -110, 35, 115, 116, + 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, + 127, -531, 11, 707, 708, 31, 274, 30, 380, 955, + 599, -613, 306, 627, 0, 938, 0, 850, 926, 725, + -154, 1317, 1288, 1287, 1289, 44, -612, 749, 290, 750, + 1194, 1196, 869, 309, 310, 917, 1018, 995, 1002, 992, + 383, -575, 446, 1003, 915, 990, 1123, 304, 1126, 381, + 1127, 977, 978, 1124, 1125, 1131, 537, 1279, 1314, 861, + 330, 760, 759, 132, 541, 973, -110, -110, -110, 1341, + 1359, 1393, 1293, 666, 542, 543, -611, 1305, 1306, 1307, + 1308, 1310, 1302, 1303, -585, -584, -583, -582, 21, -525, + 1309, 1304, 1, 32, 760, 759, 33, 938,-32766, -278, + 77, 725, -4, -16, 1286, 332, 336, 42, -575, -575, + 46,-32766,-32766,-32766, 76,-32766, 80,-32766, 81,-32766, + 82, 83,-32766, 84, -575, 85, 147,-32766,-32766,-32766, + 156,-32766, 160,-32766,-32766, 249, 379, 1286, -575,-32766, + 430, 31, 273, 338,-32766,-32766,-32766, 365,-32766, 366, + -32766,-32766,-32766, 850, 850,-32766, 367, 1317, 368, 369, + -32766,-32766,-32766, 370, 371, 372,-32766,-32766, 373, 374, + 375, 377,-32766, 430, 447, 570, 31, 274, -276, -275, + 15, 16, 78, 17,-32766, 18, 20, 414, 850, -110, + -110, 497, 1317, 1279, -110, 498, 505, 508, 509, 510, + 511, 515, 516, -110, 517, 525, 604, 711, 1088, 1084, + 1234, 543,-32766, 1305, 1306, 1307, 1308, 1310, 1302, 1303, + 1315, 1086, 1083, -50, 1064, 1274, 1309, 1304, 1279, 1060, + -280, -102, 14, 19, 306, 24, 77, 79, 415, 303, + 413, 332, 336, 336, 618, 624, 543, 652, 1305, 1306, + 1307, 1308, 1310, 1302, 1303, 717, 143, 1238, 1292, 1235, + 1372, 1309, 1304, 726, 729, 733,-32766, 734, 736, 737, + 738, 77, 1286, 419, 739, 743, 332, 336, 728,-32766, + -32766,-32766, 746,-32766, 913,-32766, 1397,-32766, 1399, 872, + -32766, 871, 967, 1010, 1398,-32766,-32766,-32766, 966,-32766, + 964,-32766,-32766, 965, 968, 1286, 1267,-32766, 430, 946, + 956, 944,-32766,-32766,-32766, 1000,-32766, 1001,-32766,-32766, + -32766, 650, 1396,-32766, 1353, 1342, 1360, 1369,-32766,-32766, + -32766, 1318,-32766, 336,-32766,-32766, 936, 0, 1286, 0, + -32766, 430, 0, 0, 0,-32766,-32766,-32766, 0,-32766, + 0,-32766,-32766,-32766, 0, 0,-32766, 0, 0, 936, + 0,-32766,-32766,-32766, 0,-32766, 0,-32766,-32766, 0, + 0, 1286, 0,-32766, 430, 0, 0, 0,-32766,-32766, + -32766, 0,-32766, 0,-32766,-32766,-32766, 0, 0,-32766, + 0, 0, 0, 501,-32766,-32766,-32766, 0,-32766, 0, + -32766,-32766, 0, 0, 1286, 606,-32766, 430, 0, 0, + 0,-32766,-32766,-32766, 0,-32766, 0,-32766,-32766,-32766, + 926, 0,-32766, 2, 0, 0, 0,-32766,-32766,-32766, + 0, 0, 0,-32766,-32766, 0, -253, -253, -253,-32766, + 430, 0, 383, 926, 0, 0, 0, 0, 0, 0, + 0,-32766, 0, 977, 978, 0, 0, 0, 537, -252, + -252, -252, 0, 0, 0, 383, 912, 973, -110, -110, + -110, 0, 0, 0, 0, 0, 977, 978, 0, 0, + 0, 537, 0, 0, 0, 0, 0, 0, 0, 912, + 973, -110, -110, -110,-32766, 0, 0, 0, 0, 938, + 1286, 0, 0, 725, -253, 0, 0,-32766,-32766,-32766, + 0,-32766, 0,-32766, 0,-32766, 0, 0,-32766, 0, + 0, 0, 938,-32766,-32766,-32766, 725, -252, 0,-32766, + -32766, 0, 0, 0, 0,-32766, 430, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0,-32766 + ); + + protected array $actionCheck = array( + 3, 4, 5, 6, 7, 8, 1, 10, 11, 12, + 13, 14, 83, 9, 32, 86, 10, 11, 12, 32, + 81, 117, 118, 119, 120, 121, 122, 123, 124, 125, + 126, 10, 11, 12, 9, 38, 39, 31, 9, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 0, 31, 9, 9, 58, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 107, 108, 109, 72, + 73, 74, 75, 76, 77, 78, 117, 9, 81, 86, + 71, 152, 153, 9, 31, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 107, 163, 109, 127, 128, 129, 130, 15, 132, + 133, 134, 135, 136, 137, 1, 9, 140, 141, 142, + 143, 144, 145, 146, 151, 148, 149, 138, 139, 1, + 168, 164, 155, 156, 157, 9, 159, 9, 3, 4, + 5, 6, 7, 8, 167, 10, 11, 12, 13, 14, + 117, 167, 119, 120, 121, 122, 123, 124, 125, 126, + 10, 11, 12, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 167, 38, 39, 142, 167, 10, 11, 12, + 9, 31, 1, 33, 34, 35, 36, 37, 38, 39, + 1, 167, 9, 58, 10, 11, 12, 83, 31, 166, + 33, 34, 35, 36, 37, 1, 1, 72, 73, 74, + 75, 76, 77, 78, 1, 31, 81, 33, 34, 35, + 36, 32, 9, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 71, + 167, 9, 127, 128, 129, 130, 71, 132, 133, 134, + 135, 136, 137, 1, 85, 140, 141, 142, 143, 144, + 145, 146, 168, 148, 149, 81, 172, 10, 11, 12, + 155, 156, 157, 165, 159, 167, 3, 4, 5, 6, + 7, 8, 167, 10, 11, 12, 13, 14, 31, 1, + 33, 34, 35, 10, 10, 11, 12, 9, 52, 107, + 108, 10, 11, 12, 10, 11, 138, 139, 1, 117, + 9, 38, 39, 138, 139, 31, 1, 33, 34, 54, + 55, 56, 154, 58, 81, 164, 1, 81, 1, 154, + 84, 58, 9, 164, 166, 70, 168, 168, 31, 31, + 164, 166, 9, 168, 168, 72, 73, 74, 75, 76, + 77, 78, 168, 168, 81, 163, 172, 172, 165, 32, + 167, 88, 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + 117, 118, 119, 120, 121, 122, 123, 71, 83, 71, + 127, 128, 129, 130, 161, 132, 133, 134, 135, 136, + 137, 168, 85, 140, 141, 142, 143, 144, 145, 146, + 168, 148, 149, 98, 117, 117, 117, 32, 155, 156, + 157, 107, 159, 109, 3, 4, 5, 6, 7, 8, + 167, 10, 11, 12, 13, 14, 9, 73, 74, 142, + 142, 142, 10, 11, 12, 81, 141, 15, 118, 119, + 107, 117, 109, 123, 138, 139, 138, 139, 9, 38, + 39, 1, 132, 166, 166, 166, 117, 81, 1, 15, + 154, 166, 154, 9, 160, 1, 142, 172, 107, 58, + 109, 164, 166, 98, 168, 168, 168, 123, 51, 52, + 53, 142, 32, 72, 73, 74, 75, 76, 77, 78, + 166, 15, 81, 133, 134, 135, 32, 38, 39, 88, + 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, + 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 85, 81, 161, 127, 128, + 129, 130, 85, 132, 133, 134, 135, 136, 137, 85, + 9, 140, 141, 142, 143, 144, 145, 146, 9, 148, + 149, 15, 10, 11, 12, 9, 155, 156, 157, 9, + 159, 3, 4, 5, 6, 7, 8, 15, 10, 11, + 12, 13, 14, 31, 102, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 127, + 58, 117, 9, 117, 164, 160, 161, 162, 168, 107, + 108, 164, 70, 9, 169, 168, 58, 15, 164, 117, + 76, 77, 168, 1, 76, 77, 142, 141, 102, 103, + 72, 73, 74, 75, 76, 77, 78, 17, 165, 81, + 167, 71, 107, 108, 107, 108, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 15, 112, 113, 127, 128, 129, 130, 15, + 132, 133, 134, 135, 136, 137, 17, 17, 140, 141, + 142, 143, 144, 145, 146, 32, 148, 149, 138, 139, + 17, 17, 17, 155, 156, 157, 2, 3, 4, 5, + 6, 7, 8, 17, 102, 17, 17, 13, 14, 107, + 16, 109, 1, 17, 17, 17, 114, 17, 168, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 1, 32, 39, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 51, 52, 32, 71, 81, + 32, 57, 71, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 32, 71, 72, 73, 74, 75, + 32, 169, 32, 79, 80, 81, 32, 83, 32, 32, + 38, 87, 88, 89, 90, 117, 92, 36, 94, 36, + 96, 36, 1, 99, 100, 36, 85, 36, 104, 105, + 106, 107, 108, 36, 110, 111, 36, 38, 58, 141, + 116, 117, 83, 38, 38, 138, 139, 123, 70, 138, + 139, 78, 128, 129, 130, 71, 81, 86, 160, 161, + 162, 154, 83, 31, 140, 141, 84, 143, 144, 145, + 146, 147, 148, 149, 150, 168, 81, 118, 119, 168, + 156, 157, 123, 90, 160, 161, 162, 163, 98, 91, + 166, 132, 83, 97, 170, 171, 172, 76, 77, 78, + 141, 93, 95, 0, 1, 164, 85, 115, 98, 168, + 98, 151, 91, 81, 93, 101, 95, 101, 97, 132, + 151, 154, 163, 137, 136, 166, 160, 114, 107, 164, + 136, 172, 168, 170, -1, -1, 71, -1, -1, 118, + 119, -1, 38, 39, 123, 141, -1, -1, -1, 117, + -1, -1, 131, 132, 133, 134, 135, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 154, 154, 141, 142, 71, 72, 154, 154, 159, + 158, 166, 163, 158, -1, 164, -1, 83, 85, 168, + 169, 87, 160, 161, 162, 164, 166, 164, 166, 164, + 60, 61, 164, 138, 139, 164, 164, 164, 164, 164, + 107, 71, 109, 164, 164, 164, 164, 114, 164, 154, + 164, 118, 119, 164, 164, 164, 123, 123, 165, 165, + 168, 38, 39, 168, 131, 132, 133, 134, 135, 165, + 165, 165, 1, 165, 140, 141, 166, 143, 144, 145, + 146, 147, 148, 149, 166, 166, 166, 166, 155, 166, + 156, 157, 166, 166, 38, 39, 166, 164, 75, 167, + 166, 168, 169, 32, 81, 171, 172, 166, 138, 139, + 166, 88, 89, 90, 166, 92, 166, 94, 166, 96, + 166, 166, 99, 166, 154, 166, 166, 104, 105, 106, + 166, 75, 166, 110, 111, 166, 168, 81, 168, 116, + 117, 71, 72, 166, 88, 89, 90, 166, 92, 166, + 94, 128, 96, 83, 83, 99, 166, 87, 166, 166, + 104, 105, 106, 166, 166, 166, 110, 111, 166, 166, + 166, 166, 116, 117, 166, 166, 71, 72, 167, 167, + 167, 167, 159, 167, 128, 167, 167, 167, 83, 118, + 119, 167, 87, 123, 123, 167, 167, 167, 167, 167, + 167, 167, 167, 132, 167, 167, 167, 167, 167, 167, + 167, 141, 141, 143, 144, 145, 146, 147, 148, 149, + 167, 167, 167, 32, 167, 167, 156, 157, 123, 167, + 167, 167, 167, 167, 163, 167, 166, 166, 169, 167, + 167, 171, 172, 172, 167, 167, 141, 167, 143, 144, + 145, 146, 147, 148, 149, 167, 32, 167, 167, 167, + 167, 156, 157, 168, 168, 168, 75, 168, 168, 168, + 168, 166, 81, 169, 168, 168, 171, 172, 168, 88, + 89, 90, 169, 92, 169, 94, 169, 96, 169, 169, + 99, 169, 169, 169, 169, 104, 105, 106, 169, 75, + 169, 110, 111, 169, 169, 81, 169, 116, 117, 169, + 169, 169, 88, 89, 90, 169, 92, 169, 94, 128, + 96, 169, 169, 99, 169, 169, 169, 169, 104, 105, + 106, 171, 75, 172, 110, 111, 1, -1, 81, -1, + 116, 117, -1, -1, -1, 88, 89, 90, -1, 92, + -1, 94, 128, 96, -1, -1, 99, -1, -1, 1, + -1, 104, 105, 106, -1, 75, -1, 110, 111, -1, + -1, 81, -1, 116, 117, -1, -1, -1, 88, 89, + 90, -1, 92, -1, 94, 128, 96, -1, -1, 99, + -1, -1, -1, 103, 104, 105, 106, -1, 75, -1, + 110, 111, -1, -1, 81, 82, 116, 117, -1, -1, + -1, 88, 89, 90, -1, 92, -1, 94, 128, 96, + 85, -1, 99, 166, -1, -1, -1, 104, 105, 106, + -1, -1, -1, 110, 111, -1, 101, 102, 103, 116, + 117, -1, 107, 85, -1, -1, -1, -1, -1, -1, + -1, 128, -1, 118, 119, -1, -1, -1, 123, 101, + 102, 103, -1, -1, -1, 107, 131, 132, 133, 134, + 135, -1, -1, -1, -1, -1, 118, 119, -1, -1, + -1, 123, -1, -1, -1, -1, -1, -1, -1, 131, + 132, 133, 134, 135, 75, -1, -1, -1, -1, 164, + 81, -1, -1, 168, 169, -1, -1, 88, 89, 90, + -1, 92, -1, 94, -1, 96, -1, -1, 99, -1, + -1, -1, 164, 104, 105, 106, 168, 169, -1, 110, + 111, -1, -1, -1, -1, 116, 117, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 128 + ); + + protected array $actionBase = array( + 0, 155, -3, 313, 471, 471, 881, 963, 1365, 1388, + 892, 134, 515, -61, 367, 524, 524, 801, 524, 209, + 510, 283, 517, 517, 517, 920, 855, 628, 628, 855, + 628, 1053, 1053, 1053, 1053, 1086, 1086, 1320, 1320, 1353, + 1254, 1221, 1449, 1449, 1449, 1449, 1449, 1287, 1449, 1449, + 1449, 1449, 1449, 1287, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, 1449, + 1449, 1449, 1449, 1449, 1449, 1449, 1449, 201, -13, 44, + 365, 744, 1102, 1120, 1107, 1121, 1096, 1095, 1103, 1108, + 1122, 1183, 1185, 837, 1186, 1187, 1182, 1188, 1110, 938, + 1098, 1118, 612, 612, 612, 612, 612, 612, 612, 612, + 612, 612, 612, 612, 612, 612, 612, 612, 612, 612, + 612, 612, 612, 612, 612, 612, 612, 612, 612, 612, + 323, 482, 334, 331, 331, 331, 331, 331, 331, 331, + 331, 331, 331, 331, 331, 331, 331, 331, 331, 331, + 331, 331, 331, 964, 964, 21, 21, 21, 324, 1135, + 1100, 1135, 1135, 1135, 1135, 1135, 1135, 1135, 1135, 297, + 204, 1000, 187, 170, 170, 6, 6, 6, 6, 6, + 692, 53, 1101, 819, 819, 138, 138, 138, 138, 542, + 14, 347, 355, -41, 348, 232, 384, 384, 487, 487, + 554, 554, 349, 349, 554, 554, 554, 399, 399, 399, + 399, 208, 215, 366, 364, -7, 864, 224, 224, 224, + 224, 864, 864, 864, 864, 829, 1190, 864, 1011, 1027, + 864, 864, 368, 767, 767, 925, 305, 305, 305, 767, + 421, -71, -71, 421, 380, -71, 225, 286, 556, 847, + 572, 543, 556, 640, 771, 233, 148, 826, 605, 826, + 1094, 831, 831, 802, 792, 921, 1140, 1123, 874, 1176, + 876, 1178, 420, 9, 791, 1093, 1093, 1093, 1093, 1093, + 1093, 1093, 1093, 1093, 1093, 1093, 1191, 519, 1094, 436, + 1191, 1191, 1191, 519, 519, 519, 519, 519, 519, 519, + 519, 805, 519, 519, 641, 436, 614, 618, 436, 860, + 519, 877, 201, 201, 201, 201, 201, 201, 201, 201, + 201, 201, 201, -18, 201, 201, -13, 292, 292, 201, + 216, 5, 292, 292, 292, 292, 201, 201, 201, 201, + 605, 840, 882, 607, 435, 885, 29, 840, 840, 840, + 4, 113, 25, 841, 843, 393, 835, 835, 835, 869, + 956, 956, 835, 839, 835, 869, 835, 835, 956, 956, + 879, 956, 146, 609, 373, 514, 616, 956, 272, 835, + 835, 835, 835, 854, 956, 45, 68, 620, 835, 203, + 191, 835, 835, 854, 848, 828, 846, 956, 956, 956, + 854, 499, 846, 846, 846, 893, 895, 873, 822, 363, + 341, 674, 127, 783, 822, 822, 835, 601, 873, 822, + 873, 822, 880, 822, 822, 822, 873, 822, 839, 477, + 822, 779, 786, 663, 74, 822, 51, 978, 980, 743, + 982, 971, 984, 1038, 985, 987, 1125, 953, 999, 974, + 989, 1039, 960, 957, 836, 763, 764, 878, 827, 951, + 838, 838, 838, 948, 949, 838, 838, 838, 838, 838, + 838, 838, 838, 763, 923, 884, 853, 1013, 765, 776, + 1069, 820, 1145, 823, 1011, 978, 987, 789, 974, 989, + 960, 957, 800, 799, 797, 798, 796, 795, 793, 794, + 808, 1071, 1072, 990, 825, 778, 1049, 1020, 1143, 922, + 1022, 1023, 1050, 1073, 898, 1083, 1147, 844, 1149, 1150, + 924, 1028, 1126, 838, 940, 875, 934, 1027, 950, 763, + 935, 1084, 1085, 1043, 824, 1054, 1058, 998, 870, 842, + 936, 1152, 1029, 1032, 1033, 1127, 1129, 891, 1044, 962, + 1059, 872, 1099, 1060, 1061, 1062, 1063, 1130, 1153, 1131, + 890, 1132, 901, 858, 1041, 856, 1154, 504, 851, 857, + 866, 1035, 536, 1007, 1136, 1134, 1155, 1064, 1065, 1067, + 1159, 1161, 994, 902, 1046, 867, 1048, 1042, 903, 904, + 606, 865, 1087, 845, 849, 859, 622, 672, 1164, 1165, + 1167, 996, 830, 833, 905, 909, 1088, 832, 1092, 1170, + 737, 910, 1171, 1070, 787, 788, 690, 750, 749, 790, + 868, 1137, 883, 852, 850, 1034, 788, 834, 911, 1172, + 912, 914, 916, 1068, 919, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 628, 628, 628, 628, 784, 784, + 784, 784, 784, 784, 784, 628, 784, 784, 784, 628, + 628, 0, 0, 628, 0, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 784, 784, 784, 784, 784, 784, 784, 784, 784, + 784, 612, 612, 612, 612, 612, 612, 612, 612, 612, + 612, 612, 612, 612, 612, 612, 612, 612, 612, 612, + 612, 612, 612, 612, 612, 612, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 612, 612, 612, 612, 612, 612, + 612, 612, 612, 612, 612, 612, 612, 612, 612, 612, + 612, 612, 612, 612, 612, 612, 612, 758, 758, 612, + 612, 612, 612, 758, 758, 758, 758, 758, 758, 758, + 758, 758, 758, 612, 612, 0, 612, 612, 612, 612, + 612, 612, 612, 612, 879, 758, 758, 758, 758, 305, + 305, 305, 305, -96, -96, 758, 758, 380, 758, 380, + 758, 758, 305, 305, 758, 758, 758, 758, 758, 758, + 758, 758, 758, 758, 758, 0, 0, 0, 436, -71, + 758, 839, 839, 839, 839, 758, 758, 758, 758, -71, + -71, 758, 414, 414, 758, 758, 0, 0, 0, 0, + 0, 0, 0, 0, 436, 0, 0, 436, 0, 0, + 839, 839, 758, 380, 879, 328, 758, 0, 0, 0, + 0, 436, 839, 436, 519, -71, -71, 519, 519, 292, + 201, 328, 596, 596, 596, 596, 0, 0, 605, 879, + 879, 879, 879, 879, 879, 879, 879, 879, 879, 879, + 839, 0, 879, 0, 839, 839, 839, 0, 0, 0, + 0, 0, 0, 0, 0, 956, 0, 0, 0, 0, + 0, 0, 0, 839, 0, 956, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 839, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 838, 870, 0, 0, 870, + 0, 838, 838, 838, 0, 0, 0, 865, 832 + ); + + protected array $actionDefault = array( + 3,32767,32767,32767, 102, 102,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 100, + 32767, 631, 631, 631, 631,32767,32767, 257, 102,32767, + 32767, 500, 415, 415, 415,32767,32767,32767, 573, 573, + 573, 573, 573, 17,32767,32767,32767,32767,32767,32767, + 32767, 500,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 36, 7, 8, 10, 11, 49, 338, + 100,32767,32767,32767,32767,32767,32767,32767,32767, 102, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 624,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 403, 494, 504, 482, 483, 485, 486, 414, + 574, 630, 344, 627, 342, 413, 146, 354, 343, 245, + 261, 505, 262, 506, 509, 510, 218, 400, 150, 151, + 446, 501, 448, 499, 503, 447, 420, 427, 428, 429, + 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, + 418, 419, 502,32767,32767, 479, 478, 477, 444,32767, + 32767,32767,32767,32767,32767,32767,32767, 102,32767, 445, + 449, 417, 452, 450, 451, 468, 469, 466, 467, 470, + 32767, 323,32767,32767,32767, 471, 472, 473, 474, 381, + 379,32767,32767, 111, 323, 111,32767,32767, 459, 460, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 517, 567, 476,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 102,32767,32767, + 32767, 100, 569, 441, 443, 537, 454, 455, 453, 421, + 32767, 542,32767, 102,32767, 544,32767,32767,32767,32767, + 32767,32767,32767, 568,32767, 575, 575,32767, 530, 100, + 196,32767, 543, 196, 196,32767,32767,32767,32767,32767, + 32767,32767,32767, 638, 530, 110, 110, 110, 110, 110, + 110, 110, 110, 110, 110, 110,32767, 196, 110,32767, + 32767,32767, 100, 196, 196, 196, 196, 196, 196, 196, + 196, 545, 196, 196, 191,32767, 271, 273, 102, 592, + 196, 547,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 530, 464, 139,32767, 532, 139, 575, 456, 457, 458, + 575, 575, 575, 319, 296,32767,32767,32767,32767,32767, + 545, 545, 100, 100, 100, 100,32767,32767,32767,32767, + 111, 516, 99, 99, 99, 99, 99, 103, 101,32767, + 32767,32767,32767, 226,32767, 101, 101, 99,32767, 101, + 101,32767,32767, 226, 228, 215, 230,32767, 596, 597, + 226, 101, 230, 230, 230, 250, 250, 519, 325, 101, + 99, 101, 101, 198, 325, 325,32767, 101, 519, 325, + 519, 325, 200, 325, 325, 325, 519, 325,32767, 101, + 325, 217, 403, 99, 99, 325,32767,32767,32767, 532, + 32767,32767,32767,32767,32767,32767,32767, 225,32767,32767, + 32767,32767,32767,32767,32767,32767, 562,32767, 580, 594, + 462, 463, 465, 579, 577, 487, 488, 489, 490, 491, + 492, 493, 496, 626,32767, 536,32767,32767,32767, 353, + 32767, 636,32767,32767,32767, 9, 74, 525, 42, 43, + 51, 57, 551, 552, 553, 554, 548, 549, 555, 550, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767, 637,32767, 575,32767, + 32767,32767,32767, 461, 557, 602,32767,32767, 576, 629, + 32767,32767,32767,32767,32767,32767,32767,32767, 139,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 562, + 32767, 137,32767,32767,32767,32767,32767,32767,32767,32767, + 558,32767,32767,32767, 575,32767,32767,32767,32767, 321, + 318,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 575,32767,32767, + 32767,32767,32767, 298,32767, 315,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 399, 532, 301, 303, 304,32767, + 32767,32767,32767, 375,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 153, 153, 3, 3, 356, + 153, 153, 153, 356, 356, 153, 356, 356, 356, 153, + 153, 153, 153, 153, 153, 283, 186, 265, 268, 250, + 250, 153, 367, 153 + ); + + protected array $goto = array( + 202, 169, 202, 202, 202, 1056, 842, 712, 359, 670, + 671, 598, 688, 689, 690, 748, 653, 655, 591, 929, + 675, 930, 1090, 721, 699, 702, 1028, 710, 719, 1024, + 171, 171, 171, 171, 226, 203, 199, 199, 181, 183, + 221, 199, 199, 199, 199, 199, 1180, 200, 200, 200, + 200, 200, 1180, 193, 194, 195, 196, 197, 198, 223, + 221, 224, 550, 551, 431, 552, 555, 556, 557, 558, + 559, 560, 561, 562, 172, 173, 174, 201, 175, 176, + 177, 170, 178, 179, 180, 182, 220, 222, 225, 245, + 248, 259, 260, 262, 263, 264, 265, 266, 267, 268, + 269, 275, 276, 277, 278, 288, 289, 326, 327, 328, + 437, 438, 439, 613, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, 241, 184, + 242, 185, 194, 195, 196, 197, 198, 223, 204, 205, + 206, 207, 246, 186, 187, 208, 188, 209, 205, 189, + 247, 204, 168, 210, 211, 190, 212, 213, 214, 191, + 215, 216, 192, 217, 218, 219, 285, 283, 285, 285, + 870, 1089, 1091, 1094, 615, 255, 255, 255, 255, 255, + 441, 677, 614, 1130, 884, 867, 436, 329, 323, 324, + 345, 608, 440, 346, 442, 654, 724, 492, 521, 715, + 896, 1128, 993, 883, 494, 253, 253, 253, 253, 250, + 256, 489, 1361, 1362, 1386, 1386, 925, 920, 921, 934, + 876, 922, 873, 923, 924, 874, 877, 363, 928, 881, + 480, 480, 868, 880, 1386, 848, 474, 363, 363, 480, + 1117, 1112, 1113, 1114, 1229, 351, 362, 362, 362, 362, + 1389, 1389, 429, 363, 363, 1017, 902, 363, 989, 1403, + 747, 360, 361, 566, 1026, 1021, 1056, 1285, 1285, 1285, + 569, 352, 351, 363, 363, 605, 1056, 1285, 848, 1056, + 848, 1056, 1056, 1137, 1138, 1056, 1056, 1056, 1056, 1056, + 1056, 1056, 1056, 1056, 1056, 1056, 357, 1261, 962, 637, + 674, 1285, 1262, 1265, 963, 1266, 1285, 1285, 1285, 1285, + 1376, 435, 1285, 628, 402, 1285, 1285, 1368, 1368, 1368, + 1368, 1347, 574, 567, 1062, 1061, 1059, 1059, 958, 958, + 697, 970, 1014, 942, 1051, 1067, 1068, 943, 565, 565, + 565, 603, 513, 522, 514, 863, 676, 863, 565, 709, + 520, 1176, 318, 567, 574, 600, 601, 319, 611, 617, + 844, 633, 634, 1080, 8, 709, 9, 449, 709, 28, + 1065, 1066, 467, 335, 316, 569, 698, 987, 987, 987, + 987, 1363, 1364, 467, 639, 639, 981, 988, 609, 631, + 1316, 1316, 1316, 1316, 1316, 1316, 1316, 1316, 1316, 1316, + 1335, 1335, 863, 469, 682, 469, 1335, 1335, 1335, 1335, + 1335, 1335, 1335, 1335, 1335, 1335, 347, 258, 258, 626, + 640, 643, 644, 645, 646, 667, 668, 669, 723, 632, + 460, 860, 460, 460, 460, 1358, 1358, 1358, 553, 553, + 1278, 985, 420, 720, 553, 1358, 553, 553, 553, 553, + 553, 553, 553, 553, 451, 889, 568, 595, 568, 647, + 649, 651, 568, 976, 595, 411, 405, 473, 886, 1276, + 1370, 1370, 1370, 1370, 909, 866, 909, 909, 1036, 483, + 612, 484, 485, 751, 563, 563, 563, 563, 894, 619, + 1101, 1394, 1395, 412, 1332, 1332, 898, 490, 1151, 1354, + 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + 279, 1105, 334, 334, 334, 998, 892, 0, 1280, 1047, + 0, 0, 863, 0, 0, 460, 460, 460, 460, 460, + 460, 460, 460, 460, 460, 460, 0, 0, 460, 1103, + 554, 554, 0, 1356, 1356, 1103, 554, 554, 554, 554, + 554, 554, 554, 554, 554, 554, 621, 622, 417, 418, + 947, 1166, 0, 686, 0, 687, 0, 422, 423, 424, + 0, 700, 1033, 0, 425, 1281, 1282, 0, 1268, 355, + 888, 0, 680, 1012, 858, 0, 0, 0, 882, 443, + 0, 1268, 0, 897, 885, 1100, 1104, 0, 0, 0, + 1275, 0, 443, 0, 1283, 1344, 1345, 996, 0, 0, + 1063, 1063, 0, 0, 0, 681, 1074, 1070, 1071, 404, + 407, 616, 620, 0, 0, 0, 0, 0, 0, 0, + 986, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1149, 901, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1031, 1031 + ); + + protected array $gotoCheck = array( + 42, 42, 42, 42, 42, 73, 6, 73, 97, 86, + 86, 48, 86, 86, 86, 48, 48, 48, 127, 65, + 48, 65, 131, 9, 48, 48, 48, 48, 48, 48, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 23, 23, 23, 23, + 15, 130, 130, 130, 134, 5, 5, 5, 5, 5, + 66, 66, 8, 8, 35, 26, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 8, 84, 8, 8, + 35, 8, 49, 35, 84, 5, 5, 5, 5, 5, + 5, 185, 185, 185, 191, 191, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, + 157, 157, 27, 15, 191, 12, 159, 14, 14, 157, + 15, 15, 15, 15, 159, 177, 24, 24, 24, 24, + 191, 191, 43, 14, 14, 50, 45, 14, 50, 14, + 50, 97, 97, 50, 50, 50, 73, 73, 73, 73, + 14, 177, 177, 14, 14, 181, 73, 73, 12, 73, + 12, 73, 73, 148, 148, 73, 73, 73, 73, 73, + 73, 73, 73, 73, 73, 73, 188, 79, 79, 56, + 56, 73, 79, 79, 79, 79, 73, 73, 73, 73, + 190, 13, 73, 13, 62, 73, 73, 9, 9, 9, + 9, 14, 76, 76, 119, 119, 89, 89, 9, 9, + 89, 89, 103, 73, 89, 89, 89, 73, 19, 19, + 19, 104, 163, 14, 163, 22, 64, 22, 19, 7, + 163, 158, 76, 76, 76, 76, 76, 76, 76, 76, + 7, 76, 76, 115, 46, 7, 46, 113, 7, 76, + 120, 120, 19, 178, 178, 14, 117, 19, 19, 19, + 19, 187, 187, 19, 108, 108, 19, 19, 2, 2, + 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, + 179, 179, 22, 83, 121, 83, 179, 179, 179, 179, + 179, 179, 179, 179, 179, 179, 29, 5, 5, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 80, + 23, 18, 23, 23, 23, 134, 134, 134, 165, 165, + 14, 93, 93, 93, 165, 134, 165, 165, 165, 165, + 165, 165, 165, 165, 83, 39, 9, 9, 9, 85, + 85, 85, 9, 92, 9, 28, 9, 9, 37, 169, + 134, 134, 134, 134, 25, 25, 25, 25, 110, 9, + 9, 9, 9, 99, 107, 107, 107, 107, 9, 107, + 133, 9, 9, 31, 180, 180, 41, 160, 151, 134, + 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, + 24, 136, 24, 24, 24, 96, 9, -1, 20, 114, + -1, -1, 22, -1, -1, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, -1, -1, 23, 134, + 182, 182, -1, 134, 134, 134, 182, 182, 182, 182, + 182, 182, 182, 182, 182, 182, 17, 17, 82, 82, + 17, 17, -1, 82, -1, 82, -1, 82, 82, 82, + -1, 82, 17, -1, 82, 20, 20, -1, 20, 82, + 17, -1, 17, 17, 20, -1, -1, -1, 17, 118, + -1, 20, -1, 16, 16, 16, 16, -1, -1, -1, + 17, -1, 118, -1, 20, 20, 20, 16, -1, -1, + 118, 118, -1, -1, -1, 118, 118, 118, 118, 59, + 59, 59, 59, -1, -1, -1, -1, -1, -1, -1, + 16, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 16, 16, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 107, 107 + ); + + protected array $gotoBase = array( + 0, 0, -339, 0, 0, 174, -7, 339, 171, 10, + 0, 0, -69, -36, -78, -186, 130, 81, 114, 66, + 117, 0, 62, 160, 240, 468, 178, 225, 118, 112, + 0, 45, 0, 0, 0, -195, 0, 119, 0, 122, + 0, 44, -1, 226, 0, 227, -387, 0, -715, 182, + 241, 0, 0, 0, 0, 0, 256, 0, 0, 570, + 0, 0, 269, 0, 102, 3, -63, 0, 0, 0, + 0, 0, 0, -5, 0, 0, -31, 0, 0, -120, + 110, 53, 54, 120, -286, -33, -724, 0, 0, 40, + 0, 0, 124, 129, 0, 0, 61, -488, 0, 67, + 0, 0, 0, 294, 295, 0, 0, 453, 141, 0, + 100, 0, 0, 83, -3, 82, 0, 86, 318, 38, + 78, 107, 0, 0, 0, 0, 0, 16, 0, 0, + 168, 20, 0, 108, 163, 0, 58, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, + 0, 43, 0, 0, 0, 0, 0, 193, 101, -38, + 46, 0, 0, -166, 0, 195, 0, 0, 0, 92, + 0, 0, 0, 0, 0, 0, 0, -60, 42, 157, + 251, 243, 297, 0, 0, -97, 0, 1, 263, 0, + 276, -101, 0, 0 + ); + + protected array $gotoDefault = array( + -32768, 526, 755, 7, 756, 951, 831, 840, 590, 544, + 722, 356, 641, 432, 1352, 927, 1165, 610, 859, 1294, + 1300, 468, 862, 340, 745, 939, 910, 911, 408, 395, + 875, 406, 665, 642, 507, 895, 464, 887, 499, 890, + 463, 899, 167, 428, 524, 903, 6, 906, 572, 937, + 991, 396, 914, 397, 693, 916, 594, 918, 919, 403, + 409, 410, 1170, 602, 638, 931, 261, 596, 932, 394, + 933, 941, 399, 401, 703, 479, 518, 512, 421, 1132, + 597, 625, 662, 457, 486, 636, 648, 635, 493, 444, + 426, 339, 975, 983, 500, 477, 997, 358, 1005, 753, + 1178, 656, 502, 1013, 657, 1020, 1023, 545, 546, 491, + 1035, 271, 1038, 503, 1048, 26, 683, 1053, 1054, 684, + 658, 1076, 659, 685, 660, 1078, 476, 592, 1179, 475, + 1093, 1099, 465, 1102, 1340, 466, 1106, 270, 1109, 284, + 427, 445, 1115, 1116, 12, 1122, 713, 714, 25, 280, + 523, 1150, 704,-32768,-32768,-32768,-32768, 462, 1177, 461, + 1249, 1251, 573, 504, 1269, 301, 1272, 696, 519, 1277, + 458, 1343, 459, 547, 487, 325, 548, 1387, 315, 343, + 322, 564, 302, 344, 549, 488, 1349, 1357, 341, 34, + 1377, 1388, 607, 630 + ); + + protected array $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, + 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, + 16, 17, 17, 18, 18, 21, 21, 22, 23, 23, + 24, 24, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 29, 29, 30, 30, 32, 34, + 34, 28, 36, 36, 33, 38, 38, 35, 35, 37, + 37, 39, 39, 31, 40, 40, 41, 43, 44, 44, + 45, 45, 46, 46, 48, 47, 47, 47, 47, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 25, 25, 50, 69, 69, 72, 72, + 71, 70, 70, 63, 75, 75, 76, 76, 77, 77, + 78, 78, 79, 79, 80, 80, 80, 80, 26, 26, + 27, 27, 27, 27, 27, 88, 88, 90, 90, 83, + 83, 91, 91, 92, 92, 92, 84, 84, 87, 87, + 85, 85, 93, 94, 94, 57, 57, 65, 65, 68, + 68, 68, 67, 95, 95, 96, 58, 58, 58, 58, + 97, 97, 98, 98, 99, 99, 100, 101, 101, 102, + 102, 103, 103, 55, 55, 51, 51, 105, 53, 53, + 106, 52, 52, 54, 54, 64, 64, 64, 64, 81, + 81, 109, 109, 111, 111, 112, 112, 112, 112, 112, + 112, 112, 112, 110, 110, 110, 115, 115, 115, 115, + 89, 89, 118, 118, 118, 119, 119, 116, 116, 120, + 120, 122, 122, 123, 123, 117, 124, 124, 121, 125, + 125, 125, 125, 113, 113, 82, 82, 82, 20, 20, + 20, 128, 128, 128, 128, 129, 129, 129, 127, 126, + 126, 131, 131, 131, 130, 130, 60, 132, 132, 133, + 61, 135, 135, 136, 136, 137, 137, 86, 138, 138, + 138, 138, 138, 138, 138, 143, 143, 144, 144, 145, + 145, 145, 145, 145, 146, 147, 147, 142, 142, 139, + 139, 141, 141, 149, 149, 148, 148, 148, 148, 148, + 148, 148, 148, 148, 148, 140, 150, 150, 152, 151, + 151, 153, 153, 114, 154, 154, 156, 156, 156, 155, + 155, 62, 104, 157, 157, 56, 56, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 164, 165, 165, 166, 158, 158, 163, + 163, 167, 168, 168, 169, 170, 171, 171, 171, 171, + 19, 19, 73, 73, 73, 73, 159, 159, 159, 159, + 173, 173, 162, 162, 162, 160, 160, 179, 179, 179, + 179, 179, 179, 179, 179, 179, 179, 180, 180, 180, + 108, 182, 182, 182, 182, 161, 161, 161, 161, 161, + 161, 161, 161, 59, 59, 176, 176, 176, 176, 176, + 183, 183, 172, 172, 172, 172, 184, 184, 184, 184, + 184, 184, 74, 74, 66, 66, 66, 66, 134, 134, + 134, 134, 187, 186, 175, 175, 175, 175, 175, 175, + 175, 174, 174, 174, 185, 185, 185, 185, 107, 181, + 189, 189, 188, 188, 190, 190, 190, 190, 190, 190, + 190, 190, 178, 178, 178, 178, 177, 192, 191, 191, + 191, 191, 191, 191, 191, 191, 193, 193, 193, 193 + ); + + protected array $ruleToLength = array( + 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 0, 1, 1, 2, 1, 3, 4, 1, 2, + 0, 1, 1, 1, 1, 4, 3, 5, 4, 3, + 4, 1, 3, 4, 1, 1, 8, 7, 2, 3, + 1, 2, 3, 1, 2, 3, 1, 1, 3, 1, + 3, 1, 2, 2, 3, 1, 3, 2, 3, 1, + 3, 3, 2, 0, 1, 1, 1, 1, 1, 3, + 7, 10, 5, 7, 9, 5, 3, 3, 3, 3, + 3, 3, 1, 2, 5, 7, 9, 6, 5, 6, + 3, 2, 1, 1, 1, 1, 0, 2, 1, 3, + 8, 0, 4, 2, 1, 3, 0, 1, 0, 1, + 0, 1, 3, 1, 1, 1, 1, 1, 8, 9, + 7, 8, 7, 6, 8, 0, 2, 0, 2, 1, + 2, 1, 2, 1, 1, 1, 0, 2, 0, 2, + 0, 2, 2, 1, 3, 1, 4, 1, 4, 1, + 1, 4, 2, 1, 3, 3, 3, 4, 4, 5, + 0, 2, 4, 3, 1, 1, 7, 0, 2, 1, + 3, 3, 4, 1, 4, 0, 2, 5, 0, 2, + 6, 0, 2, 0, 3, 1, 2, 1, 1, 2, + 0, 1, 3, 0, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 7, 9, 6, 1, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, + 3, 3, 3, 3, 3, 1, 3, 3, 1, 1, + 2, 1, 1, 0, 1, 0, 2, 2, 2, 4, + 3, 2, 4, 4, 3, 3, 1, 3, 1, 1, + 3, 2, 2, 3, 1, 1, 2, 3, 1, 1, + 2, 3, 1, 1, 3, 2, 0, 1, 5, 5, + 6, 10, 3, 5, 1, 1, 3, 0, 2, 4, + 5, 4, 4, 4, 3, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 3, 1, 1, + 3, 0, 2, 0, 5, 8, 1, 3, 3, 0, + 2, 2, 2, 3, 1, 0, 1, 1, 3, 3, + 3, 4, 4, 1, 1, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 5, 4, 3, + 4, 4, 2, 2, 4, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, + 1, 2, 4, 2, 2, 8, 9, 8, 9, 9, + 10, 9, 10, 8, 3, 2, 2, 1, 1, 0, + 4, 2, 1, 3, 2, 1, 2, 2, 2, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, + 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 3, 5, 3, + 3, 4, 1, 1, 3, 1, 1, 1, 1, 1, + 3, 2, 3, 0, 1, 1, 3, 1, 1, 1, + 1, 1, 1, 3, 1, 1, 1, 4, 4, 1, + 4, 4, 0, 1, 1, 1, 3, 3, 1, 4, + 2, 2, 1, 3, 1, 4, 4, 3, 3, 3, + 3, 1, 3, 1, 1, 3, 1, 1, 4, 1, + 1, 1, 3, 1, 1, 2, 1, 3, 4, 3, + 2, 0, 2, 2, 1, 2, 1, 1, 1, 4, + 3, 3, 3, 3, 6, 3, 1, 1, 2, 1 + ); + + protected function initReduceCallbacks(): void { + $this->reduceCallbacks = [ + 0 => null, + 1 => static function ($self, $stackPos) { + $self->semValue = $self->handleNamespaces($self->semStack[$stackPos-(1-1)]); + }, + 2 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 3 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 4 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 5 => null, + 6 => null, + 7 => null, + 8 => null, + 9 => null, + 10 => null, + 11 => null, + 12 => null, + 13 => null, + 14 => null, + 15 => null, + 16 => null, + 17 => null, + 18 => null, + 19 => null, + 20 => null, + 21 => null, + 22 => null, + 23 => null, + 24 => null, + 25 => null, + 26 => null, + 27 => null, + 28 => null, + 29 => null, + 30 => null, + 31 => null, + 32 => null, + 33 => null, + 34 => null, + 35 => null, + 36 => null, + 37 => null, + 38 => null, + 39 => null, + 40 => null, + 41 => null, + 42 => null, + 43 => null, + 44 => null, + 45 => null, + 46 => null, + 47 => null, + 48 => null, + 49 => null, + 50 => null, + 51 => null, + 52 => null, + 53 => null, + 54 => null, + 55 => null, + 56 => null, + 57 => null, + 58 => null, + 59 => null, + 60 => null, + 61 => null, + 62 => null, + 63 => null, + 64 => null, + 65 => null, + 66 => null, + 67 => null, + 68 => null, + 69 => null, + 70 => null, + 71 => null, + 72 => null, + 73 => null, + 74 => null, + 75 => null, + 76 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; if ($self->semValue === "emitError(new Error('Cannot use "getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 77 => null, + 78 => null, + 79 => null, + 80 => null, + 81 => null, + 82 => null, + 83 => null, + 84 => null, + 85 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 86 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 87 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 88 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 89 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 90 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 91 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 92 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 93 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 94 => null, + 95 => static function ($self, $stackPos) { + $self->semValue = new Name(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 96 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 97 => static function ($self, $stackPos) { + /* nothing */ + }, + 98 => static function ($self, $stackPos) { + /* nothing */ + }, + 99 => static function ($self, $stackPos) { + /* nothing */ + }, + 100 => static function ($self, $stackPos) { + $self->emitError(new Error('A trailing comma is not allowed here', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 101 => null, + 102 => null, + 103 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(1-1)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 104 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 105 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 106 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 107 => static function ($self, $stackPos) { + $self->semValue = new Node\AttributeGroup($self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 108 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 109 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 110 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 111 => null, + 112 => null, + 113 => null, + 114 => null, + 115 => static function ($self, $stackPos) { + $self->semValue = new Stmt\HaltCompiler($self->handleHaltCompiler(), $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 116 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(3-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $self->checkNamespace($self->semValue); + }, + 117 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 118 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_(null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 119 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 120 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 121 => null, + 122 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Const_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), []); + }, + 123 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Const_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(4-1)]); + $self->checkConstantAttributes($self->semValue); + }, + 124 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 125 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 126 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-6)], $self->semStack[$stackPos-(8-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 127 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(7-2)], $self->semStack[$stackPos-(7-5)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 128 => null, + 129 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 130 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 131 => null, + 132 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 133 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 134 => null, + 135 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 136 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 137 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 138 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 139 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 140 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 141 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 142 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; $self->semValue->type = $self->semStack[$stackPos-(2-1)]; + }, + 143 => null, + 144 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 145 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 146 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 147 => null, + 148 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 149 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 150 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 151 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 152 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 153 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 154 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 155 => null, + 156 => null, + 157 => null, + 158 => static function ($self, $stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 159 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Block($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 160 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(7-3)], ['stmts' => $self->semStack[$stackPos-(7-5)], 'elseifs' => $self->semStack[$stackPos-(7-6)], 'else' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 161 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(10-3)], ['stmts' => $self->semStack[$stackPos-(10-6)], 'elseifs' => $self->semStack[$stackPos-(10-7)], 'else' => $self->semStack[$stackPos-(10-8)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 162 => static function ($self, $stackPos) { + $self->semValue = new Stmt\While_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 163 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Do_($self->semStack[$stackPos-(7-5)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 164 => static function ($self, $stackPos) { + $self->semValue = new Stmt\For_(['init' => $self->semStack[$stackPos-(9-3)], 'cond' => $self->semStack[$stackPos-(9-5)], 'loop' => $self->semStack[$stackPos-(9-7)], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 165 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Switch_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 166 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Break_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 167 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Continue_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 168 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Return_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 169 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Global_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 170 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Static_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 171 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Echo_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 172 => static function ($self, $stackPos) { + + $self->semValue = new Stmt\InlineHTML($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('hasLeadingNewline', $self->inlineHtmlHasLeadingNewline($stackPos-(1-1))); + + }, + 173 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Expression($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 174 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Unset_($self->semStack[$stackPos-(5-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 175 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $self->semStack[$stackPos-(7-5)][1], 'stmts' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 176 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-7)][0], ['keyVar' => $self->semStack[$stackPos-(9-5)], 'byRef' => $self->semStack[$stackPos-(9-7)][1], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 177 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(6-3)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-4)], $self->tokenEndStack[$stackPos-(6-4)])), ['stmts' => $self->semStack[$stackPos-(6-6)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 178 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Declare_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 179 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TryCatch($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->checkTryCatch($self->semValue); + }, + 180 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Goto_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 181 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Label($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 182 => static function ($self, $stackPos) { + $self->semValue = null; /* means: no statement */ + }, + 183 => null, + 184 => static function ($self, $stackPos) { + $self->semValue = $self->maybeCreateNop($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); + }, + 185 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 186 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 187 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 188 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 189 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 190 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Catch_($self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-7)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 191 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 192 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Finally_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 193 => null, + 194 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 195 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 196 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 197 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 198 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 199 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 200 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 201 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 202 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 203 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 204 => null, + 205 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 206 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 207 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 208 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(8-3)], ['byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-5)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 209 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(9-4)], ['byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-6)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 210 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(7-2)], ['type' => $self->semStack[$stackPos-(7-1)], 'extends' => $self->semStack[$stackPos-(7-3)], 'implements' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(7-2)); + }, + 211 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(8-3)], ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(8-3)); + }, + 212 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Interface_($self->semStack[$stackPos-(7-3)], ['extends' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => $self->semStack[$stackPos-(7-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkInterface($self->semValue, $stackPos-(7-3)); + }, + 213 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Trait_($self->semStack[$stackPos-(6-3)], ['stmts' => $self->semStack[$stackPos-(6-5)], 'attrGroups' => $self->semStack[$stackPos-(6-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 214 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Enum_($self->semStack[$stackPos-(8-3)], ['scalarType' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkEnum($self->semValue, $stackPos-(8-3)); + }, + 215 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 216 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 217 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 218 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 219 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 220 => null, + 221 => null, + 222 => static function ($self, $stackPos) { + $self->checkClassModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 223 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 224 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 225 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 226 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 227 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 228 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 229 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 230 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 231 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 232 => null, + 233 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 234 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 235 => null, + 236 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 237 => null, + 238 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 239 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 240 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 241 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 242 => null, + 243 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 244 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 245 => static function ($self, $stackPos) { + $self->semValue = new Node\DeclareItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 246 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 247 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 248 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 249 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(5-3)]; + }, + 250 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 251 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 252 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_($self->semStack[$stackPos-(4-2)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 253 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_(null, $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 254 => null, + 255 => null, + 256 => static function ($self, $stackPos) { + $self->semValue = new Expr\Match_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 257 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 258 => null, + 259 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 260 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 261 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 262 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm(null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 263 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 264 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 265 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 266 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 267 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 268 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 269 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 270 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 271 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 272 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 273 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 274 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 275 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 276 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-2)], true); + }, + 277 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 278 => static function ($self, $stackPos) { + $self->semValue = array($self->fixupArrayDestructuring($self->semStack[$stackPos-(1-1)]), false); + }, + 279 => null, + 280 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 281 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 282 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 283 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 284 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 285 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 286 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 287 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 288 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC_SET; + }, + 289 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED_SET; + }, + 290 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE_SET; + }, + 291 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 292 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 293 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(7-6)], null, $self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-4)], $self->semStack[$stackPos-(7-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(7-2)], $self->semStack[$stackPos-(7-1)], $self->semStack[$stackPos-(7-7)]); + $self->checkParam($self->semValue); + $self->addPropertyNameToHooks($self->semValue); + }, + 294 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(9-6)], $self->semStack[$stackPos-(9-8)], $self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-4)], $self->semStack[$stackPos-(9-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(9-2)], $self->semStack[$stackPos-(9-1)], $self->semStack[$stackPos-(9-9)]); + $self->checkParam($self->semValue); + $self->addPropertyNameToHooks($self->semValue); + }, + 295 => static function ($self, $stackPos) { + $self->semValue = new Node\Param(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])), null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + }, + 296 => null, + 297 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 298 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 299 => null, + 300 => null, + 301 => static function ($self, $stackPos) { + $self->semValue = new Node\Name('static', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 302 => static function ($self, $stackPos) { + $self->semValue = $self->handleBuiltinTypes($self->semStack[$stackPos-(1-1)]); + }, + 303 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('array', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 304 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('callable', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 305 => null, + 306 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 307 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 308 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 309 => null, + 310 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 311 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 312 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 313 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 314 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 315 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 316 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 317 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 318 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 319 => null, + 320 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 321 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 322 => null, + 323 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 324 => null, + 325 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 326 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 327 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 328 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 329 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 330 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-2)]); + }, + 331 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 332 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 333 => static function ($self, $stackPos) { + $self->semValue = array(new Node\Arg($self->semStack[$stackPos-(4-2)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]))); + }, + 334 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-2)]); + }, + 335 => static function ($self, $stackPos) { + $self->semValue = array(new Node\Arg($self->semStack[$stackPos-(3-1)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)]); + }, + 336 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 337 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 338 => static function ($self, $stackPos) { + $self->semValue = new Node\VariadicPlaceholder($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 339 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 340 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 341 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], true, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 342 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], false, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 343 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(3-3)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(3-1)]); + }, + 344 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(1-1)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 345 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 346 => null, + 347 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 348 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 349 => null, + 350 => null, + 351 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 352 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 353 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 354 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 355 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; } else { $self->semValue = $self->semStack[$stackPos-(2-1)]; } + }, + 356 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 357 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 358 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Property($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-1)]); + }, + 359 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-1)]); + $self->checkClassConst($self->semValue, $stackPos-(5-2)); + }, + 360 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-1)], $self->semStack[$stackPos-(6-4)]); + $self->checkClassConst($self->semValue, $stackPos-(6-2)); + }, + 361 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassMethod($self->semStack[$stackPos-(10-5)], ['type' => $self->semStack[$stackPos-(10-2)], 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-7)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + $self->checkClassMethod($self->semValue, $stackPos-(10-2)); + }, + 362 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUse($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 363 => static function ($self, $stackPos) { + $self->semValue = new Stmt\EnumCase($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 364 => static function ($self, $stackPos) { + $self->semValue = null; /* will be skipped */ + }, + 365 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 366 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 367 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 368 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 369 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Precedence($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 370 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(5-1)][0], $self->semStack[$stackPos-(5-1)][1], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 371 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 372 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 373 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 374 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 375 => null, + 376 => static function ($self, $stackPos) { + $self->semValue = array(null, $self->semStack[$stackPos-(1-1)]); + }, + 377 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 378 => null, + 379 => null, + 380 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 381 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 382 => null, + 383 => null, + 384 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 385 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 386 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 387 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 388 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC_SET; + }, + 389 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED_SET; + }, + 390 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE_SET; + }, + 391 => static function ($self, $stackPos) { + $self->semValue = Modifiers::STATIC; + }, + 392 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 393 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 394 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 395 => null, + 396 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 397 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 398 => static function ($self, $stackPos) { + $self->semValue = new Node\VarLikeIdentifier(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 399 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 400 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 401 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 402 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 403 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 404 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyHook($self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-5)], ['flags' => $self->semStack[$stackPos-(5-2)], 'byRef' => $self->semStack[$stackPos-(5-3)], 'params' => [], 'attrGroups' => $self->semStack[$stackPos-(5-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + $self->checkPropertyHook($self->semValue, null); + }, + 405 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyHook($self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-8)], ['flags' => $self->semStack[$stackPos-(8-2)], 'byRef' => $self->semStack[$stackPos-(8-3)], 'params' => $self->semStack[$stackPos-(8-6)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkPropertyHook($self->semValue, $stackPos-(8-5)); + }, + 406 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 407 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 408 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 409 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 410 => static function ($self, $stackPos) { + $self->checkPropertyHookModifiers($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 411 => null, + 412 => null, + 413 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 414 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 415 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 416 => null, + 417 => null, + 418 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 419 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->fixupArrayDestructuring($self->semStack[$stackPos-(3-1)]), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 420 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 421 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 422 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + if (!$self->phpVersion->allowsAssignNewByReference()) { + $self->emitError(new Error('Cannot assign new by reference', $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]))); + } + + }, + 423 => null, + 424 => null, + 425 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall(new Node\Name($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos-(2-1)])), $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 426 => static function ($self, $stackPos) { + $self->semValue = new Expr\Clone_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 427 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 428 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 429 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 430 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 431 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 432 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 433 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 434 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 435 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 436 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 437 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 438 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 439 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 440 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostInc($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 441 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreInc($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 442 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostDec($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 443 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreDec($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 444 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 445 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 446 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 447 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 448 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 449 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 450 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 451 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 452 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 453 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 454 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 455 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 456 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 457 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 458 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 459 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 460 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 461 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 462 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryPlus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 463 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryMinus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 464 => static function ($self, $stackPos) { + $self->semValue = new Expr\BooleanNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 465 => static function ($self, $stackPos) { + $self->semValue = new Expr\BitwiseNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 466 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Identical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 467 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotIdentical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 468 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Equal($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 469 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 470 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Spaceship($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 471 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Smaller($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 472 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\SmallerOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 473 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Greater($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 474 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\GreaterOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 475 => static function ($self, $stackPos) { + $self->semValue = new Expr\Instanceof_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 476 => static function ($self, $stackPos) { + + $self->semValue = $self->semStack[$stackPos-(3-2)]; + if ($self->semValue instanceof Expr\ArrowFunction) { + $self->parenthesizedArrowFunctions->offsetSet($self->semValue); + } + + }, + 477 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 478 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(4-1)], null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 479 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 480 => static function ($self, $stackPos) { + $self->semValue = new Expr\Isset_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 481 => static function ($self, $stackPos) { + $self->semValue = new Expr\Empty_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 482 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 483 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 484 => static function ($self, $stackPos) { + $self->semValue = new Expr\Eval_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 485 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 486 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 487 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getIntCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 488 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getFloatCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs); + }, + 489 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getStringCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 490 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 491 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 492 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getBoolCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 493 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 494 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Void_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 495 => static function ($self, $stackPos) { + $self->semValue = $self->createExitExpr($self->semStack[$stackPos-(2-1)], $stackPos-(2-1), $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 496 => static function ($self, $stackPos) { + $self->semValue = new Expr\ErrorSuppress($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 497 => null, + 498 => static function ($self, $stackPos) { + $self->semValue = new Expr\ShellExec($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 499 => static function ($self, $stackPos) { + $self->semValue = new Expr\Print_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 500 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_(null, null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 501 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(2-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 502 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 503 => static function ($self, $stackPos) { + $self->semValue = new Expr\YieldFrom($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 504 => static function ($self, $stackPos) { + $self->semValue = new Expr\Throw_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 505 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'returnType' => $self->semStack[$stackPos-(8-6)], 'expr' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 506 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 507 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'uses' => $self->semStack[$stackPos-(8-6)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 508 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 509 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 510 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'returnType' => $self->semStack[$stackPos-(10-8)], 'expr' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 511 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 512 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'uses' => $self->semStack[$stackPos-(10-8)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 513 => static function ($self, $stackPos) { + $self->semValue = array(new Stmt\Class_(null, ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])), $self->semStack[$stackPos-(8-3)]); + $self->checkClass($self->semValue[0], -1); + }, + 514 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 515 => static function ($self, $stackPos) { + list($class, $ctorArgs) = $self->semStack[$stackPos-(2-2)]; $self->semValue = new Expr\New_($class, $ctorArgs, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 516 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(2-2)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 517 => null, + 518 => null, + 519 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 520 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 521 => null, + 522 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 523 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 524 => static function ($self, $stackPos) { + $self->semValue = new Node\ClosureUse($self->semStack[$stackPos-(2-2)], $self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 525 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 526 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 527 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 528 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 529 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 530 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 531 => null, + 532 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 533 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 534 => static function ($self, $stackPos) { + $self->semValue = new Name\FullyQualified(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 535 => static function ($self, $stackPos) { + $self->semValue = new Name\Relative(substr($self->semStack[$stackPos-(1-1)], 10), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 536 => null, + 537 => null, + 538 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 539 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 540 => null, + 541 => null, + 542 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 543 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); foreach ($self->semValue as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; + }, + 544 => static function ($self, $stackPos) { + foreach ($self->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 545 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 546 => null, + 547 => static function ($self, $stackPos) { + $self->semValue = new Expr\ConstFetch($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 548 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Line($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 549 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\File($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 550 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Dir($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 551 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Class_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 552 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Trait_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 553 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Method($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 554 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Function_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 555 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Namespace_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 556 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Property($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 557 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 558 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 559 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)])), $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 560 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(3-2)], $attrs); + }, + 561 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs); + $self->createdArrays->offsetSet($self->semValue); + }, + 562 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->offsetSet($self->semValue); + }, + 563 => static function ($self, $stackPos) { + $self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes()); + }, + 564 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($self->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = new Scalar\InterpolatedString($self->semStack[$stackPos-(3-2)], $attrs); + }, + 565 => static function ($self, $stackPos) { + $self->semValue = $self->parseLNumber($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->allowsInvalidOctals()); + }, + 566 => static function ($self, $stackPos) { + $self->semValue = Scalar\Float_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 567 => null, + 568 => null, + 569 => null, + 570 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 571 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(2-1)], '', $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(2-2)], $self->tokenEndStack[$stackPos-(2-2)]), true); + }, + 572 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 573 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 574 => null, + 575 => null, + 576 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 577 => null, + 578 => null, + 579 => null, + 580 => null, + 581 => null, + 582 => null, + 583 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 584 => null, + 585 => null, + 586 => null, + 587 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 588 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 589 => null, + 590 => static function ($self, $stackPos) { + $self->semValue = new Expr\MethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 591 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafeMethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 592 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 593 => null, + 594 => null, + 595 => null, + 596 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 597 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 598 => null, + 599 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 600 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 601 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])), $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 602 => static function ($self, $stackPos) { + $var = $self->semStack[$stackPos-(1-1)]->name; $self->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])) : $var; + }, + 603 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 604 => null, + 605 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 606 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 607 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 608 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 609 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 610 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 611 => null, + 612 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 613 => null, + 614 => null, + 615 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 616 => null, + 617 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 618 => static function ($self, $stackPos) { + $self->semValue = new Expr\List_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); $self->semValue->setAttribute('kind', Expr\List_::KIND_LIST); + $self->postprocessList($self->semValue); + }, + 619 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $end = count($self->semValue)-1; if ($self->semValue[$end]->value instanceof Expr\Error) array_pop($self->semValue); + }, + 620 => null, + 621 => static function ($self, $stackPos) { + /* do nothing -- prevent default action of $$=$self->semStack[$1]. See $551. */ + }, + 622 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 623 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 624 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 625 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 626 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 627 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 628 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-1)], true, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 629 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 630 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), true); + }, + 631 => static function ($self, $stackPos) { + /* Create an Error node now to remember the position. We'll later either report an error, + or convert this into a null element, depending on whether this is a creation or destructuring context. */ + $attrs = $self->createEmptyElemAttributes($self->tokenPos); + $self->semValue = new Node\ArrayItem(new Expr\Error($attrs), null, false, $attrs); + }, + 632 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 633 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 634 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 635 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)]); + }, + 636 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); $attrs['rawValue'] = $self->semStack[$stackPos-(1-1)]; $self->semValue = new Node\InterpolatedStringPart($self->semStack[$stackPos-(1-1)], $attrs); + }, + 637 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 638 => null, + 639 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 640 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 641 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 642 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 643 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 644 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 645 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 646 => static function ($self, $stackPos) { + $self->semValue = new Scalar\String_($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 647 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 648 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString('-' . $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 649 => null, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Parser/Php8.php b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php8.php new file mode 100644 index 000000000..aed45d704 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php8.php @@ -0,0 +1,2917 @@ +'", + "T_IS_GREATER_OR_EQUAL", + "T_PIPE", + "'.'", + "T_SL", + "T_SR", + "'+'", + "'-'", + "'*'", + "'/'", + "'%'", + "'!'", + "T_INSTANCEOF", + "'~'", + "T_INC", + "T_DEC", + "T_INT_CAST", + "T_DOUBLE_CAST", + "T_STRING_CAST", + "T_ARRAY_CAST", + "T_OBJECT_CAST", + "T_BOOL_CAST", + "T_UNSET_CAST", + "'@'", + "T_POW", + "'['", + "T_NEW", + "T_CLONE", + "T_EXIT", + "T_IF", + "T_ELSEIF", + "T_ELSE", + "T_ENDIF", + "T_LNUMBER", + "T_DNUMBER", + "T_STRING", + "T_STRING_VARNAME", + "T_VARIABLE", + "T_NUM_STRING", + "T_INLINE_HTML", + "T_ENCAPSED_AND_WHITESPACE", + "T_CONSTANT_ENCAPSED_STRING", + "T_ECHO", + "T_DO", + "T_WHILE", + "T_ENDWHILE", + "T_FOR", + "T_ENDFOR", + "T_FOREACH", + "T_ENDFOREACH", + "T_DECLARE", + "T_ENDDECLARE", + "T_AS", + "T_SWITCH", + "T_MATCH", + "T_ENDSWITCH", + "T_CASE", + "T_DEFAULT", + "T_BREAK", + "T_CONTINUE", + "T_GOTO", + "T_FUNCTION", + "T_FN", + "T_CONST", + "T_RETURN", + "T_TRY", + "T_CATCH", + "T_FINALLY", + "T_USE", + "T_INSTEADOF", + "T_GLOBAL", + "T_STATIC", + "T_ABSTRACT", + "T_FINAL", + "T_PRIVATE", + "T_PROTECTED", + "T_PUBLIC", + "T_READONLY", + "T_PUBLIC_SET", + "T_PROTECTED_SET", + "T_PRIVATE_SET", + "T_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "T_ENUM", + "T_EXTENDS", + "T_IMPLEMENTS", + "T_OBJECT_OPERATOR", + "T_NULLSAFE_OBJECT_OPERATOR", + "T_LIST", + "T_ARRAY", + "T_CALLABLE", + "T_CLASS_C", + "T_TRAIT_C", + "T_METHOD_C", + "T_FUNC_C", + "T_PROPERTY_C", + "T_LINE", + "T_FILE", + "T_START_HEREDOC", + "T_END_HEREDOC", + "T_DOLLAR_OPEN_CURLY_BRACES", + "T_CURLY_OPEN", + "T_PAAMAYIM_NEKUDOTAYIM", + "T_NAMESPACE", + "T_NS_C", + "T_DIR", + "T_NS_SEPARATOR", + "T_ELLIPSIS", + "T_NAME_FULLY_QUALIFIED", + "T_NAME_QUALIFIED", + "T_NAME_RELATIVE", + "T_ATTRIBUTE", + "';'", + "']'", + "'('", + "')'", + "'{'", + "'}'", + "'`'", + "'\"'", + "'$'" + ); + + protected array $tokenToSymbol = array( + 0, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 58, 172, 174, 173, 57, 174, 174, + 167, 168, 55, 53, 9, 54, 50, 56, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 32, 165, + 45, 17, 47, 31, 70, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 72, 174, 166, 37, 174, 171, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 169, 36, 170, 60, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, + 174, 174, 174, 174, 174, 174, 1, 2, 3, 4, + 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, + 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 33, 34, 35, 38, 39, 40, + 41, 42, 43, 44, 46, 48, 49, 51, 52, 59, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 71, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, + 163, 164 + ); + + protected array $action = array( + 132, 133, 134, 582, 135, 136, 162, 779, 780, 781, + 137, 41, 863,-32766, 970, 1404, -584, 974, 973, 1302, + 0, 395, 396, 455, 246, 854,-32766,-32766,-32766,-32766, + -32766, 440,-32766, 27,-32766, 773, 772,-32766,-32766,-32766, + -32766, 508,-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766, + 131,-32766,-32766,-32766,-32766, 437, 782, 859, 1148,-32766, + 949,-32766,-32766,-32766,-32766,-32766,-32766, 972, 1385, 300, + 271, 53, 398, 786, 787, 788, 789, 305, 865, 441, + -341, 39, 254, -584, -584, -195, 843, 790, 791, 792, + 793, 794, 795, 796, 797, 798, 799, 819, 583, 820, + 821, 822, 823, 811, 812, 353, 354, 814, 815, 800, + 801, 802, 804, 805, 806, 368, 846, 847, 848, 849, + 850, 584, 1062, -194, 856, 807, 808, 585, 586, 3, + 831, 829, 830, 842, 826, 827, 4, 860, 587, 588, + 825, 589, 590, 591, 592, 939, 593, 594, 5, 854, + -32766,-32766,-32766, 828, 595, 596,-32766, 138, 764, 132, + 133, 134, 582, 135, 136, 1098, 779, 780, 781, 137, + 41,-32766,-32766,-32766,-32766,-32766,-32766, -275, 1302, 613, + 153, 1071, 749, 990, 991,-32766,-32766,-32766, 992,-32766, + 891,-32766, 892,-32766, 773, 772,-32766, 986, 1309, 397, + 396,-32766,-32766,-32766, 858, 299, 630,-32766,-32766, 440, + 502, 736,-32766,-32766, 437, 782,-32767,-32767,-32767,-32767, + 106, 107, 108, 109, 951,-32766, 1021, 29, 734, 271, + 53, 398, 786, 787, 788, 789, 144, 1071, 441, -341, + 332, 38, 864, 862, -195, 843, 790, 791, 792, 793, + 794, 795, 796, 797, 798, 799, 819, 583, 820, 821, + 822, 823, 811, 812, 353, 354, 814, 815, 800, 801, + 802, 804, 805, 806, 368, 846, 847, 848, 849, 850, + 584, 863, -194, 139, 807, 808, 585, 586, 323, 831, + 829, 830, 842, 826, 827, 1370, 148, 587, 588, 825, + 589, 590, 591, 592, 245, 593, 594, 395, 396,-32766, + -32766,-32766, 828, 595, 596, -85, 138, 440, 132, 133, + 134, 582, 135, 136, 1095, 779, 780, 781, 137, 41, + -32766,-32766,-32766,-32766,-32766, 51, 578, 1302, 257,-32766, + 636, 107, 108, 109,-32766,-32766,-32766, 503,-32766, 316, + -32766,-32766,-32766, 773, 772,-32766, -383, 166, -383, 1022, + -32766,-32766,-32766, 305, 79, 1133,-32766,-32766, 1414, 762, + 332, 1415,-32766, 437, 782,-32766, 1071, 110, 111, 112, + 113, 114, -85, 283,-32766, 477, 478, 479, 271, 53, + 398, 786, 787, 788, 789, 115, 407, 441, 10,-32766, + 299, 1341, 306, 307, 843, 790, 791, 792, 793, 794, + 795, 796, 797, 798, 799, 819, 583, 820, 821, 822, + 823, 811, 812, 353, 354, 814, 815, 800, 801, 802, + 804, 805, 806, 368, 846, 847, 848, 849, 850, 584, + 320, 1068, -582, 807, 808, 585, 586, 1389, 831, 829, + 830, 842, 826, 827, 329, 1388, 587, 588, 825, 589, + 590, 591, 592, 86, 593, 594, 1071, 332,-32766,-32766, + -32766, 828, 595, 596, 349, 151, -581, 132, 133, 134, + 582, 135, 136, 1100, 779, 780, 781, 137, 41,-32766, + 290,-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767, + -32767,-32767,-32767,-32766,-32766,-32766, 891, 1175, 892, -582, + -582, 754, 773, 772, 1159, 1160, 1161, 1155, 1154, 1153, + 1162, 1156, 1157, 1158,-32766, -582,-32766,-32766,-32766,-32766, + -32766,-32766,-32766, 782,-32766,-32766,-32766, -588, -78,-32766, + -32766,-32766, 350, -581, -581,-32766,-32766, 271, 53, 398, + 786, 787, 788, 789, 383,-32766, 441,-32766,-32766, -581, + -32766, 773, 772, 843, 790, 791, 792, 793, 794, 795, + 796, 797, 798, 799, 819, 583, 820, 821, 822, 823, + 811, 812, 353, 354, 814, 815, 800, 801, 802, 804, + 805, 806, 368, 846, 847, 848, 849, 850, 584, -620, + 1068, -620, 807, 808, 585, 586, 389, 831, 829, 830, + 842, 826, 827, 441, 405, 587, 588, 825, 589, 590, + 591, 592, 333, 593, 594, 1071, 87, 88, 89, 459, + 828, 595, 596, 460, 151, 803, 774, 775, 776, 777, + 778, 854, 779, 780, 781, 816, 817, 40, 461, 90, + 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 462, 283, 1329, 1159, 1160, 1161, + 1155, 1154, 1153, 1162, 1156, 1157, 1158, 115, 869, 488, + 489, 782, 1304, 1303, 1305, 108, 109, 1132, 154,-32766, + -32766, 1134, 679, 23, 156, 783, 784, 785, 786, 787, + 788, 789, 698, 699, 852, 152, 423, -580, 393, 394, + 157, 843, 790, 791, 792, 793, 794, 795, 796, 797, + 798, 799, 819, 841, 820, 821, 822, 823, 811, 812, + 813, 840, 814, 815, 800, 801, 802, 804, 805, 806, + 845, 846, 847, 848, 849, 850, 851, 1094, -578, 863, + 807, 808, 809, 810, -58, 831, 829, 830, 842, 826, + 827, 399, 400, 818, 824, 825, 832, 833, 835, 834, + 294, 836, 837, 158, -580, -580, 160, 294, 828, 839, + 838, 54, 55, 56, 57, 534, 58, 59, 36, -110, + -580, -57, 60, 61, -110, 62, -110, 670, 671, 129, + 130, 312, -587, 140, -110, -110, -110, -110, -110, -110, + -110, -110, -110, -110, -110, -578, -578, 141, 147, 949, + 161, 712, -87, 163, 164, 165, -84, 949, -78, -73, + -72, -578, 63, 64, 143, -309, -71, 65, 332, 66, + 251, 252, 67, 68, 69, 70, 71, 72, 73, 74, + 739, 31, 276, 47, 457, 535, -357, 713, 740, 1335, + 1336, 536, -70, 863, 1068, -69, -68, 1333, 45, 22, + 537, 949, 538, -67, 539, -66, 540, 52, -65, 541, + 542, 714, 715, -46, 48, 49, 463, 392, 391, 1071, + 50, 543, -18, 145, 281, 1302, 381, 348, 291, 750, + 1304, 1303, 1305, 1295, 939, 753, 290, 948, 545, 546, + 547, 150, 939, 290, -305, 295, 288, 289, 292, 293, + 549, 550, 338, 1321, 1322, 1323, 1324, 1326, 1318, 1319, + 304, 1300, 296, 301, 302, 283, 1325, 1320, 773, 772, + 1304, 1303, 1305, 305, 308, 309, 75, -154, -154, -154, + 327, 328, 332, 966, 854, 1070, 939, 149, 115, 1416, + 388, 680, -154, 708, -154, 725, -154, 13, -154, 668, + 723, 313, 31, 277, 1304, 1303, 1305, 863, 390,-32766, + 600, 1166, 987, 951, 863, 310, 701, 734, 1333, 990, + 991, 951,-32766, 686, 544, 734, 949, 685, 606, 1340, + 485, 513, 925, 986, -110, -110, -110, 35, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 702, 949, 634, 1295, 773, 772, 741, -579, 305, + -614, 1334, 0, 0, 0, 951, 311, 949, 0, 734, + -154, 549, 550, 319, 1321, 1322, 1323, 1324, 1326, 1318, + 1319, 1209, 1211, 744, 0, 1342, 0, 1325, 1320, -544, + -534, 0, -578,-32766, -4, 949, 11, 77, 751, 1302, + 30, 387, 328, 332, 862, 43,-32766,-32766,-32766, -613, + -32766, 939,-32766, 968,-32766, 44, 759,-32766, 1330, 773, + 772, 760,-32766,-32766,-32766, -579, -579, 882,-32766,-32766, + 930, 1031, 1008, 1015,-32766, 437, 1005, 939, 1016, 928, + 1003, -579, 1137, 1140, 1141, 1138,-32766, 1177, 1139, 1145, + 37, 874, 939, -586, 1357, 1374, 1407,-32766, 673, -578, + -578, -612, -588, 1302, -587, -586, -585, 31, 276, -528, + -32766,-32766,-32766, 1,-32766, -578,-32766, 78,-32766, 863, + 939,-32766, 32, 1333, -278, 33,-32766,-32766,-32766, 42, + 1007, 46,-32766,-32766, 734, 76, 80, 81,-32766, 437, + 82, 83, 390, 84, 453, 31, 277, 85, 146, 303, + -32766, 155, 159, 990, 991, 249, 951, 863, 544, 1295, + 734, 1333, 334, 369, 370, 371, 548, 986, -110, -110, + -110, 951, 372, 326, 373, 734, 374, 550, 375, 1321, + 1322, 1323, 1324, 1326, 1318, 1319, 376, 377, 422, 378, + 21, -50, 1325, 1320, 379, 382, 454, 1295, 577, 951, + 380, 384, 77, 734, -4, -276, -275, 328, 332, 15, + 16, 17, 18, 20, 363, 550, 421, 1321, 1322, 1323, + 1324, 1326, 1318, 1319, 142, 504, 505, 512, 515, 516, + 1325, 1320, 949, 517, 518,-32766, 522, 523, 524, 531, + 77, 1302, 611, 718, 1101, 328, 332, 1097,-32766,-32766, + -32766, 1250,-32766, 1331,-32766, 949,-32766, 1099, 1096,-32766, + 1077, 1290, 1309, 1073,-32766,-32766,-32766, -280,-32766, -102, + -32766,-32766, 14, 19, 1302, 24,-32766, 437, 323, 420, + 625,-32766,-32766,-32766, 631,-32766, 659,-32766,-32766,-32766, + 724, 1254,-32766, -16, 1308, 1251, 1386,-32766,-32766,-32766, + 735,-32766, 738,-32766,-32766, 742, 743, 1302, 745,-32766, + 437, 746, 747, 748,-32766,-32766,-32766, 939,-32766, 300, + -32766,-32766,-32766, 752, 1309,-32766, 764, 737, 332, 765, + -32766,-32766,-32766, -253, -253, -253,-32766,-32766, 426, 390, + 939, 756,-32766, 437, 926, 863, 1411, 1413, 885, 884, + 990, 991, 980, 1023,-32766, 544, -252, -252, -252, 1412, + 979, 977, 390, 925, 986, -110, -110, -110, 978, 981, + 1283, 959, 969, 990, 991, 957, 1176, 1172, 544, 1126, + -110, -110, 1013, 1014, 657, -110, 925, 986, -110, -110, + -110, 1410, 2, 1368, -110, 1268, 951, 1383, 0, 0, + 734, -253, 0,-32766, 0, 0,-32766, 863, 1059, 1054, + 1053, 1052, 1058, 1055, 1056, 1057, 0, 0, 0, 951, + 0, 0, 0, 734, -252, 305, 0, 0, 79, 0, + 0, 1071, 0, 0, 332, 0, 0, 0, 0, 0, + 0, 0, -110, -110, 0, 0, 0, -110, 0, 0, + 0, 0, 0, 0, 0, 299, -110, 0, 0, 0, + 0, 0, 0, 0, 0,-32766, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 305, 0, 0, + 79, 0, 0, 0, 0, 0, 332 + ); + + protected array $actionCheck = array( + 3, 4, 5, 6, 7, 8, 17, 10, 11, 12, + 13, 14, 84, 76, 1, 87, 72, 74, 75, 82, + 0, 108, 109, 110, 15, 82, 89, 90, 91, 10, + 93, 118, 95, 103, 97, 38, 39, 100, 10, 11, + 12, 104, 105, 106, 107, 10, 11, 12, 111, 112, + 15, 10, 11, 12, 117, 118, 59, 82, 128, 31, + 1, 33, 34, 35, 36, 37, 129, 124, 1, 31, + 73, 74, 75, 76, 77, 78, 79, 164, 1, 82, + 9, 153, 154, 139, 140, 9, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 1, 9, 82, 128, 129, 130, 131, 9, + 133, 134, 135, 136, 137, 138, 9, 162, 141, 142, + 143, 144, 145, 146, 147, 86, 149, 150, 9, 82, + 10, 11, 12, 156, 157, 158, 118, 160, 169, 3, + 4, 5, 6, 7, 8, 168, 10, 11, 12, 13, + 14, 31, 76, 33, 34, 35, 36, 168, 82, 83, + 15, 143, 169, 119, 120, 89, 90, 91, 124, 93, + 108, 95, 110, 97, 38, 39, 100, 133, 1, 108, + 109, 105, 106, 107, 162, 167, 1, 111, 112, 118, + 32, 169, 118, 117, 118, 59, 45, 46, 47, 48, + 49, 50, 51, 52, 165, 129, 32, 9, 169, 73, + 74, 75, 76, 77, 78, 79, 169, 143, 82, 168, + 173, 9, 165, 161, 168, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, + 124, 84, 168, 9, 128, 129, 130, 131, 168, 133, + 134, 135, 136, 137, 138, 1, 9, 141, 142, 143, + 144, 145, 146, 147, 99, 149, 150, 108, 109, 10, + 11, 12, 156, 157, 158, 32, 160, 118, 3, 4, + 5, 6, 7, 8, 168, 10, 11, 12, 13, 14, + 31, 76, 33, 34, 35, 72, 87, 82, 9, 142, + 54, 50, 51, 52, 89, 90, 91, 169, 93, 9, + 95, 118, 97, 38, 39, 100, 108, 15, 110, 165, + 105, 106, 107, 164, 167, 165, 111, 112, 82, 169, + 173, 85, 117, 118, 59, 118, 143, 53, 54, 55, + 56, 57, 99, 59, 129, 134, 135, 136, 73, 74, + 75, 76, 77, 78, 79, 71, 108, 82, 110, 142, + 167, 152, 139, 140, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 9, 118, 72, 128, 129, 130, 131, 1, 133, 134, + 135, 136, 137, 138, 9, 9, 141, 142, 143, 144, + 145, 146, 147, 169, 149, 150, 143, 173, 10, 11, + 12, 156, 157, 158, 9, 160, 72, 3, 4, 5, + 6, 7, 8, 168, 10, 11, 12, 13, 14, 31, + 167, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 10, 11, 12, 108, 165, 110, 139, + 140, 169, 38, 39, 118, 119, 120, 121, 122, 123, + 124, 125, 126, 127, 31, 155, 33, 34, 35, 36, + 37, 38, 39, 59, 10, 11, 12, 167, 17, 10, + 11, 12, 9, 139, 140, 10, 11, 73, 74, 75, + 76, 77, 78, 79, 9, 31, 82, 33, 34, 155, + 31, 38, 39, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 123, 124, 166, + 118, 168, 128, 129, 130, 131, 9, 133, 134, 135, + 136, 137, 138, 82, 9, 141, 142, 143, 144, 145, + 146, 147, 72, 149, 150, 143, 10, 11, 12, 9, + 156, 157, 158, 9, 160, 3, 4, 5, 6, 7, + 8, 82, 10, 11, 12, 13, 14, 31, 9, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 9, 59, 1, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 71, 9, 139, + 140, 59, 161, 162, 163, 51, 52, 1, 15, 53, + 54, 170, 77, 78, 15, 73, 74, 75, 76, 77, + 78, 79, 77, 78, 82, 103, 104, 72, 108, 109, + 15, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 1, 72, 84, + 128, 129, 130, 131, 17, 133, 134, 135, 136, 137, + 138, 108, 109, 141, 142, 143, 144, 145, 146, 147, + 31, 149, 150, 15, 139, 140, 15, 31, 156, 157, + 158, 2, 3, 4, 5, 6, 7, 8, 15, 103, + 155, 17, 13, 14, 108, 16, 110, 113, 114, 17, + 17, 115, 167, 17, 118, 119, 120, 121, 122, 123, + 124, 125, 126, 127, 128, 139, 140, 17, 17, 1, + 17, 82, 32, 17, 17, 17, 32, 1, 32, 32, + 32, 155, 53, 54, 169, 36, 32, 58, 173, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 32, 72, 73, 74, 75, 76, 170, 118, 32, 80, + 81, 82, 32, 84, 118, 32, 32, 88, 89, 90, + 91, 1, 93, 32, 95, 32, 97, 72, 32, 100, + 101, 142, 143, 32, 105, 106, 107, 108, 109, 143, + 111, 112, 32, 32, 32, 82, 117, 118, 32, 32, + 161, 162, 163, 124, 86, 32, 167, 32, 129, 130, + 131, 32, 86, 167, 36, 38, 36, 36, 36, 36, + 141, 142, 36, 144, 145, 146, 147, 148, 149, 150, + 151, 118, 38, 38, 38, 59, 157, 158, 38, 39, + 161, 162, 163, 164, 139, 140, 167, 77, 78, 79, + 171, 172, 173, 39, 82, 142, 86, 72, 71, 85, + 155, 92, 92, 79, 94, 94, 96, 99, 98, 115, + 82, 116, 72, 73, 161, 162, 163, 84, 108, 87, + 91, 84, 133, 165, 84, 137, 96, 169, 88, 119, + 120, 165, 142, 102, 124, 169, 1, 98, 159, 152, + 99, 99, 132, 133, 134, 135, 136, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 102, 1, 159, 124, 38, 39, 32, 72, 164, + 167, 172, -1, -1, -1, 165, 138, 1, -1, 169, + 170, 141, 142, 137, 144, 145, 146, 147, 148, 149, + 150, 61, 62, 32, -1, 152, -1, 157, 158, 155, + 155, -1, 72, 76, 0, 1, 155, 167, 32, 82, + 155, 155, 172, 173, 161, 165, 89, 90, 91, 167, + 93, 86, 95, 160, 97, 165, 165, 100, 166, 38, + 39, 165, 105, 106, 107, 139, 140, 165, 111, 112, + 165, 165, 165, 165, 117, 118, 165, 86, 165, 165, + 165, 155, 165, 165, 165, 165, 129, 165, 165, 165, + 169, 166, 86, 167, 166, 166, 166, 76, 166, 139, + 140, 167, 167, 82, 167, 167, 167, 72, 73, 167, + 89, 90, 91, 167, 93, 155, 95, 160, 97, 84, + 86, 100, 167, 88, 168, 167, 105, 106, 107, 167, + 165, 167, 111, 112, 169, 167, 167, 167, 117, 118, + 167, 167, 108, 167, 110, 72, 73, 167, 167, 115, + 129, 167, 167, 119, 120, 167, 165, 84, 124, 124, + 169, 88, 167, 167, 167, 167, 132, 133, 134, 135, + 136, 165, 167, 169, 167, 169, 167, 142, 167, 144, + 145, 146, 147, 148, 149, 150, 167, 167, 170, 167, + 156, 32, 157, 158, 167, 167, 167, 124, 167, 165, + 167, 169, 167, 169, 170, 168, 168, 172, 173, 168, + 168, 168, 168, 168, 168, 142, 168, 144, 145, 146, + 147, 148, 149, 150, 32, 168, 168, 168, 168, 168, + 157, 158, 1, 168, 168, 76, 168, 168, 168, 168, + 167, 82, 168, 168, 168, 172, 173, 168, 89, 90, + 91, 168, 93, 168, 95, 1, 97, 168, 168, 100, + 168, 168, 1, 168, 105, 106, 107, 168, 76, 168, + 111, 112, 168, 168, 82, 168, 117, 118, 168, 168, + 168, 89, 90, 91, 168, 93, 168, 95, 129, 97, + 168, 168, 100, 32, 168, 168, 168, 105, 106, 107, + 169, 76, 169, 111, 112, 169, 169, 82, 169, 117, + 118, 169, 169, 169, 89, 90, 91, 86, 93, 31, + 95, 129, 97, 169, 1, 100, 169, 169, 173, 169, + 105, 106, 107, 102, 103, 104, 111, 112, 170, 108, + 86, 170, 117, 118, 170, 84, 170, 170, 170, 170, + 119, 120, 170, 170, 129, 124, 102, 103, 104, 170, + 170, 170, 108, 132, 133, 134, 135, 136, 170, 170, + 170, 170, 170, 119, 120, 170, 170, 170, 124, 170, + 119, 120, 170, 170, 170, 124, 132, 133, 134, 135, + 136, 170, 167, 170, 133, 171, 165, 170, -1, -1, + 169, 170, -1, 142, -1, -1, 118, 84, 120, 121, + 122, 123, 124, 125, 126, 127, -1, -1, -1, 165, + -1, -1, -1, 169, 170, 164, -1, -1, 167, -1, + -1, 143, -1, -1, 173, -1, -1, -1, -1, -1, + -1, -1, 119, 120, -1, -1, -1, 124, -1, -1, + -1, -1, -1, -1, -1, 167, 133, -1, -1, -1, + -1, -1, -1, -1, -1, 142, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 164, -1, -1, + 167, -1, -1, -1, -1, -1, 173 + ); + + protected array $actionBase = array( + 0, 156, -3, 315, 474, 474, 880, 1074, 1271, 1294, + 749, 675, 531, 559, 836, 1031, 1031, 1046, 1031, 828, + 1005, 42, 59, 59, 59, 963, 898, 632, 632, 898, + 632, 997, 997, 997, 997, 1061, 1061, -63, -63, 96, + 1232, 1199, 255, 255, 255, 255, 255, 1265, 255, 255, + 255, 255, 255, 1265, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 77, 194, 120, + 205, 1197, 783, 1150, 1163, 1152, 1166, 1145, 1144, 1151, + 1156, 1167, 1261, 1263, 889, 1254, 1267, 1158, 972, 1147, + 1162, 962, 616, 616, 616, 616, 616, 616, 616, 616, + 616, 616, 616, 616, 616, 616, 616, 616, 616, 616, + 616, 616, 616, 616, 616, 616, 616, 616, 616, 19, + 35, 535, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 529, 529, 529, 910, 910, 524, 299, 1113, + 1075, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 140, + 28, 1000, 493, 493, 458, 458, 458, 458, 458, 696, + 1328, 1301, 171, 171, 171, 171, 1363, 1363, -70, 523, + 248, 756, 291, 197, -87, 644, 38, 199, 323, 323, + 482, 482, 233, 233, 482, 482, 482, 324, 324, 94, + 94, 94, 94, 82, 249, 860, 67, 67, 67, 67, + 860, 860, 860, 860, 913, 869, 860, 1036, 1049, 860, + 860, 370, 645, 966, 646, 646, 398, -72, -72, 398, + 64, -72, 294, 286, 257, 859, 91, 433, 257, 1073, + 404, 686, 686, 815, 686, 686, 686, 923, 610, 923, + 1141, 902, 902, 861, 807, 964, 1198, 1168, 901, 1252, + 929, 1253, 1200, 342, 251, -56, 263, 550, 806, 1139, + 1139, 1139, 1139, 1139, 1139, 1139, 1139, 1139, 1139, 1139, + 1139, 1195, 523, 1141, -25, 1247, 1249, 1195, 1195, 1195, + 523, 523, 523, 523, 523, 523, 523, 523, 870, 523, + 523, 694, -25, 625, 635, -25, 896, 523, 915, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 178, 77, 77, 194, 13, 13, 77, 200, 121, 13, + 13, 13, -11, 13, 77, 77, 77, 610, 886, 849, + 663, 283, 874, 114, 886, 886, 886, 71, 9, 76, + 809, 888, 288, 882, 882, 882, 907, 986, 986, 882, + 903, 882, 907, 882, 882, 986, 986, 875, 986, 274, + 620, 465, 597, 624, 986, 340, 882, 882, 882, 882, + 916, 986, 127, 139, 639, 882, 329, 287, 882, 882, + 916, 858, 876, 908, 986, 986, 986, 916, 545, 908, + 908, 908, 931, 936, 864, 872, 445, 431, 679, 232, + 924, 872, 872, 882, 605, 864, 872, 864, 872, 933, + 872, 872, 872, 864, 872, 903, 533, 872, 813, 665, + 218, 872, 882, 20, 1008, 1009, 800, 1010, 1002, 1013, + 1069, 1014, 1016, 1171, 982, 1028, 1004, 1020, 1071, 998, + 995, 885, 792, 793, 921, 914, 979, 897, 897, 897, + 975, 977, 897, 897, 897, 897, 897, 897, 897, 897, + 792, 932, 926, 899, 1037, 796, 810, 1114, 857, 1214, + 1264, 1036, 1008, 1016, 804, 1004, 1020, 998, 995, 856, + 853, 844, 851, 843, 840, 808, 814, 871, 1116, 1119, + 1021, 920, 811, 1085, 1038, 1211, 1044, 1045, 1047, 1088, + 1123, 942, 1125, 1216, 895, 1217, 1218, 965, 1051, 1173, + 897, 974, 873, 968, 1049, 978, 792, 969, 1129, 1130, + 1081, 961, 1097, 1098, 1072, 911, 884, 970, 1219, 1059, + 1060, 1062, 1176, 1177, 930, 1082, 996, 1099, 912, 1058, + 1100, 1101, 1105, 1106, 1179, 1222, 1182, 922, 1183, 945, + 879, 1077, 909, 1223, 165, 892, 893, 906, 1068, 683, + 1035, 1184, 1208, 1229, 1108, 1109, 1110, 1230, 1231, 1024, + 946, 1083, 900, 1084, 1078, 947, 948, 689, 905, 1132, + 890, 891, 904, 705, 768, 1238, 1239, 1240, 1025, 877, + 894, 951, 953, 1133, 887, 1135, 1241, 771, 954, 1242, + 1115, 816, 817, 521, 784, 747, 818, 881, 1194, 925, + 865, 878, 1067, 817, 883, 955, 1245, 957, 958, 959, + 1111, 960, 1086, 1246, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 632, 632, 632, + 632, 789, 789, 789, 789, 789, 789, 789, 632, 789, + 789, 789, 632, 632, 0, 0, 632, 0, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, + 789, 789, 789, 789, 616, 616, 616, 616, 616, 616, + 616, 616, 616, 616, 616, 616, 616, 616, 616, 616, + 616, 616, 616, 616, 616, 616, 616, 616, 616, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 616, 616, 616, 616, + 616, 616, 616, 616, 616, 616, 616, 616, 616, 616, + 616, 616, 616, 616, 616, 616, 616, 616, 616, 616, + 616, 616, 823, 823, 616, 616, 823, 823, 823, 823, + 823, 823, 823, 823, 823, 823, 616, 616, 0, 616, + 616, 616, 616, 616, 616, 616, 875, 823, 823, 324, + 324, 324, 324, 823, 823, 396, 396, 396, 823, 324, + 823, 64, 324, 823, 64, 823, 823, 823, 823, 823, + 823, 823, 823, 823, 0, 0, 823, 823, 823, 823, + -25, -72, 823, 903, 903, 903, 903, 823, 823, 823, + 823, -72, -72, 823, -57, -57, 823, 823, 0, 0, + 0, 324, 324, -25, 0, 0, -25, 0, 0, 903, + 903, 823, 64, 875, 446, 823, 342, 0, 0, 0, + 0, 0, 0, 0, -25, 903, -25, 523, -72, -72, + 523, 523, 13, 77, 446, 612, 612, 612, 612, 77, + 0, 0, 0, 0, 0, 610, 875, 875, 875, 875, + 875, 875, 875, 875, 875, 875, 875, 875, 903, 0, + 875, 0, 875, 875, 903, 903, 903, 0, 0, 0, + 0, 0, 0, 0, 0, 986, 0, 0, 0, 0, + 0, 0, 0, 903, 0, 986, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 903, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 897, 911, 0, 0, 911, + 0, 897, 897, 897, 0, 0, 0, 905, 887 + ); + + protected array $actionDefault = array( + 3,32767,32767,32767, 102, 102,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 100, + 32767, 632, 632, 632, 632,32767,32767, 257, 102,32767, + 32767, 503, 417, 417, 417,32767,32767,32767, 576, 576, + 576, 576, 576, 17,32767,32767,32767,32767,32767,32767, + 32767, 503,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 36, 7, 8, 10, 11, 49, 338, 100, + 32767,32767,32767,32767,32767,32767,32767,32767, 102,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 404, 625,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 497, 507, 485, 486, 488, 489, 416, 577, + 631, 344, 628, 342, 415, 146, 354, 343, 245, 261, + 508, 262, 509, 512, 513, 218, 401, 150, 151, 448, + 504, 450, 502, 506, 449, 422, 429, 430, 431, 432, + 433, 434, 435, 436, 437, 438, 439, 440, 441, 420, + 421, 505, 482, 481, 480,32767,32767, 446, 447,32767, + 32767,32767,32767,32767,32767,32767,32767, 102,32767, 451, + 454, 419, 452, 453, 470, 471, 468, 469, 472,32767, + 323,32767, 473, 474, 475, 476,32767,32767, 382, 196, + 380,32767, 477,32767, 111, 455, 323, 111,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 461, 462,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767, 102,32767,32767,32767, + 100, 520, 570, 479, 456, 457,32767, 545,32767, 102, + 32767, 547,32767,32767,32767,32767,32767,32767,32767,32767, + 572, 443, 445, 540, 626, 423, 629,32767, 533, 100, + 196,32767, 546, 196, 196,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 571,32767, 639, 533, 110, + 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, + 110,32767, 196, 110,32767, 110, 110,32767,32767, 100, + 196, 196, 196, 196, 196, 196, 196, 196, 548, 196, + 196, 191,32767, 271, 273, 102, 594, 196, 550,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 404,32767,32767,32767,32767, 533, 466, 139, + 32767, 535, 139, 578, 458, 459, 460, 578, 578, 578, + 319, 296,32767,32767,32767,32767,32767, 548, 548, 100, + 100, 100, 100,32767,32767,32767,32767, 111, 519, 99, + 99, 99, 99, 99, 103, 101,32767,32767,32767,32767, + 226,32767, 101, 101, 99,32767, 101, 101,32767,32767, + 226, 228, 215, 230,32767, 598, 599, 226, 101, 230, + 230, 230, 250, 250, 522, 325, 101, 99, 101, 101, + 198, 325, 325,32767, 101, 522, 325, 522, 325, 200, + 325, 325, 325, 522, 325,32767, 101, 325, 217, 99, + 99, 325,32767,32767,32767,32767, 535,32767,32767,32767, + 32767,32767,32767,32767, 225,32767,32767,32767,32767,32767, + 32767,32767,32767, 565,32767, 583, 596, 464, 465, 467, + 582, 580, 490, 491, 492, 493, 494, 495, 496, 499, + 627,32767, 539,32767,32767,32767, 353,32767, 637,32767, + 32767,32767, 9, 74, 528, 42, 43, 51, 57, 554, + 555, 556, 557, 551, 552, 558, 553,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 638,32767, 578,32767,32767,32767,32767, + 463, 560, 604,32767,32767, 579, 630,32767,32767,32767, + 32767,32767,32767,32767,32767, 139,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767, 565,32767, 137,32767, + 32767,32767,32767,32767,32767,32767,32767, 561,32767,32767, + 32767, 578,32767,32767,32767,32767, 321, 318,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 578,32767,32767,32767,32767,32767, + 298,32767, 315,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 400, 535, 301, 303, 304,32767,32767,32767,32767, + 376,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 153, 153, 3, 3, 356, 153, + 153, 153, 356, 356, 153, 356, 356, 356, 153, 153, + 153, 153, 153, 153, 153, 283, 186, 265, 268, 250, + 250, 153, 368, 153, 402, 402, 411 + ); + + protected array $goto = array( + 201, 169, 201, 201, 201, 1069, 598, 719, 448, 684, + 644, 681, 443, 345, 341, 342, 344, 615, 447, 346, + 449, 661, 481, 728, 570, 570, 570, 570, 1245, 626, + 172, 172, 172, 172, 225, 202, 198, 198, 182, 184, + 220, 198, 198, 198, 198, 198, 1195, 199, 199, 199, + 199, 199, 1195, 192, 193, 194, 195, 196, 197, 222, + 220, 223, 557, 558, 438, 559, 562, 563, 564, 565, + 566, 567, 568, 569, 173, 174, 175, 200, 176, 177, + 178, 170, 179, 180, 181, 183, 219, 221, 224, 242, + 247, 248, 259, 260, 262, 263, 264, 265, 266, 267, + 268, 272, 273, 274, 275, 282, 285, 297, 298, 324, + 325, 444, 445, 446, 620, 226, 227, 228, 229, 230, + 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, + 241, 193, 194, 195, 196, 197, 222, 203, 204, 205, + 206, 243, 185, 186, 207, 187, 208, 204, 188, 244, + 203, 168, 209, 210, 189, 211, 212, 213, 190, 214, + 215, 171, 216, 217, 218, 191, 287, 284, 287, 287, + 883, 255, 255, 255, 255, 255, 1125, 605, 487, 487, + 622, 758, 660, 662, 1103, 359, 682, 487, 1075, 1074, + 706, 709, 1041, 717, 726, 1037, 733, 922, 879, 922, + 922, 253, 253, 253, 253, 250, 256, 646, 646, 1078, + 1079, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + 1332, 880, 351, 938, 933, 934, 947, 889, 935, 886, + 936, 937, 887, 890, 476, 941, 894, 476, 1044, 1044, + 893, 364, 364, 364, 364, 352, 351, 532, 1131, 1127, + 1128, 1351, 1351, 331, 315, 1351, 1351, 1351, 1351, 1351, + 1351, 1351, 1351, 1351, 1351, 1069, 1301, 1072, 1072, 704, + 983, 1301, 1301, 1064, 1080, 1081, 1069, 942, 1301, 943, + 458, 1069, 881, 1069, 1069, 1069, 1069, 1069, 1069, 1069, + 1069, 1069, 897, 855, 1069, 1069, 1069, 1069, 677, 678, + 1301, 695, 696, 697, 1006, 1301, 1301, 1301, 1301, 450, + 909, 1301, 436, 896, 1301, 1301, 1382, 1382, 1382, 1382, + 915, 581, 574, 499, 612, 450, 367, 971, 971, 955, + 501, 1076, 1076, 956, 1400, 1400, 367, 367, 688, 1087, + 1083, 1084, 572, 411, 414, 623, 627, 572, 572, 367, + 367, 1400, 357, 367, 572, 1417, 1377, 1378, 317, 574, + 581, 607, 608, 318, 618, 624, 1390, 640, 641, 1027, + 576, 1403, 1403, 367, 367, 28, 474, 520, 442, 521, + 635, 1000, 1000, 1000, 1000, 527, 409, 474, 1348, 1348, + 994, 1001, 1348, 1348, 1348, 1348, 1348, 1348, 1348, 1348, + 1348, 1348, 633, 647, 650, 651, 652, 653, 674, 675, + 676, 730, 732, 561, 561, 258, 258, 561, 561, 561, + 561, 561, 561, 561, 561, 561, 561, 610, 1362, 467, + 683, 467, 876, 616, 638, 876, 467, 467, 1191, 861, + 1373, 360, 361, 1093, 456, 1373, 1373, 560, 560, 705, + 432, 560, 1373, 560, 560, 560, 560, 560, 560, 560, + 560, 1277, 975, 575, 602, 575, 1278, 1281, 976, 575, + 1282, 602, 689, 412, 480, 1384, 1384, 1384, 1384, 347, + 873, 716, 576, 861, 876, 861, 490, 619, 491, 492, + 639, 8, 857, 9, 902, 907, 989, 716, 1408, 1409, + 716, 1369, 418, 1296, 278, 899, 330, 1174, 424, 425, + 1292, 330, 330, 693, 1049, 694, 1114, 429, 430, 431, + 761, 707, 1060, 905, 433, 1102, 1104, 1107, 355, 467, + 467, 467, 467, 467, 467, 467, 467, 467, 467, 467, + 467, 419, 339, 467, 911, 467, 467, 1294, 628, 629, + 1116, 497, 960, 1181, 621, 1144, 1371, 1371, 1116, 1118, + 1297, 1298, 1011, 1284, 1046, 1151, 1179, 1152, 731, 871, + 528, 722, 901, 1142, 687, 1025, 1284, 496, 1375, 1376, + 895, 910, 898, 1113, 1117, 998, 427, 727, 1165, 1299, + 1359, 1360, 1291, 1030, 386, 1009, 1002, 0, 757, 0, + 0, 573, 1039, 1034, 654, 656, 658, 0, 0, 0, + 0, 0, 0, 0, 0, 876, 0, 0, 999, 0, + 766, 766, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1163, 914 + ); + + protected array $gotoCheck = array( + 42, 42, 42, 42, 42, 73, 127, 73, 66, 66, + 56, 56, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 159, 9, 107, 107, 107, 107, 159, 107, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 23, 23, 23, 23, + 15, 5, 5, 5, 5, 5, 15, 48, 157, 157, + 134, 48, 48, 48, 131, 97, 48, 157, 119, 119, + 48, 48, 48, 48, 48, 48, 48, 25, 25, 25, + 25, 5, 5, 5, 5, 5, 5, 108, 108, 120, + 120, 108, 108, 108, 108, 108, 108, 108, 108, 108, + 108, 26, 177, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 83, 15, 15, 83, 107, 107, + 15, 24, 24, 24, 24, 177, 177, 76, 15, 15, + 15, 179, 179, 178, 178, 179, 179, 179, 179, 179, + 179, 179, 179, 179, 179, 73, 73, 89, 89, 89, + 89, 73, 73, 89, 89, 89, 73, 65, 73, 65, + 83, 73, 27, 73, 73, 73, 73, 73, 73, 73, + 73, 73, 35, 6, 73, 73, 73, 73, 86, 86, + 73, 86, 86, 86, 49, 73, 73, 73, 73, 118, + 35, 73, 43, 35, 73, 73, 9, 9, 9, 9, + 45, 76, 76, 84, 181, 118, 14, 9, 9, 73, + 84, 118, 118, 73, 191, 191, 14, 14, 118, 118, + 118, 118, 19, 59, 59, 59, 59, 19, 19, 14, + 14, 191, 188, 14, 19, 14, 187, 187, 76, 76, + 76, 76, 76, 76, 76, 76, 190, 76, 76, 103, + 14, 191, 191, 14, 14, 76, 19, 163, 13, 163, + 13, 19, 19, 19, 19, 163, 62, 19, 180, 180, + 19, 19, 180, 180, 180, 180, 180, 180, 180, 180, + 180, 180, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 81, 81, 182, 182, 5, 5, 182, 182, 182, + 182, 182, 182, 182, 182, 182, 182, 104, 14, 23, + 64, 23, 22, 2, 2, 22, 23, 23, 158, 12, + 134, 97, 97, 115, 113, 134, 134, 165, 165, 117, + 14, 165, 134, 165, 165, 165, 165, 165, 165, 165, + 165, 79, 79, 9, 9, 9, 79, 79, 79, 9, + 79, 9, 121, 9, 9, 134, 134, 134, 134, 29, + 18, 7, 14, 12, 22, 12, 9, 9, 9, 9, + 80, 46, 7, 46, 39, 9, 92, 7, 9, 9, + 7, 134, 28, 20, 24, 37, 24, 156, 82, 82, + 169, 24, 24, 82, 110, 82, 133, 82, 82, 82, + 99, 82, 114, 9, 82, 130, 130, 130, 82, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 31, 9, 23, 41, 23, 23, 14, 17, 17, + 134, 160, 17, 17, 8, 8, 134, 134, 134, 136, + 20, 20, 96, 20, 17, 149, 149, 149, 8, 20, + 8, 8, 17, 8, 17, 17, 20, 185, 185, 185, + 17, 16, 16, 16, 16, 93, 93, 93, 152, 20, + 20, 20, 17, 50, 141, 16, 50, -1, 50, -1, + -1, 50, 50, 50, 85, 85, 85, -1, -1, -1, + -1, -1, -1, -1, -1, 22, -1, -1, 16, -1, + 24, 24, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 16, 16 + ); + + protected array $gotoBase = array( + 0, 0, -303, 0, 0, 170, 280, 471, 543, 10, + 0, 0, 136, 31, 22, -186, 111, 66, 164, 71, + 95, 0, 148, 160, 235, 191, 214, 275, 155, 176, + 0, 86, 0, 0, 0, -92, 0, 156, 0, 165, + 0, 85, -1, 286, 0, 291, -270, 0, -558, 284, + 579, 0, 0, 0, 0, 0, -33, 0, 0, 294, + 0, 0, 341, 0, 184, 261, -237, 0, 0, 0, + 0, 0, 0, -5, 0, 0, -32, 0, 0, 37, + 172, 32, -3, -50, -167, 105, -444, 0, 0, -21, + 0, 0, 161, 274, 0, 0, 101, -318, 0, 97, + 0, 0, 0, 331, 381, 0, 0, -7, -38, 0, + 131, 0, 0, 158, 90, 162, 0, 159, 39, -100, + -83, 173, 0, 0, 0, 0, 0, 4, 0, 0, + 522, 182, 0, 127, 169, 0, 99, 0, 0, 0, + 0, -171, 0, 0, 0, 0, 0, 0, 0, 287, + 0, 0, 126, 0, 0, 0, 144, 141, 188, -255, + 93, 0, 0, -138, 0, 202, 0, 0, 0, 128, + 0, 0, 0, 0, 0, 0, 0, -82, -74, 6, + 143, 292, 168, 0, 0, 270, 0, -31, 319, 0, + 332, 20, 0, 0 + ); + + protected array $gotoDefault = array( + -32768, 533, 768, 7, 769, 964, 844, 853, 597, 551, + 729, 356, 648, 439, 1367, 940, 1180, 617, 872, 1310, + 1316, 475, 875, 336, 755, 952, 923, 924, 415, 402, + 888, 413, 672, 649, 514, 908, 471, 900, 506, 903, + 470, 912, 167, 435, 530, 916, 6, 919, 579, 950, + 1004, 403, 927, 404, 700, 929, 601, 931, 932, 410, + 416, 417, 1185, 609, 645, 944, 261, 603, 945, 401, + 946, 954, 406, 408, 710, 486, 525, 519, 428, 1146, + 604, 632, 669, 464, 493, 643, 655, 642, 500, 451, + 434, 335, 988, 996, 507, 484, 1010, 358, 1018, 763, + 1193, 663, 509, 1026, 664, 1033, 1036, 552, 553, 498, + 1048, 270, 1051, 510, 1061, 26, 690, 1066, 1067, 691, + 665, 1089, 666, 692, 667, 1091, 483, 599, 1194, 482, + 1106, 1112, 472, 1115, 1356, 473, 1119, 269, 1122, 286, + 362, 385, 452, 1129, 1130, 12, 1136, 720, 721, 25, + 280, 529, 1164, 711, 1170, 279, 1173, 469, 1192, 468, + 1265, 1267, 580, 511, 1285, 321, 1288, 703, 526, 1293, + 465, 1358, 466, 554, 494, 343, 555, 1401, 314, 365, + 340, 571, 322, 366, 556, 495, 1364, 1372, 337, 34, + 1391, 1402, 614, 637 + ); + + protected array $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, + 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, + 16, 17, 17, 18, 18, 21, 21, 22, 23, 23, + 24, 24, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 29, 29, 30, 30, 32, 34, + 34, 28, 36, 36, 33, 38, 38, 35, 35, 37, + 37, 39, 39, 31, 40, 40, 41, 43, 44, 44, + 45, 45, 46, 46, 48, 47, 47, 47, 47, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 25, 25, 50, 69, 69, 72, 72, + 71, 70, 70, 63, 75, 75, 76, 76, 77, 77, + 78, 78, 79, 79, 80, 80, 80, 80, 26, 26, + 27, 27, 27, 27, 27, 88, 88, 90, 90, 83, + 83, 91, 91, 92, 92, 92, 84, 84, 87, 87, + 85, 85, 93, 94, 94, 57, 57, 65, 65, 68, + 68, 68, 67, 95, 95, 96, 58, 58, 58, 58, + 97, 97, 98, 98, 99, 99, 100, 101, 101, 102, + 102, 103, 103, 55, 55, 51, 51, 105, 53, 53, + 106, 52, 52, 54, 54, 64, 64, 64, 64, 81, + 81, 109, 109, 111, 111, 112, 112, 112, 112, 112, + 112, 112, 112, 110, 110, 110, 115, 115, 115, 115, + 89, 89, 118, 118, 118, 119, 119, 116, 116, 120, + 120, 122, 122, 123, 123, 117, 124, 124, 121, 125, + 125, 125, 125, 113, 113, 82, 82, 82, 20, 20, + 20, 128, 128, 128, 128, 129, 129, 129, 127, 126, + 126, 131, 131, 131, 130, 130, 60, 132, 132, 133, + 61, 135, 135, 136, 136, 137, 137, 86, 138, 138, + 138, 138, 138, 138, 138, 138, 144, 144, 145, 145, + 146, 146, 146, 146, 146, 147, 148, 148, 143, 143, + 139, 139, 142, 142, 150, 150, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 140, 151, 151, 153, + 152, 152, 141, 141, 114, 114, 154, 154, 156, 156, + 156, 155, 155, 62, 104, 157, 157, 56, 56, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 164, 165, 165, 166, + 158, 158, 163, 163, 167, 168, 168, 169, 170, 171, + 171, 171, 171, 19, 19, 73, 73, 73, 73, 159, + 159, 159, 159, 173, 173, 162, 162, 162, 160, 160, + 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, + 180, 180, 180, 108, 182, 182, 182, 182, 161, 161, + 161, 161, 161, 161, 161, 161, 59, 59, 176, 176, + 176, 176, 176, 183, 183, 172, 172, 172, 172, 184, + 184, 184, 184, 184, 74, 74, 66, 66, 66, 66, + 134, 134, 134, 134, 187, 186, 175, 175, 175, 175, + 175, 175, 174, 174, 174, 185, 185, 185, 185, 107, + 181, 189, 189, 188, 188, 190, 190, 190, 190, 190, + 190, 190, 190, 178, 178, 178, 178, 177, 192, 191, + 191, 191, 191, 191, 191, 191, 191, 193, 193, 193, + 193 + ); + + protected array $ruleToLength = array( + 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 0, 1, 1, 2, 1, 3, 4, 1, 2, + 0, 1, 1, 1, 1, 4, 3, 5, 4, 3, + 4, 1, 3, 4, 1, 1, 8, 7, 2, 3, + 1, 2, 3, 1, 2, 3, 1, 1, 3, 1, + 3, 1, 2, 2, 3, 1, 3, 2, 3, 1, + 3, 3, 2, 0, 1, 1, 1, 1, 1, 3, + 7, 10, 5, 7, 9, 5, 3, 3, 3, 3, + 3, 3, 1, 2, 5, 7, 9, 6, 5, 6, + 3, 2, 1, 1, 1, 1, 0, 2, 1, 3, + 8, 0, 4, 2, 1, 3, 0, 1, 0, 1, + 0, 1, 3, 1, 1, 1, 1, 1, 8, 9, + 7, 8, 7, 6, 8, 0, 2, 0, 2, 1, + 2, 1, 2, 1, 1, 1, 0, 2, 0, 2, + 0, 2, 2, 1, 3, 1, 4, 1, 4, 1, + 1, 4, 2, 1, 3, 3, 3, 4, 4, 5, + 0, 2, 4, 3, 1, 1, 7, 0, 2, 1, + 3, 3, 4, 1, 4, 0, 2, 5, 0, 2, + 6, 0, 2, 0, 3, 1, 2, 1, 1, 2, + 0, 1, 3, 0, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 7, 9, 6, 1, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, + 3, 3, 3, 3, 3, 1, 3, 3, 1, 1, + 2, 1, 1, 0, 1, 0, 2, 2, 2, 4, + 3, 2, 4, 4, 3, 3, 1, 3, 1, 1, + 3, 2, 2, 3, 1, 1, 2, 3, 1, 1, + 2, 3, 1, 1, 3, 2, 0, 1, 5, 7, + 5, 6, 10, 3, 5, 1, 1, 3, 0, 2, + 4, 5, 4, 4, 4, 3, 1, 1, 1, 1, + 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 3, 1, + 1, 3, 0, 2, 0, 3, 5, 8, 1, 3, + 3, 0, 2, 2, 2, 3, 1, 0, 1, 1, + 3, 3, 3, 4, 4, 1, 1, 2, 2, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 5, 4, 3, 4, 4, 2, 2, 4, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 3, 2, 1, 2, 4, 2, 2, 8, 9, + 8, 9, 9, 10, 9, 10, 8, 3, 2, 2, + 1, 1, 0, 4, 2, 1, 3, 2, 1, 2, + 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 0, 1, 1, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 3, 5, 3, 3, 4, 1, 1, 3, 1, 1, + 1, 1, 1, 3, 2, 3, 0, 1, 1, 3, + 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, + 4, 1, 4, 4, 0, 1, 1, 1, 3, 3, + 1, 4, 2, 2, 1, 3, 1, 4, 3, 3, + 3, 3, 1, 3, 1, 1, 3, 1, 1, 4, + 1, 1, 1, 3, 1, 1, 2, 1, 3, 4, + 3, 2, 0, 2, 2, 1, 2, 1, 1, 1, + 4, 3, 3, 3, 3, 6, 3, 1, 1, 2, + 1 + ); + + protected function initReduceCallbacks(): void { + $this->reduceCallbacks = [ + 0 => null, + 1 => static function ($self, $stackPos) { + $self->semValue = $self->handleNamespaces($self->semStack[$stackPos-(1-1)]); + }, + 2 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 3 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 4 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 5 => null, + 6 => null, + 7 => null, + 8 => null, + 9 => null, + 10 => null, + 11 => null, + 12 => null, + 13 => null, + 14 => null, + 15 => null, + 16 => null, + 17 => null, + 18 => null, + 19 => null, + 20 => null, + 21 => null, + 22 => null, + 23 => null, + 24 => null, + 25 => null, + 26 => null, + 27 => null, + 28 => null, + 29 => null, + 30 => null, + 31 => null, + 32 => null, + 33 => null, + 34 => null, + 35 => null, + 36 => null, + 37 => null, + 38 => null, + 39 => null, + 40 => null, + 41 => null, + 42 => null, + 43 => null, + 44 => null, + 45 => null, + 46 => null, + 47 => null, + 48 => null, + 49 => null, + 50 => null, + 51 => null, + 52 => null, + 53 => null, + 54 => null, + 55 => null, + 56 => null, + 57 => null, + 58 => null, + 59 => null, + 60 => null, + 61 => null, + 62 => null, + 63 => null, + 64 => null, + 65 => null, + 66 => null, + 67 => null, + 68 => null, + 69 => null, + 70 => null, + 71 => null, + 72 => null, + 73 => null, + 74 => null, + 75 => null, + 76 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; if ($self->semValue === "emitError(new Error('Cannot use "getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 77 => null, + 78 => null, + 79 => null, + 80 => null, + 81 => null, + 82 => null, + 83 => null, + 84 => null, + 85 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 86 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 87 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 88 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 89 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 90 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 91 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 92 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 93 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 94 => null, + 95 => static function ($self, $stackPos) { + $self->semValue = new Name(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 96 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 97 => static function ($self, $stackPos) { + /* nothing */ + }, + 98 => static function ($self, $stackPos) { + /* nothing */ + }, + 99 => static function ($self, $stackPos) { + /* nothing */ + }, + 100 => static function ($self, $stackPos) { + $self->emitError(new Error('A trailing comma is not allowed here', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 101 => null, + 102 => null, + 103 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(1-1)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 104 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 105 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 106 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 107 => static function ($self, $stackPos) { + $self->semValue = new Node\AttributeGroup($self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 108 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 109 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 110 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 111 => null, + 112 => null, + 113 => null, + 114 => null, + 115 => static function ($self, $stackPos) { + $self->semValue = new Stmt\HaltCompiler($self->handleHaltCompiler(), $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 116 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(3-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $self->checkNamespace($self->semValue); + }, + 117 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 118 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_(null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 119 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 120 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 121 => null, + 122 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Const_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), []); + }, + 123 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Const_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(4-1)]); + $self->checkConstantAttributes($self->semValue); + }, + 124 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 125 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 126 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-6)], $self->semStack[$stackPos-(8-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 127 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(7-2)], $self->semStack[$stackPos-(7-5)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 128 => null, + 129 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 130 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 131 => null, + 132 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 133 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 134 => null, + 135 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 136 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 137 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 138 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 139 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 140 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 141 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 142 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; $self->semValue->type = $self->semStack[$stackPos-(2-1)]; + }, + 143 => null, + 144 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 145 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 146 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 147 => null, + 148 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 149 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 150 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 151 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 152 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 153 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 154 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 155 => null, + 156 => null, + 157 => null, + 158 => static function ($self, $stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 159 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Block($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 160 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(7-3)], ['stmts' => $self->semStack[$stackPos-(7-5)], 'elseifs' => $self->semStack[$stackPos-(7-6)], 'else' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 161 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(10-3)], ['stmts' => $self->semStack[$stackPos-(10-6)], 'elseifs' => $self->semStack[$stackPos-(10-7)], 'else' => $self->semStack[$stackPos-(10-8)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 162 => static function ($self, $stackPos) { + $self->semValue = new Stmt\While_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 163 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Do_($self->semStack[$stackPos-(7-5)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 164 => static function ($self, $stackPos) { + $self->semValue = new Stmt\For_(['init' => $self->semStack[$stackPos-(9-3)], 'cond' => $self->semStack[$stackPos-(9-5)], 'loop' => $self->semStack[$stackPos-(9-7)], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 165 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Switch_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 166 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Break_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 167 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Continue_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 168 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Return_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 169 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Global_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 170 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Static_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 171 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Echo_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 172 => static function ($self, $stackPos) { + + $self->semValue = new Stmt\InlineHTML($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('hasLeadingNewline', $self->inlineHtmlHasLeadingNewline($stackPos-(1-1))); + + }, + 173 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Expression($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 174 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Unset_($self->semStack[$stackPos-(5-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 175 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $self->semStack[$stackPos-(7-5)][1], 'stmts' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 176 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-7)][0], ['keyVar' => $self->semStack[$stackPos-(9-5)], 'byRef' => $self->semStack[$stackPos-(9-7)][1], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 177 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(6-3)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-4)], $self->tokenEndStack[$stackPos-(6-4)])), ['stmts' => $self->semStack[$stackPos-(6-6)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 178 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Declare_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 179 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TryCatch($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->checkTryCatch($self->semValue); + }, + 180 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Goto_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 181 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Label($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 182 => static function ($self, $stackPos) { + $self->semValue = null; /* means: no statement */ + }, + 183 => null, + 184 => static function ($self, $stackPos) { + $self->semValue = $self->maybeCreateNop($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); + }, + 185 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 186 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 187 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 188 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 189 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 190 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Catch_($self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-7)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 191 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 192 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Finally_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 193 => null, + 194 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 195 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 196 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 197 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 198 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 199 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 200 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 201 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 202 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 203 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 204 => null, + 205 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 206 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 207 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 208 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(8-3)], ['byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-5)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 209 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(9-4)], ['byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-6)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 210 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(7-2)], ['type' => $self->semStack[$stackPos-(7-1)], 'extends' => $self->semStack[$stackPos-(7-3)], 'implements' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(7-2)); + }, + 211 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(8-3)], ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(8-3)); + }, + 212 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Interface_($self->semStack[$stackPos-(7-3)], ['extends' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => $self->semStack[$stackPos-(7-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkInterface($self->semValue, $stackPos-(7-3)); + }, + 213 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Trait_($self->semStack[$stackPos-(6-3)], ['stmts' => $self->semStack[$stackPos-(6-5)], 'attrGroups' => $self->semStack[$stackPos-(6-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 214 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Enum_($self->semStack[$stackPos-(8-3)], ['scalarType' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkEnum($self->semValue, $stackPos-(8-3)); + }, + 215 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 216 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 217 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 218 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 219 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 220 => null, + 221 => null, + 222 => static function ($self, $stackPos) { + $self->checkClassModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 223 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 224 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 225 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 226 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 227 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 228 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 229 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 230 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 231 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 232 => null, + 233 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 234 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 235 => null, + 236 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 237 => null, + 238 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 239 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 240 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 241 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 242 => null, + 243 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 244 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 245 => static function ($self, $stackPos) { + $self->semValue = new Node\DeclareItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 246 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 247 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 248 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 249 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(5-3)]; + }, + 250 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 251 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 252 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_($self->semStack[$stackPos-(4-2)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 253 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_(null, $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 254 => null, + 255 => null, + 256 => static function ($self, $stackPos) { + $self->semValue = new Expr\Match_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 257 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 258 => null, + 259 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 260 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 261 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 262 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm(null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 263 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 264 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 265 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 266 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 267 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 268 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 269 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 270 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 271 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 272 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 273 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 274 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 275 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 276 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-2)], true); + }, + 277 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 278 => static function ($self, $stackPos) { + $self->semValue = array($self->fixupArrayDestructuring($self->semStack[$stackPos-(1-1)]), false); + }, + 279 => null, + 280 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 281 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 282 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 283 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 284 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 285 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 286 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 287 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 288 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC_SET; + }, + 289 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED_SET; + }, + 290 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE_SET; + }, + 291 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 292 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 293 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(7-6)], null, $self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-4)], $self->semStack[$stackPos-(7-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(7-2)], $self->semStack[$stackPos-(7-1)], $self->semStack[$stackPos-(7-7)]); + $self->checkParam($self->semValue); + $self->addPropertyNameToHooks($self->semValue); + }, + 294 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(9-6)], $self->semStack[$stackPos-(9-8)], $self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-4)], $self->semStack[$stackPos-(9-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(9-2)], $self->semStack[$stackPos-(9-1)], $self->semStack[$stackPos-(9-9)]); + $self->checkParam($self->semValue); + $self->addPropertyNameToHooks($self->semValue); + }, + 295 => static function ($self, $stackPos) { + $self->semValue = new Node\Param(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])), null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + }, + 296 => null, + 297 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 298 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 299 => null, + 300 => null, + 301 => static function ($self, $stackPos) { + $self->semValue = new Node\Name('static', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 302 => static function ($self, $stackPos) { + $self->semValue = $self->handleBuiltinTypes($self->semStack[$stackPos-(1-1)]); + }, + 303 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('array', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 304 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('callable', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 305 => null, + 306 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 307 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 308 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 309 => null, + 310 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 311 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 312 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 313 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 314 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 315 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 316 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 317 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 318 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 319 => null, + 320 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 321 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 322 => null, + 323 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 324 => null, + 325 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 326 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 327 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 328 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 329 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 330 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-2)]); + }, + 331 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 332 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 333 => static function ($self, $stackPos) { + $self->semValue = array(new Node\Arg($self->semStack[$stackPos-(4-2)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]))); + }, + 334 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-2)]); + }, + 335 => static function ($self, $stackPos) { + $self->semValue = array(new Node\Arg($self->semStack[$stackPos-(3-1)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)]); + }, + 336 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 337 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 338 => static function ($self, $stackPos) { + $self->semValue = new Node\VariadicPlaceholder($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 339 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 340 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 341 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], true, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 342 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], false, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 343 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(3-3)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(3-1)]); + }, + 344 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(1-1)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 345 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 346 => null, + 347 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 348 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 349 => null, + 350 => null, + 351 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 352 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 353 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 354 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 355 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; } else { $self->semValue = $self->semStack[$stackPos-(2-1)]; } + }, + 356 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 357 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 358 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Property($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-1)]); + }, + 359 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Property($self->semStack[$stackPos-(7-2)], $self->semStack[$stackPos-(7-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-1)], $self->semStack[$stackPos-(7-6)]); + $self->checkPropertyHooksForMultiProperty($self->semValue, $stackPos-(7-5)); + $self->checkEmptyPropertyHookList($self->semStack[$stackPos-(7-6)], $stackPos-(7-5)); + $self->addPropertyNameToHooks($self->semValue); + }, + 360 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-1)]); + $self->checkClassConst($self->semValue, $stackPos-(5-2)); + }, + 361 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-1)], $self->semStack[$stackPos-(6-4)]); + $self->checkClassConst($self->semValue, $stackPos-(6-2)); + }, + 362 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassMethod($self->semStack[$stackPos-(10-5)], ['type' => $self->semStack[$stackPos-(10-2)], 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-7)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + $self->checkClassMethod($self->semValue, $stackPos-(10-2)); + }, + 363 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUse($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 364 => static function ($self, $stackPos) { + $self->semValue = new Stmt\EnumCase($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 365 => static function ($self, $stackPos) { + $self->semValue = null; /* will be skipped */ + }, + 366 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 367 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 368 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 369 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 370 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Precedence($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 371 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(5-1)][0], $self->semStack[$stackPos-(5-1)][1], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 372 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 373 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 374 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 375 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 376 => null, + 377 => static function ($self, $stackPos) { + $self->semValue = array(null, $self->semStack[$stackPos-(1-1)]); + }, + 378 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 379 => null, + 380 => null, + 381 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 382 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 383 => null, + 384 => null, + 385 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 386 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 387 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 388 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 389 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC_SET; + }, + 390 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED_SET; + }, + 391 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE_SET; + }, + 392 => static function ($self, $stackPos) { + $self->semValue = Modifiers::STATIC; + }, + 393 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 394 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 395 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 396 => null, + 397 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 398 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 399 => static function ($self, $stackPos) { + $self->semValue = new Node\VarLikeIdentifier(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 400 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 401 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 402 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 403 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 404 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 405 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; $self->checkEmptyPropertyHookList($self->semStack[$stackPos-(3-2)], $stackPos-(3-1)); + }, + 406 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyHook($self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-5)], ['flags' => $self->semStack[$stackPos-(5-2)], 'byRef' => $self->semStack[$stackPos-(5-3)], 'params' => [], 'attrGroups' => $self->semStack[$stackPos-(5-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + $self->checkPropertyHook($self->semValue, null); + }, + 407 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyHook($self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-8)], ['flags' => $self->semStack[$stackPos-(8-2)], 'byRef' => $self->semStack[$stackPos-(8-3)], 'params' => $self->semStack[$stackPos-(8-6)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkPropertyHook($self->semValue, $stackPos-(8-5)); + }, + 408 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 409 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 410 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 411 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 412 => static function ($self, $stackPos) { + $self->checkPropertyHookModifiers($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 413 => null, + 414 => null, + 415 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 416 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 417 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 418 => null, + 419 => null, + 420 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 421 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->fixupArrayDestructuring($self->semStack[$stackPos-(3-1)]), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 422 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 423 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 424 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + if (!$self->phpVersion->allowsAssignNewByReference()) { + $self->emitError(new Error('Cannot assign new by reference', $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]))); + } + + }, + 425 => null, + 426 => null, + 427 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall(new Node\Name($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos-(2-1)])), $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 428 => static function ($self, $stackPos) { + $self->semValue = new Expr\Clone_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 429 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 430 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 431 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 432 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 433 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 434 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 435 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 436 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 437 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 438 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 439 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 440 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 441 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 442 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostInc($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 443 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreInc($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 444 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostDec($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 445 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreDec($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 446 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 447 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 448 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 449 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 450 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 451 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 452 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 453 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 454 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 455 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 456 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 457 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 458 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 459 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 460 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 461 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 462 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 463 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 464 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryPlus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 465 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryMinus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 466 => static function ($self, $stackPos) { + $self->semValue = new Expr\BooleanNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 467 => static function ($self, $stackPos) { + $self->semValue = new Expr\BitwiseNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 468 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Identical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 469 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotIdentical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 470 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Equal($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 471 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 472 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Spaceship($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 473 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Smaller($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 474 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\SmallerOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 475 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Greater($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 476 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\GreaterOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 477 => static function ($self, $stackPos) { + + $self->semValue = new Expr\BinaryOp\Pipe($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + $self->checkPipeOperatorParentheses($self->semStack[$stackPos-(3-3)]); + + }, + 478 => static function ($self, $stackPos) { + $self->semValue = new Expr\Instanceof_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 479 => static function ($self, $stackPos) { + + $self->semValue = $self->semStack[$stackPos-(3-2)]; + if ($self->semValue instanceof Expr\ArrowFunction) { + $self->parenthesizedArrowFunctions->offsetSet($self->semValue); + } + + }, + 480 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 481 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(4-1)], null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 482 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 483 => static function ($self, $stackPos) { + $self->semValue = new Expr\Isset_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 484 => static function ($self, $stackPos) { + $self->semValue = new Expr\Empty_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 485 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 486 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 487 => static function ($self, $stackPos) { + $self->semValue = new Expr\Eval_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 488 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 489 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 490 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getIntCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 491 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getFloatCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs); + }, + 492 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getStringCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 493 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 494 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 495 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getBoolCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 496 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 497 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Void_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 498 => static function ($self, $stackPos) { + $self->semValue = $self->createExitExpr($self->semStack[$stackPos-(2-1)], $stackPos-(2-1), $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 499 => static function ($self, $stackPos) { + $self->semValue = new Expr\ErrorSuppress($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 500 => null, + 501 => static function ($self, $stackPos) { + $self->semValue = new Expr\ShellExec($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 502 => static function ($self, $stackPos) { + $self->semValue = new Expr\Print_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 503 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_(null, null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 504 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(2-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 505 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 506 => static function ($self, $stackPos) { + $self->semValue = new Expr\YieldFrom($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 507 => static function ($self, $stackPos) { + $self->semValue = new Expr\Throw_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 508 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'returnType' => $self->semStack[$stackPos-(8-6)], 'expr' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 509 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 510 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'uses' => $self->semStack[$stackPos-(8-6)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 511 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 512 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 513 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'returnType' => $self->semStack[$stackPos-(10-8)], 'expr' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 514 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 515 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'uses' => $self->semStack[$stackPos-(10-8)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 516 => static function ($self, $stackPos) { + $self->semValue = array(new Stmt\Class_(null, ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])), $self->semStack[$stackPos-(8-3)]); + $self->checkClass($self->semValue[0], -1); + }, + 517 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 518 => static function ($self, $stackPos) { + list($class, $ctorArgs) = $self->semStack[$stackPos-(2-2)]; $self->semValue = new Expr\New_($class, $ctorArgs, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 519 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(2-2)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 520 => null, + 521 => null, + 522 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 523 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 524 => null, + 525 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 526 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 527 => static function ($self, $stackPos) { + $self->semValue = new Node\ClosureUse($self->semStack[$stackPos-(2-2)], $self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 528 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 529 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 530 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 531 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 532 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 533 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 534 => null, + 535 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 536 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 537 => static function ($self, $stackPos) { + $self->semValue = new Name\FullyQualified(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 538 => static function ($self, $stackPos) { + $self->semValue = new Name\Relative(substr($self->semStack[$stackPos-(1-1)], 10), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 539 => null, + 540 => null, + 541 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 542 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 543 => null, + 544 => null, + 545 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 546 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); foreach ($self->semValue as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; + }, + 547 => static function ($self, $stackPos) { + foreach ($self->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 548 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 549 => null, + 550 => static function ($self, $stackPos) { + $self->semValue = new Expr\ConstFetch($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 551 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Line($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 552 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\File($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 553 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Dir($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 554 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Class_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 555 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Trait_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 556 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Method($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 557 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Function_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 558 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Namespace_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 559 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Property($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 560 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 561 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 562 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)])), $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 563 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(3-2)], $attrs); + }, + 564 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs); + $self->createdArrays->offsetSet($self->semValue); + }, + 565 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->offsetSet($self->semValue); + }, + 566 => static function ($self, $stackPos) { + $self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes()); + }, + 567 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($self->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = new Scalar\InterpolatedString($self->semStack[$stackPos-(3-2)], $attrs); + }, + 568 => static function ($self, $stackPos) { + $self->semValue = $self->parseLNumber($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->allowsInvalidOctals()); + }, + 569 => static function ($self, $stackPos) { + $self->semValue = Scalar\Float_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 570 => null, + 571 => null, + 572 => null, + 573 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 574 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(2-1)], '', $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(2-2)], $self->tokenEndStack[$stackPos-(2-2)]), true); + }, + 575 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 576 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 577 => null, + 578 => null, + 579 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 580 => null, + 581 => null, + 582 => null, + 583 => null, + 584 => null, + 585 => null, + 586 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 587 => null, + 588 => null, + 589 => null, + 590 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 591 => null, + 592 => static function ($self, $stackPos) { + $self->semValue = new Expr\MethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 593 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafeMethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 594 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 595 => null, + 596 => null, + 597 => null, + 598 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 599 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 600 => null, + 601 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 602 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 603 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])), $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 604 => static function ($self, $stackPos) { + $var = $self->semStack[$stackPos-(1-1)]->name; $self->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])) : $var; + }, + 605 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 606 => null, + 607 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 608 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 609 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 610 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 611 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 612 => null, + 613 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 614 => null, + 615 => null, + 616 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 617 => null, + 618 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 619 => static function ($self, $stackPos) { + $self->semValue = new Expr\List_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); $self->semValue->setAttribute('kind', Expr\List_::KIND_LIST); + $self->postprocessList($self->semValue); + }, + 620 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $end = count($self->semValue)-1; if ($self->semValue[$end]->value instanceof Expr\Error) array_pop($self->semValue); + }, + 621 => null, + 622 => static function ($self, $stackPos) { + /* do nothing -- prevent default action of $$=$self->semStack[$1]. See $551. */ + }, + 623 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 624 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 625 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 626 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 627 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 628 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 629 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-1)], true, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 630 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 631 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), true); + }, + 632 => static function ($self, $stackPos) { + /* Create an Error node now to remember the position. We'll later either report an error, + or convert this into a null element, depending on whether this is a creation or destructuring context. */ + $attrs = $self->createEmptyElemAttributes($self->tokenPos); + $self->semValue = new Node\ArrayItem(new Expr\Error($attrs), null, false, $attrs); + }, + 633 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 634 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 635 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 636 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)]); + }, + 637 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); $attrs['rawValue'] = $self->semStack[$stackPos-(1-1)]; $self->semValue = new Node\InterpolatedStringPart($self->semStack[$stackPos-(1-1)], $attrs); + }, + 638 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 639 => null, + 640 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 641 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 642 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 643 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 644 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 645 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 646 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 647 => static function ($self, $stackPos) { + $self->semValue = new Scalar\String_($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 648 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 649 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString('-' . $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 650 => null, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php new file mode 100644 index 000000000..aaa663769 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php @@ -0,0 +1,1335 @@ + Map of PHP token IDs to drop */ + protected array $dropTokens; + /** @var int[] Map of external symbols (static::T_*) to internal symbols */ + protected array $tokenToSymbol; + /** @var string[] Map of symbols to their names */ + protected array $symbolToName; + /** @var array Names of the production rules (only necessary for debugging) */ + protected array $productions; + + /** @var int[] Map of states to a displacement into the $action table. The corresponding action for this + * state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the + * action is defaulted, i.e. $actionDefault[$state] should be used instead. */ + protected array $actionBase; + /** @var int[] Table of actions. Indexed according to $actionBase comment. */ + protected array $action; + /** @var int[] Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol + * then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */ + protected array $actionCheck; + /** @var int[] Map of states to their default action */ + protected array $actionDefault; + /** @var callable[] Semantic action callbacks */ + protected array $reduceCallbacks; + + /** @var int[] Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this + * non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */ + protected array $gotoBase; + /** @var int[] Table of states to goto after reduction. Indexed according to $gotoBase comment. */ + protected array $goto; + /** @var int[] Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal + * then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */ + protected array $gotoCheck; + /** @var int[] Map of non-terminals to the default state to goto after their reduction */ + protected array $gotoDefault; + + /** @var int[] Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for + * determining the state to goto after reduction. */ + protected array $ruleToNonTerminal; + /** @var int[] Map of rules to the length of their right-hand side, which is the number of elements that have to + * be popped from the stack(s) on reduction. */ + protected array $ruleToLength; + + /* + * The following members are part of the parser state: + */ + + /** @var mixed Temporary value containing the result of last semantic action (reduction) */ + protected $semValue; + /** @var mixed[] Semantic value stack (contains values of tokens and semantic action results) */ + protected array $semStack; + /** @var int[] Token start position stack */ + protected array $tokenStartStack; + /** @var int[] Token end position stack */ + protected array $tokenEndStack; + + /** @var ErrorHandler Error handler */ + protected ErrorHandler $errorHandler; + /** @var int Error state, used to avoid error floods */ + protected int $errorState; + + /** @var \SplObjectStorage|null Array nodes created during parsing, for postprocessing of empty elements. */ + protected ?\SplObjectStorage $createdArrays; + + /** @var \SplObjectStorage|null + * Arrow functions that are wrapped in parentheses, to enforce the pipe operator parentheses requirements. + */ + protected ?\SplObjectStorage $parenthesizedArrowFunctions; + + /** @var Token[] Tokens for the current parse */ + protected array $tokens; + /** @var int Current position in token array */ + protected int $tokenPos; + + /** + * Initialize $reduceCallbacks map. + */ + abstract protected function initReduceCallbacks(): void; + + /** + * Creates a parser instance. + * + * Options: + * * phpVersion: ?PhpVersion, + * + * @param Lexer $lexer A lexer + * @param PhpVersion $phpVersion PHP version to target, defaults to latest supported. This + * option is best-effort: Even if specified, parsing will generally assume the latest + * supported version and only adjust behavior in minor ways, for example by omitting + * errors in older versions and interpreting type hints as a name or identifier depending + * on version. + */ + public function __construct(Lexer $lexer, ?PhpVersion $phpVersion = null) { + $this->lexer = $lexer; + $this->phpVersion = $phpVersion ?? PhpVersion::getNewestSupported(); + + $this->initReduceCallbacks(); + $this->phpTokenToSymbol = $this->createTokenMap(); + $this->dropTokens = array_fill_keys( + [\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], true + ); + } + + /** + * Parses PHP code into a node tree. + * + * If a non-throwing error handler is used, the parser will continue parsing after an error + * occurred and attempt to build a partial AST. + * + * @param string $code The source code to parse + * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults + * to ErrorHandler\Throwing. + * + * @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and + * the parser was unable to recover from an error). + */ + public function parse(string $code, ?ErrorHandler $errorHandler = null): ?array { + $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing(); + $this->createdArrays = new \SplObjectStorage(); + $this->parenthesizedArrowFunctions = new \SplObjectStorage(); + + $this->tokens = $this->lexer->tokenize($code, $this->errorHandler); + $result = $this->doParse(); + + // Report errors for any empty elements used inside arrays. This is delayed until after the main parse, + // because we don't know a priori whether a given array expression will be used in a destructuring context + // or not. + foreach ($this->createdArrays as $node) { + foreach ($node->items as $item) { + if ($item->value instanceof Expr\Error) { + $this->errorHandler->handleError( + new Error('Cannot use empty array elements in arrays', $item->getAttributes())); + } + } + } + + // Clear out some of the interior state, so we don't hold onto unnecessary + // memory between uses of the parser + $this->tokenStartStack = []; + $this->tokenEndStack = []; + $this->semStack = []; + $this->semValue = null; + $this->createdArrays = null; + $this->parenthesizedArrowFunctions = null; + + if ($result !== null) { + $traverser = new NodeTraverser(new CommentAnnotatingVisitor($this->tokens)); + $traverser->traverse($result); + } + + return $result; + } + + public function getTokens(): array { + return $this->tokens; + } + + /** @return Stmt[]|null */ + protected function doParse(): ?array { + // We start off with no lookahead-token + $symbol = self::SYMBOL_NONE; + $tokenValue = null; + $this->tokenPos = -1; + + // Keep stack of start and end attributes + $this->tokenStartStack = []; + $this->tokenEndStack = [0]; + + // Start off in the initial state and keep a stack of previous states + $state = 0; + $stateStack = [$state]; + + // Semantic value stack (contains values of tokens and semantic action results) + $this->semStack = []; + + // Current position in the stack(s) + $stackPos = 0; + + $this->errorState = 0; + + for (;;) { + //$this->traceNewState($state, $symbol); + + if ($this->actionBase[$state] === 0) { + $rule = $this->actionDefault[$state]; + } else { + if ($symbol === self::SYMBOL_NONE) { + do { + $token = $this->tokens[++$this->tokenPos]; + $tokenId = $token->id; + } while (isset($this->dropTokens[$tokenId])); + + // Map the lexer token id to the internally used symbols. + $tokenValue = $token->text; + if (!isset($this->phpTokenToSymbol[$tokenId])) { + throw new \RangeException(sprintf( + 'The lexer returned an invalid token (id=%d, value=%s)', + $tokenId, $tokenValue + )); + } + $symbol = $this->phpTokenToSymbol[$tokenId]; + + //$this->traceRead($symbol); + } + + $idx = $this->actionBase[$state] + $symbol; + if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol) + || ($state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol)) + && ($action = $this->action[$idx]) !== $this->defaultAction) { + /* + * >= numNonLeafStates: shift and reduce + * > 0: shift + * = 0: accept + * < 0: reduce + * = -YYUNEXPECTED: error + */ + if ($action > 0) { + /* shift */ + //$this->traceShift($symbol); + + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + $this->semStack[$stackPos] = $tokenValue; + $this->tokenStartStack[$stackPos] = $this->tokenPos; + $this->tokenEndStack[$stackPos] = $this->tokenPos; + $symbol = self::SYMBOL_NONE; + + if ($this->errorState) { + --$this->errorState; + } + + if ($action < $this->numNonLeafStates) { + continue; + } + + /* $yyn >= numNonLeafStates means shift-and-reduce */ + $rule = $action - $this->numNonLeafStates; + } else { + $rule = -$action; + } + } else { + $rule = $this->actionDefault[$state]; + } + } + + for (;;) { + if ($rule === 0) { + /* accept */ + //$this->traceAccept(); + return $this->semValue; + } + if ($rule !== $this->unexpectedTokenRule) { + /* reduce */ + //$this->traceReduce($rule); + + $ruleLength = $this->ruleToLength[$rule]; + try { + $callback = $this->reduceCallbacks[$rule]; + if ($callback !== null) { + $callback($this, $stackPos); + } elseif ($ruleLength > 0) { + $this->semValue = $this->semStack[$stackPos - $ruleLength + 1]; + } + } catch (Error $e) { + if (-1 === $e->getStartLine()) { + $e->setStartLine($this->tokens[$this->tokenPos]->line); + } + + $this->emitError($e); + // Can't recover from this type of error + return null; + } + + /* Goto - shift nonterminal */ + $lastTokenEnd = $this->tokenEndStack[$stackPos]; + $stackPos -= $ruleLength; + $nonTerminal = $this->ruleToNonTerminal[$rule]; + $idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos]; + if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) { + $state = $this->goto[$idx]; + } else { + $state = $this->gotoDefault[$nonTerminal]; + } + + ++$stackPos; + $stateStack[$stackPos] = $state; + $this->semStack[$stackPos] = $this->semValue; + $this->tokenEndStack[$stackPos] = $lastTokenEnd; + if ($ruleLength === 0) { + // Empty productions use the start attributes of the lookahead token. + $this->tokenStartStack[$stackPos] = $this->tokenPos; + } + } else { + /* error */ + switch ($this->errorState) { + case 0: + $msg = $this->getErrorMessage($symbol, $state); + $this->emitError(new Error($msg, $this->getAttributesForToken($this->tokenPos))); + // Break missing intentionally + // no break + case 1: + case 2: + $this->errorState = 3; + + // Pop until error-expecting state uncovered + while (!( + (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) + || ($state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $this->errorSymbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) + ) || ($action = $this->action[$idx]) === $this->defaultAction) { // Not totally sure about this + if ($stackPos <= 0) { + // Could not recover from error + return null; + } + $state = $stateStack[--$stackPos]; + //$this->tracePop($state); + } + + //$this->traceShift($this->errorSymbol); + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + + // We treat the error symbol as being empty, so we reset the end attributes + // to the end attributes of the last non-error symbol + $this->tokenStartStack[$stackPos] = $this->tokenPos; + $this->tokenEndStack[$stackPos] = $this->tokenEndStack[$stackPos - 1]; + break; + + case 3: + if ($symbol === 0) { + // Reached EOF without recovering from error + return null; + } + + //$this->traceDiscard($symbol); + $symbol = self::SYMBOL_NONE; + break 2; + } + } + + if ($state < $this->numNonLeafStates) { + break; + } + + /* >= numNonLeafStates means shift-and-reduce */ + $rule = $state - $this->numNonLeafStates; + } + } + } + + protected function emitError(Error $error): void { + $this->errorHandler->handleError($error); + } + + /** + * Format error message including expected tokens. + * + * @param int $symbol Unexpected symbol + * @param int $state State at time of error + * + * @return string Formatted error message + */ + protected function getErrorMessage(int $symbol, int $state): string { + $expectedString = ''; + if ($expected = $this->getExpectedTokens($state)) { + $expectedString = ', expecting ' . implode(' or ', $expected); + } + + return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString; + } + + /** + * Get limited number of expected tokens in given state. + * + * @param int $state State + * + * @return string[] Expected tokens. If too many, an empty array is returned. + */ + protected function getExpectedTokens(int $state): array { + $expected = []; + + $base = $this->actionBase[$state]; + foreach ($this->symbolToName as $symbol => $name) { + $idx = $base + $symbol; + if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol + || $state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol + ) { + if ($this->action[$idx] !== $this->unexpectedTokenRule + && $this->action[$idx] !== $this->defaultAction + && $symbol !== $this->errorSymbol + ) { + if (count($expected) === 4) { + /* Too many expected tokens */ + return []; + } + + $expected[] = $name; + } + } + } + + return $expected; + } + + /** + * Get attributes for a node with the given start and end token positions. + * + * @param int $tokenStartPos Token position the node starts at + * @param int $tokenEndPos Token position the node ends at + * @return array Attributes + */ + protected function getAttributes(int $tokenStartPos, int $tokenEndPos): array { + $startToken = $this->tokens[$tokenStartPos]; + $afterEndToken = $this->tokens[$tokenEndPos + 1]; + return [ + 'startLine' => $startToken->line, + 'startTokenPos' => $tokenStartPos, + 'startFilePos' => $startToken->pos, + 'endLine' => $afterEndToken->line, + 'endTokenPos' => $tokenEndPos, + 'endFilePos' => $afterEndToken->pos - 1, + ]; + } + + /** + * Get attributes for a single token at the given token position. + * + * @return array Attributes + */ + protected function getAttributesForToken(int $tokenPos): array { + if ($tokenPos < \count($this->tokens) - 1) { + return $this->getAttributes($tokenPos, $tokenPos); + } + + // Get attributes for the sentinel token. + $token = $this->tokens[$tokenPos]; + return [ + 'startLine' => $token->line, + 'startTokenPos' => $tokenPos, + 'startFilePos' => $token->pos, + 'endLine' => $token->line, + 'endTokenPos' => $tokenPos, + 'endFilePos' => $token->pos, + ]; + } + + /* + * Tracing functions used for debugging the parser. + */ + + /* + protected function traceNewState($state, $symbol): void { + echo '% State ' . $state + . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n"; + } + + protected function traceRead($symbol): void { + echo '% Reading ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceShift($symbol): void { + echo '% Shift ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceAccept(): void { + echo "% Accepted.\n"; + } + + protected function traceReduce($n): void { + echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n"; + } + + protected function tracePop($state): void { + echo '% Recovering, uncovered state ' . $state . "\n"; + } + + protected function traceDiscard($symbol): void { + echo '% Discard ' . $this->symbolToName[$symbol] . "\n"; + } + */ + + /* + * Helper functions invoked by semantic actions + */ + + /** + * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions. + * + * @param Node\Stmt[] $stmts + * @return Node\Stmt[] + */ + protected function handleNamespaces(array $stmts): array { + $hasErrored = false; + $style = $this->getNamespacingStyle($stmts); + if (null === $style) { + // not namespaced, nothing to do + return $stmts; + } + if ('brace' === $style) { + // For braced namespaces we only have to check that there are no invalid statements between the namespaces + $afterFirstNamespace = false; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $afterFirstNamespace = true; + } elseif (!$stmt instanceof Node\Stmt\HaltCompiler + && !$stmt instanceof Node\Stmt\Nop + && $afterFirstNamespace && !$hasErrored) { + $this->emitError(new Error( + 'No code may exist outside of namespace {}', $stmt->getAttributes())); + $hasErrored = true; // Avoid one error for every statement + } + } + return $stmts; + } else { + // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts + $resultStmts = []; + $targetStmts = &$resultStmts; + $lastNs = null; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + if ($stmt->stmts === null) { + $stmt->stmts = []; + $targetStmts = &$stmt->stmts; + $resultStmts[] = $stmt; + } else { + // This handles the invalid case of mixed style namespaces + $resultStmts[] = $stmt; + $targetStmts = &$resultStmts; + } + $lastNs = $stmt; + } elseif ($stmt instanceof Node\Stmt\HaltCompiler) { + // __halt_compiler() is not moved into the namespace + $resultStmts[] = $stmt; + } else { + $targetStmts[] = $stmt; + } + } + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + return $resultStmts; + } + } + + private function fixupNamespaceAttributes(Node\Stmt\Namespace_ $stmt): void { + // We moved the statements into the namespace node, as such the end of the namespace node + // needs to be extended to the end of the statements. + if (empty($stmt->stmts)) { + return; + } + + // We only move the builtin end attributes here. This is the best we can do with the + // knowledge we have. + $endAttributes = ['endLine', 'endFilePos', 'endTokenPos']; + $lastStmt = $stmt->stmts[count($stmt->stmts) - 1]; + foreach ($endAttributes as $endAttribute) { + if ($lastStmt->hasAttribute($endAttribute)) { + $stmt->setAttribute($endAttribute, $lastStmt->getAttribute($endAttribute)); + } + } + } + + /** @return array */ + private function getNamespaceErrorAttributes(Namespace_ $node): array { + $attrs = $node->getAttributes(); + // Adjust end attributes to only cover the "namespace" keyword, not the whole namespace. + if (isset($attrs['startLine'])) { + $attrs['endLine'] = $attrs['startLine']; + } + if (isset($attrs['startTokenPos'])) { + $attrs['endTokenPos'] = $attrs['startTokenPos']; + } + if (isset($attrs['startFilePos'])) { + $attrs['endFilePos'] = $attrs['startFilePos'] + \strlen('namespace') - 1; + } + return $attrs; + } + + /** + * Determine namespacing style (semicolon or brace) + * + * @param Node[] $stmts Top-level statements. + * + * @return null|string One of "semicolon", "brace" or null (no namespaces) + */ + private function getNamespacingStyle(array $stmts): ?string { + $style = null; + $hasNotAllowedStmts = false; + foreach ($stmts as $i => $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace'; + if (null === $style) { + $style = $currentStyle; + if ($hasNotAllowedStmts) { + $this->emitError(new Error( + 'Namespace declaration statement has to be the very first statement in the script', + $this->getNamespaceErrorAttributes($stmt) + )); + } + } elseif ($style !== $currentStyle) { + $this->emitError(new Error( + 'Cannot mix bracketed namespace declarations with unbracketed namespace declarations', + $this->getNamespaceErrorAttributes($stmt) + )); + // Treat like semicolon style for namespace normalization + return 'semicolon'; + } + continue; + } + + /* declare(), __halt_compiler() and nops can be used before a namespace declaration */ + if ($stmt instanceof Node\Stmt\Declare_ + || $stmt instanceof Node\Stmt\HaltCompiler + || $stmt instanceof Node\Stmt\Nop) { + continue; + } + + /* There may be a hashbang line at the very start of the file */ + if ($i === 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) { + continue; + } + + /* Everything else if forbidden before namespace declarations */ + $hasNotAllowedStmts = true; + } + return $style; + } + + /** @return Name|Identifier */ + protected function handleBuiltinTypes(Name $name) { + if (!$name->isUnqualified()) { + return $name; + } + + $lowerName = $name->toLowerString(); + if (!$this->phpVersion->supportsBuiltinType($lowerName)) { + return $name; + } + + return new Node\Identifier($lowerName, $name->getAttributes()); + } + + /** + * Get combined start and end attributes at a stack location + * + * @param int $stackPos Stack location + * + * @return array Combined start and end attributes + */ + protected function getAttributesAt(int $stackPos): array { + return $this->getAttributes($this->tokenStartStack[$stackPos], $this->tokenEndStack[$stackPos]); + } + + protected function getFloatCastKind(string $cast): int { + $cast = strtolower($cast); + if (strpos($cast, 'float') !== false) { + return Double::KIND_FLOAT; + } + + if (strpos($cast, 'real') !== false) { + return Double::KIND_REAL; + } + + return Double::KIND_DOUBLE; + } + + protected function getIntCastKind(string $cast): int { + $cast = strtolower($cast); + if (strpos($cast, 'integer') !== false) { + return Expr\Cast\Int_::KIND_INTEGER; + } + + return Expr\Cast\Int_::KIND_INT; + } + + protected function getBoolCastKind(string $cast): int { + $cast = strtolower($cast); + if (strpos($cast, 'boolean') !== false) { + return Expr\Cast\Bool_::KIND_BOOLEAN; + } + + return Expr\Cast\Bool_::KIND_BOOL; + } + + protected function getStringCastKind(string $cast): int { + $cast = strtolower($cast); + if (strpos($cast, 'binary') !== false) { + return Expr\Cast\String_::KIND_BINARY; + } + + return Expr\Cast\String_::KIND_STRING; + } + + /** @param array $attributes */ + protected function parseLNumber(string $str, array $attributes, bool $allowInvalidOctal = false): Int_ { + try { + return Int_::fromString($str, $attributes, $allowInvalidOctal); + } catch (Error $error) { + $this->emitError($error); + // Use dummy value + return new Int_(0, $attributes); + } + } + + /** + * Parse a T_NUM_STRING token into either an integer or string node. + * + * @param string $str Number string + * @param array $attributes Attributes + * + * @return Int_|String_ Integer or string node. + */ + protected function parseNumString(string $str, array $attributes) { + if (!preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) { + return new String_($str, $attributes); + } + + $num = +$str; + if (!is_int($num)) { + return new String_($str, $attributes); + } + + return new Int_($num, $attributes); + } + + /** @param array $attributes */ + protected function stripIndentation( + string $string, int $indentLen, string $indentChar, + bool $newlineAtStart, bool $newlineAtEnd, array $attributes + ): string { + if ($indentLen === 0) { + return $string; + } + + $start = $newlineAtStart ? '(?:(?<=\n)|\A)' : '(?<=\n)'; + $end = $newlineAtEnd ? '(?:(?=[\r\n])|\z)' : '(?=[\r\n])'; + $regex = '/' . $start . '([ \t]*)(' . $end . ')?/'; + return preg_replace_callback( + $regex, + function ($matches) use ($indentLen, $indentChar, $attributes) { + $prefix = substr($matches[1], 0, $indentLen); + if (false !== strpos($prefix, $indentChar === " " ? "\t" : " ")) { + $this->emitError(new Error( + 'Invalid indentation - tabs and spaces cannot be mixed', $attributes + )); + } elseif (strlen($prefix) < $indentLen && !isset($matches[2])) { + $this->emitError(new Error( + 'Invalid body indentation level ' . + '(expecting an indentation level of at least ' . $indentLen . ')', + $attributes + )); + } + return substr($matches[0], strlen($prefix)); + }, + $string + ); + } + + /** + * @param string|(Expr|InterpolatedStringPart)[] $contents + * @param array $attributes + * @param array $endTokenAttributes + */ + protected function parseDocString( + string $startToken, $contents, string $endToken, + array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape + ): Expr { + $kind = strpos($startToken, "'") === false + ? String_::KIND_HEREDOC : String_::KIND_NOWDOC; + + $regex = '/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/'; + $result = preg_match($regex, $startToken, $matches); + assert($result === 1); + $label = $matches[1]; + + $result = preg_match('/\A[ \t]*/', $endToken, $matches); + assert($result === 1); + $indentation = $matches[0]; + + $attributes['kind'] = $kind; + $attributes['docLabel'] = $label; + $attributes['docIndentation'] = $indentation; + + $indentHasSpaces = false !== strpos($indentation, " "); + $indentHasTabs = false !== strpos($indentation, "\t"); + if ($indentHasSpaces && $indentHasTabs) { + $this->emitError(new Error( + 'Invalid indentation - tabs and spaces cannot be mixed', + $endTokenAttributes + )); + + // Proceed processing as if this doc string is not indented + $indentation = ''; + } + + $indentLen = \strlen($indentation); + $indentChar = $indentHasSpaces ? " " : "\t"; + + if (\is_string($contents)) { + if ($contents === '') { + $attributes['rawValue'] = $contents; + return new String_('', $attributes); + } + + $contents = $this->stripIndentation( + $contents, $indentLen, $indentChar, true, true, $attributes + ); + $contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents); + $attributes['rawValue'] = $contents; + + if ($kind === String_::KIND_HEREDOC) { + $contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape); + } + + return new String_($contents, $attributes); + } else { + assert(count($contents) > 0); + if (!$contents[0] instanceof Node\InterpolatedStringPart) { + // If there is no leading encapsed string part, pretend there is an empty one + $this->stripIndentation( + '', $indentLen, $indentChar, true, false, $contents[0]->getAttributes() + ); + } + + $newContents = []; + foreach ($contents as $i => $part) { + if ($part instanceof Node\InterpolatedStringPart) { + $isLast = $i === \count($contents) - 1; + $part->value = $this->stripIndentation( + $part->value, $indentLen, $indentChar, + $i === 0, $isLast, $part->getAttributes() + ); + if ($isLast) { + $part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value); + } + $part->setAttribute('rawValue', $part->value); + $part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape); + if ('' === $part->value) { + continue; + } + } + $newContents[] = $part; + } + return new InterpolatedString($newContents, $attributes); + } + } + + protected function createCommentFromToken(Token $token, int $tokenPos): Comment { + assert($token->id === \T_COMMENT || $token->id == \T_DOC_COMMENT); + return \T_DOC_COMMENT === $token->id + ? new Comment\Doc($token->text, $token->line, $token->pos, $tokenPos, + $token->getEndLine(), $token->getEndPos() - 1, $tokenPos) + : new Comment($token->text, $token->line, $token->pos, $tokenPos, + $token->getEndLine(), $token->getEndPos() - 1, $tokenPos); + } + + /** + * Get last comment before the given token position, if any + */ + protected function getCommentBeforeToken(int $tokenPos): ?Comment { + while (--$tokenPos >= 0) { + $token = $this->tokens[$tokenPos]; + if (!isset($this->dropTokens[$token->id])) { + break; + } + + if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) { + return $this->createCommentFromToken($token, $tokenPos); + } + } + return null; + } + + /** + * Create a zero-length nop to capture preceding comments, if any. + */ + protected function maybeCreateZeroLengthNop(int $tokenPos): ?Nop { + $comment = $this->getCommentBeforeToken($tokenPos); + if ($comment === null) { + return null; + } + + $commentEndLine = $comment->getEndLine(); + $commentEndFilePos = $comment->getEndFilePos(); + $commentEndTokenPos = $comment->getEndTokenPos(); + $attributes = [ + 'startLine' => $commentEndLine, + 'endLine' => $commentEndLine, + 'startFilePos' => $commentEndFilePos + 1, + 'endFilePos' => $commentEndFilePos, + 'startTokenPos' => $commentEndTokenPos + 1, + 'endTokenPos' => $commentEndTokenPos, + ]; + return new Nop($attributes); + } + + protected function maybeCreateNop(int $tokenStartPos, int $tokenEndPos): ?Nop { + if ($this->getCommentBeforeToken($tokenStartPos) === null) { + return null; + } + return new Nop($this->getAttributes($tokenStartPos, $tokenEndPos)); + } + + protected function handleHaltCompiler(): string { + // Prevent the lexer from returning any further tokens. + $nextToken = $this->tokens[$this->tokenPos + 1]; + $this->tokenPos = \count($this->tokens) - 2; + + // Return text after __halt_compiler. + return $nextToken->id === \T_INLINE_HTML ? $nextToken->text : ''; + } + + protected function inlineHtmlHasLeadingNewline(int $stackPos): bool { + $tokenPos = $this->tokenStartStack[$stackPos]; + $token = $this->tokens[$tokenPos]; + assert($token->id == \T_INLINE_HTML); + if ($tokenPos > 0) { + $prevToken = $this->tokens[$tokenPos - 1]; + assert($prevToken->id == \T_CLOSE_TAG); + return false !== strpos($prevToken->text, "\n") + || false !== strpos($prevToken->text, "\r"); + } + return true; + } + + /** + * @return array + */ + protected function createEmptyElemAttributes(int $tokenPos): array { + return $this->getAttributesForToken($tokenPos); + } + + protected function fixupArrayDestructuring(Array_ $node): Expr\List_ { + $this->createdArrays->offsetUnset($node); + return new Expr\List_(array_map(function (Node\ArrayItem $item) { + if ($item->value instanceof Expr\Error) { + // We used Error as a placeholder for empty elements, which are legal for destructuring. + return null; + } + if ($item->value instanceof Array_) { + return new Node\ArrayItem( + $this->fixupArrayDestructuring($item->value), + $item->key, $item->byRef, $item->getAttributes()); + } + return $item; + }, $node->items), ['kind' => Expr\List_::KIND_ARRAY] + $node->getAttributes()); + } + + protected function postprocessList(Expr\List_ $node): void { + foreach ($node->items as $i => $item) { + if ($item->value instanceof Expr\Error) { + // We used Error as a placeholder for empty elements, which are legal for destructuring. + $node->items[$i] = null; + } + } + } + + /** @param ElseIf_|Else_ $node */ + protected function fixupAlternativeElse($node): void { + // Make sure a trailing nop statement carrying comments is part of the node. + $numStmts = \count($node->stmts); + if ($numStmts !== 0 && $node->stmts[$numStmts - 1] instanceof Nop) { + $nopAttrs = $node->stmts[$numStmts - 1]->getAttributes(); + if (isset($nopAttrs['endLine'])) { + $node->setAttribute('endLine', $nopAttrs['endLine']); + } + if (isset($nopAttrs['endFilePos'])) { + $node->setAttribute('endFilePos', $nopAttrs['endFilePos']); + } + if (isset($nopAttrs['endTokenPos'])) { + $node->setAttribute('endTokenPos', $nopAttrs['endTokenPos']); + } + } + } + + protected function checkClassModifier(int $a, int $b, int $modifierPos): void { + try { + Modifiers::verifyClassModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + + protected function checkModifier(int $a, int $b, int $modifierPos): void { + // Jumping through some hoops here because verifyModifier() is also used elsewhere + try { + Modifiers::verifyModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + + protected function checkParam(Param $node): void { + if ($node->variadic && null !== $node->default) { + $this->emitError(new Error( + 'Variadic parameter cannot have a default value', + $node->default->getAttributes() + )); + } + } + + protected function checkTryCatch(TryCatch $node): void { + if (empty($node->catches) && null === $node->finally) { + $this->emitError(new Error( + 'Cannot use try without catch or finally', $node->getAttributes() + )); + } + } + + protected function checkNamespace(Namespace_ $node): void { + if (null !== $node->stmts) { + foreach ($node->stmts as $stmt) { + if ($stmt instanceof Namespace_) { + $this->emitError(new Error( + 'Namespace declarations cannot be nested', $stmt->getAttributes() + )); + } + } + } + } + + private function checkClassName(?Identifier $name, int $namePos): void { + if (null !== $name && $name->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $name), + $this->getAttributesAt($namePos) + )); + } + } + + /** @param Name[] $interfaces */ + private function checkImplementedInterfaces(array $interfaces): void { + foreach ($interfaces as $interface) { + if ($interface->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface), + $interface->getAttributes() + )); + } + } + } + + protected function checkClass(Class_ $node, int $namePos): void { + $this->checkClassName($node->name, $namePos); + + if ($node->extends && $node->extends->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends), + $node->extends->getAttributes() + )); + } + + $this->checkImplementedInterfaces($node->implements); + } + + protected function checkInterface(Interface_ $node, int $namePos): void { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->extends); + } + + protected function checkEnum(Enum_ $node, int $namePos): void { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->implements); + } + + protected function checkClassMethod(ClassMethod $node, int $modifierPos): void { + if ($node->flags & Modifiers::STATIC) { + switch ($node->name->toLowerString()) { + case '__construct': + $this->emitError(new Error( + sprintf('Constructor %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + case '__destruct': + $this->emitError(new Error( + sprintf('Destructor %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + case '__clone': + $this->emitError(new Error( + sprintf('Clone method %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + } + } + + if ($node->flags & Modifiers::READONLY) { + $this->emitError(new Error( + sprintf('Method %s() cannot be readonly', $node->name), + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkClassConst(ClassConst $node, int $modifierPos): void { + foreach ([Modifiers::STATIC, Modifiers::ABSTRACT, Modifiers::READONLY] as $modifier) { + if ($node->flags & $modifier) { + $this->emitError(new Error( + "Cannot use '" . Modifiers::toString($modifier) . "' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + } + } + + protected function checkUseUse(UseItem $node, int $namePos): void { + if ($node->alias && $node->alias->isSpecialClassName()) { + $this->emitError(new Error( + sprintf( + 'Cannot use %s as %s because \'%2$s\' is a special class name', + $node->name, $node->alias + ), + $this->getAttributesAt($namePos) + )); + } + } + + protected function checkPropertyHooksForMultiProperty(Property $property, int $hookPos): void { + if (count($property->props) > 1) { + $this->emitError(new Error( + 'Cannot use hooks when declaring multiple properties', $this->getAttributesAt($hookPos))); + } + } + + /** @param PropertyHook[] $hooks */ + protected function checkEmptyPropertyHookList(array $hooks, int $hookPos): void { + if (empty($hooks)) { + $this->emitError(new Error( + 'Property hook list cannot be empty', $this->getAttributesAt($hookPos))); + } + } + + protected function checkPropertyHook(PropertyHook $hook, ?int $paramListPos): void { + $name = $hook->name->toLowerString(); + if ($name !== 'get' && $name !== 'set') { + $this->emitError(new Error( + 'Unknown hook "' . $hook->name . '", expected "get" or "set"', + $hook->name->getAttributes())); + } + if ($name === 'get' && $paramListPos !== null) { + $this->emitError(new Error( + 'get hook must not have a parameter list', $this->getAttributesAt($paramListPos))); + } + } + + protected function checkPropertyHookModifiers(int $a, int $b, int $modifierPos): void { + try { + Modifiers::verifyModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + + if ($b != Modifiers::FINAL) { + $this->emitError(new Error( + 'Cannot use the ' . Modifiers::toString($b) . ' modifier on a property hook', + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkConstantAttributes(Const_ $node): void { + if ($node->attrGroups !== [] && count($node->consts) > 1) { + $this->emitError(new Error( + 'Cannot use attributes on multiple constants at once', $node->getAttributes())); + } + } + + protected function checkPipeOperatorParentheses(Expr $node): void { + if ($node instanceof Expr\ArrowFunction && !$this->parenthesizedArrowFunctions->offsetExists($node)) { + $this->emitError(new Error( + 'Arrow functions on the right hand side of |> must be parenthesized', $node->getAttributes())); + } + } + + /** + * @param Property|Param $node + */ + protected function addPropertyNameToHooks(Node $node): void { + if ($node instanceof Property) { + $name = $node->props[0]->name->toString(); + } else { + $name = $node->var->name; + } + foreach ($node->hooks as $hook) { + $hook->setAttribute('propertyName', $name); + } + } + + /** @param array $args */ + private function isSimpleExit(array $args): bool { + if (\count($args) === 0) { + return true; + } + if (\count($args) === 1) { + $arg = $args[0]; + return $arg instanceof Arg && $arg->name === null && + $arg->byRef === false && $arg->unpack === false; + } + return false; + } + + /** + * @param array $args + * @param array $attrs + */ + protected function createExitExpr(string $name, int $namePos, array $args, array $attrs): Expr { + if ($this->isSimpleExit($args)) { + // Create Exit node for backwards compatibility. + $attrs['kind'] = strtolower($name) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + return new Expr\Exit_(\count($args) === 1 ? $args[0]->value : null, $attrs); + } + return new Expr\FuncCall(new Name($name, $this->getAttributesAt($namePos)), $args, $attrs); + } + + /** + * Creates the token map. + * + * The token map maps the PHP internal token identifiers + * to the identifiers used by the Parser. Additionally it + * maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'. + * + * @return array The token map + */ + protected function createTokenMap(): array { + $tokenMap = []; + + // Single-char tokens use an identity mapping. + for ($i = 0; $i < 256; ++$i) { + $tokenMap[$i] = $i; + } + + foreach ($this->symbolToName as $name) { + if ($name[0] === 'T') { + $tokenMap[\constant($name)] = constant(static::class . '::' . $name); + } + } + + // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO + $tokenMap[\T_OPEN_TAG_WITH_ECHO] = static::T_ECHO; + // T_CLOSE_TAG is equivalent to ';' + $tokenMap[\T_CLOSE_TAG] = ord(';'); + + // We have created a map from PHP token IDs to external symbol IDs. + // Now map them to the internal symbol ID. + $fullTokenMap = []; + foreach ($tokenMap as $phpToken => $extSymbol) { + $intSymbol = $this->tokenToSymbol[$extSymbol]; + if ($intSymbol === $this->invalidSymbol) { + continue; + } + $fullTokenMap[$phpToken] = $intSymbol; + } + + return $fullTokenMap; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php b/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php new file mode 100644 index 000000000..3a7586ea2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php @@ -0,0 +1,42 @@ +isHostVersion()) { + $lexer = new Lexer(); + } else { + $lexer = new Lexer\Emulative($version); + } + if ($version->id >= 80000) { + return new Php8($lexer, $version); + } + return new Php7($lexer, $version); + } + + /** + * Create a parser targeting the newest version supported by this library. Code for older + * versions will be accepted if there have been no relevant backwards-compatibility breaks in + * PHP. + */ + public function createForNewestSupportedVersion(): Parser { + return $this->createForVersion(PhpVersion::getNewestSupported()); + } + + /** + * Create a parser targeting the host PHP version, that is the PHP version we're currently + * running on. This parser will not use any token emulation. + */ + public function createForHostVersion(): Parser { + return $this->createForVersion(PhpVersion::getHostVersion()); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PhpVersion.php b/vendor/nikic/php-parser/lib/PhpParser/PhpVersion.php new file mode 100644 index 000000000..9517d7203 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PhpVersion.php @@ -0,0 +1,175 @@ + 50100, + 'callable' => 50400, + 'bool' => 70000, + 'int' => 70000, + 'float' => 70000, + 'string' => 70000, + 'iterable' => 70100, + 'void' => 70100, + 'object' => 70200, + 'null' => 80000, + 'false' => 80000, + 'mixed' => 80000, + 'never' => 80100, + 'true' => 80200, + ]; + + private function __construct(int $id) { + $this->id = $id; + } + + /** + * Create a PhpVersion object from major and minor version components. + */ + public static function fromComponents(int $major, int $minor): self { + return new self($major * 10000 + $minor * 100); + } + + /** + * Get the newest PHP version supported by this library. Support for this version may be partial, + * if it is still under development. + */ + public static function getNewestSupported(): self { + return self::fromComponents(8, 5); + } + + /** + * Get the host PHP version, that is the PHP version we're currently running on. + */ + public static function getHostVersion(): self { + return self::fromComponents(\PHP_MAJOR_VERSION, \PHP_MINOR_VERSION); + } + + /** + * Parse the version from a string like "8.1". + */ + public static function fromString(string $version): self { + if (!preg_match('/^(\d+)\.(\d+)/', $version, $matches)) { + throw new \LogicException("Invalid PHP version \"$version\""); + } + return self::fromComponents((int) $matches[1], (int) $matches[2]); + } + + /** + * Check whether two versions are the same. + */ + public function equals(PhpVersion $other): bool { + return $this->id === $other->id; + } + + /** + * Check whether this version is greater than or equal to the argument. + */ + public function newerOrEqual(PhpVersion $other): bool { + return $this->id >= $other->id; + } + + /** + * Check whether this version is older than the argument. + */ + public function older(PhpVersion $other): bool { + return $this->id < $other->id; + } + + /** + * Check whether this is the host PHP version. + */ + public function isHostVersion(): bool { + return $this->equals(self::getHostVersion()); + } + + /** + * Check whether this PHP version supports the given builtin type. Type name must be lowercase. + */ + public function supportsBuiltinType(string $type): bool { + $minVersion = self::BUILTIN_TYPE_VERSIONS[$type] ?? null; + return $minVersion !== null && $this->id >= $minVersion; + } + + /** + * Whether this version supports [] array literals. + */ + public function supportsShortArraySyntax(): bool { + return $this->id >= 50400; + } + + /** + * Whether this version supports [] for destructuring. + */ + public function supportsShortArrayDestructuring(): bool { + return $this->id >= 70100; + } + + /** + * Whether this version supports flexible heredoc/nowdoc. + */ + public function supportsFlexibleHeredoc(): bool { + return $this->id >= 70300; + } + + /** + * Whether this version supports trailing commas in parameter lists. + */ + public function supportsTrailingCommaInParamList(): bool { + return $this->id >= 80000; + } + + /** + * Whether this version allows "$var =& new Obj". + */ + public function allowsAssignNewByReference(): bool { + return $this->id < 70000; + } + + /** + * Whether this version allows invalid octals like "08". + */ + public function allowsInvalidOctals(): bool { + return $this->id < 70000; + } + + /** + * Whether this version allows DEL (\x7f) to occur in identifiers. + */ + public function allowsDelInIdentifiers(): bool { + return $this->id < 70100; + } + + /** + * Whether this version supports yield in expression context without parentheses. + */ + public function supportsYieldWithoutParentheses(): bool { + return $this->id >= 70000; + } + + /** + * Whether this version supports unicode escape sequences in strings. + */ + public function supportsUnicodeEscapes(): bool { + return $this->id >= 70000; + } + + /* + * Whether this version supports attributes. + */ + public function supportsAttributes(): bool { + return $this->id >= 80000; + } + + public function supportsNewDereferenceWithoutParentheses(): bool { + return $this->id >= 80400; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter.php b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter.php new file mode 100644 index 000000000..892c686e5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter.php @@ -0,0 +1,51 @@ +pAttrGroups($node->attrGroups, $this->phpVersion->supportsAttributes()) + . $this->pModifiers($node->flags) + . ($node->type ? $this->p($node->type) . ' ' : '') + . ($node->byRef ? '&' : '') + . ($node->variadic ? '...' : '') + . $this->p($node->var) + . ($node->default ? ' = ' . $this->p($node->default) : '') + . ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : ''); + } + + protected function pArg(Node\Arg $node): string { + return ($node->name ? $node->name->toString() . ': ' : '') + . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node): string { + return '...'; + } + + protected function pConst(Node\Const_ $node): string { + return $node->name . ' = ' . $this->p($node->value); + } + + protected function pNullableType(Node\NullableType $node): string { + return '?' . $this->p($node->type); + } + + protected function pUnionType(Node\UnionType $node): string { + $types = []; + foreach ($node->types as $typeNode) { + if ($typeNode instanceof Node\IntersectionType) { + $types[] = '('. $this->p($typeNode) . ')'; + continue; + } + $types[] = $this->p($typeNode); + } + return implode('|', $types); + } + + protected function pIntersectionType(Node\IntersectionType $node): string { + return $this->pImplode($node->types, '&'); + } + + protected function pIdentifier(Node\Identifier $node): string { + return $node->name; + } + + protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node): string { + return '$' . $node->name; + } + + protected function pAttribute(Node\Attribute $node): string { + return $this->p($node->name) + . ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : ''); + } + + protected function pAttributeGroup(Node\AttributeGroup $node): string { + return '#[' . $this->pCommaSeparated($node->attrs) . ']'; + } + + // Names + + protected function pName(Name $node): string { + return $node->name; + } + + protected function pName_FullyQualified(Name\FullyQualified $node): string { + return '\\' . $node->name; + } + + protected function pName_Relative(Name\Relative $node): string { + return 'namespace\\' . $node->name; + } + + // Magic Constants + + protected function pScalar_MagicConst_Class(MagicConst\Class_ $node): string { + return '__CLASS__'; + } + + protected function pScalar_MagicConst_Dir(MagicConst\Dir $node): string { + return '__DIR__'; + } + + protected function pScalar_MagicConst_File(MagicConst\File $node): string { + return '__FILE__'; + } + + protected function pScalar_MagicConst_Function(MagicConst\Function_ $node): string { + return '__FUNCTION__'; + } + + protected function pScalar_MagicConst_Line(MagicConst\Line $node): string { + return '__LINE__'; + } + + protected function pScalar_MagicConst_Method(MagicConst\Method $node): string { + return '__METHOD__'; + } + + protected function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node): string { + return '__NAMESPACE__'; + } + + protected function pScalar_MagicConst_Trait(MagicConst\Trait_ $node): string { + return '__TRAIT__'; + } + + protected function pScalar_MagicConst_Property(MagicConst\Property $node): string { + return '__PROPERTY__'; + } + + // Scalars + + private function indentString(string $str): string { + return str_replace("\n", $this->nl, $str); + } + + protected function pScalar_String(Scalar\String_ $node): string { + $kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED); + switch ($kind) { + case Scalar\String_::KIND_NOWDOC: + $label = $node->getAttribute('docLabel'); + if ($label && !$this->containsEndLabel($node->value, $label)) { + $shouldIdent = $this->phpVersion->supportsFlexibleHeredoc(); + $nl = $shouldIdent ? $this->nl : $this->newline; + if ($node->value === '') { + return "<<<'$label'$nl$label{$this->docStringEndToken}"; + } + + // Make sure trailing \r is not combined with following \n into CRLF. + if ($node->value[strlen($node->value) - 1] !== "\r") { + $value = $shouldIdent ? $this->indentString($node->value) : $node->value; + return "<<<'$label'$nl$value$nl$label{$this->docStringEndToken}"; + } + } + /* break missing intentionally */ + // no break + case Scalar\String_::KIND_SINGLE_QUOTED: + return $this->pSingleQuotedString($node->value); + case Scalar\String_::KIND_HEREDOC: + $label = $node->getAttribute('docLabel'); + $escaped = $this->escapeString($node->value, null); + if ($label && !$this->containsEndLabel($escaped, $label)) { + $nl = $this->phpVersion->supportsFlexibleHeredoc() ? $this->nl : $this->newline; + if ($escaped === '') { + return "<<<$label$nl$label{$this->docStringEndToken}"; + } + + return "<<<$label$nl$escaped$nl$label{$this->docStringEndToken}"; + } + /* break missing intentionally */ + // no break + case Scalar\String_::KIND_DOUBLE_QUOTED: + return '"' . $this->escapeString($node->value, '"') . '"'; + } + throw new \Exception('Invalid string kind'); + } + + protected function pScalar_InterpolatedString(Scalar\InterpolatedString $node): string { + if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) { + $label = $node->getAttribute('docLabel'); + if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) { + $nl = $this->phpVersion->supportsFlexibleHeredoc() ? $this->nl : $this->newline; + if (count($node->parts) === 1 + && $node->parts[0] instanceof Node\InterpolatedStringPart + && $node->parts[0]->value === '' + ) { + return "<<<$label$nl$label{$this->docStringEndToken}"; + } + + return "<<<$label$nl" . $this->pEncapsList($node->parts, null) + . "$nl$label{$this->docStringEndToken}"; + } + } + return '"' . $this->pEncapsList($node->parts, '"') . '"'; + } + + protected function pScalar_Int(Scalar\Int_ $node): string { + if ($node->getAttribute('shouldPrintRawValue') === true) { + return $node->getAttribute('rawValue'); + } + + if ($node->value === -\PHP_INT_MAX - 1) { + // PHP_INT_MIN cannot be represented as a literal, + // because the sign is not part of the literal + return '(-' . \PHP_INT_MAX . '-1)'; + } + + $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); + + if (Scalar\Int_::KIND_DEC === $kind) { + return (string) $node->value; + } + + if ($node->value < 0) { + $sign = '-'; + $str = (string) -$node->value; + } else { + $sign = ''; + $str = (string) $node->value; + } + switch ($kind) { + case Scalar\Int_::KIND_BIN: + return $sign . '0b' . base_convert($str, 10, 2); + case Scalar\Int_::KIND_OCT: + return $sign . '0' . base_convert($str, 10, 8); + case Scalar\Int_::KIND_HEX: + return $sign . '0x' . base_convert($str, 10, 16); + } + throw new \Exception('Invalid number kind'); + } + + protected function pScalar_Float(Scalar\Float_ $node): string { + if (!is_finite($node->value)) { + if ($node->value === \INF) { + return '1.0E+1000'; + } + if ($node->value === -\INF) { + return '-1.0E+1000'; + } else { + return '\NAN'; + } + } + + // Try to find a short full-precision representation + $stringValue = sprintf('%.16G', $node->value); + if ($node->value !== (float) $stringValue) { + $stringValue = sprintf('%.17G', $node->value); + } + + // %G is locale dependent and there exists no locale-independent alternative. We don't want + // mess with switching locales here, so let's assume that a comma is the only non-standard + // decimal separator we may encounter... + $stringValue = str_replace(',', '.', $stringValue); + + // ensure that number is really printed as float + return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue; + } + + // Assignments + + protected function pExpr_Assign(Expr\Assign $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Assign::class, $this->p($node->var) . ' = ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignRef(Expr\AssignRef $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\AssignRef::class, $this->p($node->var) . ' =& ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Plus(AssignOp\Plus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Plus::class, $this->p($node->var) . ' += ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Minus(AssignOp\Minus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Minus::class, $this->p($node->var) . ' -= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Mul(AssignOp\Mul $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Mul::class, $this->p($node->var) . ' *= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Div(AssignOp\Div $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Div::class, $this->p($node->var) . ' /= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Concat(AssignOp\Concat $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Concat::class, $this->p($node->var) . ' .= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Mod(AssignOp\Mod $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Mod::class, $this->p($node->var) . ' %= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\BitwiseAnd::class, $this->p($node->var) . ' &= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\BitwiseOr::class, $this->p($node->var) . ' |= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\BitwiseXor::class, $this->p($node->var) . ' ^= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\ShiftLeft::class, $this->p($node->var) . ' <<= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\ShiftRight::class, $this->p($node->var) . ' >>= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Pow(AssignOp\Pow $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Pow::class, $this->p($node->var) . ' **= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Coalesce(AssignOp\Coalesce $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Coalesce::class, $this->p($node->var) . ' ??= ', $node->expr, $precedence, $lhsPrecedence); + } + + // Binary expressions + + protected function pExpr_BinaryOp_Plus(BinaryOp\Plus $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Plus::class, $node->left, ' + ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Minus(BinaryOp\Minus $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Minus::class, $node->left, ' - ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Mul(BinaryOp\Mul $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Mul::class, $node->left, ' * ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Div(BinaryOp\Div $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Div::class, $node->left, ' / ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Concat::class, $node->left, ' . ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Mod(BinaryOp\Mod $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Mod::class, $node->left, ' % ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BooleanAnd::class, $node->left, ' && ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BooleanOr::class, $node->left, ' || ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BitwiseAnd::class, $node->left, ' & ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BitwiseOr::class, $node->left, ' | ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BitwiseXor::class, $node->left, ' ^ ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\ShiftLeft::class, $node->left, ' << ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\ShiftRight::class, $node->left, ' >> ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Pow(BinaryOp\Pow $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Pow::class, $node->left, ' ** ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\LogicalAnd::class, $node->left, ' and ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\LogicalOr::class, $node->left, ' or ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\LogicalXor::class, $node->left, ' xor ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Equal(BinaryOp\Equal $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Equal::class, $node->left, ' == ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\NotEqual::class, $node->left, ' != ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Identical(BinaryOp\Identical $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Identical::class, $node->left, ' === ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\NotIdentical::class, $node->left, ' !== ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Spaceship::class, $node->left, ' <=> ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Greater(BinaryOp\Greater $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Greater::class, $node->left, ' > ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\GreaterOrEqual::class, $node->left, ' >= ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Smaller::class, $node->left, ' < ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\SmallerOrEqual::class, $node->left, ' <= ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Pipe(BinaryOp\Pipe $node, int $precedence, int $lhsPrecedence): string { + if ($node->right instanceof Expr\ArrowFunction) { + // Force parentheses around arrow functions. + $lhsPrecedence = $this->precedenceMap[Expr\ArrowFunction::class][0]; + } + return $this->pInfixOp(BinaryOp\Pipe::class, $node->left, ' |> ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_Instanceof(Expr\Instanceof_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPostfixOp( + Expr\Instanceof_::class, $node->expr, + ' instanceof ' . $this->pNewOperand($node->class), + $precedence, $lhsPrecedence); + } + + // Unary expressions + + protected function pExpr_BooleanNot(Expr\BooleanNot $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\BooleanNot::class, '!', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_BitwiseNot(Expr\BitwiseNot $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\BitwiseNot::class, '~', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_UnaryMinus(Expr\UnaryMinus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\UnaryMinus::class, '-', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_UnaryPlus(Expr\UnaryPlus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\UnaryPlus::class, '+', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_PreInc(Expr\PreInc $node): string { + return '++' . $this->p($node->var); + } + + protected function pExpr_PreDec(Expr\PreDec $node): string { + return '--' . $this->p($node->var); + } + + protected function pExpr_PostInc(Expr\PostInc $node): string { + return $this->p($node->var) . '++'; + } + + protected function pExpr_PostDec(Expr\PostDec $node): string { + return $this->p($node->var) . '--'; + } + + protected function pExpr_ErrorSuppress(Expr\ErrorSuppress $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\ErrorSuppress::class, '@', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_YieldFrom(Expr\YieldFrom $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\YieldFrom::class, 'yield from ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Print(Expr\Print_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Print_::class, 'print ', $node->expr, $precedence, $lhsPrecedence); + } + + // Casts + + protected function pExpr_Cast_Int(Cast\Int_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Int_::class, '(int) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Double(Cast\Double $node, int $precedence, int $lhsPrecedence): string { + $kind = $node->getAttribute('kind', Cast\Double::KIND_DOUBLE); + if ($kind === Cast\Double::KIND_DOUBLE) { + $cast = '(double)'; + } elseif ($kind === Cast\Double::KIND_FLOAT) { + $cast = '(float)'; + } else { + assert($kind === Cast\Double::KIND_REAL); + $cast = '(real)'; + } + return $this->pPrefixOp(Cast\Double::class, $cast . ' ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_String(Cast\String_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\String_::class, '(string) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Array(Cast\Array_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Array_::class, '(array) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Object(Cast\Object_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Object_::class, '(object) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Bool(Cast\Bool_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Bool_::class, '(bool) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Unset(Cast\Unset_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Void(Cast\Void_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Void_::class, '(void) ', $node->expr, $precedence, $lhsPrecedence); + } + + // Function calls and similar constructs + + protected function pExpr_FuncCall(Expr\FuncCall $node): string { + return $this->pCallLhs($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_MethodCall(Expr\MethodCall $node): string { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_NullsafeMethodCall(Expr\NullsafeMethodCall $node): string { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_StaticCall(Expr\StaticCall $node): string { + return $this->pStaticDereferenceLhs($node->class) . '::' + . ($node->name instanceof Expr + ? ($node->name instanceof Expr\Variable + ? $this->p($node->name) + : '{' . $this->p($node->name) . '}') + : $node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_Empty(Expr\Empty_ $node): string { + return 'empty(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Isset(Expr\Isset_ $node): string { + return 'isset(' . $this->pCommaSeparated($node->vars) . ')'; + } + + protected function pExpr_Eval(Expr\Eval_ $node): string { + return 'eval(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Include(Expr\Include_ $node, int $precedence, int $lhsPrecedence): string { + static $map = [ + Expr\Include_::TYPE_INCLUDE => 'include', + Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once', + Expr\Include_::TYPE_REQUIRE => 'require', + Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once', + ]; + + return $this->pPrefixOp(Expr\Include_::class, $map[$node->type] . ' ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_List(Expr\List_ $node): string { + $syntax = $node->getAttribute('kind', + $this->phpVersion->supportsShortArrayDestructuring() ? Expr\List_::KIND_ARRAY : Expr\List_::KIND_LIST); + if ($syntax === Expr\List_::KIND_ARRAY) { + return '[' . $this->pMaybeMultiline($node->items, true) . ']'; + } else { + return 'list(' . $this->pMaybeMultiline($node->items, true) . ')'; + } + } + + // Other + + protected function pExpr_Error(Expr\Error $node): string { + throw new \LogicException('Cannot pretty-print AST with Error nodes'); + } + + protected function pExpr_Variable(Expr\Variable $node): string { + if ($node->name instanceof Expr) { + return '${' . $this->p($node->name) . '}'; + } else { + return '$' . $node->name; + } + } + + protected function pExpr_Array(Expr\Array_ $node): string { + $syntax = $node->getAttribute('kind', + $this->shortArraySyntax ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG); + if ($syntax === Expr\Array_::KIND_SHORT) { + return '[' . $this->pMaybeMultiline($node->items, true) . ']'; + } else { + return 'array(' . $this->pMaybeMultiline($node->items, true) . ')'; + } + } + + protected function pKey(?Node $node): string { + if ($node === null) { + return ''; + } + + // => is not really an operator and does not typically participate in precedence resolution. + // However, there is an exception if yield expressions with keys are involved: + // [yield $a => $b] is interpreted as [(yield $a => $b)], so we need to ensure that + // [(yield $a) => $b] is printed with parentheses. We approximate this by lowering the LHS + // precedence to that of yield (which will also print unnecessary parentheses for rare low + // precedence unary operators like include). + $yieldPrecedence = $this->precedenceMap[Expr\Yield_::class][0]; + return $this->p($node, self::MAX_PRECEDENCE, $yieldPrecedence) . ' => '; + } + + protected function pArrayItem(Node\ArrayItem $node): string { + return $this->pKey($node->key) + . ($node->byRef ? '&' : '') + . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node): string { + return $this->pDereferenceLhs($node->var) + . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']'; + } + + protected function pExpr_ConstFetch(Expr\ConstFetch $node): string { + return $this->p($node->name); + } + + protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node): string { + return $this->pStaticDereferenceLhs($node->class) . '::' . $this->pObjectProperty($node->name); + } + + protected function pExpr_PropertyFetch(Expr\PropertyFetch $node): string { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_NullsafePropertyFetch(Expr\NullsafePropertyFetch $node): string { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node): string { + return $this->pStaticDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name); + } + + protected function pExpr_ShellExec(Expr\ShellExec $node): string { + return '`' . $this->pEncapsList($node->parts, '`') . '`'; + } + + protected function pExpr_Closure(Expr\Closure $node): string { + return $this->pAttrGroups($node->attrGroups, true) + . $this->pStatic($node->static) + . 'function ' . ($node->byRef ? '&' : '') + . '(' . $this->pParams($node->params) . ')' + . (!empty($node->uses) ? ' use (' . $this->pCommaSeparated($node->uses) . ')' : '') + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pExpr_Match(Expr\Match_ $node): string { + return 'match (' . $this->p($node->cond) . ') {' + . $this->pCommaSeparatedMultiline($node->arms, true) + . $this->nl + . '}'; + } + + protected function pMatchArm(Node\MatchArm $node): string { + $result = ''; + if ($node->conds) { + for ($i = 0, $c = \count($node->conds); $i + 1 < $c; $i++) { + $result .= $this->p($node->conds[$i]) . ', '; + } + $result .= $this->pKey($node->conds[$i]); + } else { + $result = 'default => '; + } + return $result . $this->p($node->body); + } + + protected function pExpr_ArrowFunction(Expr\ArrowFunction $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp( + Expr\ArrowFunction::class, + $this->pAttrGroups($node->attrGroups, true) + . $this->pStatic($node->static) + . 'fn' . ($node->byRef ? '&' : '') + . '(' . $this->pParams($node->params) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . ' => ', + $node->expr, $precedence, $lhsPrecedence); + } + + protected function pClosureUse(Node\ClosureUse $node): string { + return ($node->byRef ? '&' : '') . $this->p($node->var); + } + + protected function pExpr_New(Expr\New_ $node): string { + if ($node->class instanceof Stmt\Class_) { + $args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : ''; + return 'new ' . $this->pClassCommon($node->class, $args); + } + return 'new ' . $this->pNewOperand($node->class) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_Clone(Expr\Clone_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Clone_::class, 'clone ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Ternary(Expr\Ternary $node, int $precedence, int $lhsPrecedence): string { + // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator. + // this is okay because the part between ? and : never needs parentheses. + return $this->pInfixOp(Expr\Ternary::class, + $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else, + $precedence, $lhsPrecedence + ); + } + + protected function pExpr_Exit(Expr\Exit_ $node): string { + $kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE); + return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die') + . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : ''); + } + + protected function pExpr_Throw(Expr\Throw_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Throw_::class, 'throw ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Yield(Expr\Yield_ $node, int $precedence, int $lhsPrecedence): string { + if ($node->value === null) { + $opPrecedence = $this->precedenceMap[Expr\Yield_::class][0]; + return $opPrecedence >= $lhsPrecedence ? '(yield)' : 'yield'; + } else { + if (!$this->phpVersion->supportsYieldWithoutParentheses()) { + return '(yield ' . $this->pKey($node->key) . $this->p($node->value) . ')'; + } + return $this->pPrefixOp( + Expr\Yield_::class, 'yield ' . $this->pKey($node->key), + $node->value, $precedence, $lhsPrecedence); + } + } + + // Declarations + + protected function pStmt_Namespace(Stmt\Namespace_ $node): string { + if ($this->canUseSemicolonNamespaces) { + return 'namespace ' . $this->p($node->name) . ';' + . $this->nl . $this->pStmts($node->stmts, false); + } else { + return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '') + . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + } + + protected function pStmt_Use(Stmt\Use_ $node): string { + return 'use ' . $this->pUseType($node->type) + . $this->pCommaSeparated($node->uses) . ';'; + } + + protected function pStmt_GroupUse(Stmt\GroupUse $node): string { + return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix) + . '\{' . $this->pCommaSeparated($node->uses) . '};'; + } + + protected function pUseItem(Node\UseItem $node): string { + return $this->pUseType($node->type) . $this->p($node->name) + . (null !== $node->alias ? ' as ' . $node->alias : ''); + } + + protected function pUseType(int $type): string { + return $type === Stmt\Use_::TYPE_FUNCTION ? 'function ' + : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : ''); + } + + protected function pStmt_Interface(Stmt\Interface_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'interface ' . $node->name + . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Enum(Stmt\Enum_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'enum ' . $node->name + . ($node->scalarType ? ' : ' . $this->p($node->scalarType) : '') + . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Class(Stmt\Class_ $node): string { + return $this->pClassCommon($node, ' ' . $node->name); + } + + protected function pStmt_Trait(Stmt\Trait_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'trait ' . $node->name + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_EnumCase(Stmt\EnumCase $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'case ' . $node->name + . ($node->expr ? ' = ' . $this->p($node->expr) : '') + . ';'; + } + + protected function pStmt_TraitUse(Stmt\TraitUse $node): string { + return 'use ' . $this->pCommaSeparated($node->traits) + . (empty($node->adaptations) + ? ';' + : ' {' . $this->pStmts($node->adaptations) . $this->nl . '}'); + } + + protected function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node): string { + return $this->p($node->trait) . '::' . $node->method + . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';'; + } + + protected function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node): string { + return (null !== $node->trait ? $this->p($node->trait) . '::' : '') + . $node->method . ' as' + . (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '') + . (null !== $node->newName ? ' ' . $node->newName : '') + . ';'; + } + + protected function pStmt_Property(Stmt\Property $node): string { + return $this->pAttrGroups($node->attrGroups) + . (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags)) + . ($node->type ? $this->p($node->type) . ' ' : '') + . $this->pCommaSeparated($node->props) + . ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : ';'); + } + + protected function pPropertyItem(Node\PropertyItem $node): string { + return '$' . $node->name + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pPropertyHook(Node\PropertyHook $node): string { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . ($node->byRef ? '&' : '') . $node->name + . ($node->params ? '(' . $this->pParams($node->params) . ')' : '') + . (\is_array($node->body) ? ' {' . $this->pStmts($node->body) . $this->nl . '}' + : ($node->body !== null ? ' => ' . $this->p($node->body) : '') . ';'); + } + + protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pParams($node->params) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . (null !== $node->stmts + ? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}' + : ';'); + } + + protected function pStmt_ClassConst(Stmt\ClassConst $node): string { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'const ' + . (null !== $node->type ? $this->p($node->type) . ' ' : '') + . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Function(Stmt\Function_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pParams($node->params) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Const(Stmt\Const_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'const ' + . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Declare(Stmt\Declare_ $node): string { + return 'declare (' . $this->pCommaSeparated($node->declares) . ')' + . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); + } + + protected function pDeclareItem(Node\DeclareItem $node): string { + return $node->key . '=' . $this->p($node->value); + } + + // Control flow + + protected function pStmt_If(Stmt\If_ $node): string { + return 'if (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}' + . ($node->elseifs ? ' ' . $this->pImplode($node->elseifs, ' ') : '') + . (null !== $node->else ? ' ' . $this->p($node->else) : ''); + } + + protected function pStmt_ElseIf(Stmt\ElseIf_ $node): string { + return 'elseif (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Else(Stmt\Else_ $node): string { + if (\count($node->stmts) === 1 && $node->stmts[0] instanceof Stmt\If_) { + // Print as "else if" rather than "else { if }" + return 'else ' . $this->p($node->stmts[0]); + } + return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_For(Stmt\For_ $node): string { + return 'for (' + . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '') + . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '') + . $this->pCommaSeparated($node->loop) + . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Foreach(Stmt\Foreach_ $node): string { + return 'foreach (' . $this->p($node->expr) . ' as ' + . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '') + . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_While(Stmt\While_ $node): string { + return 'while (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Do(Stmt\Do_ $node): string { + return 'do {' . $this->pStmts($node->stmts) . $this->nl + . '} while (' . $this->p($node->cond) . ');'; + } + + protected function pStmt_Switch(Stmt\Switch_ $node): string { + return 'switch (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->cases) . $this->nl . '}'; + } + + protected function pStmt_Case(Stmt\Case_ $node): string { + return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':' + . $this->pStmts($node->stmts); + } + + protected function pStmt_TryCatch(Stmt\TryCatch $node): string { + return 'try {' . $this->pStmts($node->stmts) . $this->nl . '}' + . ($node->catches ? ' ' . $this->pImplode($node->catches, ' ') : '') + . ($node->finally !== null ? ' ' . $this->p($node->finally) : ''); + } + + protected function pStmt_Catch(Stmt\Catch_ $node): string { + return 'catch (' . $this->pImplode($node->types, '|') + . ($node->var !== null ? ' ' . $this->p($node->var) : '') + . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Finally(Stmt\Finally_ $node): string { + return 'finally {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Break(Stmt\Break_ $node): string { + return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Continue(Stmt\Continue_ $node): string { + return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Return(Stmt\Return_ $node): string { + return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';'; + } + + protected function pStmt_Label(Stmt\Label $node): string { + return $node->name . ':'; + } + + protected function pStmt_Goto(Stmt\Goto_ $node): string { + return 'goto ' . $node->name . ';'; + } + + // Other + + protected function pStmt_Expression(Stmt\Expression $node): string { + return $this->p($node->expr) . ';'; + } + + protected function pStmt_Echo(Stmt\Echo_ $node): string { + return 'echo ' . $this->pCommaSeparated($node->exprs) . ';'; + } + + protected function pStmt_Static(Stmt\Static_ $node): string { + return 'static ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStmt_Global(Stmt\Global_ $node): string { + return 'global ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStaticVar(Node\StaticVar $node): string { + return $this->p($node->var) + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pStmt_Unset(Stmt\Unset_ $node): string { + return 'unset(' . $this->pCommaSeparated($node->vars) . ');'; + } + + protected function pStmt_InlineHTML(Stmt\InlineHTML $node): string { + $newline = $node->getAttribute('hasLeadingNewline', true) ? $this->newline : ''; + return '?>' . $newline . $node->value . 'remaining; + } + + protected function pStmt_Nop(Stmt\Nop $node): string { + return ''; + } + + protected function pStmt_Block(Stmt\Block $node): string { + return '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + // Helpers + + protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string { + return $this->pAttrGroups($node->attrGroups, $node->name === null) + . $this->pModifiers($node->flags) + . 'class' . $afterClassToken + . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '') + . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pObjectProperty(Node $node): string { + if ($node instanceof Expr) { + return '{' . $this->p($node) . '}'; + } else { + assert($node instanceof Node\Identifier); + return $node->name; + } + } + + /** @param (Expr|Node\InterpolatedStringPart)[] $encapsList */ + protected function pEncapsList(array $encapsList, ?string $quote): string { + $return = ''; + foreach ($encapsList as $element) { + if ($element instanceof Node\InterpolatedStringPart) { + $return .= $this->escapeString($element->value, $quote); + } else { + $return .= '{' . $this->p($element) . '}'; + } + } + + return $return; + } + + protected function pSingleQuotedString(string $string): string { + // It is idiomatic to only escape backslashes when necessary, i.e. when followed by ', \ or + // the end of the string ('Foo\Bar' instead of 'Foo\\Bar'). However, we also don't want to + // produce an odd number of backslashes, so '\\\\a' should not get rendered as '\\\a', even + // though that would be legal. + $regex = '/\'|\\\\(?=[\'\\\\]|$)|(?<=\\\\)\\\\/'; + return '\'' . preg_replace($regex, '\\\\$0', $string) . '\''; + } + + protected function escapeString(string $string, ?string $quote): string { + if (null === $quote) { + // For doc strings, don't escape newlines + $escaped = addcslashes($string, "\t\f\v$\\"); + // But do escape isolated \r. Combined with the terminating newline, it might get + // interpreted as \r\n and dropped from the string contents. + $escaped = preg_replace('/\r(?!\n)/', '\\r', $escaped); + if ($this->phpVersion->supportsFlexibleHeredoc()) { + $escaped = $this->indentString($escaped); + } + } else { + $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\"); + } + + // Escape control characters and non-UTF-8 characters. + // Regex based on https://stackoverflow.com/a/11709412/385378. + $regex = '/( + [\x00-\x08\x0E-\x1F] # Control characters + | [\xC0-\xC1] # Invalid UTF-8 Bytes + | [\xF5-\xFF] # Invalid UTF-8 Bytes + | \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point + | \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point + | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start + | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start + | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start + | (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle + | (? $part) { + if ($part instanceof Node\InterpolatedStringPart + && $this->containsEndLabel($this->escapeString($part->value, null), $label, $i === 0) + ) { + return true; + } + } + return false; + } + + protected function pDereferenceLhs(Node $node): string { + if (!$this->dereferenceLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pStaticDereferenceLhs(Node $node): string { + if (!$this->staticDereferenceLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pCallLhs(Node $node): string { + if (!$this->callLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pNewOperand(Node $node): string { + if (!$this->newOperandRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + /** + * @param Node[] $nodes + */ + protected function hasNodeWithComments(array $nodes): bool { + foreach ($nodes as $node) { + if ($node && $node->getComments()) { + return true; + } + } + return false; + } + + /** @param Node[] $nodes */ + protected function pMaybeMultiline(array $nodes, bool $trailingComma = false): string { + if (!$this->hasNodeWithComments($nodes)) { + return $this->pCommaSeparated($nodes); + } else { + return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl; + } + } + + /** @param Node\Param[] $params + */ + private function hasParamWithAttributes(array $params): bool { + foreach ($params as $param) { + if ($param->attrGroups) { + return true; + } + } + return false; + } + + /** @param Node\Param[] $params */ + protected function pParams(array $params): string { + if ($this->hasNodeWithComments($params) || + ($this->hasParamWithAttributes($params) && !$this->phpVersion->supportsAttributes()) + ) { + return $this->pCommaSeparatedMultiline($params, $this->phpVersion->supportsTrailingCommaInParamList()) . $this->nl; + } + return $this->pCommaSeparated($params); + } + + /** @param Node\AttributeGroup[] $nodes */ + protected function pAttrGroups(array $nodes, bool $inline = false): string { + $result = ''; + $sep = $inline ? ' ' : $this->nl; + foreach ($nodes as $node) { + $result .= $this->p($node) . $sep; + } + + return $result; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php new file mode 100644 index 000000000..448bc8491 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php @@ -0,0 +1,1706 @@ + */ + protected array $precedenceMap = [ + // [precedence, precedenceLHS, precedenceRHS] + // Where the latter two are the precedences to use for the LHS and RHS of a binary operator, + // where 1 is added to one of the sides depending on associativity. This information is not + // used for unary operators and set to -1. + Expr\Clone_::class => [-10, 0, 1], + BinaryOp\Pow::class => [ 0, 0, 1], + Expr\BitwiseNot::class => [ 10, -1, -1], + Expr\UnaryPlus::class => [ 10, -1, -1], + Expr\UnaryMinus::class => [ 10, -1, -1], + Cast\Int_::class => [ 10, -1, -1], + Cast\Double::class => [ 10, -1, -1], + Cast\String_::class => [ 10, -1, -1], + Cast\Array_::class => [ 10, -1, -1], + Cast\Object_::class => [ 10, -1, -1], + Cast\Bool_::class => [ 10, -1, -1], + Cast\Unset_::class => [ 10, -1, -1], + Expr\ErrorSuppress::class => [ 10, -1, -1], + Expr\Instanceof_::class => [ 20, -1, -1], + Expr\BooleanNot::class => [ 30, -1, -1], + BinaryOp\Mul::class => [ 40, 41, 40], + BinaryOp\Div::class => [ 40, 41, 40], + BinaryOp\Mod::class => [ 40, 41, 40], + BinaryOp\Plus::class => [ 50, 51, 50], + BinaryOp\Minus::class => [ 50, 51, 50], + // FIXME: This precedence is incorrect for PHP 8. + BinaryOp\Concat::class => [ 50, 51, 50], + BinaryOp\ShiftLeft::class => [ 60, 61, 60], + BinaryOp\ShiftRight::class => [ 60, 61, 60], + BinaryOp\Pipe::class => [ 65, 66, 65], + BinaryOp\Smaller::class => [ 70, 70, 70], + BinaryOp\SmallerOrEqual::class => [ 70, 70, 70], + BinaryOp\Greater::class => [ 70, 70, 70], + BinaryOp\GreaterOrEqual::class => [ 70, 70, 70], + BinaryOp\Equal::class => [ 80, 80, 80], + BinaryOp\NotEqual::class => [ 80, 80, 80], + BinaryOp\Identical::class => [ 80, 80, 80], + BinaryOp\NotIdentical::class => [ 80, 80, 80], + BinaryOp\Spaceship::class => [ 80, 80, 80], + BinaryOp\BitwiseAnd::class => [ 90, 91, 90], + BinaryOp\BitwiseXor::class => [100, 101, 100], + BinaryOp\BitwiseOr::class => [110, 111, 110], + BinaryOp\BooleanAnd::class => [120, 121, 120], + BinaryOp\BooleanOr::class => [130, 131, 130], + BinaryOp\Coalesce::class => [140, 140, 141], + Expr\Ternary::class => [150, 150, 150], + Expr\Assign::class => [160, -1, -1], + Expr\AssignRef::class => [160, -1, -1], + AssignOp\Plus::class => [160, -1, -1], + AssignOp\Minus::class => [160, -1, -1], + AssignOp\Mul::class => [160, -1, -1], + AssignOp\Div::class => [160, -1, -1], + AssignOp\Concat::class => [160, -1, -1], + AssignOp\Mod::class => [160, -1, -1], + AssignOp\BitwiseAnd::class => [160, -1, -1], + AssignOp\BitwiseOr::class => [160, -1, -1], + AssignOp\BitwiseXor::class => [160, -1, -1], + AssignOp\ShiftLeft::class => [160, -1, -1], + AssignOp\ShiftRight::class => [160, -1, -1], + AssignOp\Pow::class => [160, -1, -1], + AssignOp\Coalesce::class => [160, -1, -1], + Expr\YieldFrom::class => [170, -1, -1], + Expr\Yield_::class => [175, -1, -1], + Expr\Print_::class => [180, -1, -1], + BinaryOp\LogicalAnd::class => [190, 191, 190], + BinaryOp\LogicalXor::class => [200, 201, 200], + BinaryOp\LogicalOr::class => [210, 211, 210], + Expr\Include_::class => [220, -1, -1], + Expr\ArrowFunction::class => [230, -1, -1], + Expr\Throw_::class => [240, -1, -1], + Expr\Cast\Void_::class => [250, -1, -1], + ]; + + /** @var int Current indentation level. */ + protected int $indentLevel; + /** @var string String for single level of indentation */ + private string $indent; + /** @var int Width in spaces to indent by. */ + private int $indentWidth; + /** @var bool Whether to use tab indentation. */ + private bool $useTabs; + /** @var int Width in spaces of one tab. */ + private int $tabWidth = 4; + + /** @var string Newline style. Does not include current indentation. */ + protected string $newline; + /** @var string Newline including current indentation. */ + protected string $nl; + /** @var string|null Token placed at end of doc string to ensure it is followed by a newline. + * Null if flexible doc strings are used. */ + protected ?string $docStringEndToken; + /** @var bool Whether semicolon namespaces can be used (i.e. no global namespace is used) */ + protected bool $canUseSemicolonNamespaces; + /** @var bool Whether to use short array syntax if the node specifies no preference */ + protected bool $shortArraySyntax; + /** @var PhpVersion PHP version to target */ + protected PhpVersion $phpVersion; + + /** @var TokenStream|null Original tokens for use in format-preserving pretty print */ + protected ?TokenStream $origTokens; + /** @var Internal\Differ Differ for node lists */ + protected Differ $nodeListDiffer; + /** @var array Map determining whether a certain character is a label character */ + protected array $labelCharMap; + /** + * @var array> Map from token classes and subnode names to FIXUP_* constants. + * This is used during format-preserving prints to place additional parens/braces if necessary. + */ + protected array $fixupMap; + /** + * @var array Map from "{$node->getType()}->{$subNode}" + * to ['left' => $l, 'right' => $r], where $l and $r specify the token type that needs to be stripped + * when removing this node. + */ + protected array $removalMap; + /** + * @var array Map from + * "{$node->getType()}->{$subNode}" to [$find, $beforeToken, $extraLeft, $extraRight]. + * $find is an optional token after which the insertion occurs. $extraLeft/Right + * are optionally added before/after the main insertions. + */ + protected array $insertionMap; + /** + * @var array Map From "{$class}->{$subNode}" to string that should be inserted + * between elements of this list subnode. + */ + protected array $listInsertionMap; + + /** + * @var array + */ + protected array $emptyListInsertionMap; + /** @var array + * Map from "{$class}->{$subNode}" to [$printFn, $skipToken, $findToken] where $printFn is the function to + * print the modifiers, $skipToken is the token to skip at the start and $findToken is the token before which + * the modifiers should be reprinted. */ + protected array $modifierChangeMap; + + /** + * Creates a pretty printer instance using the given options. + * + * Supported options: + * * PhpVersion $phpVersion: The PHP version to target (default to PHP 7.4). This option + * controls compatibility of the generated code with older PHP + * versions in cases where a simple stylistic choice exists (e.g. + * array() vs []). It is safe to pretty-print an AST for a newer + * PHP version while specifying an older target (but the result will + * of course not be compatible with the older version in that case). + * * string $newline: The newline style to use. Should be "\n" (default) or "\r\n". + * * string $indent: The indentation to use. Should either be all spaces or a single + * tab. Defaults to four spaces (" "). + * * bool $shortArraySyntax: Whether to use [] instead of array() as the default array + * syntax, if the node does not specify a format. Defaults to whether + * the phpVersion support short array syntax. + * + * @param array{ + * phpVersion?: PhpVersion, newline?: string, indent?: string, shortArraySyntax?: bool + * } $options Dictionary of formatting options + */ + public function __construct(array $options = []) { + $this->phpVersion = $options['phpVersion'] ?? PhpVersion::fromComponents(7, 4); + + $this->newline = $options['newline'] ?? "\n"; + if ($this->newline !== "\n" && $this->newline != "\r\n") { + throw new \LogicException('Option "newline" must be one of "\n" or "\r\n"'); + } + + $this->shortArraySyntax = + $options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax(); + $this->docStringEndToken = + $this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand(); + + $this->indent = $indent = $options['indent'] ?? ' '; + if ($indent === "\t") { + $this->useTabs = true; + $this->indentWidth = $this->tabWidth; + } elseif ($indent === \str_repeat(' ', \strlen($indent))) { + $this->useTabs = false; + $this->indentWidth = \strlen($indent); + } else { + throw new \LogicException('Option "indent" must either be all spaces or a single tab'); + } + } + + /** + * Reset pretty printing state. + */ + protected function resetState(): void { + $this->indentLevel = 0; + $this->nl = $this->newline; + $this->origTokens = null; + } + + /** + * Set indentation level + * + * @param int $level Level in number of spaces + */ + protected function setIndentLevel(int $level): void { + $this->indentLevel = $level; + if ($this->useTabs) { + $tabs = \intdiv($level, $this->tabWidth); + $spaces = $level % $this->tabWidth; + $this->nl = $this->newline . \str_repeat("\t", $tabs) . \str_repeat(' ', $spaces); + } else { + $this->nl = $this->newline . \str_repeat(' ', $level); + } + } + + /** + * Increase indentation level. + */ + protected function indent(): void { + $this->indentLevel += $this->indentWidth; + $this->nl .= $this->indent; + } + + /** + * Decrease indentation level. + */ + protected function outdent(): void { + assert($this->indentLevel >= $this->indentWidth); + $this->setIndentLevel($this->indentLevel - $this->indentWidth); + } + + /** + * Pretty prints an array of statements. + * + * @param Node[] $stmts Array of statements + * + * @return string Pretty printed statements + */ + public function prettyPrint(array $stmts): string { + $this->resetState(); + $this->preprocessNodes($stmts); + + return ltrim($this->handleMagicTokens($this->pStmts($stmts, false))); + } + + /** + * Pretty prints an expression. + * + * @param Expr $node Expression node + * + * @return string Pretty printed node + */ + public function prettyPrintExpr(Expr $node): string { + $this->resetState(); + return $this->handleMagicTokens($this->p($node)); + } + + /** + * Pretty prints a file of statements (includes the opening newline . $this->newline; + } + + $p = "newline . $this->newline . $this->prettyPrint($stmts); + + if ($stmts[0] instanceof Stmt\InlineHTML) { + $p = preg_replace('/^<\?php\s+\?>\r?\n?/', '', $p); + } + if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) { + $p = preg_replace('/<\?php$/', '', rtrim($p)); + } + + return $p; + } + + /** + * Preprocesses the top-level nodes to initialize pretty printer state. + * + * @param Node[] $nodes Array of nodes + */ + protected function preprocessNodes(array $nodes): void { + /* We can use semicolon-namespaces unless there is a global namespace declaration */ + $this->canUseSemicolonNamespaces = true; + foreach ($nodes as $node) { + if ($node instanceof Stmt\Namespace_ && null === $node->name) { + $this->canUseSemicolonNamespaces = false; + break; + } + } + } + + /** + * Handles (and removes) doc-string-end tokens. + */ + protected function handleMagicTokens(string $str): string { + if ($this->docStringEndToken !== null) { + // Replace doc-string-end tokens with nothing or a newline + $str = str_replace( + $this->docStringEndToken . ';' . $this->newline, + ';' . $this->newline, + $str); + $str = str_replace($this->docStringEndToken, $this->newline, $str); + } + + return $str; + } + + /** + * Pretty prints an array of nodes (statements) and indents them optionally. + * + * @param Node[] $nodes Array of nodes + * @param bool $indent Whether to indent the printed nodes + * + * @return string Pretty printed statements + */ + protected function pStmts(array $nodes, bool $indent = true): string { + if ($indent) { + $this->indent(); + } + + $result = ''; + foreach ($nodes as $node) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + if ($node instanceof Stmt\Nop) { + continue; + } + } + + $result .= $this->nl . $this->p($node); + } + + if ($indent) { + $this->outdent(); + } + + return $result; + } + + /** + * Pretty-print an infix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param Node $leftNode Left-hand side node + * @param string $operatorString String representation of the operator + * @param Node $rightNode Right-hand side node + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * + * @return string Pretty printed infix operation + */ + protected function pInfixOp( + string $class, Node $leftNode, string $operatorString, Node $rightNode, + int $precedence, int $lhsPrecedence + ): string { + list($opPrecedence, $newPrecedenceLHS, $newPrecedenceRHS) = $this->precedenceMap[$class]; + $prefix = ''; + $suffix = ''; + if ($opPrecedence >= $precedence) { + $prefix = '('; + $suffix = ')'; + $lhsPrecedence = self::MAX_PRECEDENCE; + } + return $prefix . $this->p($leftNode, $newPrecedenceLHS, $newPrecedenceLHS) + . $operatorString . $this->p($rightNode, $newPrecedenceRHS, $lhsPrecedence) . $suffix; + } + + /** + * Pretty-print a prefix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * + * @return string Pretty printed prefix operation + */ + protected function pPrefixOp(string $class, string $operatorString, Node $node, int $precedence, int $lhsPrecedence): string { + $opPrecedence = $this->precedenceMap[$class][0]; + $prefix = ''; + $suffix = ''; + if ($opPrecedence >= $lhsPrecedence) { + $prefix = '('; + $suffix = ')'; + $lhsPrecedence = self::MAX_PRECEDENCE; + } + $printedArg = $this->p($node, $opPrecedence, $lhsPrecedence); + if (($operatorString === '+' && $printedArg[0] === '+') || + ($operatorString === '-' && $printedArg[0] === '-') + ) { + // Avoid printing +(+$a) as ++$a and similar. + $printedArg = '(' . $printedArg . ')'; + } + return $prefix . $operatorString . $printedArg . $suffix; + } + + /** + * Pretty-print a postfix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * + * @return string Pretty printed postfix operation + */ + protected function pPostfixOp(string $class, Node $node, string $operatorString, int $precedence, int $lhsPrecedence): string { + $opPrecedence = $this->precedenceMap[$class][0]; + $prefix = ''; + $suffix = ''; + if ($opPrecedence >= $precedence) { + $prefix = '('; + $suffix = ')'; + $lhsPrecedence = self::MAX_PRECEDENCE; + } + if ($opPrecedence < $lhsPrecedence) { + $lhsPrecedence = $opPrecedence; + } + return $prefix . $this->p($node, $opPrecedence, $lhsPrecedence) . $operatorString . $suffix; + } + + /** + * Pretty prints an array of nodes and implodes the printed values. + * + * @param Node[] $nodes Array of Nodes to be printed + * @param string $glue Character to implode with + * + * @return string Imploded pretty printed nodes> $pre + */ + protected function pImplode(array $nodes, string $glue = ''): string { + $pNodes = []; + foreach ($nodes as $node) { + if (null === $node) { + $pNodes[] = ''; + } else { + $pNodes[] = $this->p($node); + } + } + + return implode($glue, $pNodes); + } + + /** + * Pretty prints an array of nodes and implodes the printed values with commas. + * + * @param Node[] $nodes Array of Nodes to be printed + * + * @return string Comma separated pretty printed nodes + */ + protected function pCommaSeparated(array $nodes): string { + return $this->pImplode($nodes, ', '); + } + + /** + * Pretty prints a comma-separated list of nodes in multiline style, including comments. + * + * The result includes a leading newline and one level of indentation (same as pStmts). + * + * @param Node[] $nodes Array of Nodes to be printed + * @param bool $trailingComma Whether to use a trailing comma + * + * @return string Comma separated pretty printed nodes in multiline style + */ + protected function pCommaSeparatedMultiline(array $nodes, bool $trailingComma): string { + $this->indent(); + + $result = ''; + $lastIdx = count($nodes) - 1; + foreach ($nodes as $idx => $node) { + if ($node !== null) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + } + + $result .= $this->nl . $this->p($node); + } else { + $result .= $this->nl; + } + if ($trailingComma || $idx !== $lastIdx) { + $result .= ','; + } + } + + $this->outdent(); + return $result; + } + + /** + * Prints reformatted text of the passed comments. + * + * @param Comment[] $comments List of comments + * + * @return string Reformatted text of comments + */ + protected function pComments(array $comments): string { + $formattedComments = []; + + foreach ($comments as $comment) { + $formattedComments[] = str_replace("\n", $this->nl, $comment->getReformattedText()); + } + + return implode($this->nl, $formattedComments); + } + + /** + * Perform a format-preserving pretty print of an AST. + * + * The format preservation is best effort. For some changes to the AST the formatting will not + * be preserved (at least not locally). + * + * In order to use this method a number of prerequisites must be satisfied: + * * The startTokenPos and endTokenPos attributes in the lexer must be enabled. + * * The CloningVisitor must be run on the AST prior to modification. + * * The original tokens must be provided, using the getTokens() method on the lexer. + * + * @param Node[] $stmts Modified AST with links to original AST + * @param Node[] $origStmts Original AST with token offset information + * @param Token[] $origTokens Tokens of the original code + */ + public function printFormatPreserving(array $stmts, array $origStmts, array $origTokens): string { + $this->initializeNodeListDiffer(); + $this->initializeLabelCharMap(); + $this->initializeFixupMap(); + $this->initializeRemovalMap(); + $this->initializeInsertionMap(); + $this->initializeListInsertionMap(); + $this->initializeEmptyListInsertionMap(); + $this->initializeModifierChangeMap(); + + $this->resetState(); + $this->origTokens = new TokenStream($origTokens, $this->tabWidth); + + $this->preprocessNodes($stmts); + + $pos = 0; + $result = $this->pArray($stmts, $origStmts, $pos, 0, 'File', 'stmts', null); + if (null !== $result) { + $result .= $this->origTokens->getTokenCode($pos, count($origTokens) - 1, 0); + } else { + // Fallback + // TODO Add newline . $this->pStmts($stmts, false); + } + + return $this->handleMagicTokens($result); + } + + protected function pFallback(Node $node, int $precedence, int $lhsPrecedence): string { + return $this->{'p' . $node->getType()}($node, $precedence, $lhsPrecedence); + } + + /** + * Pretty prints a node. + * + * This method also handles formatting preservation for nodes. + * + * @param Node $node Node to be pretty printed + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * @param bool $parentFormatPreserved Whether parent node has preserved formatting + * + * @return string Pretty printed node + */ + protected function p( + Node $node, int $precedence = self::MAX_PRECEDENCE, int $lhsPrecedence = self::MAX_PRECEDENCE, + bool $parentFormatPreserved = false + ): string { + // No orig tokens means this is a normal pretty print without preservation of formatting + if (!$this->origTokens) { + return $this->{'p' . $node->getType()}($node, $precedence, $lhsPrecedence); + } + + /** @var Node|null $origNode */ + $origNode = $node->getAttribute('origNode'); + if (null === $origNode) { + return $this->pFallback($node, $precedence, $lhsPrecedence); + } + + $class = \get_class($node); + \assert($class === \get_class($origNode)); + + $startPos = $origNode->getStartTokenPos(); + $endPos = $origNode->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + + $fallbackNode = $node; + if ($node instanceof Expr\New_ && $node->class instanceof Stmt\Class_) { + // Normalize node structure of anonymous classes + assert($origNode instanceof Expr\New_); + $node = PrintableNewAnonClassNode::fromNewNode($node); + $origNode = PrintableNewAnonClassNode::fromNewNode($origNode); + $class = PrintableNewAnonClassNode::class; + } + + // InlineHTML node does not contain closing and opening PHP tags. If the parent formatting + // is not preserved, then we need to use the fallback code to make sure the tags are + // printed. + if ($node instanceof Stmt\InlineHTML && !$parentFormatPreserved) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + $indentAdjustment = $this->indentLevel - $this->origTokens->getIndentationBefore($startPos); + + $type = $node->getType(); + $fixupInfo = $this->fixupMap[$class] ?? null; + + $result = ''; + $pos = $startPos; + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNode = $node->$subNodeName; + $origSubNode = $origNode->$subNodeName; + + if ((!$subNode instanceof Node && $subNode !== null) + || (!$origSubNode instanceof Node && $origSubNode !== null) + ) { + if ($subNode === $origSubNode) { + // Unchanged, can reuse old code + continue; + } + + if (is_array($subNode) && is_array($origSubNode)) { + // Array subnode changed, we might be able to reconstruct it + $listResult = $this->pArray( + $subNode, $origSubNode, $pos, $indentAdjustment, $class, $subNodeName, + $fixupInfo[$subNodeName] ?? null + ); + if (null === $listResult) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + $result .= $listResult; + continue; + } + + // Check if this is a modifier change + $key = $class . '->' . $subNodeName; + if (!isset($this->modifierChangeMap[$key])) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + [$printFn, $skipToken, $findToken] = $this->modifierChangeMap[$key]; + $skipWSPos = $this->origTokens->skipRight($pos, $skipToken); + $result .= $this->origTokens->getTokenCode($pos, $skipWSPos, $indentAdjustment); + $result .= $this->$printFn($subNode); + $pos = $this->origTokens->findRight($skipWSPos, $findToken); + continue; + } + + $extraLeft = ''; + $extraRight = ''; + if ($origSubNode !== null) { + $subStartPos = $origSubNode->getStartTokenPos(); + $subEndPos = $origSubNode->getEndTokenPos(); + \assert($subStartPos >= 0 && $subEndPos >= 0); + } else { + if ($subNode === null) { + // Both null, nothing to do + continue; + } + + // A node has been inserted, check if we have insertion information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->insertionMap[$key])) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + list($findToken, $beforeToken, $extraLeft, $extraRight) = $this->insertionMap[$key]; + if (null !== $findToken) { + $subStartPos = $this->origTokens->findRight($pos, $findToken) + + (int) !$beforeToken; + } else { + $subStartPos = $pos; + } + + if (null === $extraLeft && null !== $extraRight) { + // If inserting on the right only, skipping whitespace looks better + $subStartPos = $this->origTokens->skipRightWhitespace($subStartPos); + } + $subEndPos = $subStartPos - 1; + } + + if (null === $subNode) { + // A node has been removed, check if we have removal information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->removalMap[$key])) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + // Adjust positions to account for additional tokens that must be skipped + $removalInfo = $this->removalMap[$key]; + if (isset($removalInfo['left'])) { + $subStartPos = $this->origTokens->skipLeft($subStartPos - 1, $removalInfo['left']) + 1; + } + if (isset($removalInfo['right'])) { + $subEndPos = $this->origTokens->skipRight($subEndPos + 1, $removalInfo['right']) - 1; + } + } + + $result .= $this->origTokens->getTokenCode($pos, $subStartPos, $indentAdjustment); + + if (null !== $subNode) { + $result .= $extraLeft; + + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel(max($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment, 0)); + + // If it's the same node that was previously in this position, it certainly doesn't + // need fixup. It's important to check this here, because our fixup checks are more + // conservative than strictly necessary. + if (isset($fixupInfo[$subNodeName]) + && $subNode->getAttribute('origNode') !== $origSubNode + ) { + $fixup = $fixupInfo[$subNodeName]; + $res = $this->pFixup($fixup, $subNode, $class, $subStartPos, $subEndPos); + } else { + $res = $this->p($subNode, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true); + } + + $this->safeAppend($result, $res); + $this->setIndentLevel($origIndentLevel); + + $result .= $extraRight; + } + + $pos = $subEndPos + 1; + } + + $result .= $this->origTokens->getTokenCode($pos, $endPos + 1, $indentAdjustment); + return $result; + } + + /** + * Perform a format-preserving pretty print of an array. + * + * @param Node[] $nodes New nodes + * @param Node[] $origNodes Original nodes + * @param int $pos Current token position (updated by reference) + * @param int $indentAdjustment Adjustment for indentation + * @param string $parentNodeClass Class of the containing node. + * @param string $subNodeName Name of array subnode. + * @param null|int $fixup Fixup information for array item nodes + * + * @return null|string Result of pretty print or null if cannot preserve formatting + */ + protected function pArray( + array $nodes, array $origNodes, int &$pos, int $indentAdjustment, + string $parentNodeClass, string $subNodeName, ?int $fixup + ): ?string { + $diff = $this->nodeListDiffer->diffWithReplacements($origNodes, $nodes); + + $mapKey = $parentNodeClass . '->' . $subNodeName; + $insertStr = $this->listInsertionMap[$mapKey] ?? null; + $isStmtList = $subNodeName === 'stmts'; + + $beforeFirstKeepOrReplace = true; + $skipRemovedNode = false; + $delayedAdd = []; + $lastElemIndentLevel = $this->indentLevel; + + $insertNewline = false; + if ($insertStr === "\n") { + $insertStr = ''; + $insertNewline = true; + } + + if ($isStmtList && \count($origNodes) === 1 && \count($nodes) !== 1) { + $startPos = $origNodes[0]->getStartTokenPos(); + $endPos = $origNodes[0]->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + if (!$this->origTokens->haveBraces($startPos, $endPos)) { + // This was a single statement without braces, but either additional statements + // have been added, or the single statement has been removed. This requires the + // addition of braces. For now fall back. + // TODO: Try to preserve formatting + return null; + } + } + + $result = ''; + foreach ($diff as $i => $diffElem) { + $diffType = $diffElem->type; + /** @var Node|string|null $arrItem */ + $arrItem = $diffElem->new; + /** @var Node|string|null $origArrItem */ + $origArrItem = $diffElem->old; + + if ($diffType === DiffElem::TYPE_KEEP || $diffType === DiffElem::TYPE_REPLACE) { + $beforeFirstKeepOrReplace = false; + + if ($origArrItem === null || $arrItem === null) { + // We can only handle the case where both are null + if ($origArrItem === $arrItem) { + continue; + } + return null; + } + + if (!$arrItem instanceof Node || !$origArrItem instanceof Node) { + // We can only deal with nodes. This can occur for Names, which use string arrays. + return null; + } + + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos); + + $origIndentLevel = $this->indentLevel; + $lastElemIndentLevel = max($this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment, 0); + $this->setIndentLevel($lastElemIndentLevel); + + $comments = $arrItem->getComments(); + $origComments = $origArrItem->getComments(); + $commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos; + \assert($commentStartPos >= 0); + + if ($commentStartPos < $pos) { + // Comments may be assigned to multiple nodes if they start at the same position. + // Make sure we don't try to print them multiple times. + $commentStartPos = $itemStartPos; + } + + if ($skipRemovedNode) { + if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) { + // We'd remove an opening/closing PHP tag. + // TODO: Preserve formatting. + $this->setIndentLevel($origIndentLevel); + return null; + } + } else { + $result .= $this->origTokens->getTokenCode( + $pos, $commentStartPos, $indentAdjustment); + } + + if (!empty($delayedAdd)) { + /** @var Node $delayedAddNode */ + foreach ($delayedAdd as $delayedAddNode) { + if ($insertNewline) { + $delayedAddComments = $delayedAddNode->getComments(); + if ($delayedAddComments) { + $result .= $this->pComments($delayedAddComments) . $this->nl; + } + } + + $this->safeAppend($result, $this->p($delayedAddNode, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true)); + + if ($insertNewline) { + $result .= $insertStr . $this->nl; + } else { + $result .= $insertStr; + } + } + + $delayedAdd = []; + } + + if ($comments !== $origComments) { + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $this->origTokens->getTokenCode( + $commentStartPos, $itemStartPos, $indentAdjustment); + } + + // If we had to remove anything, we have done so now. + $skipRemovedNode = false; + } elseif ($diffType === DiffElem::TYPE_ADD) { + if (null === $insertStr) { + // We don't have insertion information for this list type + return null; + } + + if (!$arrItem instanceof Node) { + // We only support list insertion of nodes. + return null; + } + + // We go multiline if the original code was multiline, + // or if it's an array item with a comment above it. + // Match always uses multiline formatting. + if ($insertStr === ', ' && + ($this->isMultiline($origNodes) || $arrItem->getComments() || + $parentNodeClass === Expr\Match_::class) + ) { + $insertStr = ','; + $insertNewline = true; + } + + if ($beforeFirstKeepOrReplace) { + // Will be inserted at the next "replace" or "keep" element + $delayedAdd[] = $arrItem; + continue; + } + + $itemStartPos = $pos; + $itemEndPos = $pos - 1; + + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel($lastElemIndentLevel); + + if ($insertNewline) { + $result .= $insertStr . $this->nl; + $comments = $arrItem->getComments(); + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $insertStr; + } + } elseif ($diffType === DiffElem::TYPE_REMOVE) { + if (!$origArrItem instanceof Node) { + // We only support removal for nodes + return null; + } + + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0); + + // Consider comments part of the node. + $origComments = $origArrItem->getComments(); + if ($origComments) { + $itemStartPos = $origComments[0]->getStartTokenPos(); + } + + if ($i === 0) { + // If we're removing from the start, keep the tokens before the node and drop those after it, + // instead of the other way around. + $result .= $this->origTokens->getTokenCode( + $pos, $itemStartPos, $indentAdjustment); + $skipRemovedNode = true; + } else { + if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) { + // We'd remove an opening/closing PHP tag. + // TODO: Preserve formatting. + return null; + } + } + + $pos = $itemEndPos + 1; + continue; + } else { + throw new \Exception("Shouldn't happen"); + } + + if (null !== $fixup && $arrItem->getAttribute('origNode') !== $origArrItem) { + $res = $this->pFixup($fixup, $arrItem, null, $itemStartPos, $itemEndPos); + } else { + $res = $this->p($arrItem, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true); + } + $this->safeAppend($result, $res); + + $this->setIndentLevel($origIndentLevel); + $pos = $itemEndPos + 1; + } + + if ($skipRemovedNode) { + // TODO: Support removing single node. + return null; + } + + if (!empty($delayedAdd)) { + if (!isset($this->emptyListInsertionMap[$mapKey])) { + return null; + } + + list($findToken, $extraLeft, $extraRight) = $this->emptyListInsertionMap[$mapKey]; + if (null !== $findToken) { + $insertPos = $this->origTokens->findRight($pos, $findToken) + 1; + $result .= $this->origTokens->getTokenCode($pos, $insertPos, $indentAdjustment); + $pos = $insertPos; + } + + $first = true; + $result .= $extraLeft; + foreach ($delayedAdd as $delayedAddNode) { + if (!$first) { + $result .= $insertStr; + if ($insertNewline) { + $result .= $this->nl; + } + } + $result .= $this->p($delayedAddNode, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true); + $first = false; + } + $result .= $extraRight === "\n" ? $this->nl : $extraRight; + } + + return $result; + } + + /** + * Print node with fixups. + * + * Fixups here refer to the addition of extra parentheses, braces or other characters, that + * are required to preserve program semantics in a certain context (e.g. to maintain precedence + * or because only certain expressions are allowed in certain places). + * + * @param int $fixup Fixup type + * @param Node $subNode Subnode to print + * @param string|null $parentClass Class of parent node + * @param int $subStartPos Original start pos of subnode + * @param int $subEndPos Original end pos of subnode + * + * @return string Result of fixed-up print of subnode + */ + protected function pFixup(int $fixup, Node $subNode, ?string $parentClass, int $subStartPos, int $subEndPos): string { + switch ($fixup) { + case self::FIXUP_PREC_LEFT: + // We use a conservative approximation where lhsPrecedence == precedence. + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + $precedence = $this->precedenceMap[$parentClass][1]; + return $this->p($subNode, $precedence, $precedence); + } + break; + case self::FIXUP_PREC_RIGHT: + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + $precedence = $this->precedenceMap[$parentClass][2]; + return $this->p($subNode, $precedence, $precedence); + } + break; + case self::FIXUP_PREC_UNARY: + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + $precedence = $this->precedenceMap[$parentClass][0]; + return $this->p($subNode, $precedence, $precedence); + } + break; + case self::FIXUP_CALL_LHS: + if ($this->callLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_DEREF_LHS: + if ($this->dereferenceLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_STATIC_DEREF_LHS: + if ($this->staticDereferenceLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_NEW: + if ($this->newOperandRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos)) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_BRACED_NAME: + case self::FIXUP_VAR_BRACED_NAME: + if ($subNode instanceof Expr + && !$this->origTokens->haveBraces($subStartPos, $subEndPos) + ) { + return ($fixup === self::FIXUP_VAR_BRACED_NAME ? '$' : '') + . '{' . $this->p($subNode) . '}'; + } + break; + case self::FIXUP_ENCAPSED: + if (!$subNode instanceof Node\InterpolatedStringPart + && !$this->origTokens->haveBraces($subStartPos, $subEndPos) + ) { + return '{' . $this->p($subNode) . '}'; + } + break; + default: + throw new \Exception('Cannot happen'); + } + + // Nothing special to do + return $this->p($subNode); + } + + /** + * Appends to a string, ensuring whitespace between label characters. + * + * Example: "echo" and "$x" result in "echo$x", but "echo" and "x" result in "echo x". + * Without safeAppend the result would be "echox", which does not preserve semantics. + */ + protected function safeAppend(string &$str, string $append): void { + if ($str === "") { + $str = $append; + return; + } + + if ($append === "") { + return; + } + + if (!$this->labelCharMap[$append[0]] + || !$this->labelCharMap[$str[\strlen($str) - 1]]) { + $str .= $append; + } else { + $str .= " " . $append; + } + } + + /** + * Determines whether the LHS of a call must be wrapped in parenthesis. + * + * @param Node $node LHS of a call + * + * @return bool Whether parentheses are required + */ + protected function callLhsRequiresParens(Node $node): bool { + if ($node instanceof Expr\New_) { + return !$this->phpVersion->supportsNewDereferenceWithoutParentheses(); + } + return !($node instanceof Node\Name + || $node instanceof Expr\Variable + || $node instanceof Expr\ArrayDimFetch + || $node instanceof Expr\FuncCall + || $node instanceof Expr\MethodCall + || $node instanceof Expr\NullsafeMethodCall + || $node instanceof Expr\StaticCall + || $node instanceof Expr\Array_); + } + + /** + * Determines whether the LHS of an array/object operation must be wrapped in parentheses. + * + * @param Node $node LHS of dereferencing operation + * + * @return bool Whether parentheses are required + */ + protected function dereferenceLhsRequiresParens(Node $node): bool { + // A constant can occur on the LHS of an array/object deref, but not a static deref. + return $this->staticDereferenceLhsRequiresParens($node) + && !$node instanceof Expr\ConstFetch; + } + + /** + * Determines whether the LHS of a static operation must be wrapped in parentheses. + * + * @param Node $node LHS of dereferencing operation + * + * @return bool Whether parentheses are required + */ + protected function staticDereferenceLhsRequiresParens(Node $node): bool { + if ($node instanceof Expr\New_) { + return !$this->phpVersion->supportsNewDereferenceWithoutParentheses(); + } + return !($node instanceof Expr\Variable + || $node instanceof Node\Name + || $node instanceof Expr\ArrayDimFetch + || $node instanceof Expr\PropertyFetch + || $node instanceof Expr\NullsafePropertyFetch + || $node instanceof Expr\StaticPropertyFetch + || $node instanceof Expr\FuncCall + || $node instanceof Expr\MethodCall + || $node instanceof Expr\NullsafeMethodCall + || $node instanceof Expr\StaticCall + || $node instanceof Expr\Array_ + || $node instanceof Scalar\String_ + || $node instanceof Expr\ClassConstFetch); + } + + /** + * Determines whether an expression used in "new" or "instanceof" requires parentheses. + * + * @param Node $node New or instanceof operand + * + * @return bool Whether parentheses are required + */ + protected function newOperandRequiresParens(Node $node): bool { + if ($node instanceof Node\Name || $node instanceof Expr\Variable) { + return false; + } + if ($node instanceof Expr\ArrayDimFetch || $node instanceof Expr\PropertyFetch || + $node instanceof Expr\NullsafePropertyFetch + ) { + return $this->newOperandRequiresParens($node->var); + } + if ($node instanceof Expr\StaticPropertyFetch) { + return $this->newOperandRequiresParens($node->class); + } + return true; + } + + /** + * Print modifiers, including trailing whitespace. + * + * @param int $modifiers Modifier mask to print + * + * @return string Printed modifiers + */ + protected function pModifiers(int $modifiers): string { + return ($modifiers & Modifiers::FINAL ? 'final ' : '') + . ($modifiers & Modifiers::ABSTRACT ? 'abstract ' : '') + . ($modifiers & Modifiers::PUBLIC ? 'public ' : '') + . ($modifiers & Modifiers::PROTECTED ? 'protected ' : '') + . ($modifiers & Modifiers::PRIVATE ? 'private ' : '') + . ($modifiers & Modifiers::PUBLIC_SET ? 'public(set) ' : '') + . ($modifiers & Modifiers::PROTECTED_SET ? 'protected(set) ' : '') + . ($modifiers & Modifiers::PRIVATE_SET ? 'private(set) ' : '') + . ($modifiers & Modifiers::STATIC ? 'static ' : '') + . ($modifiers & Modifiers::READONLY ? 'readonly ' : ''); + } + + protected function pStatic(bool $static): string { + return $static ? 'static ' : ''; + } + + /** + * Determine whether a list of nodes uses multiline formatting. + * + * @param (Node|null)[] $nodes Node list + * + * @return bool Whether multiline formatting is used + */ + protected function isMultiline(array $nodes): bool { + if (\count($nodes) < 2) { + return false; + } + + $pos = -1; + foreach ($nodes as $node) { + if (null === $node) { + continue; + } + + $endPos = $node->getEndTokenPos() + 1; + if ($pos >= 0) { + $text = $this->origTokens->getTokenCode($pos, $endPos, 0); + if (false === strpos($text, "\n")) { + // We require that a newline is present between *every* item. If the formatting + // is inconsistent, with only some items having newlines, we don't consider it + // as multiline + return false; + } + } + $pos = $endPos; + } + + return true; + } + + /** + * Lazily initializes label char map. + * + * The label char map determines whether a certain character may occur in a label. + */ + protected function initializeLabelCharMap(): void { + if (isset($this->labelCharMap)) { + return; + } + + $this->labelCharMap = []; + for ($i = 0; $i < 256; $i++) { + $chr = chr($i); + $this->labelCharMap[$chr] = $i >= 0x80 || ctype_alnum($chr); + } + + if ($this->phpVersion->allowsDelInIdentifiers()) { + $this->labelCharMap["\x7f"] = true; + } + } + + /** + * Lazily initializes node list differ. + * + * The node list differ is used to determine differences between two array subnodes. + */ + protected function initializeNodeListDiffer(): void { + if (isset($this->nodeListDiffer)) { + return; + } + + $this->nodeListDiffer = new Internal\Differ(function ($a, $b) { + if ($a instanceof Node && $b instanceof Node) { + return $a === $b->getAttribute('origNode'); + } + // Can happen for array destructuring + return $a === null && $b === null; + }); + } + + /** + * Lazily initializes fixup map. + * + * The fixup map is used to determine whether a certain subnode of a certain node may require + * some kind of "fixup" operation, e.g. the addition of parenthesis or braces. + */ + protected function initializeFixupMap(): void { + if (isset($this->fixupMap)) { + return; + } + + $this->fixupMap = [ + Expr\Instanceof_::class => [ + 'expr' => self::FIXUP_PREC_UNARY, + 'class' => self::FIXUP_NEW, + ], + Expr\Ternary::class => [ + 'cond' => self::FIXUP_PREC_LEFT, + 'else' => self::FIXUP_PREC_RIGHT, + ], + Expr\Yield_::class => ['value' => self::FIXUP_PREC_UNARY], + + Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS], + Expr\StaticCall::class => ['class' => self::FIXUP_STATIC_DEREF_LHS], + Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS], + Expr\ClassConstFetch::class => [ + 'class' => self::FIXUP_STATIC_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\New_::class => ['class' => self::FIXUP_NEW], + Expr\MethodCall::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\NullsafeMethodCall::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\StaticPropertyFetch::class => [ + 'class' => self::FIXUP_STATIC_DEREF_LHS, + 'name' => self::FIXUP_VAR_BRACED_NAME, + ], + Expr\PropertyFetch::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\NullsafePropertyFetch::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Scalar\InterpolatedString::class => [ + 'parts' => self::FIXUP_ENCAPSED, + ], + ]; + + $binaryOps = [ + BinaryOp\Pow::class, BinaryOp\Mul::class, BinaryOp\Div::class, BinaryOp\Mod::class, + BinaryOp\Plus::class, BinaryOp\Minus::class, BinaryOp\Concat::class, + BinaryOp\ShiftLeft::class, BinaryOp\ShiftRight::class, BinaryOp\Smaller::class, + BinaryOp\SmallerOrEqual::class, BinaryOp\Greater::class, BinaryOp\GreaterOrEqual::class, + BinaryOp\Equal::class, BinaryOp\NotEqual::class, BinaryOp\Identical::class, + BinaryOp\NotIdentical::class, BinaryOp\Spaceship::class, BinaryOp\BitwiseAnd::class, + BinaryOp\BitwiseXor::class, BinaryOp\BitwiseOr::class, BinaryOp\BooleanAnd::class, + BinaryOp\BooleanOr::class, BinaryOp\Coalesce::class, BinaryOp\LogicalAnd::class, + BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class, BinaryOp\Pipe::class, + ]; + foreach ($binaryOps as $binaryOp) { + $this->fixupMap[$binaryOp] = [ + 'left' => self::FIXUP_PREC_LEFT, + 'right' => self::FIXUP_PREC_RIGHT + ]; + } + + $prefixOps = [ + Expr\Clone_::class, Expr\BitwiseNot::class, Expr\BooleanNot::class, Expr\UnaryPlus::class, Expr\UnaryMinus::class, + Cast\Int_::class, Cast\Double::class, Cast\String_::class, Cast\Array_::class, + Cast\Object_::class, Cast\Bool_::class, Cast\Unset_::class, Expr\ErrorSuppress::class, + Expr\YieldFrom::class, Expr\Print_::class, Expr\Include_::class, + Expr\Assign::class, Expr\AssignRef::class, AssignOp\Plus::class, AssignOp\Minus::class, + AssignOp\Mul::class, AssignOp\Div::class, AssignOp\Concat::class, AssignOp\Mod::class, + AssignOp\BitwiseAnd::class, AssignOp\BitwiseOr::class, AssignOp\BitwiseXor::class, + AssignOp\ShiftLeft::class, AssignOp\ShiftRight::class, AssignOp\Pow::class, AssignOp\Coalesce::class, + Expr\ArrowFunction::class, Expr\Throw_::class, + ]; + foreach ($prefixOps as $prefixOp) { + $this->fixupMap[$prefixOp] = ['expr' => self::FIXUP_PREC_UNARY]; + } + } + + /** + * Lazily initializes the removal map. + * + * The removal map is used to determine which additional tokens should be removed when a + * certain node is replaced by null. + */ + protected function initializeRemovalMap(): void { + if (isset($this->removalMap)) { + return; + } + + $stripBoth = ['left' => \T_WHITESPACE, 'right' => \T_WHITESPACE]; + $stripLeft = ['left' => \T_WHITESPACE]; + $stripRight = ['right' => \T_WHITESPACE]; + $stripDoubleArrow = ['right' => \T_DOUBLE_ARROW]; + $stripColon = ['left' => ':']; + $stripEquals = ['left' => '=']; + $this->removalMap = [ + 'Expr_ArrayDimFetch->dim' => $stripBoth, + 'ArrayItem->key' => $stripDoubleArrow, + 'Expr_ArrowFunction->returnType' => $stripColon, + 'Expr_Closure->returnType' => $stripColon, + 'Expr_Exit->expr' => $stripBoth, + 'Expr_Ternary->if' => $stripBoth, + 'Expr_Yield->key' => $stripDoubleArrow, + 'Expr_Yield->value' => $stripBoth, + 'Param->type' => $stripRight, + 'Param->default' => $stripEquals, + 'Stmt_Break->num' => $stripBoth, + 'Stmt_Catch->var' => $stripLeft, + 'Stmt_ClassConst->type' => $stripRight, + 'Stmt_ClassMethod->returnType' => $stripColon, + 'Stmt_Class->extends' => ['left' => \T_EXTENDS], + 'Stmt_Enum->scalarType' => $stripColon, + 'Stmt_EnumCase->expr' => $stripEquals, + 'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS], + 'Stmt_Continue->num' => $stripBoth, + 'Stmt_Foreach->keyVar' => $stripDoubleArrow, + 'Stmt_Function->returnType' => $stripColon, + 'Stmt_If->else' => $stripLeft, + 'Stmt_Namespace->name' => $stripLeft, + 'Stmt_Property->type' => $stripRight, + 'PropertyItem->default' => $stripEquals, + 'Stmt_Return->expr' => $stripBoth, + 'Stmt_StaticVar->default' => $stripEquals, + 'Stmt_TraitUseAdaptation_Alias->newName' => $stripLeft, + 'Stmt_TryCatch->finally' => $stripLeft, + // 'Stmt_Case->cond': Replace with "default" + // 'Stmt_Class->name': Unclear what to do + // 'Stmt_Declare->stmts': Not a plain node + // 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a plain node + ]; + } + + protected function initializeInsertionMap(): void { + if (isset($this->insertionMap)) { + return; + } + + // TODO: "yield" where both key and value are inserted doesn't work + // [$find, $beforeToken, $extraLeft, $extraRight] + $this->insertionMap = [ + 'Expr_ArrayDimFetch->dim' => ['[', false, null, null], + 'ArrayItem->key' => [null, false, null, ' => '], + 'Expr_ArrowFunction->returnType' => [')', false, ': ', null], + 'Expr_Closure->returnType' => [')', false, ': ', null], + 'Expr_Ternary->if' => ['?', false, ' ', ' '], + 'Expr_Yield->key' => [\T_YIELD, false, null, ' => '], + 'Expr_Yield->value' => [\T_YIELD, false, ' ', null], + 'Param->type' => [null, false, null, ' '], + 'Param->default' => [null, false, ' = ', null], + 'Stmt_Break->num' => [\T_BREAK, false, ' ', null], + 'Stmt_Catch->var' => [null, false, ' ', null], + 'Stmt_ClassMethod->returnType' => [')', false, ': ', null], + 'Stmt_ClassConst->type' => [\T_CONST, false, ' ', null], + 'Stmt_Class->extends' => [null, false, ' extends ', null], + 'Stmt_Enum->scalarType' => [null, false, ' : ', null], + 'Stmt_EnumCase->expr' => [null, false, ' = ', null], + 'Expr_PrintableNewAnonClass->extends' => [null, false, ' extends ', null], + 'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null], + 'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '], + 'Stmt_Function->returnType' => [')', false, ': ', null], + 'Stmt_If->else' => [null, false, ' ', null], + 'Stmt_Namespace->name' => [\T_NAMESPACE, false, ' ', null], + 'Stmt_Property->type' => [\T_VARIABLE, true, null, ' '], + 'PropertyItem->default' => [null, false, ' = ', null], + 'Stmt_Return->expr' => [\T_RETURN, false, ' ', null], + 'Stmt_StaticVar->default' => [null, false, ' = ', null], + //'Stmt_TraitUseAdaptation_Alias->newName' => [T_AS, false, ' ', null], // TODO + 'Stmt_TryCatch->finally' => [null, false, ' ', null], + + // 'Expr_Exit->expr': Complicated due to optional () + // 'Stmt_Case->cond': Conversion from default to case + // 'Stmt_Class->name': Unclear + // 'Stmt_Declare->stmts': Not a proper node + // 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a proper node + ]; + } + + protected function initializeListInsertionMap(): void { + if (isset($this->listInsertionMap)) { + return; + } + + $this->listInsertionMap = [ + // special + //'Expr_ShellExec->parts' => '', // TODO These need to be treated more carefully + //'Scalar_InterpolatedString->parts' => '', + Stmt\Catch_::class . '->types' => '|', + UnionType::class . '->types' => '|', + IntersectionType::class . '->types' => '&', + Stmt\If_::class . '->elseifs' => ' ', + Stmt\TryCatch::class . '->catches' => ' ', + + // comma-separated lists + Expr\Array_::class . '->items' => ', ', + Expr\ArrowFunction::class . '->params' => ', ', + Expr\Closure::class . '->params' => ', ', + Expr\Closure::class . '->uses' => ', ', + Expr\FuncCall::class . '->args' => ', ', + Expr\Isset_::class . '->vars' => ', ', + Expr\List_::class . '->items' => ', ', + Expr\MethodCall::class . '->args' => ', ', + Expr\NullsafeMethodCall::class . '->args' => ', ', + Expr\New_::class . '->args' => ', ', + PrintableNewAnonClassNode::class . '->args' => ', ', + Expr\StaticCall::class . '->args' => ', ', + Stmt\ClassConst::class . '->consts' => ', ', + Stmt\ClassMethod::class . '->params' => ', ', + Stmt\Class_::class . '->implements' => ', ', + Stmt\Enum_::class . '->implements' => ', ', + PrintableNewAnonClassNode::class . '->implements' => ', ', + Stmt\Const_::class . '->consts' => ', ', + Stmt\Declare_::class . '->declares' => ', ', + Stmt\Echo_::class . '->exprs' => ', ', + Stmt\For_::class . '->init' => ', ', + Stmt\For_::class . '->cond' => ', ', + Stmt\For_::class . '->loop' => ', ', + Stmt\Function_::class . '->params' => ', ', + Stmt\Global_::class . '->vars' => ', ', + Stmt\GroupUse::class . '->uses' => ', ', + Stmt\Interface_::class . '->extends' => ', ', + Expr\Match_::class . '->arms' => ', ', + Stmt\Property::class . '->props' => ', ', + Stmt\StaticVar::class . '->vars' => ', ', + Stmt\TraitUse::class . '->traits' => ', ', + Stmt\TraitUseAdaptation\Precedence::class . '->insteadof' => ', ', + Stmt\Unset_::class . '->vars' => ', ', + Stmt\UseUse::class . '->uses' => ', ', + MatchArm::class . '->conds' => ', ', + AttributeGroup::class . '->attrs' => ', ', + PropertyHook::class . '->params' => ', ', + + // statement lists + Expr\Closure::class . '->stmts' => "\n", + Stmt\Case_::class . '->stmts' => "\n", + Stmt\Catch_::class . '->stmts' => "\n", + Stmt\Class_::class . '->stmts' => "\n", + Stmt\Enum_::class . '->stmts' => "\n", + PrintableNewAnonClassNode::class . '->stmts' => "\n", + Stmt\Interface_::class . '->stmts' => "\n", + Stmt\Trait_::class . '->stmts' => "\n", + Stmt\ClassMethod::class . '->stmts' => "\n", + Stmt\Declare_::class . '->stmts' => "\n", + Stmt\Do_::class . '->stmts' => "\n", + Stmt\ElseIf_::class . '->stmts' => "\n", + Stmt\Else_::class . '->stmts' => "\n", + Stmt\Finally_::class . '->stmts' => "\n", + Stmt\Foreach_::class . '->stmts' => "\n", + Stmt\For_::class . '->stmts' => "\n", + Stmt\Function_::class . '->stmts' => "\n", + Stmt\If_::class . '->stmts' => "\n", + Stmt\Namespace_::class . '->stmts' => "\n", + Stmt\Block::class . '->stmts' => "\n", + + // Attribute groups + Stmt\Class_::class . '->attrGroups' => "\n", + Stmt\Enum_::class . '->attrGroups' => "\n", + Stmt\EnumCase::class . '->attrGroups' => "\n", + Stmt\Interface_::class . '->attrGroups' => "\n", + Stmt\Trait_::class . '->attrGroups' => "\n", + Stmt\Function_::class . '->attrGroups' => "\n", + Stmt\ClassMethod::class . '->attrGroups' => "\n", + Stmt\ClassConst::class . '->attrGroups' => "\n", + Stmt\Property::class . '->attrGroups' => "\n", + PrintableNewAnonClassNode::class . '->attrGroups' => ' ', + Expr\Closure::class . '->attrGroups' => ' ', + Expr\ArrowFunction::class . '->attrGroups' => ' ', + Param::class . '->attrGroups' => ' ', + PropertyHook::class . '->attrGroups' => ' ', + + Stmt\Switch_::class . '->cases' => "\n", + Stmt\TraitUse::class . '->adaptations' => "\n", + Stmt\TryCatch::class . '->stmts' => "\n", + Stmt\While_::class . '->stmts' => "\n", + PropertyHook::class . '->body' => "\n", + Stmt\Property::class . '->hooks' => "\n", + Param::class . '->hooks' => "\n", + + // dummy for top-level context + 'File->stmts' => "\n", + ]; + } + + protected function initializeEmptyListInsertionMap(): void { + if (isset($this->emptyListInsertionMap)) { + return; + } + + // TODO Insertion into empty statement lists. + + // [$find, $extraLeft, $extraRight] + $this->emptyListInsertionMap = [ + Expr\ArrowFunction::class . '->params' => ['(', '', ''], + Expr\Closure::class . '->uses' => [')', ' use (', ')'], + Expr\Closure::class . '->params' => ['(', '', ''], + Expr\FuncCall::class . '->args' => ['(', '', ''], + Expr\MethodCall::class . '->args' => ['(', '', ''], + Expr\NullsafeMethodCall::class . '->args' => ['(', '', ''], + Expr\New_::class . '->args' => ['(', '', ''], + PrintableNewAnonClassNode::class . '->args' => ['(', '', ''], + PrintableNewAnonClassNode::class . '->implements' => [null, ' implements ', ''], + Expr\StaticCall::class . '->args' => ['(', '', ''], + Stmt\Class_::class . '->implements' => [null, ' implements ', ''], + Stmt\Enum_::class . '->implements' => [null, ' implements ', ''], + Stmt\ClassMethod::class . '->params' => ['(', '', ''], + Stmt\Interface_::class . '->extends' => [null, ' extends ', ''], + Stmt\Function_::class . '->params' => ['(', '', ''], + Stmt\Interface_::class . '->attrGroups' => [null, '', "\n"], + Stmt\Class_::class . '->attrGroups' => [null, '', "\n"], + Stmt\ClassConst::class . '->attrGroups' => [null, '', "\n"], + Stmt\ClassMethod::class . '->attrGroups' => [null, '', "\n"], + Stmt\Function_::class . '->attrGroups' => [null, '', "\n"], + Stmt\Property::class . '->attrGroups' => [null, '', "\n"], + Stmt\Trait_::class . '->attrGroups' => [null, '', "\n"], + Expr\ArrowFunction::class . '->attrGroups' => [null, '', ' '], + Expr\Closure::class . '->attrGroups' => [null, '', ' '], + Stmt\Const_::class . '->attrGroups' => [null, '', "\n"], + PrintableNewAnonClassNode::class . '->attrGroups' => [\T_NEW, ' ', ''], + + /* These cannot be empty to start with: + * Expr_Isset->vars + * Stmt_Catch->types + * Stmt_Const->consts + * Stmt_ClassConst->consts + * Stmt_Declare->declares + * Stmt_Echo->exprs + * Stmt_Global->vars + * Stmt_GroupUse->uses + * Stmt_Property->props + * Stmt_StaticVar->vars + * Stmt_TraitUse->traits + * Stmt_TraitUseAdaptation_Precedence->insteadof + * Stmt_Unset->vars + * Stmt_Use->uses + * UnionType->types + */ + + /* TODO + * Stmt_If->elseifs + * Stmt_TryCatch->catches + * Expr_Array->items + * Expr_List->items + * Stmt_For->init + * Stmt_For->cond + * Stmt_For->loop + */ + ]; + } + + protected function initializeModifierChangeMap(): void { + if (isset($this->modifierChangeMap)) { + return; + } + + $this->modifierChangeMap = [ + Stmt\ClassConst::class . '->flags' => ['pModifiers', \T_WHITESPACE, \T_CONST], + Stmt\ClassMethod::class . '->flags' => ['pModifiers', \T_WHITESPACE, \T_FUNCTION], + Stmt\Class_::class . '->flags' => ['pModifiers', \T_WHITESPACE, \T_CLASS], + Stmt\Property::class . '->flags' => ['pModifiers', \T_WHITESPACE, \T_VARIABLE], + PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_NEW, \T_CLASS], + Param::class . '->flags' => ['pModifiers', \T_WHITESPACE, \T_VARIABLE], + PropertyHook::class . '->flags' => ['pModifiers', \T_WHITESPACE, \T_STRING], + Expr\Closure::class . '->static' => ['pStatic', \T_WHITESPACE, \T_FUNCTION], + Expr\ArrowFunction::class . '->static' => ['pStatic', \T_WHITESPACE, \T_FN], + //Stmt\TraitUseAdaptation\Alias::class . '->newModifier' => 0, // TODO + ]; + + // List of integer subnodes that are not modifiers: + // Expr_Include->type + // Stmt_GroupUse->type + // Stmt_Use->type + // UseItem->type + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Token.php b/vendor/nikic/php-parser/lib/PhpParser/Token.php new file mode 100644 index 000000000..6683310f1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Token.php @@ -0,0 +1,18 @@ +pos + \strlen($this->text); + } + + /** Get 1-based end line number of the token. */ + public function getEndLine(): int { + return $this->line + \substr_count($this->text, "\n"); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/compatibility_tokens.php b/vendor/nikic/php-parser/lib/PhpParser/compatibility_tokens.php new file mode 100644 index 000000000..ced038d47 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/compatibility_tokens.php @@ -0,0 +1,71 @@ +fqsen = $fqsen; + + if (isset($matches[2])) { + $this->name = $matches[2]; + } else { + $matches = explode('\\', $fqsen); + $name = end($matches); + assert(is_string($name)); + $this->name = trim($name, '()'); + } + } + + /** + * converts this class to string. + */ + public function __toString() : string + { + return $this->fqsen; + } + + /** + * Returns the name of the element without path. + */ + public function getName() : string + { + return $this->name; + } +} diff --git a/vendor/phpdocumentor/reflection-common/src/Location.php b/vendor/phpdocumentor/reflection-common/src/Location.php new file mode 100644 index 000000000..177deede6 --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/src/Location.php @@ -0,0 +1,53 @@ +lineNumber = $lineNumber; + $this->columnNumber = $columnNumber; + } + + /** + * Returns the line number that is covered by this location. + */ + public function getLineNumber() : int + { + return $this->lineNumber; + } + + /** + * Returns the column number (character position on a line) for this location object. + */ + public function getColumnNumber() : int + { + return $this->columnNumber; + } +} diff --git a/vendor/phpdocumentor/reflection-common/src/Project.php b/vendor/phpdocumentor/reflection-common/src/Project.php new file mode 100644 index 000000000..57839fd14 --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/src/Project.php @@ -0,0 +1,25 @@ +create($docComment); +``` + +The `create` method will yield an object of type `\phpDocumentor\Reflection\DocBlock` +whose methods can be queried: + +```php +// Contains the summary for this DocBlock +$summary = $docblock->getSummary(); + +// Contains \phpDocumentor\Reflection\DocBlock\Description object +$description = $docblock->getDescription(); + +// You can either cast it to string +$description = (string) $docblock->getDescription(); + +// Or use the render method to get a string representation of the Description. +$description = $docblock->getDescription()->render(); +``` + +> For more examples it would be best to review the scripts in the [`/examples` folder](/examples). diff --git a/vendor/phpdocumentor/reflection-docblock/composer.json b/vendor/phpdocumentor/reflection-docblock/composer.json new file mode 100644 index 000000000..5e4f03924 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/composer.json @@ -0,0 +1,57 @@ +{ + "name": "phpdocumentor/reflection-docblock", + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "require": { + "php": "^7.4 || ^8.0", + "phpdocumentor/type-resolver": "^1.7", + "webmozart/assert": "^1.9.1 || ^2", + "phpdocumentor/reflection-common": "^2.2", + "ext-filter": "*", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "doctrine/deprecations": "^1.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "psalm/phar": "^5.26" + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "phpDocumentor\\Reflection\\": ["tests/unit", "tests/integration"] + } + }, + "config": { + "platform": { + "php":"7.4.0" + }, + "allow-plugins": { + "phpstan/extension-installer": true + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php new file mode 100644 index 000000000..90d8066d5 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php @@ -0,0 +1,228 @@ +summary = $summary; + $this->description = $description ?: new DocBlock\Description(''); + foreach ($tags as $tag) { + $this->addTag($tag); + } + + $this->context = $context; + $this->location = $location; + + $this->isTemplateEnd = $isTemplateEnd; + $this->isTemplateStart = $isTemplateStart; + } + + public function getSummary(): string + { + return $this->summary; + } + + public function getDescription(): DocBlock\Description + { + return $this->description; + } + + /** + * Returns the current context. + */ + public function getContext(): ?Types\Context + { + return $this->context; + } + + /** + * Returns the current location. + */ + public function getLocation(): ?Location + { + return $this->location; + } + + /** + * Returns whether this DocBlock is the start of a Template section. + * + * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker + * (`#@+`) that is appended directly after the opening `/**` of a DocBlock. + * + * An example of such an opening is: + * + * ``` + * /**#@+ + * * My DocBlock + * * / + * ``` + * + * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all + * elements that follow until another DocBlock is found that contains the closing marker (`#@-`). + * + * @see self::isTemplateEnd() for the check whether a closing marker was provided. + */ + public function isTemplateStart(): bool + { + return $this->isTemplateStart; + } + + /** + * Returns whether this DocBlock is the end of a Template section. + * + * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality. + */ + public function isTemplateEnd(): bool + { + return $this->isTemplateEnd; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags(): array + { + return $this->tags; + } + + /** + * Returns an array of tags matching the given name. If no tags are found + * an empty array is returned. + * + * @param string $name String to search by. + * + * @return Tag[] + */ + public function getTagsByName(string $name): array + { + $result = []; + + foreach ($this->getTags() as $tag) { + if ($tag->getName() !== $name) { + continue; + } + + $result[] = $tag; + } + + return $result; + } + + /** + * Returns an array of tags with type matching the given name. If no tags are found + * an empty array is returned. + * + * @param string $name String to search by. + * + * @return TagWithType[] + */ + public function getTagsWithTypeByName(string $name): array + { + $result = []; + + foreach ($this->getTagsByName($name) as $tag) { + if (!$tag instanceof TagWithType) { + continue; + } + + $result[] = $tag; + } + + return $result; + } + + /** + * Checks if a tag of a certain type is present in this DocBlock. + * + * @param string $name Tag name to check for. + */ + public function hasTag(string $name): bool + { + foreach ($this->getTags() as $tag) { + if ($tag->getName() === $name) { + return true; + } + } + + return false; + } + + /** + * Remove a tag from this DocBlock. + * + * @param Tag $tagToRemove The tag to remove. + */ + public function removeTag(Tag $tagToRemove): void + { + foreach ($this->tags as $key => $tag) { + if ($tag === $tagToRemove) { + unset($this->tags[$key]); + break; + } + } + } + + /** + * Adds a tag to this DocBlock. + * + * @param Tag $tag The tag to add. + */ + private function addTag(Tag $tag): void + { + $this->tags[] = $tag; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php new file mode 100644 index 000000000..a188ae30f --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php @@ -0,0 +1,118 @@ +create('This is a {@see Description}', $context); + * + * The description factory will interpret the given body and create a body template and list of tags from them, and pass + * that onto the constructor if this class. + * + * > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace + * > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial + * > type names and FQSENs. + * + * If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this: + * + * $description = new Description( + * 'This is a %1$s', + * [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ] + * ); + * + * It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object + * is mainly responsible for rendering. + * + * @see DescriptionFactory to create a new Description. + * @see Tags\Formatter for the formatting of the body and tags. + */ +class Description +{ + private string $bodyTemplate; + + /** @var Tag[] */ + private array $tags; + + /** + * Initializes a Description with its body (template) and a listing of the tags used in the body template. + * + * @param Tag[] $tags + */ + public function __construct(string $bodyTemplate, array $tags = []) + { + $this->bodyTemplate = $bodyTemplate; + $this->tags = $tags; + } + + /** + * Returns the body template. + */ + public function getBodyTemplate(): string + { + return $this->bodyTemplate; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags(): array + { + return $this->tags; + } + + /** + * Renders this description as a string where the provided formatter will format the tags in the expected string + * format. + */ + public function render(?Formatter $formatter = null): string + { + if ($this->tags === []) { + return vsprintf($this->bodyTemplate, []); + } + + if ($formatter === null) { + $formatter = new PassthroughFormatter(); + } + + $tags = []; + foreach ($this->tags as $tag) { + $tags[] = '{' . $formatter->format($tag) . '}'; + } + + return vsprintf($this->bodyTemplate, $tags); + } + + /** + * Returns a plain string representation of this description. + */ + public function __toString(): string + { + return $this->render(); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php new file mode 100644 index 000000000..6915c16ff --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php @@ -0,0 +1,179 @@ +tagFactory = $tagFactory; + } + + /** + * Returns the parsed text of this description. + */ + public function create(string $contents, ?TypeContext $context = null): Description + { + $tokens = $this->lex($contents); + $count = count($tokens); + $tagCount = 0; + $tags = []; + + for ($i = 1; $i < $count; $i += 2) { + $tags[] = $this->tagFactory->create($tokens[$i], $context); + $tokens[$i] = '%' . ++$tagCount . '$s'; + } + + //In order to allow "literal" inline tags, the otherwise invalid + //sequence "{@}" is changed to "@", and "{}" is changed to "}". + //"%" is escaped to "%%" because of vsprintf. + //See unit tests for examples. + for ($i = 0; $i < $count; $i += 2) { + $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]); + } + + return new Description(implode('', $tokens), $tags); + } + + /** + * Strips the contents from superfluous whitespace and splits the description into a series of tokens. + * + * @return string[] A series of tokens of which the description text is composed. + */ + private function lex(string $contents): array + { + $contents = $this->removeSuperfluousStartingWhitespace($contents); + + // performance optimalization; if there is no inline tag, don't bother splitting it up. + if (strpos($contents, '{@') === false) { + return [$contents]; + } + + return Utils::pregSplit( + '/\{ + # "{@}" and "{@*}" are not a valid inline tags. This ensures that we do not treat them as one, but treat + # them literally. + (?!(?:@\}|@\*\}) ) + # We want to capture the whole tag line, but without the inline tag delimiters. + (\@ + # Match everything up to the next delimiter. + [^{}]* + # Nested inline tag content should not be captured, or it will appear in the result separately. + (?: + # Match nested inline tags. + (?: + # Because we did not catch the tag delimiters earlier, we must be explicit with them here. + # Notice that this also matches "{}", as a way to later introduce it as an escape sequence. + \{(?1)?\} + | + # Make sure we match hanging "{". + \{ + ) + # Match content after the nested inline tag. + [^{}]* + )* # If there are more inline tags, match them as well. We use "*" since there may not be any + # nested inline tags. + ) + \}/Sux', + $contents, + 0, + PREG_SPLIT_DELIM_CAPTURE + ); + } + + /** + * Removes the superfluous from a multi-line description. + * + * When a description has more than one line then it can happen that the second and subsequent lines have an + * additional indentation. This is commonly in use with tags like this: + * + * {@}since 1.1.0 This is an example + * description where we have an + * indentation in the second and + * subsequent lines. + * + * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent + * lines and this may cause rendering issues when, for example, using a Markdown converter. + */ + private function removeSuperfluousStartingWhitespace(string $contents): string + { + $lines = Utils::pregSplit("/\r\n?|\n/", $contents); + + // if there is only one line then we don't have lines with superfluous whitespace and + // can use the contents as-is + if (count($lines) <= 1) { + return $contents; + } + + // determine how many whitespace characters need to be stripped + $startingSpaceCount = 9999999; + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + // lines with a no length do not count as they are not indented at all + if (trim($lines[$i]) === '') { + continue; + } + + // determine the number of prefixing spaces by checking the difference in line length before and after + // an ltrim + $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i]))); + } + + // strip the number of spaces from each line + if ($startingSpaceCount > 0) { + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + $lines[$i] = substr($lines[$i], $startingSpaceCount); + } + } + + return implode("\n", $lines); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php new file mode 100644 index 000000000..0fb24a68b --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php @@ -0,0 +1,158 @@ +getFilePath(); + + $file = $this->getExampleFileContents($filename); + if ($file === null) { + return sprintf('** File not found : %s **', $filename); + } + + return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount())); + } + + /** + * Registers the project's root directory where an 'examples' folder can be expected. + */ + public function setSourceDirectory(string $directory = ''): void + { + $this->sourceDirectory = $directory; + } + + /** + * Returns the project's root directory where an 'examples' folder can be expected. + */ + public function getSourceDirectory(): string + { + return $this->sourceDirectory; + } + + /** + * Registers a series of directories that may contain examples. + * + * @param string[] $directories + */ + public function setExampleDirectories(array $directories): void + { + $this->exampleDirectories = $directories; + } + + /** + * Returns a series of directories that may contain examples. + * + * @return string[] + */ + public function getExampleDirectories(): array + { + return $this->exampleDirectories; + } + + /** + * Attempts to find the requested example file and returns its contents or null if no file was found. + * + * This method will try several methods in search of the given example file, the first one it encounters is + * returned: + * + * 1. Iterates through all examples folders for the given filename + * 2. Checks the source folder for the given filename + * 3. Checks the 'examples' folder in the current working directory for examples + * 4. Checks the path relative to the current working directory for the given filename + * + * @return string[] all lines of the example file + */ + private function getExampleFileContents(string $filename): ?array + { + $normalizedPath = null; + + foreach ($this->exampleDirectories as $directory) { + $exampleFileFromConfig = $this->constructExamplePath($directory, $filename); + if (is_readable($exampleFileFromConfig)) { + $normalizedPath = $exampleFileFromConfig; + break; + } + } + + if ($normalizedPath === null) { + if (is_readable($this->getExamplePathFromSource($filename))) { + $normalizedPath = $this->getExamplePathFromSource($filename); + } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) { + $normalizedPath = $this->getExamplePathFromExampleDirectory($filename); + } elseif (is_readable($filename)) { + $normalizedPath = $filename; + } + } + + $lines = $normalizedPath !== null && is_readable($normalizedPath) ? file($normalizedPath) : false; + + return $lines !== false ? $lines : null; + } + + /** + * Get example filepath based on the example directory inside your project. + */ + private function getExamplePathFromExampleDirectory(string $file): string + { + return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file; + } + + /** + * Returns a path to the example file in the given directory.. + */ + private function constructExamplePath(string $directory, string $file): string + { + return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file; + } + + /** + * Get example filepath based on sourcecode. + */ + private function getExamplePathFromSource(string $file): string + { + return sprintf( + '%s%s%s', + trim($this->getSourceDirectory(), '\\/'), + DIRECTORY_SEPARATOR, + trim($file, '"') + ); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php new file mode 100644 index 000000000..2c257dd50 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php @@ -0,0 +1,156 @@ +indent = $indent; + $this->indentString = $indentString; + $this->isFirstLineIndented = $indentFirstLine; + $this->lineLength = $lineLength; + $this->tagFormatter = $tagFormatter ?: new PassthroughFormatter(); + $this->lineEnding = $lineEnding; + } + + /** + * Generate a DocBlock comment. + * + * @param DocBlock $docblock The DocBlock to serialize. + * + * @return string The serialized doc block. + */ + public function getDocComment(DocBlock $docblock): string + { + $indent = str_repeat($this->indentString, $this->indent); + $firstIndent = $this->isFirstLineIndented ? $indent : ''; + // 3 === strlen(' * ') + $wrapLength = $this->lineLength !== null ? $this->lineLength - strlen($indent) - 3 : null; + + $text = $this->removeTrailingSpaces( + $indent, + $this->addAsterisksForEachLine( + $indent, + $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength) + ) + ); + + $comment = $firstIndent . "/**\n"; + if ($text) { + $comment .= $indent . ' * ' . $text . "\n"; + $comment .= $indent . " *\n"; + } + + $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment); + + return str_replace("\n", $this->lineEnding, $comment . $indent . ' */'); + } + + private function removeTrailingSpaces(string $indent, string $text): string + { + return str_replace( + sprintf("\n%s * \n", $indent), + sprintf("\n%s *\n", $indent), + $text + ); + } + + private function addAsterisksForEachLine(string $indent, string $text): string + { + return str_replace( + "\n", + sprintf("\n%s * ", $indent), + $text + ); + } + + private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, ?int $wrapLength): string + { + $text = $docblock->getSummary() . ((string) $docblock->getDescription() ? "\n\n" . $docblock->getDescription() + : ''); + if ($wrapLength !== null) { + $text = wordwrap($text, $wrapLength); + + return $text; + } + + return $text; + } + + private function addTagBlock(DocBlock $docblock, ?int $wrapLength, string $indent, string $comment): string + { + foreach ($docblock->getTags() as $tag) { + $tagText = $this->tagFormatter->format($tag); + if ($wrapLength !== null) { + $tagText = wordwrap($tagText, $wrapLength); + } + + $tagText = str_replace( + "\n", + sprintf("\n%s * ", $indent), + $tagText + ); + + $comment .= sprintf("%s * %s\n", $indent, $tagText); + } + + return $comment; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php new file mode 100644 index 000000000..e580ef6a1 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php @@ -0,0 +1,363 @@ + Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise + * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to + * > verify that a dependency is actually passed. + * + * This Factory also features a Service Locator component that is used to pass the right dependencies to the + * `create` method of a tag; each dependency should be registered as a service or as a parameter. + * + * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass + * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface. + */ +final class StandardTagFactory implements TagFactory +{ + /** PCRE regular expression matching a tag name. */ + public const REGEX_TAGNAME = '[\w\-\_\\\\:]+'; + + /** + * @var array|Factory> An array with a tag as a key, and an + * FQCN to a class that handles it as an array value. + */ + private array $tagHandlerMappings = [ + 'author' => Author::class, + 'covers' => Covers::class, + 'deprecated' => Deprecated::class, + // 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example', + 'link' => LinkTag::class, + 'mixin' => Mixin::class, + 'method' => Method::class, + 'param' => Param::class, + 'property-read' => PropertyRead::class, + 'property' => Property::class, + 'property-write' => PropertyWrite::class, + 'return' => Return_::class, + 'see' => SeeTag::class, + 'since' => Since::class, + 'source' => Source::class, + 'template-covariant' => TemplateCovariant::class, + 'throw' => Throws::class, + 'throws' => Throws::class, + 'uses' => Uses::class, + 'var' => Var_::class, + 'version' => Version::class, + ]; + + /** + * @var array> An array with an annotation as a key, and an + * FQCN to a class that handles it as an array value. + */ + private array $annotationMappings = []; + + /** + * @var ReflectionParameter[][] a lazy-loading cache containing parameters + * for each tagHandler that has been used. + */ + private array $tagHandlerParameterCache = []; + + private FqsenResolver $fqsenResolver; + + /** + * @var mixed[] an array representing a simple Service Locator where we can store parameters and + * services that can be inserted into the Factory Methods of Tag Handlers. + */ + private array $serviceLocator = []; + + /** + * Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers. + * + * If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property + * is used. + * + * @see self::registerTagHandler() to add a new tag handler to the existing default list. + * + * @param array> $tagHandlers + */ + public function __construct(FqsenResolver $fqsenResolver, ?array $tagHandlers = null) + { + $this->fqsenResolver = $fqsenResolver; + if ($tagHandlers !== null) { + $this->tagHandlerMappings = $tagHandlers; + } + + $this->addService($fqsenResolver, FqsenResolver::class); + } + + public function create(string $tagLine, ?TypeContext $context = null): Tag + { + if (!$context) { + $context = new TypeContext(''); + } + + [$tagName, $tagBody] = $this->extractTagParts($tagLine); + + return $this->createTag(trim($tagBody), $tagName, $context); + } + + /** + * @param mixed $value + */ + public function addParameter(string $name, $value): void + { + $this->serviceLocator[$name] = $value; + } + + public function addService(object $service, ?string $alias = null): void + { + $this->serviceLocator[$alias ?? get_class($service)] = $service; + } + + /** {@inheritDoc} */ + public function registerTagHandler(string $tagName, $handler): void + { + Assert::stringNotEmpty($tagName); + if (strpos($tagName, '\\') !== false && $tagName[0] !== '\\') { + throw new InvalidArgumentException( + 'A namespaced tag must have a leading backslash as it must be fully qualified' + ); + } + + if (is_object($handler)) { + Assert::isInstanceOf($handler, Factory::class); + $this->tagHandlerMappings[$tagName] = $handler; + + return; + } + + Assert::classExists($handler); + Assert::implementsInterface($handler, Tag::class); + $this->tagHandlerMappings[$tagName] = $handler; + } + + /** + * Extracts all components for a tag. + * + * @return string[] + */ + private function extractTagParts(string $tagLine): array + { + $matches = []; + if (!preg_match('/^@(' . self::REGEX_TAGNAME . ')((?:[\s\(\{])\s*([^\s].*)|$)/us', $tagLine, $matches)) { + throw new InvalidArgumentException( + 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors' + ); + } + + return array_slice($matches, 1); + } + + /** + * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the + * body was invalid. + */ + private function createTag(string $body, string $name, TypeContext $context): Tag + { + $handlerClassName = $this->findHandlerClassName($name, $context); + $arguments = $this->getArgumentsForParametersFromWiring( + $this->fetchParametersForHandlerFactoryMethod($handlerClassName), + $this->getServiceLocatorWithDynamicParameters($context, $name, $body) + ); + + if (array_key_exists('tagLine', $arguments)) { + $arguments['tagLine'] = sprintf('@%s %s', $name, $body); + } + + try { + $callable = [$handlerClassName, 'create']; + Assert::isCallable($callable); + /** @phpstan-var callable(string): ?Tag $callable */ + $tag = call_user_func_array($callable, $arguments); + + return $tag ?? InvalidTag::create($body, $name); + } catch (InvalidArgumentException $e) { + return InvalidTag::create($body, $name)->withError($e); + } + } + + /** + * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). + * + * @return class-string|Factory + */ + private function findHandlerClassName(string $tagName, TypeContext $context) + { + $handlerClassName = Generic::class; + if (isset($this->tagHandlerMappings[$tagName])) { + $handlerClassName = $this->tagHandlerMappings[$tagName]; + } elseif ($this->isAnnotation($tagName)) { + // TODO: Annotation support is planned for a later stage and as such is disabled for now + $tagName = (string) $this->fqsenResolver->resolve($tagName, $context); + if (isset($this->annotationMappings[$tagName])) { + $handlerClassName = $this->annotationMappings[$tagName]; + } + } + + return $handlerClassName; + } + + /** + * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters. + * + * @param ReflectionParameter[] $parameters + * @param mixed[] $locator + * + * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters + * is provided with this method. + */ + private function getArgumentsForParametersFromWiring(array $parameters, array $locator): array + { + $arguments = []; + foreach ($parameters as $parameter) { + $type = $parameter->getType(); + $typeHint = null; + if ($type instanceof ReflectionNamedType) { + $typeHint = $type->getName(); + if ($typeHint === 'self') { + $declaringClass = $parameter->getDeclaringClass(); + if ($declaringClass !== null) { + $typeHint = $declaringClass->getName(); + } + } + } + + $parameterName = $parameter->getName(); + if (isset($locator[$typeHint ?? ''])) { + $arguments[$parameterName] = $locator[$typeHint ?? '']; + continue; + } + + if (isset($locator[$parameterName])) { + $arguments[$parameterName] = $locator[$parameterName]; + continue; + } + + $arguments[$parameterName] = null; + } + + return $arguments; + } + + /** + * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given + * tag handler class name. + * + * @param class-string|Factory $handler + * + * @return ReflectionParameter[] + */ + private function fetchParametersForHandlerFactoryMethod($handler): array + { + $handlerClassName = is_object($handler) ? get_class($handler) : $handler; + + if (!isset($this->tagHandlerParameterCache[$handlerClassName])) { + $methodReflection = new ReflectionMethod($handlerClassName, 'create'); + $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); + } + + return $this->tagHandlerParameterCache[$handlerClassName]; + } + + /** + * Returns a copy of this class' Service Locator with added dynamic parameters, + * such as the tag's name, body and Context. + * + * @param TypeContext $context The Context (namespace and aliases) that may be + * passed and is used to resolve FQSENs. + * @param string $tagName The name of the tag that may be + * passed onto the factory method of the Tag class. + * @param string $tagBody The body of the tag that may be + * passed onto the factory method of the Tag class. + * + * @return mixed[] + */ + private function getServiceLocatorWithDynamicParameters( + TypeContext $context, + string $tagName, + string $tagBody + ): array { + return array_merge( + $this->serviceLocator, + [ + 'name' => $tagName, + 'body' => $tagBody, + TypeContext::class => $context, + ] + ); + } + + /** + * Returns whether the given tag belongs to an annotation. + * + * @todo this method should be populated once we implement Annotation notation support. + */ + private function isAnnotation(string $tagContent): bool + { + // 1. Contains a namespace separator + // 2. Contains parenthesis + // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part + // of the annotation class name matches the found tag name + + return false; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php new file mode 100644 index 000000000..7cf07b4dd --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php @@ -0,0 +1,31 @@ +|Factory $handler FQCN of handler. + * + * @throws InvalidArgumentException If the tag name is not a string. + * @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but + * does not start with a backslash. + * @throws InvalidArgumentException If the handler is not a string. + * @throws InvalidArgumentException If the handler is not an existing class. + * @throws InvalidArgumentException If the handler does not implement the {@see Tag} interface. + */ + public function registerTagHandler(string $tagName, $handler): void; +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php new file mode 100644 index 000000000..290e5a957 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php @@ -0,0 +1,102 @@ +authorName = $authorName; + $this->authorEmail = $authorEmail; + } + + /** + * Gets the author's name. + * + * @return string The author's name. + */ + public function getAuthorName(): string + { + return $this->authorName; + } + + /** + * Returns the author's email. + * + * @return string The author's email. + */ + public function getEmail(): string + { + return $this->authorEmail; + } + + /** + * Returns this tag in string form. + */ + public function __toString(): string + { + if ($this->authorEmail) { + $authorEmail = '<' . $this->authorEmail . '>'; + } else { + $authorEmail = ''; + } + + $authorName = $this->authorName; + + return $authorName . ($authorEmail !== '' ? ($authorName !== '' ? ' ' : '') . $authorEmail : ''); + } + + /** + * Attempts to create a new Author object based on the tag body. + */ + public static function create(string $body): ?self + { + $splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches); + if (!$splitTagContent) { + return null; + } + + $authorName = trim($matches[1]); + $email = isset($matches[2]) ? trim($matches[2]) : ''; + + return new static($authorName, $email); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php new file mode 100644 index 000000000..98b0d881d --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php @@ -0,0 +1,53 @@ +name; + } + + public function getDescription(): ?Description + { + return $this->description; + } + + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php new file mode 100644 index 000000000..022594e20 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php @@ -0,0 +1,99 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?FqsenResolver $resolver = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + Assert::notNull($resolver); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + + return new static( + self::resolveFqsen($parts[0], $resolver, $context), + $descriptionFactory->create($parts[1] ?? '', $context) + ); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the structural element this tag refers to. + */ + public function getReference(): Fqsen + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php new file mode 100644 index 000000000..fff591f9d --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php @@ -0,0 +1,108 @@ +version = $version; + $this->description = $description; + } + + /** + * @return static + */ + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + if ($body === null || $body === '') { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return new static( + null, + $descriptionFactory !== null ? $descriptionFactory->create($body, $context) : null + ); + } + + Assert::notNull($descriptionFactory); + + return new static( + $matches[1], + $descriptionFactory->create($matches[2] ?? '', $context) + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php new file mode 100644 index 000000000..3ddbb686c --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php @@ -0,0 +1,197 @@ +filePath = $filePath; + $this->startingLine = $startingLine; + $this->lineCount = $lineCount; + if ($content !== null) { + $this->content = trim($content); + } + + $this->isURI = $isURI; + } + + public function getContent(): string + { + if ($this->content === null || $this->content === '') { + $filePath = $this->filePath; + if ($this->isURI) { + $filePath = $this->isUriRelative($this->filePath) + ? str_replace('%2F', '/', rawurlencode($this->filePath)) + : $this->filePath; + } + + return trim($filePath); + } + + return $this->content; + } + + public function getDescription(): ?string + { + return $this->content; + } + + public static function create(string $body): ?Tag + { + // File component: File path in quotes or File URI / Source information + if (!preg_match('/^\s*(?:(\"[^\"]+\")|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) { + return null; + } + + $filePath = null; + $fileUri = null; + if (array_key_exists(1, $matches) && $matches[1] !== '') { + $filePath = $matches[1]; + } else { + $fileUri = array_key_exists(2, $matches) ? $matches[2] : ''; + } + + $startingLine = 1; + $lineCount = 0; + $description = null; + + if (array_key_exists(3, $matches)) { + $description = $matches[3]; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)(?:\s+((?1))\s*)?(.*)$/sux', $matches[3], $contentMatches)) { + $startingLine = (int) $contentMatches[1]; + if (isset($contentMatches[2])) { + $lineCount = (int) $contentMatches[2]; + } + + if (array_key_exists(3, $contentMatches)) { + $description = $contentMatches[3]; + } + } + } + + return new static( + $filePath ?? ($fileUri ?? ''), + $fileUri !== null, + $startingLine, + $lineCount, + $description + ); + } + + /** + * Returns the file path. + * + * @return string Path to a file to use as an example. + * May also be an absolute URI. + */ + public function getFilePath(): string + { + return trim($this->filePath, '"'); + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + $filePath = $this->filePath; + $isDefaultLine = $this->startingLine === 1 && $this->lineCount === 0; + $startingLine = !$isDefaultLine ? (string) $this->startingLine : ''; + $lineCount = !$isDefaultLine ? (string) $this->lineCount : ''; + $content = (string) $this->content; + + return $filePath + . ($startingLine !== '' + ? ($filePath !== '' ? ' ' : '') . $startingLine + : '') + . ($lineCount !== '' + ? ($filePath !== '' || $startingLine !== '' ? ' ' : '') . $lineCount + : '') + . ($content !== '' + ? ($filePath !== '' || $startingLine !== '' || $lineCount !== '' ? ' ' : '') . $content + : ''); + } + + /** + * Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute). + */ + private function isUriRelative(string $uri): bool + { + return strpos($uri, ':') === false; + } + + public function getStartingLine(): int + { + return $this->startingLine; + } + + public function getLineCount(): int + { + return $this->lineCount; + } + + public function getName(): string + { + return 'example'; + } + + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Extends_.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Extends_.php new file mode 100644 index 000000000..831af03d6 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Extends_.php @@ -0,0 +1,48 @@ +name = 'extends'; + $this->type = $type; + $this->description = $description; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create(string $body): ?Tag + { + Deprecation::trigger( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361', + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + ); + + return null; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php new file mode 100644 index 000000000..f8bababfb --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php @@ -0,0 +1,136 @@ + true, 'lines' => true]); + $this->lexer = new Lexer($config); + $constParser = new ConstExprParser($config); + $this->parser = new PhpDocParser( + $config, + new TypeParser($config, $constParser), + $constParser + ); + } else { + $this->lexer = new Lexer(true); + $constParser = new ConstExprParser(true, true, ['lines' => true, 'indexes' => true]); + $this->parser = new PhpDocParser( + new TypeParser($constParser, true, ['lines' => true, 'indexes' => true]), + $constParser, + true, + true, + ['lines' => true, 'indexes' => true], + true + ); + } + + $this->factories = $factories; + } + + public function create(string $tagLine, ?TypeContext $context = null): Tag + { + $tokens = $this->tokenizeLine($tagLine . "\n"); + $ast = $this->parser->parseTag($tokens); + if (property_exists($ast->value, 'description') === true) { + $ast->value->setAttribute( + 'description', + rtrim($ast->value->description . $tokens->joinUntil(Lexer::TOKEN_END), "\n") + ); + } + + if ($context === null) { + $context = new TypeContext(''); + } + + try { + foreach ($this->factories as $factory) { + if ($factory->supports($ast, $context)) { + return $factory->create($ast, $context); + } + } + } catch (RuntimeException $e) { + return InvalidTag::create((string) $ast->value, 'method')->withError($e); + } + + return InvalidTag::create( + (string) $ast->value, + $ast->name + ); + } + + /** + * Solve the issue with the lexer not tokenizing the line correctly + * + * This method is a workaround for the lexer that includes newline tokens with spaces. For + * phpstan this isn't an issue, as it doesn't do a lot of things with the indentation of descriptions. + * But for us is important to keep the indentation of the descriptions, so we need to fix the lexer output. + */ + private function tokenizeLine(string $tagLine): TokenIterator + { + $tokens = $this->lexer->tokenize($tagLine); + $fixed = []; + foreach ($tokens as $token) { + if (($token[1] === Lexer::TOKEN_PHPDOC_EOL) && rtrim($token[0], " \t") !== $token[0]) { + $fixed[] = [ + rtrim($token[Lexer::VALUE_OFFSET], " \t"), + Lexer::TOKEN_PHPDOC_EOL, + $token[2] ?? 0, + ]; + $fixed[] = [ + ltrim($token[Lexer::VALUE_OFFSET], "\n\r"), + Lexer::TOKEN_HORIZONTAL_WS, + ($token[2] ?? 0) + 1, + ]; + continue; + } + + $fixed[] = $token; + } + + return new TokenIterator($fixed); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ExtendsFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ExtendsFactory.php new file mode 100644 index 000000000..9a5528de2 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ExtendsFactory.php @@ -0,0 +1,52 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ExtendsTagValueNode && $node->name === '@extends'; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ExtendsTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Extends_( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Factory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Factory.php new file mode 100644 index 000000000..190d3ff85 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Factory.php @@ -0,0 +1,41 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ImplementsTagValueNode && $node->name === '@implements'; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ImplementsTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Implements_( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodFactory.php new file mode 100644 index 000000000..17c768f85 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodFactory.php @@ -0,0 +1,83 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, MethodTagValueNode::class); + + return new Method( + $tagValue->methodName, + [], + $this->createReturnType($tagValue, $context), + $tagValue->isStatic, + $this->descriptionFactory->create($tagValue->description, $context), + false, + array_map( + function (MethodTagValueParameterNode $param) use ($context) { + return new MethodParameter( + trim($param->parameterName, '$'), + $param->type === null ? new Mixed_() : $this->typeResolver->createType( + $param->type, + $context + ), + $param->isReference, + $param->isVariadic, + $param->defaultValue === null ? + MethodParameter::NO_DEFAULT_VALUE : + (string) $param->defaultValue + ); + }, + $tagValue->parameters + ), + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof MethodTagValueNode; + } + + private function createReturnType(MethodTagValueNode $tagValue, Context $context): Type + { + if ($tagValue->returnType === null) { + return new Void_(); + } + + return $this->typeResolver->createType($tagValue->returnType, $context); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodParameterFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodParameterFactory.php new file mode 100644 index 000000000..da968967b --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/MethodParameterFactory.php @@ -0,0 +1,100 @@ +{$method}($defaultValue); + } + + return ''; + } + + private function formatDouble(float $defaultValue): string + { + return var_export($defaultValue, true); + } + + /** + * @param mixed $defaultValue + */ + private function formatNull($defaultValue): string + { + return 'null'; + } + + private function formatInteger(int $defaultValue): string + { + return var_export($defaultValue, true); + } + + private function formatString(string $defaultValue): string + { + return var_export($defaultValue, true); + } + + private function formatBoolean(bool $defaultValue): string + { + return var_export($defaultValue, true); + } + + /** + * @param array<(array|int|float|bool|string|object|null)> $defaultValue + */ + private function formatArray(array $defaultValue): string + { + $formatedValue = '['; + + foreach ($defaultValue as $key => $value) { + $method = 'format' . ucfirst(gettype($value)); + if (!method_exists($this, $method)) { + continue; + } + + $formatedValue .= $this->{$method}($value); + + if ($key === array_key_last($defaultValue)) { + continue; + } + + $formatedValue .= ','; + } + + return $formatedValue . ']'; + } + + private function formatObject(object $defaultValue): string + { + return 'new ' . get_class($defaultValue) . '()'; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PHPStanFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PHPStanFactory.php new file mode 100644 index 000000000..cf04a06e6 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PHPStanFactory.php @@ -0,0 +1,16 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + + if ($tagValue instanceof InvalidTagValueNode) { + Deprecation::trigger( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/362', + sprintf( + 'Param tag value "%s" is invalid, falling back to legacy parsing. Please update your docblocks.', + $tagValue->value + ) + ); + + return Param::create($tagValue->value, $this->typeResolver, $this->descriptionFactory, $context); + } + + Assert::isInstanceOfAny( + $tagValue, + [ + ParamTagValueNode::class, + TypelessParamTagValueNode::class, + ] + ); + + if (($tagValue->type ?? null) instanceof OffsetAccessTypeNode) { + return InvalidTag::create( + (string) $tagValue, + 'param' + ); + } + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Param( + trim($tagValue->parameterName, '$'), + $this->typeResolver->createType($tagValue->type ?? new IdentifierTypeNode('mixed'), $context), + $tagValue->isVariadic, + $this->descriptionFactory->create($description, $context), + $tagValue->isReference + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ParamTagValueNode + || $node->value instanceof TypelessParamTagValueNode + || $node->name === '@param'; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyFactory.php new file mode 100644 index 000000000..b744ed08f --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyFactory.php @@ -0,0 +1,54 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Property( + trim($tagValue->propertyName, '$'), + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof PropertyTagValueNode && $node->name === '@property'; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyReadFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyReadFactory.php new file mode 100644 index 000000000..b0898aa73 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyReadFactory.php @@ -0,0 +1,54 @@ +typeResolver = $typeResolver; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new PropertyRead( + trim($tagValue->propertyName, '$'), + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof PropertyTagValueNode && $node->name === '@property-read'; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyWriteFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyWriteFactory.php new file mode 100644 index 000000000..749b1eda9 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/PropertyWriteFactory.php @@ -0,0 +1,54 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new PropertyWrite( + trim($tagValue->propertyName, '$'), + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof PropertyTagValueNode && $node->name === '@property-write'; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ReturnFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ReturnFactory.php new file mode 100644 index 000000000..4a17dc245 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/ReturnFactory.php @@ -0,0 +1,52 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ReturnTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Return_( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ReturnTagValueNode; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php new file mode 100644 index 000000000..f6f0bb5a4 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php @@ -0,0 +1,25 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ExtendsTagValueNode && $node->name === '@template-extends'; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ExtendsTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new TemplateExtends( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateFactory.php new file mode 100644 index 000000000..9e4b3c896 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateFactory.php @@ -0,0 +1,56 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + + Assert::isInstanceOf($tagValue, TemplateTagValueNode::class); + $name = $tagValue->name; + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Template( + $name, + $this->typeResolver->createType($tagValue->bound, $context), + $this->typeResolver->createType($tagValue->default, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof TemplateTagValueNode && $node->name === '@template'; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateImplementsFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateImplementsFactory.php new file mode 100644 index 000000000..bb3d11dad --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/TemplateImplementsFactory.php @@ -0,0 +1,52 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof ImplementsTagValueNode && $node->name === '@template-implements'; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ImplementsTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new TemplateImplements( + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/VarFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/VarFactory.php new file mode 100644 index 000000000..479ceb27d --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/VarFactory.php @@ -0,0 +1,54 @@ +descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, VarTagValueNode::class); + + $description = $tagValue->getAttribute('description'); + if (is_string($description) === false) { + $description = $tagValue->description; + } + + return new Var_( + trim($tagValue->variableName, '$'), + $this->typeResolver->createType($tagValue->type, $context), + $this->descriptionFactory->create($description, $context) + ); + } + + public function supports(PhpDocTagNode $node, Context $context): bool + { + return $node->value instanceof VarTagValueNode; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php new file mode 100644 index 000000000..36b9983ea --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php @@ -0,0 +1,24 @@ +maxLen = max($this->maxLen, strlen($tag->getName())); + } + } + + /** + * Formats the given tag to return a simple plain text version. + */ + public function format(Tag $tag): string + { + return '@' . $tag->getName() . + str_repeat( + ' ', + $this->maxLen - strlen($tag->getName()) + 1 + ) . + $tag; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php new file mode 100644 index 000000000..2afdfe55d --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php @@ -0,0 +1,30 @@ +getName() . ' ' . $tag); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php new file mode 100644 index 000000000..bc1ab10c1 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php @@ -0,0 +1,89 @@ +validateTagName($name); + + $this->name = $name; + $this->description = $description; + } + + /** + * Creates a new tag that represents any unknown tag type. + * + * @return static + */ + public static function create( + string $body, + string $name = '', + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($name); + Assert::notNull($descriptionFactory); + + $description = $body !== '' ? $descriptionFactory->create($body, $context) : null; + + return new static($name, $description); + } + + /** + * Returns the tag as a serialized string + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + return $description; + } + + /** + * Validates if the tag name matches the expected format, otherwise throws an exception. + */ + private function validateTagName(string $name): void + { + if (!preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) { + throw new InvalidArgumentException( + 'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, ' + . 'hyphens and backslashes.' + ); + } + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Implements_.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Implements_.php new file mode 100644 index 000000000..2070828cc --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Implements_.php @@ -0,0 +1,48 @@ +name = 'implements'; + $this->type = $type; + $this->description = $description; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create(string $body): ?Tag + { + Deprecation::trigger( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361', + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + ); + + return null; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php new file mode 100644 index 000000000..848f34d7b --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php @@ -0,0 +1,150 @@ +name = $name; + $this->body = $body; + } + + public function getException(): ?Throwable + { + return $this->throwable; + } + + public function getName(): string + { + return $this->name; + } + + public static function create(string $body, string $name = ''): self + { + return new self($name, $body); + } + + public function withError(Throwable $exception): self + { + $this->flattenExceptionBacktrace($exception); + $tag = new self($this->name, $this->body); + $tag->throwable = $exception; + + return $tag; + } + + /** + * Removes all complex types from backtrace + * + * Not all objects are serializable. So we need to remove them from the + * stored exception to be sure that we do not break existing library usage. + */ + private function flattenExceptionBacktrace(Throwable $exception): void + { + $traceProperty = (new ReflectionClass(Exception::class))->getProperty('trace'); + if (PHP_VERSION_ID < 80100) { + $traceProperty->setAccessible(true); + } + + do { + $trace = $exception->getTrace(); + if (isset($trace[0]['args'])) { + $trace = array_map( + function (array $call): array { + $call['args'] = array_map([$this, 'flattenArguments'], $call['args'] ?? []); + + return $call; + }, + $trace + ); + } + + $traceProperty->setValue($exception, $trace); + $exception = $exception->getPrevious(); + } while ($exception !== null); + + if (PHP_VERSION_ID >= 80100) { + return; + } + + $traceProperty->setAccessible(false); + } + + /** + * @param mixed $value + * + * @return mixed + * + * @throws ReflectionException + */ + private function flattenArguments($value) + { + if ($value instanceof Closure) { + $closureReflection = new ReflectionFunction($value); + $value = sprintf( + '(Closure at %s:%s)', + $closureReflection->getFileName(), + $closureReflection->getStartLine() + ); + } elseif (is_object($value)) { + $value = sprintf('object(%s)', get_class($value)); + } elseif (is_resource($value)) { + $value = sprintf('resource(%s)', get_resource_type($value)); + } elseif (is_array($value)) { + $value = array_map([$this, 'flattenArguments'], $value); + } + + return $value; + } + + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } + + public function __toString(): string + { + return $this->body; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php new file mode 100644 index 000000000..fcb6ec193 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php @@ -0,0 +1,76 @@ +link = $link; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + return new static($parts[0], $description); + } + + /** + * Gets the link + */ + public function getLink(): string + { + return $this->link; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $link = $this->link; + + return $link . ($description !== '' ? ($link !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php new file mode 100644 index 000000000..41e0eed19 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php @@ -0,0 +1,355 @@ +> $arguments + * @param MethodParameter[] $parameters + * @phpstan-param array $arguments + */ + public function __construct( + string $methodName, + array $arguments = [], + ?Type $returnType = null, + bool $static = false, + ?Description $description = null, + bool $returnsReference = false, + ?array $parameters = null + ) { + Assert::stringNotEmpty($methodName); + + if ($returnType === null) { + $returnType = new Void_(); + } + + $arguments = $this->filterArguments($arguments); + + $this->methodName = $methodName; + $this->returnType = $returnType; + $this->isStatic = $static; + $this->description = $description; + $this->returnsReference = $returnsReference; + $this->parameters = $parameters ?? $this->fromLegacyArguments($arguments); + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): ?self { + trigger_error( + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + E_USER_DEPRECATED + ); + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + // 1. none or more whitespace + // 2. optionally the keyword "static" followed by whitespace + // 3. optionally a word with underscores followed by whitespace : as + // type for the return value + // 4. optionally an ampersand followed or not by whitespace : as + // a reference + // 5. then optionally a word with underscores followed by () and + // whitespace : as method name as used by phpDocumentor + // 6. then a word with underscores, followed by ( and any character + // until a ) and whitespace : as method name with signature + // 7. any remaining text : as description + if ( + !preg_match( + '/^ + # Static keyword + # Declares a static method ONLY if type is also present + (?: + (static) + \s+ + )? + # Return type + (?: + ( + (?:[\w\|_\\\\]*\$this[\w\|_\\\\]*) + | + (?: + (?:[\w\|_\\\\]+) + # array notation + (?:\[\])* + )*+ + ) + \s+ + )? + # Returns reference + (?: + (&) + \s* + )? + # Method name + ([\w_]+) + # Arguments + (?: + \(([^\)]*)\) + )? + \s* + # Description + (.*) + $/sux', + $body, + $matches + ) + ) { + return null; + } + + [, $static, $returnType, $returnsReference, $methodName, $argumentLines, $description] = $matches; + + $static = $static === 'static'; + + if ($returnType === '') { + $returnType = 'void'; + } + + $returnsReference = $returnsReference === '&'; + + $returnType = $typeResolver->resolve($returnType, $context); + $description = $descriptionFactory->create($description, $context); + + /** @phpstan-var array $arguments */ + $arguments = []; + if ($argumentLines !== '') { + $argumentsExploded = explode(',', $argumentLines); + foreach ($argumentsExploded as $argument) { + $argument = explode(' ', self::stripRestArg(trim($argument)), 2); + if (strpos($argument[0], '$') === 0) { + $argumentName = substr($argument[0], 1); + $argumentType = new Mixed_(); + } else { + $argumentType = $typeResolver->resolve($argument[0], $context); + $argumentName = ''; + if (isset($argument[1])) { + $argument[1] = self::stripRestArg($argument[1]); + $argumentName = substr($argument[1], 1); + } + } + + $arguments[] = ['name' => $argumentName, 'type' => $argumentType]; + } + } + + return new static( + $methodName, + $arguments, + $returnType, + $static, + $description, + $returnsReference + ); + } + + /** + * Retrieves the method name. + */ + public function getMethodName(): string + { + return $this->methodName; + } + + /** + * @deprecated Method deprecated, use {@see self::getParameters()} + * + * @return array> + * @phpstan-return array + */ + public function getArguments(): array + { + trigger_error('Method deprecated, use ::getParameters()', E_USER_DEPRECATED); + + return array_map( + static function (MethodParameter $methodParameter) { + return ['name' => $methodParameter->getName(), 'type' => $methodParameter->getType()]; + }, + $this->parameters + ); + } + + /** @return MethodParameter[] */ + public function getParameters(): array + { + return $this->parameters; + } + + /** + * Checks whether the method tag describes a static method or not. + * + * @return bool TRUE if the method declaration is for a static method, FALSE otherwise. + */ + public function isStatic(): bool + { + return $this->isStatic; + } + + public function getReturnType(): Type + { + return $this->returnType; + } + + public function returnsReference(): bool + { + return $this->returnsReference; + } + + public function __toString(): string + { + $arguments = []; + foreach ($this->parameters as $parameter) { + $arguments[] = (string) $parameter; + } + + $argumentStr = '(' . implode(', ', $arguments) . ')'; + + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $static = $this->isStatic ? 'static' : ''; + + $returnType = (string) $this->returnType; + + $methodName = $this->methodName; + + $reference = $this->returnsReference ? '&' : ''; + + return $static + . ($returnType !== '' ? ($static !== '' ? ' ' : '') . $returnType : '') + . ($methodName !== '' ? ($static !== '' || $returnType !== '' ? ' ' : '') . $reference . $methodName : '') + . $argumentStr + . ($description !== '' ? ' ' . $description : ''); + } + + /** + * @param mixed[][]|string[] $arguments + * @phpstan-param array $arguments + * + * @return mixed[][] + * @phpstan-return array + */ + private function filterArguments(array $arguments = []): array + { + $result = []; + foreach ($arguments as $argument) { + if (is_string($argument)) { + $argument = ['name' => $argument]; + } + + if (!isset($argument['type'])) { + $argument['type'] = new Mixed_(); + } + + $keys = array_keys($argument); + sort($keys); + if ($keys !== ['name', 'type']) { + throw new InvalidArgumentException( + 'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true) + ); + } + + $result[] = $argument; + } + + return $result; + } + + private static function stripRestArg(string $argument): string + { + if (strpos($argument, '...') === 0) { + $argument = trim(substr($argument, 3)); + } + + return $argument; + } + + /** + * @param array{name: string, type: Type} $arguments + * @phpstan-param array $arguments + * + * @return MethodParameter[] + */ + private function fromLegacyArguments(array $arguments): array + { + trigger_error( + 'Create method parameters via legacy format is deprecated add parameters via the constructor', + E_USER_DEPRECATED + ); + + return array_map( + static function ($arg) { + return new MethodParameter( + $arg['name'], + $arg['type'] + ); + }, + $arguments + ); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/MethodParameter.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/MethodParameter.php new file mode 100644 index 000000000..ceb870241 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/MethodParameter.php @@ -0,0 +1,91 @@ +type = $type; + $this->isReference = $isReference; + $this->isVariadic = $isVariadic; + $this->name = $name; + $this->defaultValue = $defaultValue; + } + + public function getName(): string + { + return $this->name; + } + + public function getType(): Type + { + return $this->type; + } + + public function isReference(): bool + { + return $this->isReference; + } + + public function isVariadic(): bool + { + return $this->isVariadic; + } + + public function getDefaultValue(): ?string + { + if ($this->defaultValue === self::NO_DEFAULT_VALUE) { + return null; + } + + return (new MethodParameterFactory())->format($this->defaultValue); + } + + public function __toString(): string + { + return $this->getType() . ' ' . + ($this->isReference() ? '&' : '') . + ($this->isVariadic() ? '...' : '') . + '$' . $this->getName() . + ( + $this->defaultValue !== self::NO_DEFAULT_VALUE ? + (new MethodParameterFactory())->format($this->defaultValue) : + '' + ); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Mixin.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Mixin.php new file mode 100644 index 000000000..c15d30c11 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Mixin.php @@ -0,0 +1,51 @@ +name = 'mixin'; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$type, $description] = self::extractTypeFromBody($body); + + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + + return new static($type, $description); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php new file mode 100644 index 000000000..6d46686d5 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php @@ -0,0 +1,185 @@ +name = 'param'; + $this->variableName = $variableName; + $this->type = $type; + $this->isVariadic = $isVariadic; + $this->description = $description; + $this->isReference = $isReference; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Deprecation::triggerIfCalledFromOutside( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361', + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + ); + + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + $isVariadic = false; + $isReference = false; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && !self::strStartsWithVariable($firstPart)) { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ or ...$ or &$ or &...$ it must be the variable name + if (isset($parts[0]) && self::strStartsWithVariable($parts[0])) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + if (strpos($variableName, '$') === 0) { + $variableName = substr($variableName, 1); + } elseif (strpos($variableName, '&$') === 0) { + $isReference = true; + $variableName = substr($variableName, 2); + } elseif (strpos($variableName, '...$') === 0) { + $isVariadic = true; + $variableName = substr($variableName, 4); + } elseif (strpos($variableName, '&...$') === 0) { + $isVariadic = true; + $isReference = true; + $variableName = substr($variableName, 5); + } + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $isVariadic, $description, $isReference); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns whether this tag is variadic. + */ + public function isVariadic(): bool + { + return $this->isVariadic; + } + + /** + * Returns whether this tag is passed by reference. + */ + public function isReference(): bool + { + return $this->isReference; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $variableName = ''; + if ($this->variableName !== null && $this->variableName !== '') { + $variableName .= ($this->isReference ? '&' : '') . ($this->isVariadic ? '...' : ''); + $variableName .= '$' . $this->variableName; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } + + private static function strStartsWithVariable(string $str): bool + { + return strpos($str, '$') === 0 + || + strpos($str, '...$') === 0 + || + strpos($str, '&$') === 0 + || + strpos($str, '&...$') === 0; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php new file mode 100644 index 000000000..3328b0807 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php @@ -0,0 +1,132 @@ +name = 'property'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Deprecation::triggerIfCalledFromOutside( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361', + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + ); + + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description !== null) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName !== null && $this->variableName !== '') { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php new file mode 100644 index 000000000..8ac1eb02b --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php @@ -0,0 +1,132 @@ +name = 'property-read'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Deprecation::triggerIfCalledFromOutside( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361', + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + ); + + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description !== null) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName !== null && $this->variableName !== '') { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php new file mode 100644 index 000000000..57e7eb103 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php @@ -0,0 +1,132 @@ +name = 'property-write'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Deprecation::triggerIfCalledFromOutside( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361', + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + ); + + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php new file mode 100644 index 000000000..e4e7e31c6 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php @@ -0,0 +1,37 @@ +fqsen = $fqsen; + } + + /** + * @return string string representation of the referenced fqsen + */ + public function __toString(): string + { + return (string) $this->fqsen; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php new file mode 100644 index 000000000..e7dea868d --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php @@ -0,0 +1,22 @@ +uri = $uri; + } + + public function __toString(): string + { + return $this->uri; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php new file mode 100644 index 000000000..7e9b0c7a9 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php @@ -0,0 +1,63 @@ +name = 'return'; + $this->type = $type; + $this->description = $description; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Deprecation::triggerIfCalledFromOutside( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361', + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + ); + + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$type, $description] = self::extractTypeFromBody($body); + + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + + return new static($type, $description); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php new file mode 100644 index 000000000..e7330e887 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php @@ -0,0 +1,104 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?FqsenResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + // https://tools.ietf.org/html/rfc2396#section-3 + if (preg_match('#\w://\w#', $parts[0])) { + return new static(new Url($parts[0]), $description); + } + + return new static(new FqsenRef(self::resolveFqsen($parts[0], $typeResolver, $context)), $description); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the ref of this tag. + */ + public function getReference(): Reference + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php new file mode 100644 index 000000000..8ec59e91c --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php @@ -0,0 +1,102 @@ +version = $version; + $this->description = $description; + } + + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): ?self { + if ($body === null || $body === '') { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + Assert::notNull($descriptionFactory); + + return new static( + $matches[1], + $descriptionFactory->create($matches[2] ?? '', $context) + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description !== null) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php new file mode 100644 index 000000000..f6b4f57fa --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php @@ -0,0 +1,115 @@ +startingLine = (int) $startingLine; + $this->lineCount = $lineCount !== null ? (int) $lineCount : null; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + + $startingLine = 1; + $lineCount = null; + $description = null; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) { + $startingLine = (int) $matches[1]; + if (isset($matches[2]) && $matches[2] !== '') { + $lineCount = (int) $matches[2]; + } + + $description = $matches[3]; + } + + return new static($startingLine, $lineCount, $descriptionFactory->create($description ?? '', $context)); + } + + /** + * Gets the starting line. + * + * @return int The starting line, relative to the structural element's + * location. + */ + public function getStartingLine(): int + { + return $this->startingLine; + } + + /** + * Returns the number of lines. + * + * @return int|null The number of lines, relative to the starting line. NULL + * means "to the end". + */ + public function getLineCount(): ?int + { + return $this->lineCount; + } + + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $startingLine = (string) $this->startingLine; + + $lineCount = $this->lineCount !== null ? ' ' . $this->lineCount : ''; + + return $startingLine + . $lineCount + . ($description !== '' + ? ' ' . $description + : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php new file mode 100644 index 000000000..89e29e5ea --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php @@ -0,0 +1,87 @@ +type; + } + + /** + * @return string[] + */ + protected static function extractTypeFromBody(string $body): array + { + $type = ''; + $nestingLevel = 0; + for ($i = 0, $iMax = strlen($body); $i < $iMax; $i++) { + $character = $body[$i]; + + if ($nestingLevel === 0 && trim($character) === '') { + break; + } + + $type .= $character; + if (in_array($character, ['<', '(', '[', '{'])) { + $nestingLevel++; + continue; + } + + if (in_array($character, ['>', ')', ']', '}'])) { + $nestingLevel--; + continue; + } + } + + if ($nestingLevel < 0 || $nestingLevel > 0) { + throw new InvalidArgumentException( + sprintf('Could not find type in %s, please check for malformed notations', $body) + ); + } + + $description = trim(substr($body, strlen($type))); + + return [$type, $description]; + } + + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $type = (string) $this->type; + + return $type . ($description !== '' ? ($type !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Template.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Template.php new file mode 100644 index 000000000..cfd6b6996 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Template.php @@ -0,0 +1,92 @@ +name = 'template'; + $this->templateName = $templateName; + $this->bound = $bound; + $this->default = $default; + $this->description = $description; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create(string $body): ?Tag + { + Deprecation::trigger( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361', + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + ); + + return null; + } + + public function getTemplateName(): string + { + return $this->templateName; + } + + public function getBound(): ?Type + { + return $this->bound; + } + + public function getDefault(): ?Type + { + return $this->default; + } + + public function __toString(): string + { + $bound = $this->bound !== null ? ' of ' . $this->bound : ''; + $default = $this->default !== null ? ' = ' . $this->default : ''; + + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + return $this->templateName . $bound . $default . ($description !== '' ? ' ' . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateCovariant.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateCovariant.php new file mode 100644 index 000000000..d5d509872 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateCovariant.php @@ -0,0 +1,51 @@ +name = 'template-covariant'; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$type, $description] = self::extractTypeFromBody($body); + + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + + return new static($type, $description); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateExtends.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateExtends.php new file mode 100644 index 000000000..8de4681d8 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateExtends.php @@ -0,0 +1,29 @@ +name = 'template-extends'; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateImplements.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateImplements.php new file mode 100644 index 000000000..1fb073d99 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TemplateImplements.php @@ -0,0 +1,29 @@ +name = 'template-implements'; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php new file mode 100644 index 000000000..e08184681 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php @@ -0,0 +1,51 @@ +name = 'throws'; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$type, $description] = self::extractTypeFromBody($body); + + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + + return new static($type, $description); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php new file mode 100644 index 000000000..d9aa3608f --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php @@ -0,0 +1,98 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?FqsenResolver $resolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($resolver); + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + + return new static( + self::resolveFqsen($parts[0], $resolver, $context), + $descriptionFactory->create($parts[1] ?? '', $context) + ); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the structural element this tag refers to. + */ + public function getReference(): Fqsen + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php new file mode 100644 index 000000000..b0bd8c21e --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php @@ -0,0 +1,132 @@ +name = 'var'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Deprecation::triggerIfCalledFromOutside( + 'phpdocumentor/reflection-docblock', + 'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361', + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + ); + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $type = null; + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description !== null) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName !== null && $this->variableName !== '') { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php new file mode 100644 index 000000000..8ea96ed38 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php @@ -0,0 +1,105 @@ +version = $version; + $this->description = $description; + } + + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): ?self { + if ($body === null || $body === '') { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + $description = null; + if ($descriptionFactory !== null) { + $description = $descriptionFactory->create($matches[2] ?? '', $context); + } + + return new static( + $matches[1], + $description + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php new file mode 100644 index 000000000..ca33fbbb6 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php @@ -0,0 +1,328 @@ +descriptionFactory = $descriptionFactory; + $this->tagFactory = $tagFactory; + } + + /** + * Factory method for easy instantiation. + * + * @param array|Factory> $additionalTags + */ + public static function createInstance(array $additionalTags = []): DocBlockFactoryInterface + { + $fqsenResolver = new FqsenResolver(); + $tagFactory = new StandardTagFactory($fqsenResolver); + $descriptionFactory = new DescriptionFactory($tagFactory); + $typeResolver = new TypeResolver($fqsenResolver); + + $phpstanTagFactory = new AbstractPHPStanFactory( + new ParamFactory($typeResolver, $descriptionFactory), + new VarFactory($typeResolver, $descriptionFactory), + new ReturnFactory($typeResolver, $descriptionFactory), + new PropertyFactory($typeResolver, $descriptionFactory), + new PropertyReadFactory($typeResolver, $descriptionFactory), + new PropertyWriteFactory($typeResolver, $descriptionFactory), + new MethodFactory($typeResolver, $descriptionFactory), + new ImplementsFactory($typeResolver, $descriptionFactory), + new ExtendsFactory($typeResolver, $descriptionFactory), + new TemplateFactory($typeResolver, $descriptionFactory), + new TemplateImplementsFactory($typeResolver, $descriptionFactory), + new TemplateExtendsFactory($typeResolver, $descriptionFactory), + ); + + $tagFactory->addService($descriptionFactory); + $tagFactory->addService($typeResolver); + $tagFactory->registerTagHandler('param', $phpstanTagFactory); + $tagFactory->registerTagHandler('var', $phpstanTagFactory); + $tagFactory->registerTagHandler('return', $phpstanTagFactory); + $tagFactory->registerTagHandler('property', $phpstanTagFactory); + $tagFactory->registerTagHandler('property-read', $phpstanTagFactory); + $tagFactory->registerTagHandler('property-write', $phpstanTagFactory); + $tagFactory->registerTagHandler('method', $phpstanTagFactory); + $tagFactory->registerTagHandler('extends', $phpstanTagFactory); + $tagFactory->registerTagHandler('implements', $phpstanTagFactory); + $tagFactory->registerTagHandler('template', $phpstanTagFactory); + $tagFactory->registerTagHandler('template-extends', $phpstanTagFactory); + $tagFactory->registerTagHandler('template-implements', $phpstanTagFactory); + + $docBlockFactory = new self($descriptionFactory, $tagFactory); + foreach ($additionalTags as $tagName => $tagHandler) { + $docBlockFactory->registerTagHandler($tagName, $tagHandler); + } + + return $docBlockFactory; + } + + /** + * @param object|string $docblock A string containing the DocBlock to parse or an object supporting the + * getDocComment method (such as a ReflectionClass object). + */ + public function create($docblock, ?Types\Context $context = null, ?Location $location = null): DocBlock + { + if (is_object($docblock)) { + if (!method_exists($docblock, 'getDocComment')) { + $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method'; + + throw new InvalidArgumentException($exceptionMessage); + } + + $docblock = $docblock->getDocComment(); + Assert::string($docblock); + } + + Assert::stringNotEmpty($docblock); + + if ($context === null) { + $context = new Types\Context(''); + } + + $parts = $this->splitDocBlock($this->stripDocComment($docblock)); + + [$templateMarker, $summary, $description, $tags] = $parts; + + return new DocBlock( + $summary, + $description ? $this->descriptionFactory->create($description, $context) : null, + $this->parseTagBlock($tags, $context), + $context, + $location, + $templateMarker === '#@+', + $templateMarker === '#@-' + ); + } + + /** + * @param class-string|Factory $handler + */ + public function registerTagHandler(string $tagName, $handler): void + { + $this->tagFactory->registerTagHandler($tagName, $handler); + } + + /** + * Strips the asterisks from the DocBlock comment. + * + * @param string $comment String containing the comment text. + */ + private function stripDocComment(string $comment): string + { + $comment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]?(.*)?#u', '$1', $comment); + Assert::string($comment); + $comment = trim($comment); + + // reg ex above is not able to remove */ from a single line docblock + if (substr($comment, -2) === '*/') { + $comment = trim(substr($comment, 0, -2)); + } + + return str_replace(["\r\n", "\r"], "\n", $comment); + } + + // phpcs:disable + + /** + * Splits the DocBlock into a template marker, summary, description and block of tags. + * + * @param string $comment Comment to split into the sub-parts. + * + * @return string[] containing the template marker (if any), summary, description and a string containing the tags. + * + * @author Mike van Riel for extending the regex with template marker support. + * + * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split. + */ + private function splitDocBlock(string $comment): array + { + // phpcs:enable + // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This + // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the + // performance impact of running a regular expression + if (strpos($comment, '@') === 0) { + return ['', '', '', $comment]; + } + + // clears all extra horizontal whitespace from the line endings to prevent parsing issues + $comment = preg_replace('/\h*$/Sum', '', $comment); + Assert::string($comment); + /* + * Splits the docblock into a template marker, summary, description and tags section. + * + * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may + * occur after it and will be stripped). + * - The short description is started from the first character until a dot is encountered followed by a + * newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing + * errors). This is optional. + * - The long description, any character until a new line is encountered followed by an @ and word + * characters (a tag). This is optional. + * - Tags; the remaining characters + * + * Big thanks to RichardJ for contributing this Regular Expression + */ + preg_match( + '/ + \A + # 1. Extract the template marker + (?:(\#\@\+|\#\@\-)\n?)? + + # 2. Extract the summary + (?: + (?! @\pL ) # The summary may not start with an @ + ( + [^\n.]+ + (?: + (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines + [\n.]* (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line + [^\n.]+ # Include anything else + )* + \.? + )? + ) + + # 3. Extract the description + (?: + \s* # Some form of whitespace _must_ precede a description because a summary must be there + (?! @\pL ) # The description may not start with an @ + ( + [^\n]+ + (?: \n+ + (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line + [^\n]+ # Include anything else + )* + ) + )? + + # 4. Extract the tags (anything that follows) + (\s+ [\s\S]*)? # everything that follows + /ux', + $comment, + $matches + ); + array_shift($matches); + + while (count($matches) < 4) { + $matches[] = ''; + } + + return $matches; + } + + /** + * Creates the tag objects. + * + * @param string $tags Tag block to parse. + * @param Types\Context $context Context of the parsed Tag + * + * @return DocBlock\Tag[] + */ + private function parseTagBlock(string $tags, Types\Context $context): array + { + $tags = $this->filterTagBlock($tags); + if ($tags === null) { + return []; + } + + $result = []; + $lines = $this->splitTagBlockIntoTagLines($tags); + foreach ($lines as $key => $tagLine) { + $result[$key] = $this->tagFactory->create(trim($tagLine), $context); + } + + return $result; + } + + /** + * @return string[] + */ + private function splitTagBlockIntoTagLines(string $tags): array + { + $result = []; + foreach (explode("\n", $tags) as $tagLine) { + if ($tagLine !== '' && strpos($tagLine, '@') === 0) { + $result[] = $tagLine; + } else { + $result[count($result) - 1] .= "\n" . $tagLine; + } + } + + return $result; + } + + private function filterTagBlock(string $tags): ?string + { + $tags = trim($tags); + if (!$tags) { + return null; + } + + if ($tags[0] !== '@') { + // @codeCoverageIgnoreStart + // Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that + // we didn't foresee. + + throw new LogicException('A tag block started with text instead of an at-sign(@): ' . $tags); + + // @codeCoverageIgnoreEnd + } + + return $tags; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php new file mode 100644 index 000000000..cacc382e6 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php @@ -0,0 +1,23 @@ +> $additionalTags + */ + public static function createInstance(array $additionalTags = []): self; + + /** + * @param string|object $docblock + */ + public function create($docblock, ?Types\Context $context = null, ?Location $location = null): DocBlock; +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/Exception/PcreException.php b/vendor/phpdocumentor/reflection-docblock/src/Exception/PcreException.php new file mode 100644 index 000000000..b8b6da8cf --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/Exception/PcreException.php @@ -0,0 +1,44 @@ + please note that if you want to pass partial class names that additional steps are necessary, see the + > chapter `Resolving partial classes and FQSENs` for more information. + +Where the FqsenResolver can resolve: + +- Constant expressions (i.e. `@see \MyNamespace\MY_CONSTANT`) +- Function expressions (i.e. `@see \MyNamespace\myFunction()`) +- Class expressions (i.e. `@see \MyNamespace\MyClass`) +- Interface expressions (i.e. `@see \MyNamespace\MyInterface`) +- Trait expressions (i.e. `@see \MyNamespace\MyTrait`) +- Class constant expressions (i.e. `@see \MyNamespace\MyClass::MY_CONSTANT`) +- Property expressions (i.e. `@see \MyNamespace\MyClass::$myProperty`) +- Method expressions (i.e. `@see \MyNamespace\MyClass::myMethod()`) + +## Resolving a type + +In order to resolve a type you will have to instantiate the class `\phpDocumentor\Reflection\TypeResolver` and call its `resolve` method like this: + +```php +$typeResolver = new \phpDocumentor\Reflection\TypeResolver(); +$type = $typeResolver->resolve('string|integer'); +``` + +In this example you will receive a Value Object of class `\phpDocumentor\Reflection\Types\Compound` that has two +elements, one of type `\phpDocumentor\Reflection\Types\String_` and one of type +`\phpDocumentor\Reflection\Types\Integer`. + +The real power of this resolver is in its capability to expand partial class names into fully qualified class names; but in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +### Resolving nullable types + +Php 7.1 introduced nullable types e.g. `?string`. Type resolver will resolve the original type without the nullable notation `?` +just like it would do without the `?`. After that the type is wrapped in a `\phpDocumentor\Reflection\Types\Nullable` object. +The `Nullable` type has a method to fetch the actual type. + +## Resolving an FQSEN + +A Fully Qualified Structural Element Name is a reference to another element in your code bases and can be resolved using the `\phpDocumentor\Reflection\FqsenResolver` class' `resolve` method, like this: + +```php +$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver(); +$fqsen = $fqsenResolver->resolve('\phpDocumentor\Reflection\FqsenResolver::resolve()'); +``` + +In this example we resolve a Fully Qualified Structural Element Name (meaning that it includes the full namespace, class name and element name) and receive a Value Object of type `\phpDocumentor\Reflection\Fqsen`. + +The real power of this resolver is in its capability to expand partial element names into Fully Qualified Structural Element Names; but in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +## Resolving partial Classes and Structural Element Names + +Perhaps the best feature of this library is that it knows how to resolve partial class names into fully qualified class names. + +For example, you have this file: + +```php +namespace My\Example; + +use phpDocumentor\Reflection\Types; + +class Classy +{ + /** + * @var Types\Context + * @see Classy::otherFunction() + */ + public function __construct($context) {} + + public function otherFunction(){} +} +``` + +Suppose that you would want to resolve (and expand) the type in the `@var` tag and the element name in the `@see` tag. + +For the resolvers to know how to expand partial names you have to provide a bit of _Context_ for them by instantiating a new class named `\phpDocumentor\Reflection\Types\Context` with the name of the namespace and the aliases that are in play. + +### Creating a Context + +You can do this by manually creating a Context like this: + +```php +$context = new \phpDocumentor\Reflection\Types\Context( + '\My\Example', + [ 'Types' => '\phpDocumentor\Reflection\Types'] +); +``` + +Or by using the `\phpDocumentor\Reflection\Types\ContextFactory` to instantiate a new context based on a Reflector object or by providing the namespace that you'd like to extract and the source code of the file in which the given type expression occurs. + +```php +$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory(); +$context = $contextFactory->createFromReflector(new ReflectionMethod('\My\Example\Classy', '__construct')); +``` + +or + +```php +$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory(); +$context = $contextFactory->createForNamespace('\My\Example', file_get_contents('My/Example/Classy.php')); +``` + +### Using the Context + +After you have obtained a Context it is just a matter of passing it along with the `resolve` method of either Resolver class as second argument and the Resolvers will take this into account when resolving partial names. + +To obtain the resolved class name for the `@var` tag in the example above you can do: + +```php +$typeResolver = new \phpDocumentor\Reflection\TypeResolver(); +$type = $typeResolver->resolve('Types\Context', $context); +``` + +When you do this you will receive an object of class `\phpDocumentor\Reflection\Types\Object_` for which you can call the `getFqsen` method to receive a Value Object that represents the complete FQSEN. So that would be `phpDocumentor\Reflection\Types\Context`. + +> Why is the FQSEN wrapped in another object `Object_`? +> +> The resolve method of the TypeResolver only returns object with the interface `Type` and the FQSEN is a common type that does not represent a Type. Also: in some cases a type can represent an "Untyped Object", meaning that it is an object (signified by the `object` keyword) but does not refer to a specific element using an FQSEN. + +Another example is on how to resolve the FQSEN of a method as can be seen with the `@see` tag in the example above. To resolve that you can do the following: + +```php +$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver(); +$type = $fqsenResolver->resolve('Classy::otherFunction()', $context); +``` + +Because Classy is a Class in the current namespace its FQSEN will have the `My\Example` namespace and by calling the `resolve` method of the FQSEN Resolver you will receive an `Fqsen` object that refers to `\My\Example\Classy::otherFunction()`. diff --git a/vendor/phpdocumentor/type-resolver/composer.json b/vendor/phpdocumentor/type-resolver/composer.json new file mode 100644 index 000000000..5c280e323 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/composer.json @@ -0,0 +1,51 @@ +{ + "name": "phpdocumentor/type-resolver", + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "require": { + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0", + "doctrine/deprecations": "^1.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/extension-installer": "^1.1", + "vimeo/psalm": "^4.25", + "rector/rector": "^0.13.9", + "phpbench/phpbench": "^1.2" + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "phpDocumentor\\Reflection\\": ["tests/unit", "tests/benchmark"] + } + }, + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "allow-plugins": { + "phpstan/extension-installer": true + } + } +} diff --git a/vendor/phpdocumentor/type-resolver/docs/getting-started.rst b/vendor/phpdocumentor/type-resolver/docs/getting-started.rst new file mode 100644 index 000000000..1c8ea676a --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/docs/getting-started.rst @@ -0,0 +1,32 @@ +=============== +Getting started +=============== + +On this page you will find a brief introduction on how to use the TypeResolver in your project. + +Installation +============ + +The TypeResolver is available on Packagist and can be installed using Composer: + +.. code:: bash + composer require phpdocumentor/type-resolver + + +General usage +=========== + +After you installed the TypeResolver you can use it in your project. This can be done by creating a new instance +of the :php:class:`\phpDocumentor\Reflection\TypeResolver` class and calling +:php:method:`\phpDocumentor\Reflection\TypeResolver::resolve()` with the type you want to resolve. + +.. code:: php + $typeResolver = new \phpDocumentor\Reflection\TypeResolver(); + $type = $typeResolver->resolve('string'); + echo get_class($type); // phpDocumentor\Reflection\Types\String_ + +The real power of this resolver is in its capability to expand partial class names into fully qualified class names; +but in order to do that we need an additional :php:class:`\phpDocumentor\Reflection\Types\Context` class that +will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +Read more about the Context class in the next section. diff --git a/vendor/phpdocumentor/type-resolver/docs/index.rst b/vendor/phpdocumentor/type-resolver/docs/index.rst new file mode 100644 index 000000000..0ff9a8b25 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/docs/index.rst @@ -0,0 +1,17 @@ +============= +Type resolver +============= + +This project part of the phpDocumentor project. It is capable of creating an object structure of the type +specifications found in the PHPDoc blocks of a project. This can be useful for static analysis of a project +or other behavior that requires knowledge of the types used in a project like automatically build forms. + +This project aims to cover all types that are available in PHPDoc and PHP itself. And is open for extension by +third party developers. + +.. toctree:: + :maxdepth: 2 + :hidden: + + index + getting-started diff --git a/vendor/phpdocumentor/type-resolver/phpdoc.dist.xml b/vendor/phpdocumentor/type-resolver/phpdoc.dist.xml new file mode 100644 index 000000000..6c9899114 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/phpdoc.dist.xml @@ -0,0 +1,46 @@ + + + Type Resolver + + build/docs + + + latest + + + src/ + + api + + + php + + + template + template-extends + template-implements + extends + implements + + phpDocumentor + + + + docs + + guides + + + +